1// SPDX-License-Identifier: GPL-2.0-only 2#include <linux/io.h> 3#include <linux/delay.h> 4#include <linux/module.h> 5#include <linux/thermal.h> 6#include <linux/platform_device.h> 7 8/* 9 * According to a data sheet draft, "this temperature sensor uses a bandgap 10 * type of circuit to compare a voltage which has a negative temperature 11 * coefficient with a voltage that is proportional to absolute temperature. 12 * A resistor bank allows 41 different temperature thresholds to be selected 13 * and the logic output will then indicate whether the actual die temperature 14 * lies above or below the selected threshold." 15 */ 16 17#define TEMPSI_CMD 0 18#define TEMPSI_RES 4 19#define TEMPSI_CFG 8 20 21#define CMD_OFF 0 22#define CMD_ON 1 23#define CMD_READ 2 24 25#define IDX_MIN 15 26#define IDX_MAX 40 27 28struct tango_thermal_priv { 29 void __iomem *base; 30 int thresh_idx; 31}; 32 33static bool temp_above_thresh(void __iomem *base, int thresh_idx) 34{ 35 writel(CMD_READ | thresh_idx << 8, base + TEMPSI_CMD); 36 usleep_range(10, 20); 37 writel(CMD_READ | thresh_idx << 8, base + TEMPSI_CMD); 38 39 return readl(base + TEMPSI_RES); 40} 41 42static int tango_get_temp(void *arg, int *res) 43{ 44 struct tango_thermal_priv *priv = arg; 45 int idx = priv->thresh_idx; 46 47 if (temp_above_thresh(priv->base, idx)) { 48 /* Search upward by incrementing thresh_idx */ 49 while (idx < IDX_MAX && temp_above_thresh(priv->base, ++idx)) 50 cpu_relax(); 51 idx = idx - 1; /* always return lower bound */ 52 } else { 53 /* Search downward by decrementing thresh_idx */ 54 while (idx > IDX_MIN && !temp_above_thresh(priv->base, --idx)) 55 cpu_relax(); 56 } 57 58 *res = (idx * 9 / 2 - 38) * 1000; /* millidegrees Celsius */ 59 priv->thresh_idx = idx; 60 61 return 0; 62} 63 64static const struct thermal_zone_of_device_ops ops = { 65 .get_temp = tango_get_temp, 66}; 67 68static void tango_thermal_init(struct tango_thermal_priv *priv) 69{ 70 writel(0, priv->base + TEMPSI_CFG); 71 writel(CMD_ON, priv->base + TEMPSI_CMD); 72} 73 74static int tango_thermal_probe(struct platform_device *pdev) 75{ 76 struct resource *res; 77 struct tango_thermal_priv *priv; 78 struct thermal_zone_device *tzdev; 79 80 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 81 if (!priv) 82 return -ENOMEM; 83 84 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 85 priv->base = devm_ioremap_resource(&pdev->dev, res); 86 if (IS_ERR(priv->base)) 87 return PTR_ERR(priv->base); 88 89 platform_set_drvdata(pdev, priv); 90 priv->thresh_idx = IDX_MIN; 91 tango_thermal_init(priv); 92 93 tzdev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, priv, &ops); 94 return PTR_ERR_OR_ZERO(tzdev); 95} 96 97static int __maybe_unused tango_thermal_resume(struct device *dev) 98{ 99 tango_thermal_init(dev_get_drvdata(dev)); 100 return 0; 101} 102 103static SIMPLE_DEV_PM_OPS(tango_thermal_pm, NULL, tango_thermal_resume); 104 105static const struct of_device_id tango_sensor_ids[] = { 106 { 107 .compatible = "sigma,smp8758-thermal", 108 }, 109 { /* sentinel */ } 110}; 111MODULE_DEVICE_TABLE(of, tango_sensor_ids); 112 113static struct platform_driver tango_thermal_driver = { 114 .probe = tango_thermal_probe, 115 .driver = { 116 .name = "tango-thermal", 117 .of_match_table = tango_sensor_ids, 118 .pm = &tango_thermal_pm, 119 }, 120}; 121 122module_platform_driver(tango_thermal_driver); 123 124MODULE_LICENSE("GPL"); 125MODULE_AUTHOR("Sigma Designs"); 126MODULE_DESCRIPTION("Tango temperature sensor"); 127