All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] thermal: add imx thermal driver support
@ 2013-06-04  7:13 ` Shawn Guo
  0 siblings, 0 replies; 14+ messages in thread
From: Shawn Guo @ 2013-06-04  7:13 UTC (permalink / raw)
  To: linux-pm; +Cc: Zhang Rui, Eduardo Valentin, linux-arm-kernel, Shawn Guo

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>
---
 .../devicetree/bindings/thermal/imx-thermal.txt    |   14 +
 drivers/thermal/Kconfig                            |    8 +
 drivers/thermal/Makefile                           |    1 +
 drivers/thermal/imx_thermal.c                      |  421 ++++++++++++++++++++
 4 files changed, 444 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..c606e2b
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/imx-thermal.txt
@@ -0,0 +1,14 @@
+* Temperature Monitor (TEMPMON) on Freescale i.MX SoCs
+
+Required properties:
+- compatible : "fsl,imx6q-thermal"
+- fsl,tempmon : phandle pointer to TEMPMON control registers
+- fsl,tempmon-data : phandle pointer to TEMPMON calibration data
+
+Example:
+
+tempmon {
+	compatible = "fsl,imx6q-tempmon";
+	fsl,tempmon = <&anatop>;
+	fsl,tempmon-data = <&ocotp>;
+};
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 5e3c025..935fcbe 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -91,6 +91,14 @@ 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.
+
 config SPEAR_THERMAL
 	bool "SPEAr thermal sensor driver"
 	depends on PLAT_SPEAR
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index c054d41..6910b2d 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
 
diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c
new file mode 100644
index 0000000..bdfcadb
--- /dev/null
+++ b/drivers/thermal/imx_thermal.c
@@ -0,0 +1,421 @@
+/*
+ * 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 active trip point and 1 critical trip point */
+enum imx_thermal_trip {
+	IMX_TRIP_ACTIVE,
+	IMX_TRIP_CRITICAL,
+	IMX_TRIP_NUM,
+};
+
+/*
+ * It defines the temperature in millicelsius for active trip point
+ * that will trigger cooling action when crossed.
+ */
+#define IMX_TEMP_ACTIVE			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_THERMAL_POLLING_INTERVAL	1000 /* millisecond */
+
+struct imx_thermal_data {
+	struct thermal_zone_device *tz;
+	struct thermal_cooling_device *cdev;
+	enum thermal_device_mode mode;
+	struct regmap *tempmon;
+	bool meas_suspended;
+	int c1, c2; /* See forumla 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.  But this timing isn't checked on every
+	 * part nor is it specified in the datasheet, so sleeping at least 1ms
+	 * should provide plenty of time.  Sleeping longer than 1ms is ok so no
+	 * need for usleep_range.
+	 */
+	msleep(1);
+
+	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 forumla 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;
+
+	tz->polling_delay = (mode == THERMAL_DEVICE_ENABLED) ?
+				IMX_THERMAL_POLLING_INTERVAL : 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_ACTIVE) ? THERMAL_TRIP_ACTIVE :
+					    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_ACTIVE) ? IMX_TEMP_ACTIVE :
+					    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_ACTIVE, 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_ACTIVE, 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, 0,
+						IMX_THERMAL_POLLING_INTERVAL);
+	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);
+
+	/* Was a measurement taking place?  If not, nothing to do. */
+	if (val & TEMPSENSE0_POWER_DOWN)
+		return 0;
+
+	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
+	regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
+
+	data->meas_suspended = true;
+
+	return 0;
+}
+
+static int imx_thermal_resume(struct device *dev)
+{
+	struct imx_thermal_data *data = dev_get_drvdata(dev);
+	struct regmap *map = data->tempmon;
+
+	/*
+	 * If a measurement was taking place while suspend, re-take the
+	 * measurement.
+	 */
+	if (data->meas_suspended) {
+		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.  But this timing
+		 * isn't checked on every part nor is it specified in the
+		 * datasheet, so delay 50us for timing margin.
+		 */
+		udelay(50);
+		data->meas_suspended = false;
+	}
+
+	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,
+};
+
+static int __init imx_thermal_init(void)
+{
+	return platform_driver_register(&imx_thermal);
+}
+late_initcall(imx_thermal_init);
+
+static void __exit imx_thermal_exit(void)
+{
+	platform_driver_unregister(&imx_thermal);
+}
+module_exit(imx_thermal_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-thermal");
-- 
1.7.9.5



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH] thermal: add imx thermal driver support
@ 2013-06-04  7:13 ` Shawn Guo
  0 siblings, 0 replies; 14+ messages in thread
From: Shawn Guo @ 2013-06-04  7:13 UTC (permalink / raw)
  To: linux-arm-kernel

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>
---
 .../devicetree/bindings/thermal/imx-thermal.txt    |   14 +
 drivers/thermal/Kconfig                            |    8 +
 drivers/thermal/Makefile                           |    1 +
 drivers/thermal/imx_thermal.c                      |  421 ++++++++++++++++++++
 4 files changed, 444 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..c606e2b
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/imx-thermal.txt
@@ -0,0 +1,14 @@
+* Temperature Monitor (TEMPMON) on Freescale i.MX SoCs
+
+Required properties:
+- compatible : "fsl,imx6q-thermal"
+- fsl,tempmon : phandle pointer to TEMPMON control registers
+- fsl,tempmon-data : phandle pointer to TEMPMON calibration data
+
+Example:
+
+tempmon {
+	compatible = "fsl,imx6q-tempmon";
+	fsl,tempmon = <&anatop>;
+	fsl,tempmon-data = <&ocotp>;
+};
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 5e3c025..935fcbe 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -91,6 +91,14 @@ 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.
+
 config SPEAR_THERMAL
 	bool "SPEAr thermal sensor driver"
 	depends on PLAT_SPEAR
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index c054d41..6910b2d 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
 
diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c
new file mode 100644
index 0000000..bdfcadb
--- /dev/null
+++ b/drivers/thermal/imx_thermal.c
@@ -0,0 +1,421 @@
+/*
+ * 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 active trip point and 1 critical trip point */
+enum imx_thermal_trip {
+	IMX_TRIP_ACTIVE,
+	IMX_TRIP_CRITICAL,
+	IMX_TRIP_NUM,
+};
+
+/*
+ * It defines the temperature in millicelsius for active trip point
+ * that will trigger cooling action when crossed.
+ */
+#define IMX_TEMP_ACTIVE			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_THERMAL_POLLING_INTERVAL	1000 /* millisecond */
+
+struct imx_thermal_data {
+	struct thermal_zone_device *tz;
+	struct thermal_cooling_device *cdev;
+	enum thermal_device_mode mode;
+	struct regmap *tempmon;
+	bool meas_suspended;
+	int c1, c2; /* See forumla 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.  But this timing isn't checked on every
+	 * part nor is it specified in the datasheet, so sleeping at least 1ms
+	 * should provide plenty of time.  Sleeping longer than 1ms is ok so no
+	 * need for usleep_range.
+	 */
+	msleep(1);
+
+	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 forumla 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;
+
+	tz->polling_delay = (mode == THERMAL_DEVICE_ENABLED) ?
+				IMX_THERMAL_POLLING_INTERVAL : 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_ACTIVE) ? THERMAL_TRIP_ACTIVE :
+					    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_ACTIVE) ? IMX_TEMP_ACTIVE :
+					    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_ACTIVE, 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_ACTIVE, 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, 0,
+						IMX_THERMAL_POLLING_INTERVAL);
+	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);
+
+	/* Was a measurement taking place?  If not, nothing to do. */
+	if (val & TEMPSENSE0_POWER_DOWN)
+		return 0;
+
+	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
+	regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
+
+	data->meas_suspended = true;
+
+	return 0;
+}
+
+static int imx_thermal_resume(struct device *dev)
+{
+	struct imx_thermal_data *data = dev_get_drvdata(dev);
+	struct regmap *map = data->tempmon;
+
+	/*
+	 * If a measurement was taking place while suspend, re-take the
+	 * measurement.
+	 */
+	if (data->meas_suspended) {
+		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.  But this timing
+		 * isn't checked on every part nor is it specified in the
+		 * datasheet, so delay 50us for timing margin.
+		 */
+		udelay(50);
+		data->meas_suspended = false;
+	}
+
+	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,
+};
+
+static int __init imx_thermal_init(void)
+{
+	return platform_driver_register(&imx_thermal);
+}
+late_initcall(imx_thermal_init);
+
+static void __exit imx_thermal_exit(void)
+{
+	platform_driver_unregister(&imx_thermal);
+}
+module_exit(imx_thermal_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-thermal");
-- 
1.7.9.5

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH] ARM: dts: imx: add tempmon node for imx6q thermal support
  2013-06-04  7:13 ` Shawn Guo
  (?)
@ 2013-06-04  7:17 ` Shawn Guo
  2013-07-09 14:59   ` Stefano Babic
  -1 siblings, 1 reply; 14+ messages in thread
From: Shawn Guo @ 2013-06-04  7:17 UTC (permalink / raw)
  To: linux-arm-kernel

Mark ocotp as a syscon node and add tempmon for imx6q thermal support.

Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
---
 arch/arm/boot/dts/imx6qdl.dtsi |   11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/arch/arm/boot/dts/imx6qdl.dtsi b/arch/arm/boot/dts/imx6qdl.dtsi
index 9e8296e..13e1d7f 100644
--- a/arch/arm/boot/dts/imx6qdl.dtsi
+++ b/arch/arm/boot/dts/imx6qdl.dtsi
@@ -489,6 +489,13 @@
 				};
 			};
 
+			tempmon: tempmon {
+				compatible = "fsl,imx6q-tempmon";
+				interrupts = <0 49 0x04>;
+				fsl,tempmon = <&anatop>;
+				fsl,tempmon-data = <&ocotp>;
+			};
+
 			usbphy1: usbphy at 020c9000 {
 				compatible = "fsl,imx6q-usbphy", "fsl,imx23-usbphy";
 				reg = <0x020c9000 0x1000>;
@@ -747,8 +754,8 @@
 				interrupts = <0 14 0x04>;
 			};
 
-			ocotp at 021bc000 {
-				compatible = "fsl,imx6q-ocotp";
+			ocotp: ocotp at 021bc000 {
+				compatible = "fsl,imx6q-ocotp", "syscon";
 				reg = <0x021bc000 0x4000>;
 			};
 
-- 
1.7.9.5

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH] thermal: add imx thermal driver support
  2013-06-04  7:13 ` Shawn Guo
@ 2013-06-13  0:16   ` Shawn Guo
  -1 siblings, 0 replies; 14+ messages in thread
From: Shawn Guo @ 2013-06-13  0:16 UTC (permalink / raw)
  To: Zhang Rui; +Cc: Eduardo Valentin, linux-arm-kernel, linux-pm

On Tue, Jun 04, 2013 at 03:13:28PM +0800, Shawn Guo wrote:
> 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>

Hi Rui,

Do you have you any comment on this patch?

Shawn

> ---
>  .../devicetree/bindings/thermal/imx-thermal.txt    |   14 +
>  drivers/thermal/Kconfig                            |    8 +
>  drivers/thermal/Makefile                           |    1 +
>  drivers/thermal/imx_thermal.c                      |  421 ++++++++++++++++++++
>  4 files changed, 444 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..c606e2b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/thermal/imx-thermal.txt
> @@ -0,0 +1,14 @@
> +* Temperature Monitor (TEMPMON) on Freescale i.MX SoCs
> +
> +Required properties:
> +- compatible : "fsl,imx6q-thermal"
> +- fsl,tempmon : phandle pointer to TEMPMON control registers
> +- fsl,tempmon-data : phandle pointer to TEMPMON calibration data
> +
> +Example:
> +
> +tempmon {
> +	compatible = "fsl,imx6q-tempmon";
> +	fsl,tempmon = <&anatop>;
> +	fsl,tempmon-data = <&ocotp>;
> +};
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 5e3c025..935fcbe 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -91,6 +91,14 @@ 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.
> +
>  config SPEAR_THERMAL
>  	bool "SPEAr thermal sensor driver"
>  	depends on PLAT_SPEAR
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index c054d41..6910b2d 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
>  
> diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c
> new file mode 100644
> index 0000000..bdfcadb
> --- /dev/null
> +++ b/drivers/thermal/imx_thermal.c
> @@ -0,0 +1,421 @@
> +/*
> + * 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 active trip point and 1 critical trip point */
> +enum imx_thermal_trip {
> +	IMX_TRIP_ACTIVE,
> +	IMX_TRIP_CRITICAL,
> +	IMX_TRIP_NUM,
> +};
> +
> +/*
> + * It defines the temperature in millicelsius for active trip point
> + * that will trigger cooling action when crossed.
> + */
> +#define IMX_TEMP_ACTIVE			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_THERMAL_POLLING_INTERVAL	1000 /* millisecond */
> +
> +struct imx_thermal_data {
> +	struct thermal_zone_device *tz;
> +	struct thermal_cooling_device *cdev;
> +	enum thermal_device_mode mode;
> +	struct regmap *tempmon;
> +	bool meas_suspended;
> +	int c1, c2; /* See forumla 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.  But this timing isn't checked on every
> +	 * part nor is it specified in the datasheet, so sleeping at least 1ms
> +	 * should provide plenty of time.  Sleeping longer than 1ms is ok so no
> +	 * need for usleep_range.
> +	 */
> +	msleep(1);
> +
> +	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 forumla 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;
> +
> +	tz->polling_delay = (mode == THERMAL_DEVICE_ENABLED) ?
> +				IMX_THERMAL_POLLING_INTERVAL : 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_ACTIVE) ? THERMAL_TRIP_ACTIVE :
> +					    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_ACTIVE) ? IMX_TEMP_ACTIVE :
> +					    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_ACTIVE, 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_ACTIVE, 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, 0,
> +						IMX_THERMAL_POLLING_INTERVAL);
> +	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);
> +
> +	/* Was a measurement taking place?  If not, nothing to do. */
> +	if (val & TEMPSENSE0_POWER_DOWN)
> +		return 0;
> +
> +	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
> +	regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
> +
> +	data->meas_suspended = true;
> +
> +	return 0;
> +}
> +
> +static int imx_thermal_resume(struct device *dev)
> +{
> +	struct imx_thermal_data *data = dev_get_drvdata(dev);
> +	struct regmap *map = data->tempmon;
> +
> +	/*
> +	 * If a measurement was taking place while suspend, re-take the
> +	 * measurement.
> +	 */
> +	if (data->meas_suspended) {
> +		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.  But this timing
> +		 * isn't checked on every part nor is it specified in the
> +		 * datasheet, so delay 50us for timing margin.
> +		 */
> +		udelay(50);
> +		data->meas_suspended = false;
> +	}
> +
> +	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,
> +};
> +
> +static int __init imx_thermal_init(void)
> +{
> +	return platform_driver_register(&imx_thermal);
> +}
> +late_initcall(imx_thermal_init);
> +
> +static void __exit imx_thermal_exit(void)
> +{
> +	platform_driver_unregister(&imx_thermal);
> +}
> +module_exit(imx_thermal_exit);
> +
> +MODULE_AUTHOR("Freescale Semiconductor, Inc.");
> +MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:imx-thermal");
> -- 
> 1.7.9.5
> 
> 

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH] thermal: add imx thermal driver support
@ 2013-06-13  0:16   ` Shawn Guo
  0 siblings, 0 replies; 14+ messages in thread
From: Shawn Guo @ 2013-06-13  0:16 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jun 04, 2013 at 03:13:28PM +0800, Shawn Guo wrote:
> 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>

Hi Rui,

Do you have you any comment on this patch?

Shawn

> ---
>  .../devicetree/bindings/thermal/imx-thermal.txt    |   14 +
>  drivers/thermal/Kconfig                            |    8 +
>  drivers/thermal/Makefile                           |    1 +
>  drivers/thermal/imx_thermal.c                      |  421 ++++++++++++++++++++
>  4 files changed, 444 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..c606e2b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/thermal/imx-thermal.txt
> @@ -0,0 +1,14 @@
> +* Temperature Monitor (TEMPMON) on Freescale i.MX SoCs
> +
> +Required properties:
> +- compatible : "fsl,imx6q-thermal"
> +- fsl,tempmon : phandle pointer to TEMPMON control registers
> +- fsl,tempmon-data : phandle pointer to TEMPMON calibration data
> +
> +Example:
> +
> +tempmon {
> +	compatible = "fsl,imx6q-tempmon";
> +	fsl,tempmon = <&anatop>;
> +	fsl,tempmon-data = <&ocotp>;
> +};
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 5e3c025..935fcbe 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -91,6 +91,14 @@ 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.
> +
>  config SPEAR_THERMAL
>  	bool "SPEAr thermal sensor driver"
>  	depends on PLAT_SPEAR
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index c054d41..6910b2d 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
>  
> diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c
> new file mode 100644
> index 0000000..bdfcadb
> --- /dev/null
> +++ b/drivers/thermal/imx_thermal.c
> @@ -0,0 +1,421 @@
> +/*
> + * 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 active trip point and 1 critical trip point */
> +enum imx_thermal_trip {
> +	IMX_TRIP_ACTIVE,
> +	IMX_TRIP_CRITICAL,
> +	IMX_TRIP_NUM,
> +};
> +
> +/*
> + * It defines the temperature in millicelsius for active trip point
> + * that will trigger cooling action when crossed.
> + */
> +#define IMX_TEMP_ACTIVE			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_THERMAL_POLLING_INTERVAL	1000 /* millisecond */
> +
> +struct imx_thermal_data {
> +	struct thermal_zone_device *tz;
> +	struct thermal_cooling_device *cdev;
> +	enum thermal_device_mode mode;
> +	struct regmap *tempmon;
> +	bool meas_suspended;
> +	int c1, c2; /* See forumla 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.  But this timing isn't checked on every
> +	 * part nor is it specified in the datasheet, so sleeping at least 1ms
> +	 * should provide plenty of time.  Sleeping longer than 1ms is ok so no
> +	 * need for usleep_range.
> +	 */
> +	msleep(1);
> +
> +	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 forumla 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;
> +
> +	tz->polling_delay = (mode == THERMAL_DEVICE_ENABLED) ?
> +				IMX_THERMAL_POLLING_INTERVAL : 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_ACTIVE) ? THERMAL_TRIP_ACTIVE :
> +					    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_ACTIVE) ? IMX_TEMP_ACTIVE :
> +					    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_ACTIVE, 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_ACTIVE, 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, 0,
> +						IMX_THERMAL_POLLING_INTERVAL);
> +	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);
> +
> +	/* Was a measurement taking place?  If not, nothing to do. */
> +	if (val & TEMPSENSE0_POWER_DOWN)
> +		return 0;
> +
> +	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
> +	regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
> +
> +	data->meas_suspended = true;
> +
> +	return 0;
> +}
> +
> +static int imx_thermal_resume(struct device *dev)
> +{
> +	struct imx_thermal_data *data = dev_get_drvdata(dev);
> +	struct regmap *map = data->tempmon;
> +
> +	/*
> +	 * If a measurement was taking place while suspend, re-take the
> +	 * measurement.
> +	 */
> +	if (data->meas_suspended) {
> +		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.  But this timing
> +		 * isn't checked on every part nor is it specified in the
> +		 * datasheet, so delay 50us for timing margin.
> +		 */
> +		udelay(50);
> +		data->meas_suspended = false;
> +	}
> +
> +	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,
> +};
> +
> +static int __init imx_thermal_init(void)
> +{
> +	return platform_driver_register(&imx_thermal);
> +}
> +late_initcall(imx_thermal_init);
> +
> +static void __exit imx_thermal_exit(void)
> +{
> +	platform_driver_unregister(&imx_thermal);
> +}
> +module_exit(imx_thermal_exit);
> +
> +MODULE_AUTHOR("Freescale Semiconductor, Inc.");
> +MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:imx-thermal");
> -- 
> 1.7.9.5
> 
> 

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] thermal: add imx thermal driver support
  2013-06-04  7:13 ` Shawn Guo
@ 2013-06-13  3:12   ` Zhang Rui
  -1 siblings, 0 replies; 14+ messages in thread
From: Zhang Rui @ 2013-06-13  3:12 UTC (permalink / raw)
  To: Shawn Guo; +Cc: linux-pm, Eduardo Valentin, linux-arm-kernel

On Tue, 2013-06-04 at 15:13 +0800, Shawn Guo wrote:
> 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.
> 
you're using the cpufrq_cooling to throttle cpus at 85C, right?
then you should register it as THERMAL_TRIP_PASSIVE instead of
THERMAL_TRIP_ACTIVE.

thanks,
rui
> Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
> ---
>  .../devicetree/bindings/thermal/imx-thermal.txt    |   14 +
>  drivers/thermal/Kconfig                            |    8 +
>  drivers/thermal/Makefile                           |    1 +
>  drivers/thermal/imx_thermal.c                      |  421 ++++++++++++++++++++
>  4 files changed, 444 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..c606e2b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/thermal/imx-thermal.txt
> @@ -0,0 +1,14 @@
> +* Temperature Monitor (TEMPMON) on Freescale i.MX SoCs
> +
> +Required properties:
> +- compatible : "fsl,imx6q-thermal"
> +- fsl,tempmon : phandle pointer to TEMPMON control registers
> +- fsl,tempmon-data : phandle pointer to TEMPMON calibration data
> +
> +Example:
> +
> +tempmon {
> +	compatible = "fsl,imx6q-tempmon";
> +	fsl,tempmon = <&anatop>;
> +	fsl,tempmon-data = <&ocotp>;
> +};
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 5e3c025..935fcbe 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -91,6 +91,14 @@ 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.
> +
>  config SPEAR_THERMAL
>  	bool "SPEAr thermal sensor driver"
>  	depends on PLAT_SPEAR
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index c054d41..6910b2d 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
>  
> diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c
> new file mode 100644
> index 0000000..bdfcadb
> --- /dev/null
> +++ b/drivers/thermal/imx_thermal.c
> @@ -0,0 +1,421 @@
> +/*
> + * 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 active trip point and 1 critical trip point */
> +enum imx_thermal_trip {
> +	IMX_TRIP_ACTIVE,
> +	IMX_TRIP_CRITICAL,
> +	IMX_TRIP_NUM,
> +};
> +
> +/*
> + * It defines the temperature in millicelsius for active trip point
> + * that will trigger cooling action when crossed.
> + */
> +#define IMX_TEMP_ACTIVE			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_THERMAL_POLLING_INTERVAL	1000 /* millisecond */
> +
> +struct imx_thermal_data {
> +	struct thermal_zone_device *tz;
> +	struct thermal_cooling_device *cdev;
> +	enum thermal_device_mode mode;
> +	struct regmap *tempmon;
> +	bool meas_suspended;
> +	int c1, c2; /* See forumla 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.  But this timing isn't checked on every
> +	 * part nor is it specified in the datasheet, so sleeping at least 1ms
> +	 * should provide plenty of time.  Sleeping longer than 1ms is ok so no
> +	 * need for usleep_range.
> +	 */
> +	msleep(1);
> +
> +	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 forumla 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;
> +
> +	tz->polling_delay = (mode == THERMAL_DEVICE_ENABLED) ?
> +				IMX_THERMAL_POLLING_INTERVAL : 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_ACTIVE) ? THERMAL_TRIP_ACTIVE :
> +					    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_ACTIVE) ? IMX_TEMP_ACTIVE :
> +					    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_ACTIVE, 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_ACTIVE, 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, 0,
> +						IMX_THERMAL_POLLING_INTERVAL);
> +	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);
> +
> +	/* Was a measurement taking place?  If not, nothing to do. */
> +	if (val & TEMPSENSE0_POWER_DOWN)
> +		return 0;
> +
> +	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
> +	regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
> +
> +	data->meas_suspended = true;
> +
> +	return 0;
> +}
> +
> +static int imx_thermal_resume(struct device *dev)
> +{
> +	struct imx_thermal_data *data = dev_get_drvdata(dev);
> +	struct regmap *map = data->tempmon;
> +
> +	/*
> +	 * If a measurement was taking place while suspend, re-take the
> +	 * measurement.
> +	 */
> +	if (data->meas_suspended) {
> +		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.  But this timing
> +		 * isn't checked on every part nor is it specified in the
> +		 * datasheet, so delay 50us for timing margin.
> +		 */
> +		udelay(50);
> +		data->meas_suspended = false;
> +	}
> +
> +	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,
> +};
> +
> +static int __init imx_thermal_init(void)
> +{
> +	return platform_driver_register(&imx_thermal);
> +}
> +late_initcall(imx_thermal_init);
> +
> +static void __exit imx_thermal_exit(void)
> +{
> +	platform_driver_unregister(&imx_thermal);
> +}
> +module_exit(imx_thermal_exit);
> +
> +MODULE_AUTHOR("Freescale Semiconductor, Inc.");
> +MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:imx-thermal");



^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH] thermal: add imx thermal driver support
@ 2013-06-13  3:12   ` Zhang Rui
  0 siblings, 0 replies; 14+ messages in thread
From: Zhang Rui @ 2013-06-13  3:12 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, 2013-06-04 at 15:13 +0800, Shawn Guo wrote:
> 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.
> 
you're using the cpufrq_cooling to throttle cpus at 85C, right?
then you should register it as THERMAL_TRIP_PASSIVE instead of
THERMAL_TRIP_ACTIVE.

thanks,
rui
> Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
> ---
>  .../devicetree/bindings/thermal/imx-thermal.txt    |   14 +
>  drivers/thermal/Kconfig                            |    8 +
>  drivers/thermal/Makefile                           |    1 +
>  drivers/thermal/imx_thermal.c                      |  421 ++++++++++++++++++++
>  4 files changed, 444 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..c606e2b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/thermal/imx-thermal.txt
> @@ -0,0 +1,14 @@
> +* Temperature Monitor (TEMPMON) on Freescale i.MX SoCs
> +
> +Required properties:
> +- compatible : "fsl,imx6q-thermal"
> +- fsl,tempmon : phandle pointer to TEMPMON control registers
> +- fsl,tempmon-data : phandle pointer to TEMPMON calibration data
> +
> +Example:
> +
> +tempmon {
> +	compatible = "fsl,imx6q-tempmon";
> +	fsl,tempmon = <&anatop>;
> +	fsl,tempmon-data = <&ocotp>;
> +};
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 5e3c025..935fcbe 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -91,6 +91,14 @@ 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.
> +
>  config SPEAR_THERMAL
>  	bool "SPEAr thermal sensor driver"
>  	depends on PLAT_SPEAR
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index c054d41..6910b2d 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
>  
> diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c
> new file mode 100644
> index 0000000..bdfcadb
> --- /dev/null
> +++ b/drivers/thermal/imx_thermal.c
> @@ -0,0 +1,421 @@
> +/*
> + * 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 active trip point and 1 critical trip point */
> +enum imx_thermal_trip {
> +	IMX_TRIP_ACTIVE,
> +	IMX_TRIP_CRITICAL,
> +	IMX_TRIP_NUM,
> +};
> +
> +/*
> + * It defines the temperature in millicelsius for active trip point
> + * that will trigger cooling action when crossed.
> + */
> +#define IMX_TEMP_ACTIVE			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_THERMAL_POLLING_INTERVAL	1000 /* millisecond */
> +
> +struct imx_thermal_data {
> +	struct thermal_zone_device *tz;
> +	struct thermal_cooling_device *cdev;
> +	enum thermal_device_mode mode;
> +	struct regmap *tempmon;
> +	bool meas_suspended;
> +	int c1, c2; /* See forumla 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.  But this timing isn't checked on every
> +	 * part nor is it specified in the datasheet, so sleeping at least 1ms
> +	 * should provide plenty of time.  Sleeping longer than 1ms is ok so no
> +	 * need for usleep_range.
> +	 */
> +	msleep(1);
> +
> +	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 forumla 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;
> +
> +	tz->polling_delay = (mode == THERMAL_DEVICE_ENABLED) ?
> +				IMX_THERMAL_POLLING_INTERVAL : 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_ACTIVE) ? THERMAL_TRIP_ACTIVE :
> +					    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_ACTIVE) ? IMX_TEMP_ACTIVE :
> +					    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_ACTIVE, 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_ACTIVE, 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, 0,
> +						IMX_THERMAL_POLLING_INTERVAL);
> +	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);
> +
> +	/* Was a measurement taking place?  If not, nothing to do. */
> +	if (val & TEMPSENSE0_POWER_DOWN)
> +		return 0;
> +
> +	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
> +	regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
> +
> +	data->meas_suspended = true;
> +
> +	return 0;
> +}
> +
> +static int imx_thermal_resume(struct device *dev)
> +{
> +	struct imx_thermal_data *data = dev_get_drvdata(dev);
> +	struct regmap *map = data->tempmon;
> +
> +	/*
> +	 * If a measurement was taking place while suspend, re-take the
> +	 * measurement.
> +	 */
> +	if (data->meas_suspended) {
> +		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.  But this timing
> +		 * isn't checked on every part nor is it specified in the
> +		 * datasheet, so delay 50us for timing margin.
> +		 */
> +		udelay(50);
> +		data->meas_suspended = false;
> +	}
> +
> +	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,
> +};
> +
> +static int __init imx_thermal_init(void)
> +{
> +	return platform_driver_register(&imx_thermal);
> +}
> +late_initcall(imx_thermal_init);
> +
> +static void __exit imx_thermal_exit(void)
> +{
> +	platform_driver_unregister(&imx_thermal);
> +}
> +module_exit(imx_thermal_exit);
> +
> +MODULE_AUTHOR("Freescale Semiconductor, Inc.");
> +MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:imx-thermal");

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] thermal: add imx thermal driver support
  2013-06-13  3:12   ` Zhang Rui
@ 2013-06-13  4:12     ` Shawn Guo
  -1 siblings, 0 replies; 14+ messages in thread
From: Shawn Guo @ 2013-06-13  4:12 UTC (permalink / raw)
  To: Zhang Rui; +Cc: linux-pm, Eduardo Valentin, linux-arm-kernel

On Thu, Jun 13, 2013 at 11:12:57AM +0800, Zhang Rui wrote:
> On Tue, 2013-06-04 at 15:13 +0800, Shawn Guo wrote:
> > 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.
> > 
> you're using the cpufrq_cooling to throttle cpus at 85C, right?

Yes.

> then you should register it as THERMAL_TRIP_PASSIVE instead of
> THERMAL_TRIP_ACTIVE.

Yes, literally it should be a passive trip point rather than active
one.  But I'm not sure if there is any real functional differences
between them in my thermal case, except I have to give both
passive_delay and polling_delay in thermal_zone_device_register()
call for passive type while it only needs polling_delay for active type
to work properly.

Besides, both exynos and db8500 thermal drivers register active trip
point while they are using cpufreq as the cooling device.  So I thought
it's fine to not have the trip type literally correct.

Shawn


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH] thermal: add imx thermal driver support
@ 2013-06-13  4:12     ` Shawn Guo
  0 siblings, 0 replies; 14+ messages in thread
From: Shawn Guo @ 2013-06-13  4:12 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Jun 13, 2013 at 11:12:57AM +0800, Zhang Rui wrote:
> On Tue, 2013-06-04 at 15:13 +0800, Shawn Guo wrote:
> > 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.
> > 
> you're using the cpufrq_cooling to throttle cpus at 85C, right?

Yes.

> then you should register it as THERMAL_TRIP_PASSIVE instead of
> THERMAL_TRIP_ACTIVE.

Yes, literally it should be a passive trip point rather than active
one.  But I'm not sure if there is any real functional differences
between them in my thermal case, except I have to give both
passive_delay and polling_delay in thermal_zone_device_register()
call for passive type while it only needs polling_delay for active type
to work properly.

Besides, both exynos and db8500 thermal drivers register active trip
point while they are using cpufreq as the cooling device.  So I thought
it's fine to not have the trip type literally correct.

Shawn

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] thermal: add imx thermal driver support
  2013-06-13  4:12     ` Shawn Guo
@ 2013-06-13  5:14       ` Zhang Rui
  -1 siblings, 0 replies; 14+ messages in thread
From: Zhang Rui @ 2013-06-13  5:14 UTC (permalink / raw)
  To: Shawn Guo; +Cc: linux-pm, Eduardo Valentin, linux-arm-kernel

On Thu, 2013-06-13 at 12:12 +0800, Shawn Guo wrote:
> On Thu, Jun 13, 2013 at 11:12:57AM +0800, Zhang Rui wrote:
> > On Tue, 2013-06-04 at 15:13 +0800, Shawn Guo wrote:
> > > 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.
> > > 
> > you're using the cpufrq_cooling to throttle cpus at 85C, right?
> 
> Yes.
> 
> > then you should register it as THERMAL_TRIP_PASSIVE instead of
> > THERMAL_TRIP_ACTIVE.
> 
> Yes, literally it should be a passive trip point rather than active
> one.  But I'm not sure if there is any real functional differences
> between them in my thermal case,

well, no, the code for handling passive and active trip point is the
same.
But "passive" and "active" are meaningful for thermal strategy, which
has not been supported so far.
Say, when running on battery, "passive" cooling should be preferred
because it consumes less power, while "active" cooling should be
preferred when running on AC because it provides better performance.

>  except I have to give both
> passive_delay and polling_delay in thermal_zone_device_register()
> call for passive type while it only needs polling_delay for active type
> to work properly.
> 
polling_delay is for systems w/o thermal interrupt.
but passive_delay is used for the condition that the processors are
being throttled.
they have different meanings. Say, you may want to check the temperature
every 30 seconds (polling_delay) in normal condition, but when the
system is overheating, you may need to check the temperature and put the
processors to a lower/higher cooling state every 5 seconds to avoid
1. the fast temperature increment shuts down the system when you just
started to throttle.
2. the rapid temperature decrement leaves the system idle but processors
throttled, for a long time.

> Besides, both exynos and db8500 thermal drivers register active trip
> point while they are using cpufreq as the cooling device.

oh, I did not notice that.
Then that is something needs fixing. you can cook up twp patch for
that. :)

thanks,
rui


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH] thermal: add imx thermal driver support
@ 2013-06-13  5:14       ` Zhang Rui
  0 siblings, 0 replies; 14+ messages in thread
From: Zhang Rui @ 2013-06-13  5:14 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, 2013-06-13 at 12:12 +0800, Shawn Guo wrote:
> On Thu, Jun 13, 2013 at 11:12:57AM +0800, Zhang Rui wrote:
> > On Tue, 2013-06-04 at 15:13 +0800, Shawn Guo wrote:
> > > 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.
> > > 
> > you're using the cpufrq_cooling to throttle cpus at 85C, right?
> 
> Yes.
> 
> > then you should register it as THERMAL_TRIP_PASSIVE instead of
> > THERMAL_TRIP_ACTIVE.
> 
> Yes, literally it should be a passive trip point rather than active
> one.  But I'm not sure if there is any real functional differences
> between them in my thermal case,

well, no, the code for handling passive and active trip point is the
same.
But "passive" and "active" are meaningful for thermal strategy, which
has not been supported so far.
Say, when running on battery, "passive" cooling should be preferred
because it consumes less power, while "active" cooling should be
preferred when running on AC because it provides better performance.

>  except I have to give both
> passive_delay and polling_delay in thermal_zone_device_register()
> call for passive type while it only needs polling_delay for active type
> to work properly.
> 
polling_delay is for systems w/o thermal interrupt.
but passive_delay is used for the condition that the processors are
being throttled.
they have different meanings. Say, you may want to check the temperature
every 30 seconds (polling_delay) in normal condition, but when the
system is overheating, you may need to check the temperature and put the
processors to a lower/higher cooling state every 5 seconds to avoid
1. the fast temperature increment shuts down the system when you just
started to throttle.
2. the rapid temperature decrement leaves the system idle but processors
throttled, for a long time.

> Besides, both exynos and db8500 thermal drivers register active trip
> point while they are using cpufreq as the cooling device.

oh, I did not notice that.
Then that is something needs fixing. you can cook up twp patch for
that. :)

thanks,
rui

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH] ARM: dts: imx: add tempmon node for imx6q thermal support
  2013-06-04  7:17 ` [PATCH] ARM: dts: imx: add tempmon node for imx6q thermal support Shawn Guo
@ 2013-07-09 14:59   ` Stefano Babic
  2013-07-10  7:09     ` Shawn Guo
  0 siblings, 1 reply; 14+ messages in thread
From: Stefano Babic @ 2013-07-09 14:59 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Shawn,

On 04/06/2013 09:17, Shawn Guo wrote:
> Mark ocotp as a syscon node and add tempmon for imx6q thermal support.
> 
> Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
> ---
>  arch/arm/boot/dts/imx6qdl.dtsi |   11 +++++++++--
>  1 file changed, 9 insertions(+), 2 deletions(-)
> 
> diff --git a/arch/arm/boot/dts/imx6qdl.dtsi b/arch/arm/boot/dts/imx6qdl.dtsi
> index 9e8296e..13e1d7f 100644
> --- a/arch/arm/boot/dts/imx6qdl.dtsi
> +++ b/arch/arm/boot/dts/imx6qdl.dtsi
> @@ -489,6 +489,13 @@
>  				};
>  			};
>  
> +			tempmon: tempmon {
> +				compatible = "fsl,imx6q-tempmon";
> +				interrupts = <0 49 0x04>;
> +				fsl,tempmon = <&anatop>;
> +				fsl,tempmon-data = <&ocotp>;
> +			};
> +
>  			usbphy1: usbphy at 020c9000 {
>  				compatible = "fsl,imx6q-usbphy", "fsl,imx23-usbphy";
>  				reg = <0x020c9000 0x1000>;
> @@ -747,8 +754,8 @@
>  				interrupts = <0 14 0x04>;
>  			};
>  
> -			ocotp at 021bc000 {
> -				compatible = "fsl,imx6q-ocotp";
> +			ocotp: ocotp at 021bc000 {
> +				compatible = "fsl,imx6q-ocotp", "syscon";
>  				reg = <0x021bc000 0x4000>;

Sorry to check this very late - is ocotp at 021bc000 the right address for
the thermal data ? According to User Manual (Table 62.3), the start
address is 0x20C8180. Using your patch, by loading the driver I get :

imx_thermal tempmon.7: invalid sensor calibration data
imx_thermal tempmon.7: failed to get sensor data
imx_thermal: probe of tempmon.7 failed with error -22

The driver is loaded successfully if I changed the tempdata to point to
020C8180. Not sure if I get the right values from temp1_crit and
temp1_input, but no error at the initialisation. Am I missing something ?

Best regards,
Stefano

-- 
=====================================================================
DENX Software Engineering GmbH,     MD: Wolfgang Denk & Detlev Zundel
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: +49-8142-66989-53 Fax: +49-8142-66989-80 Email: sbabic at denx.de
=====================================================================

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH] ARM: dts: imx: add tempmon node for imx6q thermal support
  2013-07-09 14:59   ` Stefano Babic
@ 2013-07-10  7:09     ` Shawn Guo
  2013-07-10  8:10       ` Stefano Babic
  0 siblings, 1 reply; 14+ messages in thread
From: Shawn Guo @ 2013-07-10  7:09 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Stefano,

On Tue, Jul 09, 2013 at 04:59:57PM +0200, Stefano Babic wrote:
> > -			ocotp at 021bc000 {
> > -				compatible = "fsl,imx6q-ocotp";
> > +			ocotp: ocotp at 021bc000 {
> > +				compatible = "fsl,imx6q-ocotp", "syscon";
> >  				reg = <0x021bc000 0x4000>;
> 
> Sorry to check this very late - is ocotp at 021bc000 the right address for
> the thermal data ? According to User Manual (Table 62.3), the start
> address is 0x20C8180. Using your patch, by loading the driver I get :

Address 0x20C8180 is where TEMPMON control registers are - they are
embedded in ANATOP block, while calibration data is stored in OCOTP fuse
block.

> imx_thermal tempmon.7: invalid sensor calibration data
> imx_thermal tempmon.7: failed to get sensor data
> imx_thermal: probe of tempmon.7 failed with error -22

What's your chip revision?  Early revisions may not have the calibration
data.  TO1.2 should have, I think.

> The driver is loaded successfully if I changed the tempdata to point to
> 020C8180. Not sure if I get the right values from temp1_crit and
> temp1_input, but no error at the initialisation. Am I missing something ?

It reads some data but that's not calibration data, so it should not work.

Also, you need a cpu_cooling fix [1], if you want to play the thermal
patches on imx6q.

Shawn

[1] http://thread.gmane.org/gmane.linux.power-management.general/34389

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH] ARM: dts: imx: add tempmon node for imx6q thermal support
  2013-07-10  7:09     ` Shawn Guo
@ 2013-07-10  8:10       ` Stefano Babic
  0 siblings, 0 replies; 14+ messages in thread
From: Stefano Babic @ 2013-07-10  8:10 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Shawn,

On 10/07/2013 09:09, Shawn Guo wrote:

> Address 0x20C8180 is where TEMPMON control registers are - they are
> embedded in ANATOP block, while calibration data is stored in OCOTP fuse
> block.
> 

Thanks, it is clear now.

>> imx_thermal tempmon.7: invalid sensor calibration data
>> imx_thermal tempmon.7: failed to get sensor data
>> imx_thermal: probe of tempmon.7 failed with error -22
> 
> What's your chip revision?  Early revisions may not have the calibration
> data.  TO1.2 should have, I think.

That is the point - on the board I tested there is a T01.0 running.

> 
>> The driver is loaded successfully if I changed the tempdata to point to
>> 020C8180. Not sure if I get the right values from temp1_crit and
>> temp1_input, but no error at the initialisation. Am I missing something ?
> 
> It reads some data but that's not calibration data, so it should not work.
> 
> Also, you need a cpu_cooling fix [1], if you want to play the thermal
> patches on imx6q.

Understood, thanks.

Stefano

-- 
=====================================================================
DENX Software Engineering GmbH,     MD: Wolfgang Denk & Detlev Zundel
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: +49-8142-66989-53 Fax: +49-8142-66989-80 Email: sbabic at denx.de
=====================================================================

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2013-07-10  8:10 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-06-04  7:13 [PATCH] thermal: add imx thermal driver support Shawn Guo
2013-06-04  7:13 ` Shawn Guo
2013-06-04  7:17 ` [PATCH] ARM: dts: imx: add tempmon node for imx6q thermal support Shawn Guo
2013-07-09 14:59   ` Stefano Babic
2013-07-10  7:09     ` Shawn Guo
2013-07-10  8:10       ` Stefano Babic
2013-06-13  0:16 ` [PATCH] thermal: add imx thermal driver support Shawn Guo
2013-06-13  0:16   ` Shawn Guo
2013-06-13  3:12 ` Zhang Rui
2013-06-13  3:12   ` Zhang Rui
2013-06-13  4:12   ` Shawn Guo
2013-06-13  4:12     ` Shawn Guo
2013-06-13  5:14     ` Zhang Rui
2013-06-13  5:14       ` Zhang Rui

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.