diff options
author | Luka Perkov <luka@openwrt.org> | 2013-09-27 10:49:28 +0000 |
---|---|---|
committer | Luka Perkov <luka@openwrt.org> | 2013-09-27 10:49:28 +0000 |
commit | 8ec28437c30fcbc0e9be3ba4ec5dec6a7dae4cd9 (patch) | |
tree | 52cc07e7e7116e0bcf8262b3857e24f889d52e61 /target/linux/imx6/patches-3.10/0015-thermal-add-imx-thermal-driver-support.patch | |
parent | 9c8a5ef1153e3402d8e8c69715bcfdef55c1ea4a (diff) | |
download | upstream-8ec28437c30fcbc0e9be3ba4ec5dec6a7dae4cd9.tar.gz upstream-8ec28437c30fcbc0e9be3ba4ec5dec6a7dae4cd9.tar.bz2 upstream-8ec28437c30fcbc0e9be3ba4ec5dec6a7dae4cd9.zip |
imx6: backport thermal driver
Signed-off-by: Luka Perkov <luka@openwrt.org>
SVN-Revision: 38229
Diffstat (limited to 'target/linux/imx6/patches-3.10/0015-thermal-add-imx-thermal-driver-support.patch')
-rw-r--r-- | target/linux/imx6/patches-3.10/0015-thermal-add-imx-thermal-driver-support.patch | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/target/linux/imx6/patches-3.10/0015-thermal-add-imx-thermal-driver-support.patch b/target/linux/imx6/patches-3.10/0015-thermal-add-imx-thermal-driver-support.patch new file mode 100644 index 0000000000..bd20d2d25c --- /dev/null +++ b/target/linux/imx6/patches-3.10/0015-thermal-add-imx-thermal-driver-support.patch @@ -0,0 +1,490 @@ +From ca3de46b50809000b5ba708634e26ad979a4a63a Mon Sep 17 00:00:00 2001 +From: Shawn Guo <shawn.guo@linaro.org> +Date: Mon, 24 Jun 2013 14:30:44 +0800 +Subject: [PATCH] thermal: add imx thermal driver support + +This is based on the initial imx thermal work done by +Rob Lee <rob.lee@linaro.org> (Not sure if the email address is still +valid). Since he is no longer interested in the work and I have +rewritten a significant amount of the code, I just took the authorship +over from him. + +It adds the imx thermal support using Temperature Monitor (TEMPMON) +block found on some Freescale i.MX SoCs. The driver uses syscon regmap +interface to access TEMPMON control registers and calibration data, and +supports cpufreq as the cooling device. + +Signed-off-by: Shawn Guo <shawn.guo@linaro.org> +Signed-off-by: Eduardo Valentin <eduardo.valentin@ti.com> +--- + .../devicetree/bindings/thermal/imx-thermal.txt | 17 + + drivers/thermal/Kconfig | 11 + + drivers/thermal/Makefile | 1 + + drivers/thermal/imx_thermal.c | 397 +++++++++++++++++++++ + 4 files changed, 426 insertions(+) + create mode 100644 Documentation/devicetree/bindings/thermal/imx-thermal.txt + create mode 100644 drivers/thermal/imx_thermal.c + +diff --git a/Documentation/devicetree/bindings/thermal/imx-thermal.txt b/Documentation/devicetree/bindings/thermal/imx-thermal.txt +new file mode 100644 +index 0000000..541c25e +--- /dev/null ++++ b/Documentation/devicetree/bindings/thermal/imx-thermal.txt +@@ -0,0 +1,17 @@ ++* Temperature Monitor (TEMPMON) on Freescale i.MX SoCs ++ ++Required properties: ++- compatible : "fsl,imx6q-thermal" ++- fsl,tempmon : phandle pointer to system controller that contains TEMPMON ++ control registers, e.g. ANATOP on imx6q. ++- fsl,tempmon-data : phandle pointer to fuse controller that contains TEMPMON ++ calibration data, e.g. OCOTP on imx6q. The details about calibration data ++ can be found in SoC Reference Manual. ++ ++Example: ++ ++tempmon { ++ compatible = "fsl,imx6q-tempmon"; ++ fsl,tempmon = <&anatop>; ++ fsl,tempmon-data = <&ocotp>; ++}; +diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig +index e988c81..69eed55 100644 +--- a/drivers/thermal/Kconfig ++++ b/drivers/thermal/Kconfig +@@ -91,6 +91,17 @@ config THERMAL_EMULATION + because userland can easily disable the thermal policy by simply + flooding this sysfs node with low temperature values. + ++config IMX_THERMAL ++ tristate "Temperature sensor driver for Freescale i.MX SoCs" ++ depends on CPU_THERMAL ++ depends on MFD_SYSCON ++ depends on OF ++ help ++ Support for Temperature Monitor (TEMPMON) found on Freescale i.MX SoCs. ++ It supports one critical trip point and one passive trip point. The ++ cpufreq is used as the cooling device to throttle CPUs when the ++ passive trip is crossed. ++ + config SPEAR_THERMAL + bool "SPEAr thermal sensor driver" + depends on PLAT_SPEAR +diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile +index 67184a2..dff19c6 100644 +--- a/drivers/thermal/Makefile ++++ b/drivers/thermal/Makefile +@@ -21,6 +21,7 @@ obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o + obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o + obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o + obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o ++obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o + obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o + obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o + obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o +diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c +new file mode 100644 +index 0000000..d16c33c +--- /dev/null ++++ b/drivers/thermal/imx_thermal.c +@@ -0,0 +1,397 @@ ++/* ++ * Copyright 2013 Freescale Semiconductor, Inc. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ * ++ */ ++ ++#include <linux/cpu_cooling.h> ++#include <linux/cpufreq.h> ++#include <linux/delay.h> ++#include <linux/device.h> ++#include <linux/init.h> ++#include <linux/io.h> ++#include <linux/kernel.h> ++#include <linux/mfd/syscon.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/platform_device.h> ++#include <linux/regmap.h> ++#include <linux/slab.h> ++#include <linux/thermal.h> ++#include <linux/types.h> ++ ++#define REG_SET 0x4 ++#define REG_CLR 0x8 ++#define REG_TOG 0xc ++ ++#define MISC0 0x0150 ++#define MISC0_REFTOP_SELBIASOFF (1 << 3) ++ ++#define TEMPSENSE0 0x0180 ++#define TEMPSENSE0_TEMP_CNT_SHIFT 8 ++#define TEMPSENSE0_TEMP_CNT_MASK (0xfff << TEMPSENSE0_TEMP_CNT_SHIFT) ++#define TEMPSENSE0_FINISHED (1 << 2) ++#define TEMPSENSE0_MEASURE_TEMP (1 << 1) ++#define TEMPSENSE0_POWER_DOWN (1 << 0) ++ ++#define TEMPSENSE1 0x0190 ++#define TEMPSENSE1_MEASURE_FREQ 0xffff ++ ++#define OCOTP_ANA1 0x04e0 ++ ++/* The driver supports 1 passive trip point and 1 critical trip point */ ++enum imx_thermal_trip { ++ IMX_TRIP_PASSIVE, ++ IMX_TRIP_CRITICAL, ++ IMX_TRIP_NUM, ++}; ++ ++/* ++ * It defines the temperature in millicelsius for passive trip point ++ * that will trigger cooling action when crossed. ++ */ ++#define IMX_TEMP_PASSIVE 85000 ++ ++/* ++ * The maximum die temperature on imx parts is 105C, let's give some cushion ++ * for noise and possible temperature rise between measurements. ++ */ ++#define IMX_TEMP_CRITICAL 100000 ++ ++#define IMX_POLLING_DELAY 2000 /* millisecond */ ++#define IMX_PASSIVE_DELAY 1000 ++ ++struct imx_thermal_data { ++ struct thermal_zone_device *tz; ++ struct thermal_cooling_device *cdev; ++ enum thermal_device_mode mode; ++ struct regmap *tempmon; ++ int c1, c2; /* See formula in imx_get_sensor_data() */ ++}; ++ ++static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp) ++{ ++ struct imx_thermal_data *data = tz->devdata; ++ struct regmap *map = data->tempmon; ++ static unsigned long last_temp; ++ unsigned int n_meas; ++ u32 val; ++ ++ /* ++ * Every time we measure the temperature, we will power on the ++ * temperature sensor, enable measurements, take a reading, ++ * disable measurements, power off the temperature sensor. ++ */ ++ regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); ++ regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); ++ ++ /* ++ * According to the temp sensor designers, it may require up to ~17us ++ * to complete a measurement. ++ */ ++ usleep_range(20, 50); ++ ++ regmap_read(map, TEMPSENSE0, &val); ++ regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); ++ regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); ++ ++ if ((val & TEMPSENSE0_FINISHED) == 0) { ++ dev_dbg(&tz->device, "temp measurement never finished\n"); ++ return -EAGAIN; ++ } ++ ++ n_meas = (val & TEMPSENSE0_TEMP_CNT_MASK) >> TEMPSENSE0_TEMP_CNT_SHIFT; ++ ++ /* See imx_get_sensor_data() for formula derivation */ ++ *temp = data->c2 + data->c1 * n_meas; ++ ++ if (*temp != last_temp) { ++ dev_dbg(&tz->device, "millicelsius: %ld\n", *temp); ++ last_temp = *temp; ++ } ++ ++ return 0; ++} ++ ++static int imx_get_mode(struct thermal_zone_device *tz, ++ enum thermal_device_mode *mode) ++{ ++ struct imx_thermal_data *data = tz->devdata; ++ ++ *mode = data->mode; ++ ++ return 0; ++} ++ ++static int imx_set_mode(struct thermal_zone_device *tz, ++ enum thermal_device_mode mode) ++{ ++ struct imx_thermal_data *data = tz->devdata; ++ ++ if (mode == THERMAL_DEVICE_ENABLED) { ++ tz->polling_delay = IMX_POLLING_DELAY; ++ tz->passive_delay = IMX_PASSIVE_DELAY; ++ } else { ++ tz->polling_delay = 0; ++ tz->passive_delay = 0; ++ } ++ ++ data->mode = mode; ++ thermal_zone_device_update(tz); ++ ++ return 0; ++} ++ ++static int imx_get_trip_type(struct thermal_zone_device *tz, int trip, ++ enum thermal_trip_type *type) ++{ ++ *type = (trip == IMX_TRIP_PASSIVE) ? THERMAL_TRIP_PASSIVE : ++ THERMAL_TRIP_CRITICAL; ++ return 0; ++} ++ ++static int imx_get_crit_temp(struct thermal_zone_device *tz, ++ unsigned long *temp) ++{ ++ *temp = IMX_TEMP_CRITICAL; ++ return 0; ++} ++ ++static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip, ++ unsigned long *temp) ++{ ++ *temp = (trip == IMX_TRIP_PASSIVE) ? IMX_TEMP_PASSIVE : ++ IMX_TEMP_CRITICAL; ++ return 0; ++} ++ ++static int imx_bind(struct thermal_zone_device *tz, ++ struct thermal_cooling_device *cdev) ++{ ++ int ret; ++ ++ ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev, ++ THERMAL_NO_LIMIT, ++ THERMAL_NO_LIMIT); ++ if (ret) { ++ dev_err(&tz->device, ++ "binding zone %s with cdev %s failed:%d\n", ++ tz->type, cdev->type, ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int imx_unbind(struct thermal_zone_device *tz, ++ struct thermal_cooling_device *cdev) ++{ ++ int ret; ++ ++ ret = thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev); ++ if (ret) { ++ dev_err(&tz->device, ++ "unbinding zone %s with cdev %s failed:%d\n", ++ tz->type, cdev->type, ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct thermal_zone_device_ops imx_tz_ops = { ++ .bind = imx_bind, ++ .unbind = imx_unbind, ++ .get_temp = imx_get_temp, ++ .get_mode = imx_get_mode, ++ .set_mode = imx_set_mode, ++ .get_trip_type = imx_get_trip_type, ++ .get_trip_temp = imx_get_trip_temp, ++ .get_crit_temp = imx_get_crit_temp, ++}; ++ ++static int imx_get_sensor_data(struct platform_device *pdev) ++{ ++ struct imx_thermal_data *data = platform_get_drvdata(pdev); ++ struct regmap *map; ++ int t1, t2, n1, n2; ++ int ret; ++ u32 val; ++ ++ map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, ++ "fsl,tempmon-data"); ++ if (IS_ERR(map)) { ++ ret = PTR_ERR(map); ++ dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret); ++ return ret; ++ } ++ ++ ret = regmap_read(map, OCOTP_ANA1, &val); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret); ++ return ret; ++ } ++ ++ if (val == 0 || val == ~0) { ++ dev_err(&pdev->dev, "invalid sensor calibration data\n"); ++ return -EINVAL; ++ } ++ ++ /* ++ * Sensor data layout: ++ * [31:20] - sensor value @ 25C ++ * [19:8] - sensor value of hot ++ * [7:0] - hot temperature value ++ */ ++ n1 = val >> 20; ++ n2 = (val & 0xfff00) >> 8; ++ t2 = val & 0xff; ++ t1 = 25; /* t1 always 25C */ ++ ++ /* ++ * Derived from linear interpolation, ++ * Tmeas = T2 + (Nmeas - N2) * (T1 - T2) / (N1 - N2) ++ * We want to reduce this down to the minimum computation necessary ++ * for each temperature read. Also, we want Tmeas in millicelsius ++ * and we don't want to lose precision from integer division. So... ++ * milli_Tmeas = 1000 * T2 + 1000 * (Nmeas - N2) * (T1 - T2) / (N1 - N2) ++ * Let constant c1 = 1000 * (T1 - T2) / (N1 - N2) ++ * milli_Tmeas = (1000 * T2) + c1 * (Nmeas - N2) ++ * milli_Tmeas = (1000 * T2) + (c1 * Nmeas) - (c1 * N2) ++ * Let constant c2 = (1000 * T2) - (c1 * N2) ++ * milli_Tmeas = c2 + (c1 * Nmeas) ++ */ ++ data->c1 = 1000 * (t1 - t2) / (n1 - n2); ++ data->c2 = 1000 * t2 - data->c1 * n2; ++ ++ return 0; ++} ++ ++static int imx_thermal_probe(struct platform_device *pdev) ++{ ++ struct imx_thermal_data *data; ++ struct cpumask clip_cpus; ++ struct regmap *map; ++ int ret; ++ ++ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon"); ++ if (IS_ERR(map)) { ++ ret = PTR_ERR(map); ++ dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret); ++ return ret; ++ } ++ data->tempmon = map; ++ ++ platform_set_drvdata(pdev, data); ++ ++ ret = imx_get_sensor_data(pdev); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to get sensor data\n"); ++ return ret; ++ } ++ ++ /* Make sure sensor is in known good state for measurements */ ++ regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); ++ regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); ++ regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ); ++ regmap_write(map, MISC0 + REG_SET, MISC0_REFTOP_SELBIASOFF); ++ regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); ++ ++ cpumask_set_cpu(0, &clip_cpus); ++ data->cdev = cpufreq_cooling_register(&clip_cpus); ++ if (IS_ERR(data->cdev)) { ++ ret = PTR_ERR(data->cdev); ++ dev_err(&pdev->dev, ++ "failed to register cpufreq cooling device: %d\n", ret); ++ return ret; ++ } ++ ++ data->tz = thermal_zone_device_register("imx_thermal_zone", ++ IMX_TRIP_NUM, 0, data, ++ &imx_tz_ops, NULL, ++ IMX_PASSIVE_DELAY, ++ IMX_POLLING_DELAY); ++ if (IS_ERR(data->tz)) { ++ ret = PTR_ERR(data->tz); ++ dev_err(&pdev->dev, ++ "failed to register thermal zone device %d\n", ret); ++ cpufreq_cooling_unregister(data->cdev); ++ return ret; ++ } ++ ++ data->mode = THERMAL_DEVICE_ENABLED; ++ ++ return 0; ++} ++ ++static int imx_thermal_remove(struct platform_device *pdev) ++{ ++ struct imx_thermal_data *data = platform_get_drvdata(pdev); ++ ++ thermal_zone_device_unregister(data->tz); ++ cpufreq_cooling_unregister(data->cdev); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_PM_SLEEP ++static int imx_thermal_suspend(struct device *dev) ++{ ++ struct imx_thermal_data *data = dev_get_drvdata(dev); ++ struct regmap *map = data->tempmon; ++ u32 val; ++ ++ regmap_read(map, TEMPSENSE0, &val); ++ if ((val & TEMPSENSE0_POWER_DOWN) == 0) { ++ /* ++ * If a measurement is taking place, wait for a long enough ++ * time for it to finish, and then check again. If it still ++ * does not finish, something must go wrong. ++ */ ++ udelay(50); ++ regmap_read(map, TEMPSENSE0, &val); ++ if ((val & TEMPSENSE0_POWER_DOWN) == 0) ++ return -ETIMEDOUT; ++ } ++ ++ return 0; ++} ++ ++static int imx_thermal_resume(struct device *dev) ++{ ++ /* Nothing to do for now */ ++ return 0; ++} ++#endif ++ ++static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops, ++ imx_thermal_suspend, imx_thermal_resume); ++ ++static const struct of_device_id of_imx_thermal_match[] = { ++ { .compatible = "fsl,imx6q-tempmon", }, ++ { /* end */ } ++}; ++ ++static struct platform_driver imx_thermal = { ++ .driver = { ++ .name = "imx_thermal", ++ .owner = THIS_MODULE, ++ .pm = &imx_thermal_pm_ops, ++ .of_match_table = of_imx_thermal_match, ++ }, ++ .probe = imx_thermal_probe, ++ .remove = imx_thermal_remove, ++}; ++module_platform_driver(imx_thermal); ++ ++MODULE_AUTHOR("Freescale Semiconductor, Inc."); ++MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:imx-thermal"); +-- +1.8.4 + |