]> rtime.felk.cvut.cz Git - sojka/nv-tegra/linux-3.10.git/commitdiff
thermal: as3722 thermal alarm driver
authorBibek Basu <bbasu@nvidia.com>
Tue, 17 Jun 2014 05:55:31 +0000 (11:25 +0530)
committerLaxman Dewangan <ldewangan@nvidia.com>
Wed, 23 Jul 2014 06:14:07 +0000 (23:14 -0700)
Added driver which detects over temperature based on
sensors present along with the regulators SD0, SD1 and SD6.
If there is over temperature due to overcurrent drawn,
the driver helps in detecting it and start passive throttling

Change-Id: I976ded28320e848c61aa4a0115ce9f955056ed54
Signed-off-by: Bibek Basu <bbasu@nvidia.com>
Reviewed-on: http://git-master/r/424063
GVS: Gerrit_Virtual_Submit
Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
drivers/mfd/as3722.c
drivers/thermal/Kconfig
drivers/thermal/Makefile
drivers/thermal/as3722_thermal.c [new file with mode: 0644]

index 4bfe4833027a3b0f3eb54e0d565177f5b7c231f9..31aa07e7575ee291cc5b12ee51ff2ca49f2c3d3f 100644 (file)
@@ -43,6 +43,7 @@ enum {
        AS3722_ADC,
        AS3722_POWER_OFF_ID,
        AS3722_CLK_ID,
+       AS3722_THERMAL_ID,
        AS3722_WATCHDOG_ID,
 };
 
@@ -64,6 +65,29 @@ static const struct resource as3722_adc_resource[] = {
        },
 };
 
+static const struct resource as3722_thermal_resource[] = {
+       {
+               .name = "as3722-sd0-alarm",
+               .start = AS3722_IRQ_TEMP_SD0_ALARM,
+               .end = AS3722_IRQ_TEMP_SD0_ALARM,
+               .flags = IORESOURCE_IRQ,
+       },
+       {
+               .name = "as3722-sd1-alarm",
+               .start = AS3722_IRQ_TEMP_SD1_ALARM,
+               .end = AS3722_IRQ_TEMP_SD1_ALARM,
+               .flags = IORESOURCE_IRQ,
+       },
+       {
+               .name = "as3722-sd6-alarm",
+               .start = AS3722_IRQ_TEMP_SD6_ALARM,
+               .end = AS3722_IRQ_TEMP_SD6_ALARM,
+               .flags = IORESOURCE_IRQ,
+       },
+};
+
+
+
 static struct mfd_cell as3722_devs[] = {
        {
                .name = "as3722-pinctrl",
@@ -93,6 +117,12 @@ static struct mfd_cell as3722_devs[] = {
                .name = "as3722-power-off",
                .id = AS3722_POWER_OFF_ID,
        },
+       {
+               .name = "as3722-thermal",
+               .num_resources = ARRAY_SIZE(as3722_thermal_resource),
+               .resources = as3722_thermal_resource,
+               .id = AS3722_THERMAL_ID,
+       },
        {
                .name = "as3722-wdt",
                .id = AS3722_WATCHDOG_ID,
index 5b6d70b297712881888270ab8fdd64c776b7ecad..a7ed7ee5ab855fc018966046e8b78433b4be42b4 100644 (file)
@@ -237,6 +237,16 @@ config MAX77620_THERMAL
          different critical temperature thresholds from which we can notify
          thermal core to orderly power off the system.
 
+config AS3722_THERMAL
+       bool "AMS AS3722 thermal driver"
+       depends on THERMAL_OF && MFD_AS3722
+       default n
+       help
+         AS3722 thermal driver which detects temperature trips
+         on regulators due to high current drainage.It then provides
+         current temp on the regulators using ADC and uses platform
+         cooling hooks to throttle CPU/GPU speeds.
+
 config SENSORS_TMP006
        tristate "Skin Temperature Sensor"
        depends on I2C
index 656b3fddd31ae17c1ab526b83b9386d3c95f3edc..6a8c243d13c70c88d1c5592c108cb8e2018ee048 100644 (file)
@@ -33,5 +33,6 @@ obj-$(CONFIG_GENERIC_ADC_THERMAL)     += generic_adc_thermal.o
 obj-$(CONFIG_GENERIC_ADC_THERMAL)      += of_generic_adc_thermal.o
 obj-$(CONFIG_PALMAS_THERMAL)   += palmas_thermal.o
 obj-$(CONFIG_MAX77620_THERMAL)    += max77620-thermal.o
+obj-$(CONFIG_AS3722_THERMAL)    += as3722_thermal.o
 obj-$(CONFIG_SENSORS_TMP006)   += tmp006.o
 
diff --git a/drivers/thermal/as3722_thermal.c b/drivers/thermal/as3722_thermal.c
new file mode 100644 (file)
index 0000000..ad2f466
--- /dev/null
@@ -0,0 +1,290 @@
+/*
+ * AMS AS3722 THERMAL.
+ *
+ * Copyright (c) 2014, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * Author: Bibek Basu <bbasu@nvidia.com>
+ *
+ * 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/debugfs.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/suspend.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/thermal.h>
+#include <linux/mfd/as3722.h>
+#include <linux/iio/consumer.h>
+#include <linux/iio/types.h>
+
+#define AS3722_MAX_TEMP                        145000
+#define AS3722_ALARM_TEMP              110000
+
+enum sensor_id {
+       SD0 = 0,
+       SD1,
+       SD6,
+       SD_MAX,
+};
+
+struct as3722_therm_zone {
+       struct device                   *dev;
+       struct as3722                   *as3722;
+       struct delayed_work             timeout_work;
+       struct thermal_zone_device      *tzd[SD_MAX];
+       struct iio_channel              *adc_ch[SD_MAX];
+       long                            cur_temp[SD_MAX];
+       long                            high_limit[SD_MAX];
+       struct mutex                    update_lock;
+       int                             irq[SD_MAX];
+       bool                            irq_masked[SD_MAX];
+};
+
+static void as3722_timeout_work(struct work_struct *work)
+{
+       struct as3722_therm_zone *ptherm_zone = container_of(work,
+                       struct as3722_therm_zone, timeout_work.work);
+       int i;
+
+       mutex_lock(&ptherm_zone->update_lock);
+       for (i = 0; i < SD_MAX; i++) {
+               if (ptherm_zone->irq_masked[i]) {
+                       enable_irq(ptherm_zone->irq[i]);
+                       ptherm_zone->irq_masked[i] = false;
+               }
+       }
+       mutex_unlock(&ptherm_zone->update_lock);
+}
+static int as3722_therm_get_temp(struct iio_channel *channel,
+                                       long *temp)
+{
+       int tries = 10;
+       int ret;
+       int val = 0;
+
+       do {
+               ret = iio_read_channel_raw(channel, &val);
+               /* Temp (mC) = 326500 - (ADC result * 3734) / 10 */
+               *temp = 326500 - (val * 3734) / 10;
+               if (*temp <= AS3722_MAX_TEMP)
+                       return 0;
+       } while (--tries > 0);
+
+       return -ETIMEDOUT;
+}
+
+static int as3722_therm_get_sd0_temp(void *dev, long *temp)
+{
+       struct as3722_therm_zone *ptherm_zone = dev;
+       int ret;
+
+       ret = as3722_therm_get_temp(ptherm_zone->adc_ch[SD0], temp);
+
+       return ret;
+}
+
+static int as3722_therm_get_sd1_temp(void *dev, long *temp)
+{
+       struct as3722_therm_zone *ptherm_zone = dev;
+       int ret;
+
+       ret = as3722_therm_get_temp(ptherm_zone->adc_ch[SD1], temp);
+
+       return ret;
+}
+
+static int as3722_therm_get_sd6_temp(void *dev, long *temp)
+{
+       struct as3722_therm_zone *ptherm_zone = dev;
+       int ret;
+
+       ret = as3722_therm_get_temp(ptherm_zone->adc_ch[SD6], temp);
+
+       return ret;
+}
+
+static void as3722_therm_update_limits(struct as3722_therm_zone *ptherm_zone,
+                                       int sensor)
+{
+       long high_temp = AS3722_MAX_TEMP;
+       long trip_temp;
+       int i;
+
+       for (i = 0; i < ptherm_zone->tzd[sensor]->trips; i++) {
+               ptherm_zone->tzd[sensor]->ops->get_trip_temp(
+                       ptherm_zone->tzd[sensor], i, &trip_temp);
+                       high_temp = min(high_temp, trip_temp);
+       }
+       ptherm_zone->high_limit[sensor] = high_temp;
+}
+
+static irqreturn_t as3722_thermal_irq(int irq, void *data)
+{
+       struct as3722_therm_zone *ptherm_zone = data;
+       int i;
+
+       for (i = 0; i < SD_MAX; i++) {
+               if (irq != ptherm_zone->irq[i])
+                       continue;
+               if (thermal_zone_get_temp(ptherm_zone->tzd[i],
+                                       &ptherm_zone->cur_temp[i]))
+                       ptherm_zone->cur_temp[i] = AS3722_ALARM_TEMP;
+
+               if (ptherm_zone->cur_temp[i] >= ptherm_zone->high_limit[i]) {
+                       /*
+                        * Interrupt is disabled to stop flooding of
+                        * interrupts due to high temp alarm.
+                        */
+                       mutex_lock(&ptherm_zone->update_lock);
+                       disable_irq_nosync(irq);
+                       ptherm_zone->irq_masked[i] = true;
+                       mutex_unlock(&ptherm_zone->update_lock);
+                       thermal_zone_device_update(ptherm_zone->tzd[i]);
+                       /* When the timer expires, interrupt is re-enabled */
+                       mod_delayed_work(system_freezable_wq,
+                                       &ptherm_zone->timeout_work,
+                                       msecs_to_jiffies(200));
+                       dev_info(ptherm_zone->dev,
+                                "Thermal alarm, current temp %ldmC sensor %d\n",
+                                ptherm_zone->cur_temp[i], i);
+               }
+       }
+       return IRQ_HANDLED;
+}
+
+static int as3722_thermal_probe(struct platform_device *pdev)
+{
+       struct as3722 *as3722 = dev_get_drvdata(pdev->dev.parent);
+       struct as3722_therm_zone *ptherm_zone;
+       int i, ret;
+
+       ptherm_zone = devm_kzalloc(&pdev->dev, sizeof(*ptherm_zone),
+                                          GFP_KERNEL);
+
+       if (!ptherm_zone) {
+               dev_err(&pdev->dev, "No available free memory\n");
+               return -ENOMEM;
+       }
+
+       ptherm_zone->dev = &pdev->dev;
+       ptherm_zone->as3722 = as3722;
+       mutex_init(&ptherm_zone->update_lock);
+       INIT_DELAYED_WORK(&ptherm_zone->timeout_work, as3722_timeout_work);
+       platform_set_drvdata(pdev, ptherm_zone);
+       ptherm_zone->adc_ch[SD0] = iio_channel_get(as3722->dev, "sd0");
+       if (IS_ERR(ptherm_zone->adc_ch[SD0])) {
+               dev_err(&pdev->dev, "%s: Failed to get channel %s, %ld\n",
+                       __func__, "sd0",
+                       PTR_ERR(ptherm_zone->adc_ch[SD0]));
+               return PTR_ERR(ptherm_zone->adc_ch[SD0]);
+       }
+       ptherm_zone->tzd[SD0] =
+               thermal_zone_of_sensor_register(as3722->dev, SD0, ptherm_zone,
+                                               as3722_therm_get_sd0_temp,
+                                               NULL);
+       if (IS_ERR(ptherm_zone->tzd[SD0])) {
+               ret = PTR_ERR(ptherm_zone->tzd[SD0]);
+               goto err;
+       }
+       ptherm_zone->adc_ch[SD1] = iio_channel_get(as3722->dev, "sd1");
+       if (IS_ERR(ptherm_zone->adc_ch[SD1])) {
+               dev_err(&pdev->dev, "%s: Failed to get channel %s, %ld\n",
+                       __func__, "sd1",
+                       PTR_ERR(ptherm_zone->adc_ch[SD1]));
+               return PTR_ERR(ptherm_zone->adc_ch[SD1]);
+       }
+       ptherm_zone->tzd[SD1] =
+               thermal_zone_of_sensor_register(as3722->dev, SD1, ptherm_zone,
+                                               as3722_therm_get_sd1_temp,
+                                               NULL);
+       if (IS_ERR(ptherm_zone->tzd[SD1])) {
+               ret = PTR_ERR(ptherm_zone->tzd[SD1]);
+               goto err;
+       }
+       ptherm_zone->adc_ch[SD6] = iio_channel_get(as3722->dev, "sd6");
+       if (IS_ERR(ptherm_zone->adc_ch[SD6])) {
+               dev_err(&pdev->dev, "%s: Failed to get channel %s, %ld\n",
+                       __func__, "sd6",
+                       PTR_ERR(ptherm_zone->adc_ch[SD6]));
+               return PTR_ERR(ptherm_zone->adc_ch[SD6]);
+       }
+       ptherm_zone->tzd[SD6] =
+               thermal_zone_of_sensor_register(as3722->dev, SD6, ptherm_zone,
+                                               as3722_therm_get_sd6_temp,
+                                               NULL);
+       if (IS_ERR(ptherm_zone->tzd[SD6])) {
+               ret = PTR_ERR(ptherm_zone->tzd[SD6]);
+               goto err;
+       }
+
+       for (i = 0; i < SD_MAX; i++) {
+               as3722_therm_update_limits(ptherm_zone, i);
+               if (thermal_zone_get_temp(ptherm_zone->tzd[i],
+                                       &ptherm_zone->cur_temp[i]))
+                       ptherm_zone->cur_temp[i] = 25000;
+               ptherm_zone->irq[i] = platform_get_irq(pdev, i);
+               ret = devm_request_threaded_irq(&pdev->dev, ptherm_zone->irq[i],
+                               NULL, as3722_thermal_irq, IRQF_ONESHOT,
+                               dev_name(&pdev->dev), ptherm_zone);
+               if (ret < 0) {
+                       dev_err(&pdev->dev, "Request irq %d failed: %d\nn",
+                               ptherm_zone->irq[i], ret);
+                       goto err;
+               }
+       }
+       return 0;
+
+err:
+       cancel_delayed_work_sync(&ptherm_zone->timeout_work);
+       for (i = 0; i < SD_MAX; i++) {
+               if (!IS_ERR(ptherm_zone->tzd[i]))
+                       thermal_zone_of_sensor_unregister(as3722->dev,
+                                                       ptherm_zone->tzd[i]);
+       }
+       return ret;
+}
+
+static int as3722_thermal_remove(struct platform_device *pdev)
+{
+       struct as3722_therm_zone *ptherm_zone = platform_get_drvdata(pdev);
+       struct as3722 *as3722 = dev_get_drvdata(pdev->dev.parent);
+       int i;
+
+       cancel_delayed_work_sync(&ptherm_zone->timeout_work);
+       for (i = 0; i < SD_MAX; i++)
+               thermal_zone_of_sensor_unregister(as3722->dev,
+                                               ptherm_zone->tzd[i]);
+       return 0;
+}
+
+static struct platform_driver as3722_thermal_driver = {
+       .probe = as3722_thermal_probe,
+       .remove = as3722_thermal_remove,
+       .driver = {
+               .name = "as3722-thermal",
+               .owner = THIS_MODULE,
+       },
+};
+module_platform_driver(as3722_thermal_driver);
+
+MODULE_DESCRIPTION("AS3722 Thermal driver");
+MODULE_AUTHOR("Bibek Basu <bbasu@nvidia.com>");
+MODULE_ALIAS("platform:as3722-thermal");
+MODULE_LICENSE("GPL v2");