1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2b3aef78fSLaxman Dewangan /* 3b3aef78fSLaxman Dewangan * Generic ADC thermal driver 4b3aef78fSLaxman Dewangan * 5b3aef78fSLaxman Dewangan * Copyright (C) 2016 NVIDIA CORPORATION. All rights reserved. 6b3aef78fSLaxman Dewangan * 7b3aef78fSLaxman Dewangan * Author: Laxman Dewangan <ldewangan@nvidia.com> 8b3aef78fSLaxman Dewangan */ 9b3aef78fSLaxman Dewangan #include <linux/iio/consumer.h> 10b3aef78fSLaxman Dewangan #include <linux/kernel.h> 11b3aef78fSLaxman Dewangan #include <linux/module.h> 12b3aef78fSLaxman Dewangan #include <linux/platform_device.h> 13b3aef78fSLaxman Dewangan #include <linux/slab.h> 14b3aef78fSLaxman Dewangan #include <linux/thermal.h> 15b3aef78fSLaxman Dewangan 16b3aef78fSLaxman Dewangan struct gadc_thermal_info { 17b3aef78fSLaxman Dewangan struct device *dev; 18b3aef78fSLaxman Dewangan struct thermal_zone_device *tz_dev; 19b3aef78fSLaxman Dewangan struct iio_channel *channel; 20b3aef78fSLaxman Dewangan s32 *lookup_table; 21b3aef78fSLaxman Dewangan int nlookup_table; 22b3aef78fSLaxman Dewangan }; 23b3aef78fSLaxman Dewangan 24b3aef78fSLaxman Dewangan static int gadc_thermal_adc_to_temp(struct gadc_thermal_info *gti, int val) 25b3aef78fSLaxman Dewangan { 269d216211SBjorn Andersson int temp, temp_hi, temp_lo, adc_hi, adc_lo; 27b3aef78fSLaxman Dewangan int i; 28b3aef78fSLaxman Dewangan 29d36e2fa0SJean-Francois Dagenais if (!gti->lookup_table) 30d36e2fa0SJean-Francois Dagenais return val; 31d36e2fa0SJean-Francois Dagenais 32b3aef78fSLaxman Dewangan for (i = 0; i < gti->nlookup_table; i++) { 33b3aef78fSLaxman Dewangan if (val >= gti->lookup_table[2 * i + 1]) 34b3aef78fSLaxman Dewangan break; 35b3aef78fSLaxman Dewangan } 36b3aef78fSLaxman Dewangan 37b3aef78fSLaxman Dewangan if (i == 0) { 38b3aef78fSLaxman Dewangan temp = gti->lookup_table[0]; 399d216211SBjorn Andersson } else if (i >= gti->nlookup_table) { 40b3aef78fSLaxman Dewangan temp = gti->lookup_table[2 * (gti->nlookup_table - 1)]; 41b3aef78fSLaxman Dewangan } else { 42b3aef78fSLaxman Dewangan adc_hi = gti->lookup_table[2 * i - 1]; 43b3aef78fSLaxman Dewangan adc_lo = gti->lookup_table[2 * i + 1]; 449d216211SBjorn Andersson 459d216211SBjorn Andersson temp_hi = gti->lookup_table[2 * i - 2]; 469d216211SBjorn Andersson temp_lo = gti->lookup_table[2 * i]; 479d216211SBjorn Andersson 489d216211SBjorn Andersson temp = temp_hi + mult_frac(temp_lo - temp_hi, val - adc_hi, 499d216211SBjorn Andersson adc_lo - adc_hi); 50b3aef78fSLaxman Dewangan } 51b3aef78fSLaxman Dewangan 52b3aef78fSLaxman Dewangan return temp; 53b3aef78fSLaxman Dewangan } 54b3aef78fSLaxman Dewangan 55*2ff66cbaSDaniel Lezcano static int gadc_thermal_get_temp(struct thermal_zone_device *tz, int *temp) 56b3aef78fSLaxman Dewangan { 57*2ff66cbaSDaniel Lezcano struct gadc_thermal_info *gti = tz->devdata; 58b3aef78fSLaxman Dewangan int val; 59b3aef78fSLaxman Dewangan int ret; 60b3aef78fSLaxman Dewangan 61b3aef78fSLaxman Dewangan ret = iio_read_channel_processed(gti->channel, &val); 62b3aef78fSLaxman Dewangan if (ret < 0) { 63b3aef78fSLaxman Dewangan dev_err(gti->dev, "IIO channel read failed %d\n", ret); 64b3aef78fSLaxman Dewangan return ret; 65b3aef78fSLaxman Dewangan } 66b3aef78fSLaxman Dewangan *temp = gadc_thermal_adc_to_temp(gti, val); 67b3aef78fSLaxman Dewangan 68b3aef78fSLaxman Dewangan return 0; 69b3aef78fSLaxman Dewangan } 70b3aef78fSLaxman Dewangan 71*2ff66cbaSDaniel Lezcano static const struct thermal_zone_device_ops gadc_thermal_ops = { 72b3aef78fSLaxman Dewangan .get_temp = gadc_thermal_get_temp, 73b3aef78fSLaxman Dewangan }; 74b3aef78fSLaxman Dewangan 75b3aef78fSLaxman Dewangan static int gadc_thermal_read_linear_lookup_table(struct device *dev, 76b3aef78fSLaxman Dewangan struct gadc_thermal_info *gti) 77b3aef78fSLaxman Dewangan { 78b3aef78fSLaxman Dewangan struct device_node *np = dev->of_node; 7907d243a6SMartin Blumenstingl enum iio_chan_type chan_type; 80b3aef78fSLaxman Dewangan int ntable; 81b3aef78fSLaxman Dewangan int ret; 82b3aef78fSLaxman Dewangan 83b3aef78fSLaxman Dewangan ntable = of_property_count_elems_of_size(np, "temperature-lookup-table", 84b3aef78fSLaxman Dewangan sizeof(u32)); 85d36e2fa0SJean-Francois Dagenais if (ntable <= 0) { 8607d243a6SMartin Blumenstingl ret = iio_get_channel_type(gti->channel, &chan_type); 8707d243a6SMartin Blumenstingl if (ret || chan_type != IIO_TEMP) 8807d243a6SMartin Blumenstingl dev_notice(dev, 8907d243a6SMartin Blumenstingl "no lookup table, assuming DAC channel returns milliCelcius\n"); 90d36e2fa0SJean-Francois Dagenais return 0; 91b3aef78fSLaxman Dewangan } 92b3aef78fSLaxman Dewangan 93b3aef78fSLaxman Dewangan if (ntable % 2) { 94b3aef78fSLaxman Dewangan dev_err(dev, "Pair of temperature vs ADC read value missing\n"); 95b3aef78fSLaxman Dewangan return -EINVAL; 96b3aef78fSLaxman Dewangan } 97b3aef78fSLaxman Dewangan 98a86854d0SKees Cook gti->lookup_table = devm_kcalloc(dev, 99a86854d0SKees Cook ntable, sizeof(*gti->lookup_table), 100a86854d0SKees Cook GFP_KERNEL); 101b3aef78fSLaxman Dewangan if (!gti->lookup_table) 102b3aef78fSLaxman Dewangan return -ENOMEM; 103b3aef78fSLaxman Dewangan 104b3aef78fSLaxman Dewangan ret = of_property_read_u32_array(np, "temperature-lookup-table", 105b3aef78fSLaxman Dewangan (u32 *)gti->lookup_table, ntable); 106b3aef78fSLaxman Dewangan if (ret < 0) { 107b3aef78fSLaxman Dewangan dev_err(dev, "Failed to read temperature lookup table: %d\n", 108b3aef78fSLaxman Dewangan ret); 109b3aef78fSLaxman Dewangan return ret; 110b3aef78fSLaxman Dewangan } 111b3aef78fSLaxman Dewangan 112b3aef78fSLaxman Dewangan gti->nlookup_table = ntable / 2; 113b3aef78fSLaxman Dewangan 114b3aef78fSLaxman Dewangan return 0; 115b3aef78fSLaxman Dewangan } 116b3aef78fSLaxman Dewangan 117b3aef78fSLaxman Dewangan static int gadc_thermal_probe(struct platform_device *pdev) 118b3aef78fSLaxman Dewangan { 119b3aef78fSLaxman Dewangan struct gadc_thermal_info *gti; 120b3aef78fSLaxman Dewangan int ret; 121b3aef78fSLaxman Dewangan 122b3aef78fSLaxman Dewangan if (!pdev->dev.of_node) { 123b3aef78fSLaxman Dewangan dev_err(&pdev->dev, "Only DT based supported\n"); 124b3aef78fSLaxman Dewangan return -ENODEV; 125b3aef78fSLaxman Dewangan } 126b3aef78fSLaxman Dewangan 127b3aef78fSLaxman Dewangan gti = devm_kzalloc(&pdev->dev, sizeof(*gti), GFP_KERNEL); 128b3aef78fSLaxman Dewangan if (!gti) 129b3aef78fSLaxman Dewangan return -ENOMEM; 130b3aef78fSLaxman Dewangan 131d377aba1SDaniel Lezcano gti->channel = devm_iio_channel_get(&pdev->dev, "sensor-channel"); 132b3aef78fSLaxman Dewangan if (IS_ERR(gti->channel)) { 133b3aef78fSLaxman Dewangan ret = PTR_ERR(gti->channel); 13486bd20a5SHsin-Yi Wang if (ret != -EPROBE_DEFER) 135b3aef78fSLaxman Dewangan dev_err(&pdev->dev, "IIO channel not found: %d\n", ret); 136b3aef78fSLaxman Dewangan return ret; 137b3aef78fSLaxman Dewangan } 138b3aef78fSLaxman Dewangan 139c1fde6e1SMartin Blumenstingl ret = gadc_thermal_read_linear_lookup_table(&pdev->dev, gti); 140c1fde6e1SMartin Blumenstingl if (ret < 0) 141c1fde6e1SMartin Blumenstingl return ret; 142c1fde6e1SMartin Blumenstingl 143c1fde6e1SMartin Blumenstingl gti->dev = &pdev->dev; 144c1fde6e1SMartin Blumenstingl platform_set_drvdata(pdev, gti); 145c1fde6e1SMartin Blumenstingl 146*2ff66cbaSDaniel Lezcano gti->tz_dev = devm_thermal_of_zone_register(&pdev->dev, 0, gti, 147d377aba1SDaniel Lezcano &gadc_thermal_ops); 148b3aef78fSLaxman Dewangan if (IS_ERR(gti->tz_dev)) { 149b3aef78fSLaxman Dewangan ret = PTR_ERR(gti->tz_dev); 15086bd20a5SHsin-Yi Wang if (ret != -EPROBE_DEFER) 15186bd20a5SHsin-Yi Wang dev_err(&pdev->dev, 15286bd20a5SHsin-Yi Wang "Thermal zone sensor register failed: %d\n", 153b3aef78fSLaxman Dewangan ret); 154b3aef78fSLaxman Dewangan return ret; 155b3aef78fSLaxman Dewangan } 156b3aef78fSLaxman Dewangan 157b3aef78fSLaxman Dewangan return 0; 158b3aef78fSLaxman Dewangan } 159b3aef78fSLaxman Dewangan 160b3aef78fSLaxman Dewangan static const struct of_device_id of_adc_thermal_match[] = { 161b3aef78fSLaxman Dewangan { .compatible = "generic-adc-thermal", }, 162b3aef78fSLaxman Dewangan {}, 163b3aef78fSLaxman Dewangan }; 164b3aef78fSLaxman Dewangan MODULE_DEVICE_TABLE(of, of_adc_thermal_match); 165b3aef78fSLaxman Dewangan 166b3aef78fSLaxman Dewangan static struct platform_driver gadc_thermal_driver = { 167b3aef78fSLaxman Dewangan .driver = { 168b3aef78fSLaxman Dewangan .name = "generic-adc-thermal", 169b3aef78fSLaxman Dewangan .of_match_table = of_adc_thermal_match, 170b3aef78fSLaxman Dewangan }, 171b3aef78fSLaxman Dewangan .probe = gadc_thermal_probe, 172b3aef78fSLaxman Dewangan }; 173b3aef78fSLaxman Dewangan 174b3aef78fSLaxman Dewangan module_platform_driver(gadc_thermal_driver); 175b3aef78fSLaxman Dewangan 176b3aef78fSLaxman Dewangan MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); 177b3aef78fSLaxman Dewangan MODULE_DESCRIPTION("Generic ADC thermal driver using IIO framework with DT"); 178b3aef78fSLaxman Dewangan MODULE_LICENSE("GPL v2"); 179