thermal: OMAP3/DM3730 band gap thermal sensor driver
authorGrazvydas Ignotas <notasas@gmail.com>
Mon, 1 Sep 2014 00:18:02 +0000 (03:18 +0300)
committerGrazvydas Ignotas <notasas@gmail.com>
Sun, 14 Sep 2014 15:38:02 +0000 (18:38 +0300)
Could have used ti-soc-thermal framework, but it's too much work to
backport it to 3.2 and it's DT only, so here is a standalone driver.

Note that proper hwmod data is needed for this driver to work
(more specifically runtime_pm to be able to control the clock).

drivers/thermal/Kconfig
drivers/thermal/Makefile
drivers/thermal/omap3_thermal.c [new file with mode: 0644]

index f7f71b2..21ece56 100644 (file)
@@ -18,3 +18,11 @@ config THERMAL_HWMON
        depends on THERMAL
        depends on HWMON=y || HWMON=THERMAL
        default y
+
+config OMAP3_THERMAL
+       bool "OMAP3/DM3730 band gap thermal sensor driver"
+       depends on THERMAL
+       depends on ARCH_OMAP
+       help
+         Enable this to plug the OMAP3/DM3730 band gap thermal sensor driver
+         into the Linux thermal framework
index 31108a0..bf93496 100644 (file)
@@ -3,3 +3,4 @@
 #
 
 obj-$(CONFIG_THERMAL)          += thermal_sys.o
+obj-$(CONFIG_OMAP3_THERMAL)    += omap3_thermal.o
diff --git a/drivers/thermal/omap3_thermal.c b/drivers/thermal/omap3_thermal.c
new file mode 100644 (file)
index 0000000..8aee654
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * OMAP3/DM3730 band gap thermal driver.
+ *
+ * Copyright (C) 2014 Grazvydas Ignotas
+ * based on SPEAr Thermal Sensor driver (spear_thermal.c)
+ * Copyright (C) 2011-2012 ST Microelectronics
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/thermal.h>
+#include <linux/pm_runtime.h>
+#include <plat/cpu.h>
+
+#define ADC_CODE_MASK 0x7f
+
+struct omap3_thermal_dev {
+       struct device *dev;
+       void __iomem *thermal_base;
+       const int *adc_to_temp;
+       u32 bgap_soc_mask;
+       u32 bgap_eocz_mask;
+};
+
+static const int omap3630_adc_to_temp[128] = {
+       -40000, -40000, -40000, -40000, -40000, -40000, -40000, -40000, // 7
+       -40000, -40000, -40000, -40000, -40000, -39000, -36500, -34500, // 15
+       -33000, -31000, -29000, -27000, -25000, -23000, -21000, -19250, // 23
+       -17750, -16000, -14250, -12750, -11000,  -9000,  -7250,  -5750, // 31
+        -4250,  -2500,   -750,   1000,   2750,   4250,   5750,   7500, // 39
+         9250,  11000,  12750,  14250,  16000,  18000,  20000,  22000, // 47
+        24000,  26000,  27750,  29250,  31000,  32750,  34250,  36000, // 55
+        37750,  39250,  41000,  42750,  44250,  46000,  47750,  49250, // 63
+        51000,  52750,  54250,    560,  57750,  59250,  61000,  63000, // 71
+        65000,  67000,  69000,  70750,  72500,  74250,  76000,  77750, // 79
+        79250,  81000,  82750,  84250,  86000,  87750,  89250,  91000, // 87
+        92750,  94250,  96000,  97750,  99250, 101000, 102750, 104250, // 95
+       106000, 108000, 110000, 112000, 114000, 116000, 117750, 119250, // 103
+       121000, 122750, 124025, 125000, 125000, 125000, 125000, 125000, // 111
+       125000, 125000, 125000, 125000, 125000, 125000, 125000, 125000, // 119
+       125000, 125000, 125000, 125000, 125000, 125000, 125000, 125000  // 127
+};
+
+static const int omap3530_adc_to_temp[128] = {
+       -40000, -40000, -40000, -40000, -40000, -39500, -38200, -36800, // 7
+       -34700, -32500, -31100, -29700, -28200, -26800, -25400, -24000, // 15
+       -22600, -21200, -19800, -18400, -17000, -15600, -14100, -12700, // 23
+       -11300,  -9900,  -8500,  -7100,  -5700,  -4250,  -2800,  -1400, // 31
+           50,   1550,   3000,   4400,   5850,   7300,   8700,  10100, // 39
+        11550,  13000,  14400,  15800,  17200,  18850,  20100,  21500, // 47
+        22900,  24350,  25800,  27200,  28600,  30000,  31400,  32800, // 55
+        34200,  35650,  37100,  38500,  39900,  41300,  42700,  44150, // 63
+        45600,  47000,  48400,  49800,  51300,  52600,  53950,  55300, // 71
+        56700,  58100,  59500,  60900,  62300,  63700,  70050,  66400, // 79
+        67800,  69200,  70600,  72000,  73400,  74800,  76200,  77600, // 87
+        79000,  80400,  81700,  83050,  84500,  85850,  87200,  88600, // 95
+        89950,  91300,  92700,  94050,  95400,  96800,  98200,  99550, // 103
+       100900, 102300, 103650, 105000, 106400, 107800, 109150, 110500, // 111
+       111900, 113300, 114650, 116000, 117400, 118750, 120100, 121500, // 119
+       122850, 124200, 124950, 125000, 125000, 125000, 125000, 125000  // 127
+};
+
+static int omap3_thermal_get_temp(struct thermal_zone_device *thermal,
+                                 unsigned long *temp)
+{
+       struct omap3_thermal_dev *tdev = thermal->devdata;
+       int timeout;
+       u32 val;
+       int ret;
+
+       ret = pm_runtime_get_sync(tdev->dev);
+       if (ret < 0) {
+               dev_err(tdev->dev, "pm_runtime_get_sync failed: %d\n", ret);
+               return ret;
+       }
+
+       val = readl(tdev->thermal_base);
+       val |= tdev->bgap_soc_mask; /* start of conversion */
+
+       writel(val, tdev->thermal_base);
+       usleep_range(428, 1000); /* at least 14 32k cycles */
+
+       val &= ~tdev->bgap_soc_mask;
+       writel(val, tdev->thermal_base);
+
+       usleep_range(1221, 2000); /* at least 36+4 32k cycles */
+       for (timeout = 1000; timeout > 0; timeout--) {
+               val = readl(tdev->thermal_base);
+               if (!(val & tdev->bgap_eocz_mask))
+                       break;
+               cpu_relax();
+       }
+
+       pm_runtime_mark_last_busy(tdev->dev);
+       ret = pm_runtime_put_autosuspend(tdev->dev);
+
+       if (timeout == 0)
+               dev_err(tdev->dev, "timeout waiting for eocz\n");
+
+       *temp = tdev->adc_to_temp[val & ADC_CODE_MASK];
+       return 0;
+}
+
+static const struct thermal_zone_device_ops omap3_thermal_ops = {
+       .get_temp = omap3_thermal_get_temp,
+};
+
+static int omap3_thermal_probe(struct platform_device *pdev)
+{
+       struct thermal_zone_device *omap3_thermal = NULL;
+       struct omap3_thermal_dev *tdev;
+       int ret = 0;
+       struct resource *stres = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+       if (!stres) {
+               dev_err(&pdev->dev, "memory resource missing\n");
+               return -ENODEV;
+       }
+
+       tdev = devm_kzalloc(&pdev->dev, sizeof(*tdev), GFP_KERNEL);
+       if (!tdev)
+               return -ENOMEM;
+
+       tdev->dev = &pdev->dev;
+
+       if (cpu_is_omap3630()) {
+               tdev->bgap_soc_mask = BIT(9);
+               tdev->bgap_eocz_mask = BIT(8);
+               tdev->adc_to_temp = omap3630_adc_to_temp;
+       } else if (cpu_is_omap34xx()) {
+               tdev->bgap_soc_mask = BIT(8);
+               tdev->bgap_eocz_mask = BIT(7);
+               tdev->adc_to_temp = omap3530_adc_to_temp;
+       } else {
+               dev_err(&pdev->dev, "not OMAP3 family\n");
+               return -ENODEV;
+       }
+
+       tdev->thermal_base = devm_ioremap(&pdev->dev, stres->start,
+                       resource_size(stres));
+       if (!tdev->thermal_base) {
+               dev_err(&pdev->dev, "ioremap failed\n");
+               return -ENOMEM;
+       }
+
+       pm_runtime_enable(&pdev->dev);
+       pm_runtime_set_autosuspend_delay(&pdev->dev, 2000);
+       pm_runtime_use_autosuspend(&pdev->dev);
+
+       omap3_thermal = thermal_zone_device_register("omap3-thermal", 0,
+                               tdev, &omap3_thermal_ops, 0, 0, 0, 0);
+       if (!omap3_thermal) {
+               dev_err(&pdev->dev, "thermal zone device is NULL\n");
+               ret = -EINVAL;
+               goto put_pm;
+       }
+
+       platform_set_drvdata(pdev, omap3_thermal);
+
+       return 0;
+
+put_pm:
+       pm_runtime_disable(&pdev->dev);
+       return ret;
+}
+
+static int omap3_thermal_exit(struct platform_device *pdev)
+{
+       struct thermal_zone_device *omap3_thermal = platform_get_drvdata(pdev);
+
+       thermal_zone_device_unregister(omap3_thermal);
+       platform_set_drvdata(pdev, NULL);
+       pm_runtime_disable(&pdev->dev);
+
+       return 0;
+}
+
+static struct platform_driver omap3_thermal_driver = {
+       .probe = omap3_thermal_probe,
+       .remove = omap3_thermal_exit,
+       .driver = {
+               .name = "omap3-thermal",
+               .owner = THIS_MODULE,
+       },
+};
+
+module_platform_driver(omap3_thermal_driver);
+
+MODULE_AUTHOR("Grazvydas Ignotas <notasas@gmail.com>");
+MODULE_DESCRIPTION("OMAP3/DM3730 thermal driver");
+MODULE_LICENSE("GPL");