]> rtime.felk.cvut.cz Git - hercules2020/nv-tegra/linux-4.4.git/commitdiff
thermal: as3722 thermal alarm driver
authorBibek Basu <bbasu@nvidia.com>
Tue, 17 Jun 2014 05:55:31 +0000 (11:25 +0530)
committerDan Willemsen <dwillemsen@nvidia.com>
Wed, 18 Mar 2015 19:18:38 +0000 (12:18 -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/thermal/Kconfig
drivers/thermal/Makefile
drivers/thermal/as3722_thermal.c [new file with mode: 0644]

index 692cd76e09c262b5d838b7f537e50ed23805c7ad..4eb2b866b84fdcb31f4d0f7fde212bb1a3e0ec36 100644 (file)
@@ -298,6 +298,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 421f5b5f74b4b7f6400df63d6354d52eebf96c1a..36afa69d1f24ead0c900b1659cb6a86c50a6442c 100644 (file)
@@ -40,4 +40,5 @@ 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");