1b3aef78fSLaxman Dewangan /* 2b3aef78fSLaxman Dewangan * Generic ADC thermal driver 3b3aef78fSLaxman Dewangan * 4b3aef78fSLaxman Dewangan * Copyright (C) 2016 NVIDIA CORPORATION. All rights reserved. 5b3aef78fSLaxman Dewangan * 6b3aef78fSLaxman Dewangan * Author: Laxman Dewangan <ldewangan@nvidia.com> 7b3aef78fSLaxman Dewangan * 8b3aef78fSLaxman Dewangan * This program is free software; you can redistribute it and/or modify 9b3aef78fSLaxman Dewangan * it under the terms of the GNU General Public License version 2 as 10b3aef78fSLaxman Dewangan * published by the Free Software Foundation. 11b3aef78fSLaxman Dewangan */ 12b3aef78fSLaxman Dewangan #include <linux/iio/consumer.h> 13b3aef78fSLaxman Dewangan #include <linux/kernel.h> 14b3aef78fSLaxman Dewangan #include <linux/module.h> 15b3aef78fSLaxman Dewangan #include <linux/platform_device.h> 16b3aef78fSLaxman Dewangan #include <linux/slab.h> 17b3aef78fSLaxman Dewangan #include <linux/thermal.h> 18b3aef78fSLaxman Dewangan 19b3aef78fSLaxman Dewangan struct gadc_thermal_info { 20b3aef78fSLaxman Dewangan struct device *dev; 21b3aef78fSLaxman Dewangan struct thermal_zone_device *tz_dev; 22b3aef78fSLaxman Dewangan struct iio_channel *channel; 23b3aef78fSLaxman Dewangan s32 *lookup_table; 24b3aef78fSLaxman Dewangan int nlookup_table; 25b3aef78fSLaxman Dewangan }; 26b3aef78fSLaxman Dewangan 27b3aef78fSLaxman Dewangan static int gadc_thermal_adc_to_temp(struct gadc_thermal_info *gti, int val) 28b3aef78fSLaxman Dewangan { 29b3aef78fSLaxman Dewangan int temp, adc_hi, adc_lo; 30b3aef78fSLaxman Dewangan int i; 31b3aef78fSLaxman Dewangan 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]; 39b3aef78fSLaxman Dewangan } else if (i >= (gti->nlookup_table - 1)) { 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]; 44b3aef78fSLaxman Dewangan temp = gti->lookup_table[2 * i]; 45b3aef78fSLaxman Dewangan temp -= ((val - adc_lo) * 1000) / (adc_hi - adc_lo); 46b3aef78fSLaxman Dewangan } 47b3aef78fSLaxman Dewangan 48b3aef78fSLaxman Dewangan return temp; 49b3aef78fSLaxman Dewangan } 50b3aef78fSLaxman Dewangan 51b3aef78fSLaxman Dewangan static int gadc_thermal_get_temp(void *data, int *temp) 52b3aef78fSLaxman Dewangan { 53b3aef78fSLaxman Dewangan struct gadc_thermal_info *gti = data; 54b3aef78fSLaxman Dewangan int val; 55b3aef78fSLaxman Dewangan int ret; 56b3aef78fSLaxman Dewangan 57b3aef78fSLaxman Dewangan ret = iio_read_channel_processed(gti->channel, &val); 58b3aef78fSLaxman Dewangan if (ret < 0) { 59b3aef78fSLaxman Dewangan dev_err(gti->dev, "IIO channel read failed %d\n", ret); 60b3aef78fSLaxman Dewangan return ret; 61b3aef78fSLaxman Dewangan } 62b3aef78fSLaxman Dewangan *temp = gadc_thermal_adc_to_temp(gti, val); 63b3aef78fSLaxman Dewangan 64b3aef78fSLaxman Dewangan return 0; 65b3aef78fSLaxman Dewangan } 66b3aef78fSLaxman Dewangan 67b3aef78fSLaxman Dewangan static const struct thermal_zone_of_device_ops gadc_thermal_ops = { 68b3aef78fSLaxman Dewangan .get_temp = gadc_thermal_get_temp, 69b3aef78fSLaxman Dewangan }; 70b3aef78fSLaxman Dewangan 71b3aef78fSLaxman Dewangan static int gadc_thermal_read_linear_lookup_table(struct device *dev, 72b3aef78fSLaxman Dewangan struct gadc_thermal_info *gti) 73b3aef78fSLaxman Dewangan { 74b3aef78fSLaxman Dewangan struct device_node *np = dev->of_node; 75b3aef78fSLaxman Dewangan int ntable; 76b3aef78fSLaxman Dewangan int ret; 77b3aef78fSLaxman Dewangan 78b3aef78fSLaxman Dewangan ntable = of_property_count_elems_of_size(np, "temperature-lookup-table", 79b3aef78fSLaxman Dewangan sizeof(u32)); 80b3aef78fSLaxman Dewangan if (ntable < 0) { 81b3aef78fSLaxman Dewangan dev_err(dev, "Lookup table is not provided\n"); 82b3aef78fSLaxman Dewangan return ntable; 83b3aef78fSLaxman Dewangan } 84b3aef78fSLaxman Dewangan 85b3aef78fSLaxman Dewangan if (ntable % 2) { 86b3aef78fSLaxman Dewangan dev_err(dev, "Pair of temperature vs ADC read value missing\n"); 87b3aef78fSLaxman Dewangan return -EINVAL; 88b3aef78fSLaxman Dewangan } 89b3aef78fSLaxman Dewangan 90b3aef78fSLaxman Dewangan gti->lookup_table = devm_kzalloc(dev, sizeof(*gti->lookup_table) * 91b3aef78fSLaxman Dewangan ntable, GFP_KERNEL); 92b3aef78fSLaxman Dewangan if (!gti->lookup_table) 93b3aef78fSLaxman Dewangan return -ENOMEM; 94b3aef78fSLaxman Dewangan 95b3aef78fSLaxman Dewangan ret = of_property_read_u32_array(np, "temperature-lookup-table", 96b3aef78fSLaxman Dewangan (u32 *)gti->lookup_table, ntable); 97b3aef78fSLaxman Dewangan if (ret < 0) { 98b3aef78fSLaxman Dewangan dev_err(dev, "Failed to read temperature lookup table: %d\n", 99b3aef78fSLaxman Dewangan ret); 100b3aef78fSLaxman Dewangan return ret; 101b3aef78fSLaxman Dewangan } 102b3aef78fSLaxman Dewangan 103b3aef78fSLaxman Dewangan gti->nlookup_table = ntable / 2; 104b3aef78fSLaxman Dewangan 105b3aef78fSLaxman Dewangan return 0; 106b3aef78fSLaxman Dewangan } 107b3aef78fSLaxman Dewangan 108b3aef78fSLaxman Dewangan static int gadc_thermal_probe(struct platform_device *pdev) 109b3aef78fSLaxman Dewangan { 110b3aef78fSLaxman Dewangan struct gadc_thermal_info *gti; 111b3aef78fSLaxman Dewangan int ret; 112b3aef78fSLaxman Dewangan 113b3aef78fSLaxman Dewangan if (!pdev->dev.of_node) { 114b3aef78fSLaxman Dewangan dev_err(&pdev->dev, "Only DT based supported\n"); 115b3aef78fSLaxman Dewangan return -ENODEV; 116b3aef78fSLaxman Dewangan } 117b3aef78fSLaxman Dewangan 118b3aef78fSLaxman Dewangan gti = devm_kzalloc(&pdev->dev, sizeof(*gti), GFP_KERNEL); 119b3aef78fSLaxman Dewangan if (!gti) 120b3aef78fSLaxman Dewangan return -ENOMEM; 121b3aef78fSLaxman Dewangan 122b3aef78fSLaxman Dewangan ret = gadc_thermal_read_linear_lookup_table(&pdev->dev, gti); 123b3aef78fSLaxman Dewangan if (ret < 0) 124b3aef78fSLaxman Dewangan return ret; 125b3aef78fSLaxman Dewangan 126b3aef78fSLaxman Dewangan gti->dev = &pdev->dev; 127b3aef78fSLaxman Dewangan platform_set_drvdata(pdev, gti); 128b3aef78fSLaxman Dewangan 129d377aba1SDaniel Lezcano gti->channel = devm_iio_channel_get(&pdev->dev, "sensor-channel"); 130b3aef78fSLaxman Dewangan if (IS_ERR(gti->channel)) { 131b3aef78fSLaxman Dewangan ret = PTR_ERR(gti->channel); 132b3aef78fSLaxman Dewangan dev_err(&pdev->dev, "IIO channel not found: %d\n", ret); 133b3aef78fSLaxman Dewangan return ret; 134b3aef78fSLaxman Dewangan } 135b3aef78fSLaxman Dewangan 136d377aba1SDaniel Lezcano gti->tz_dev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, gti, 137d377aba1SDaniel Lezcano &gadc_thermal_ops); 138b3aef78fSLaxman Dewangan if (IS_ERR(gti->tz_dev)) { 139b3aef78fSLaxman Dewangan ret = PTR_ERR(gti->tz_dev); 140b3aef78fSLaxman Dewangan dev_err(&pdev->dev, "Thermal zone sensor register failed: %d\n", 141b3aef78fSLaxman Dewangan ret); 142b3aef78fSLaxman Dewangan return ret; 143b3aef78fSLaxman Dewangan } 144b3aef78fSLaxman Dewangan 145b3aef78fSLaxman Dewangan return 0; 146b3aef78fSLaxman Dewangan } 147b3aef78fSLaxman Dewangan 148b3aef78fSLaxman Dewangan static const struct of_device_id of_adc_thermal_match[] = { 149b3aef78fSLaxman Dewangan { .compatible = "generic-adc-thermal", }, 150b3aef78fSLaxman Dewangan {}, 151b3aef78fSLaxman Dewangan }; 152b3aef78fSLaxman Dewangan MODULE_DEVICE_TABLE(of, of_adc_thermal_match); 153b3aef78fSLaxman Dewangan 154b3aef78fSLaxman Dewangan static struct platform_driver gadc_thermal_driver = { 155b3aef78fSLaxman Dewangan .driver = { 156b3aef78fSLaxman Dewangan .name = "generic-adc-thermal", 157b3aef78fSLaxman Dewangan .of_match_table = of_adc_thermal_match, 158b3aef78fSLaxman Dewangan }, 159b3aef78fSLaxman Dewangan .probe = gadc_thermal_probe, 160b3aef78fSLaxman Dewangan }; 161b3aef78fSLaxman Dewangan 162b3aef78fSLaxman Dewangan module_platform_driver(gadc_thermal_driver); 163b3aef78fSLaxman Dewangan 164b3aef78fSLaxman Dewangan MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); 165b3aef78fSLaxman Dewangan MODULE_DESCRIPTION("Generic ADC thermal driver using IIO framework with DT"); 166b3aef78fSLaxman Dewangan MODULE_LICENSE("GPL v2"); 167