Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * Generic ADC thermal driver
0004  *
0005  * Copyright (C) 2016 NVIDIA CORPORATION. All rights reserved.
0006  *
0007  * Author: Laxman Dewangan <ldewangan@nvidia.com>
0008  */
0009 #include <linux/iio/consumer.h>
0010 #include <linux/kernel.h>
0011 #include <linux/module.h>
0012 #include <linux/platform_device.h>
0013 #include <linux/slab.h>
0014 #include <linux/thermal.h>
0015 
0016 struct gadc_thermal_info {
0017     struct device *dev;
0018     struct thermal_zone_device *tz_dev;
0019     struct iio_channel *channel;
0020     s32 *lookup_table;
0021     int nlookup_table;
0022 };
0023 
0024 static int gadc_thermal_adc_to_temp(struct gadc_thermal_info *gti, int val)
0025 {
0026     int temp, temp_hi, temp_lo, adc_hi, adc_lo;
0027     int i;
0028 
0029     if (!gti->lookup_table)
0030         return val;
0031 
0032     for (i = 0; i < gti->nlookup_table; i++) {
0033         if (val >= gti->lookup_table[2 * i + 1])
0034             break;
0035     }
0036 
0037     if (i == 0) {
0038         temp = gti->lookup_table[0];
0039     } else if (i >= gti->nlookup_table) {
0040         temp = gti->lookup_table[2 * (gti->nlookup_table - 1)];
0041     } else {
0042         adc_hi = gti->lookup_table[2 * i - 1];
0043         adc_lo = gti->lookup_table[2 * i + 1];
0044 
0045         temp_hi = gti->lookup_table[2 * i - 2];
0046         temp_lo = gti->lookup_table[2 * i];
0047 
0048         temp = temp_hi + mult_frac(temp_lo - temp_hi, val - adc_hi,
0049                        adc_lo - adc_hi);
0050     }
0051 
0052     return temp;
0053 }
0054 
0055 static int gadc_thermal_get_temp(void *data, int *temp)
0056 {
0057     struct gadc_thermal_info *gti = data;
0058     int val;
0059     int ret;
0060 
0061     ret = iio_read_channel_processed(gti->channel, &val);
0062     if (ret < 0) {
0063         dev_err(gti->dev, "IIO channel read failed %d\n", ret);
0064         return ret;
0065     }
0066     *temp = gadc_thermal_adc_to_temp(gti, val);
0067 
0068     return 0;
0069 }
0070 
0071 static const struct thermal_zone_of_device_ops gadc_thermal_ops = {
0072     .get_temp = gadc_thermal_get_temp,
0073 };
0074 
0075 static int gadc_thermal_read_linear_lookup_table(struct device *dev,
0076                          struct gadc_thermal_info *gti)
0077 {
0078     struct device_node *np = dev->of_node;
0079     enum iio_chan_type chan_type;
0080     int ntable;
0081     int ret;
0082 
0083     ntable = of_property_count_elems_of_size(np, "temperature-lookup-table",
0084                          sizeof(u32));
0085     if (ntable <= 0) {
0086         ret = iio_get_channel_type(gti->channel, &chan_type);
0087         if (ret || chan_type != IIO_TEMP)
0088             dev_notice(dev,
0089                    "no lookup table, assuming DAC channel returns milliCelcius\n");
0090         return 0;
0091     }
0092 
0093     if (ntable % 2) {
0094         dev_err(dev, "Pair of temperature vs ADC read value missing\n");
0095         return -EINVAL;
0096     }
0097 
0098     gti->lookup_table = devm_kcalloc(dev,
0099                      ntable, sizeof(*gti->lookup_table),
0100                      GFP_KERNEL);
0101     if (!gti->lookup_table)
0102         return -ENOMEM;
0103 
0104     ret = of_property_read_u32_array(np, "temperature-lookup-table",
0105                      (u32 *)gti->lookup_table, ntable);
0106     if (ret < 0) {
0107         dev_err(dev, "Failed to read temperature lookup table: %d\n",
0108             ret);
0109         return ret;
0110     }
0111 
0112     gti->nlookup_table = ntable / 2;
0113 
0114     return 0;
0115 }
0116 
0117 static int gadc_thermal_probe(struct platform_device *pdev)
0118 {
0119     struct gadc_thermal_info *gti;
0120     int ret;
0121 
0122     if (!pdev->dev.of_node) {
0123         dev_err(&pdev->dev, "Only DT based supported\n");
0124         return -ENODEV;
0125     }
0126 
0127     gti = devm_kzalloc(&pdev->dev, sizeof(*gti), GFP_KERNEL);
0128     if (!gti)
0129         return -ENOMEM;
0130 
0131     gti->channel = devm_iio_channel_get(&pdev->dev, "sensor-channel");
0132     if (IS_ERR(gti->channel)) {
0133         ret = PTR_ERR(gti->channel);
0134         if (ret != -EPROBE_DEFER)
0135             dev_err(&pdev->dev, "IIO channel not found: %d\n", ret);
0136         return ret;
0137     }
0138 
0139     ret = gadc_thermal_read_linear_lookup_table(&pdev->dev, gti);
0140     if (ret < 0)
0141         return ret;
0142 
0143     gti->dev = &pdev->dev;
0144     platform_set_drvdata(pdev, gti);
0145 
0146     gti->tz_dev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, gti,
0147                                &gadc_thermal_ops);
0148     if (IS_ERR(gti->tz_dev)) {
0149         ret = PTR_ERR(gti->tz_dev);
0150         if (ret != -EPROBE_DEFER)
0151             dev_err(&pdev->dev,
0152                 "Thermal zone sensor register failed: %d\n",
0153                 ret);
0154         return ret;
0155     }
0156 
0157     return 0;
0158 }
0159 
0160 static const struct of_device_id of_adc_thermal_match[] = {
0161     { .compatible = "generic-adc-thermal", },
0162     {},
0163 };
0164 MODULE_DEVICE_TABLE(of, of_adc_thermal_match);
0165 
0166 static struct platform_driver gadc_thermal_driver = {
0167     .driver = {
0168         .name = "generic-adc-thermal",
0169         .of_match_table = of_adc_thermal_match,
0170     },
0171     .probe = gadc_thermal_probe,
0172 };
0173 
0174 module_platform_driver(gadc_thermal_driver);
0175 
0176 MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
0177 MODULE_DESCRIPTION("Generic ADC thermal driver using IIO framework with DT");
0178 MODULE_LICENSE("GPL v2");