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 { 299d216211SBjorn Andersson int temp, temp_hi, temp_lo, 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]; 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 55b3aef78fSLaxman Dewangan static int gadc_thermal_get_temp(void *data, int *temp) 56b3aef78fSLaxman Dewangan { 57b3aef78fSLaxman Dewangan struct gadc_thermal_info *gti = data; 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 71b3aef78fSLaxman Dewangan static const struct thermal_zone_of_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; 79b3aef78fSLaxman Dewangan int ntable; 80b3aef78fSLaxman Dewangan int ret; 81b3aef78fSLaxman Dewangan 82b3aef78fSLaxman Dewangan ntable = of_property_count_elems_of_size(np, "temperature-lookup-table", 83b3aef78fSLaxman Dewangan sizeof(u32)); 84b3aef78fSLaxman Dewangan if (ntable < 0) { 85b3aef78fSLaxman Dewangan dev_err(dev, "Lookup table is not provided\n"); 86b3aef78fSLaxman Dewangan return ntable; 87b3aef78fSLaxman Dewangan } 88b3aef78fSLaxman Dewangan 89b3aef78fSLaxman Dewangan if (ntable % 2) { 90b3aef78fSLaxman Dewangan dev_err(dev, "Pair of temperature vs ADC read value missing\n"); 91b3aef78fSLaxman Dewangan return -EINVAL; 92b3aef78fSLaxman Dewangan } 93b3aef78fSLaxman Dewangan 94a86854d0SKees Cook gti->lookup_table = devm_kcalloc(dev, 95a86854d0SKees Cook ntable, sizeof(*gti->lookup_table), 96a86854d0SKees Cook GFP_KERNEL); 97b3aef78fSLaxman Dewangan if (!gti->lookup_table) 98b3aef78fSLaxman Dewangan return -ENOMEM; 99b3aef78fSLaxman Dewangan 100b3aef78fSLaxman Dewangan ret = of_property_read_u32_array(np, "temperature-lookup-table", 101b3aef78fSLaxman Dewangan (u32 *)gti->lookup_table, ntable); 102b3aef78fSLaxman Dewangan if (ret < 0) { 103b3aef78fSLaxman Dewangan dev_err(dev, "Failed to read temperature lookup table: %d\n", 104b3aef78fSLaxman Dewangan ret); 105b3aef78fSLaxman Dewangan return ret; 106b3aef78fSLaxman Dewangan } 107b3aef78fSLaxman Dewangan 108b3aef78fSLaxman Dewangan gti->nlookup_table = ntable / 2; 109b3aef78fSLaxman Dewangan 110b3aef78fSLaxman Dewangan return 0; 111b3aef78fSLaxman Dewangan } 112b3aef78fSLaxman Dewangan 113b3aef78fSLaxman Dewangan static int gadc_thermal_probe(struct platform_device *pdev) 114b3aef78fSLaxman Dewangan { 115b3aef78fSLaxman Dewangan struct gadc_thermal_info *gti; 116b3aef78fSLaxman Dewangan int ret; 117b3aef78fSLaxman Dewangan 118b3aef78fSLaxman Dewangan if (!pdev->dev.of_node) { 119b3aef78fSLaxman Dewangan dev_err(&pdev->dev, "Only DT based supported\n"); 120b3aef78fSLaxman Dewangan return -ENODEV; 121b3aef78fSLaxman Dewangan } 122b3aef78fSLaxman Dewangan 123b3aef78fSLaxman Dewangan gti = devm_kzalloc(&pdev->dev, sizeof(*gti), GFP_KERNEL); 124b3aef78fSLaxman Dewangan if (!gti) 125b3aef78fSLaxman Dewangan return -ENOMEM; 126b3aef78fSLaxman Dewangan 127b3aef78fSLaxman Dewangan ret = gadc_thermal_read_linear_lookup_table(&pdev->dev, gti); 128b3aef78fSLaxman Dewangan if (ret < 0) 129b3aef78fSLaxman Dewangan return ret; 130b3aef78fSLaxman Dewangan 131b3aef78fSLaxman Dewangan gti->dev = &pdev->dev; 132b3aef78fSLaxman Dewangan platform_set_drvdata(pdev, gti); 133b3aef78fSLaxman Dewangan 134d377aba1SDaniel Lezcano gti->channel = devm_iio_channel_get(&pdev->dev, "sensor-channel"); 135b3aef78fSLaxman Dewangan if (IS_ERR(gti->channel)) { 136b3aef78fSLaxman Dewangan ret = PTR_ERR(gti->channel); 137b3aef78fSLaxman Dewangan dev_err(&pdev->dev, "IIO channel not found: %d\n", ret); 138b3aef78fSLaxman Dewangan return ret; 139b3aef78fSLaxman Dewangan } 140b3aef78fSLaxman Dewangan 141d377aba1SDaniel Lezcano gti->tz_dev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, gti, 142d377aba1SDaniel Lezcano &gadc_thermal_ops); 143b3aef78fSLaxman Dewangan if (IS_ERR(gti->tz_dev)) { 144b3aef78fSLaxman Dewangan ret = PTR_ERR(gti->tz_dev); 145b3aef78fSLaxman Dewangan dev_err(&pdev->dev, "Thermal zone sensor register failed: %d\n", 146b3aef78fSLaxman Dewangan ret); 147b3aef78fSLaxman Dewangan return ret; 148b3aef78fSLaxman Dewangan } 149b3aef78fSLaxman Dewangan 150b3aef78fSLaxman Dewangan return 0; 151b3aef78fSLaxman Dewangan } 152b3aef78fSLaxman Dewangan 153b3aef78fSLaxman Dewangan static const struct of_device_id of_adc_thermal_match[] = { 154b3aef78fSLaxman Dewangan { .compatible = "generic-adc-thermal", }, 155b3aef78fSLaxman Dewangan {}, 156b3aef78fSLaxman Dewangan }; 157b3aef78fSLaxman Dewangan MODULE_DEVICE_TABLE(of, of_adc_thermal_match); 158b3aef78fSLaxman Dewangan 159b3aef78fSLaxman Dewangan static struct platform_driver gadc_thermal_driver = { 160b3aef78fSLaxman Dewangan .driver = { 161b3aef78fSLaxman Dewangan .name = "generic-adc-thermal", 162b3aef78fSLaxman Dewangan .of_match_table = of_adc_thermal_match, 163b3aef78fSLaxman Dewangan }, 164b3aef78fSLaxman Dewangan .probe = gadc_thermal_probe, 165b3aef78fSLaxman Dewangan }; 166b3aef78fSLaxman Dewangan 167b3aef78fSLaxman Dewangan module_platform_driver(gadc_thermal_driver); 168b3aef78fSLaxman Dewangan 169b3aef78fSLaxman Dewangan MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); 170b3aef78fSLaxman Dewangan MODULE_DESCRIPTION("Generic ADC thermal driver using IIO framework with DT"); 171b3aef78fSLaxman Dewangan MODULE_LICENSE("GPL v2"); 172