All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v6] ARM: imx: Add basic imx6q thermal driver
@ 2012-06-27  4:51 ` Robert Lee
  0 siblings, 0 replies; 10+ messages in thread
From: Robert Lee @ 2012-06-27  4:51 UTC (permalink / raw)
  To: kernel, shawn.guo
  Cc: linux, richard.zhao, dirk.behme, amit.kachhap, amit.kucheria,
	lenb, linux-arm-kernel, linux-kernel, linaro-dev, patches

Based on v3.5-rc4

This patch add basic thermal driver support for the imx6q platform.  This
was implemented by hooking into the linux thermal framework which provides
sysfs temperature readings and other data and a method to shutdown the system
if it gets too hot.

A major change in this revision is that cooling device implementation
was removed as it is dependent on cpufreq and no imx6q cpufreq exist upstream
and there is no recent activity on efforts to do so.

Other changes in v6.
1.  Fixed several problems that were pointed out in v5 review.
2.  Re-worked the sensor reading code to better handle suspend/resume case
3.  Made various naming changes to make it more specific to the anatop
 peripheral instead of the imx6q platform.  This should reduce the changes
 required to use this driver in future platforms.
4.  Removed test support for imx6q parts without calibrated temperature
 sensor fuses.  These devices will simply exit thermal driver initialization
 with a warning message.
5.  Made the thermal driver and the anatopmfd driver defaulted to be enabled
 in the Kconfig files which will still dependend on imx6q_soc being enabled.
 Not sure if this is the right place to do this or if it should be done
 in the defconfig file.

Known future work will be to add back in cooling device support once cpufreq
is supported.  Improvements to coincide with that work may involve adding
the active cooling trip points into device tree for hardware application
specific settings.

link to previous submissions:
v5" https://lkml.org/lkml/2012/6/20/57
v4: http://comments.gmane.org/gmane.linux.acpi.devel/51779
v3: http://www.spinics.net/lists/arm-kernel/msg155955.html
v2: http://www.spinics.net/lists/arm-kernel/msg155790.html
v1: http://www.spinics.net/lists/arm-kernel/msg155111.html

Changes in v5:
1. Modified to use anatop mfd driver for accessing anatop registers
2. Made necessary changes to work with latest generic CPU cooling code.
3. Added Config changes and functionality to allow testing on parts without
 without programmed temperature sensor calibration values.
4. General cleanup and addition of comments.

Changes in v4:
1.  Removed bad suspend/resume assignment into thermal class.  After further
examination and discussion with SoC designers, a sequence is now used
for making measurements that is is unaffected by system suspendresumes.
Temp Sensor automatically powers off in hardware during the low power mode
caused by a system suspend.
2.  Moved some structures from static to dynamic allocation.
3.  Added some noise handling to temperatuer sensor readings.

Changes in v3:
1. Fixed the various issues pointed out in v2
2. Made other code cleanup and a bit of re-organizing
3. Removed unecessary platform driver and device.

Changes in v2:
1. Cleaned up some style issues pointed out in v1
2. Made various other code cleanup and re-organizing
3. Added temperature sensor calibration
4. Created platform driver and device to hook into pm suspend.


Performed some basic testing to ensure proper cooling operating.  If
you want to test this, full testing requires imx6q cpufreq
implementation (not yet in v3.5).

Robert Lee (1):
  ARM: imx: Add basic imx6q thermal driver

 arch/arm/boot/dts/imx6q.dtsi         |    5 +
 drivers/mfd/Kconfig                  |    1 +
 drivers/thermal/Kconfig              |    9 +
 drivers/thermal/Makefile             |    1 +
 drivers/thermal/imx_anatop_thermal.c |  465 ++++++++++++++++++++++++++++++++++
 5 files changed, 481 insertions(+)
 create mode 100644 drivers/thermal/imx_anatop_thermal.c

-- 
1.7.10


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

* [PATCH v6] ARM: imx: Add basic imx6q thermal driver
@ 2012-06-27  4:51 ` Robert Lee
  0 siblings, 0 replies; 10+ messages in thread
From: Robert Lee @ 2012-06-27  4:51 UTC (permalink / raw)
  To: linux-arm-kernel

Based on v3.5-rc4

This patch add basic thermal driver support for the imx6q platform.  This
was implemented by hooking into the linux thermal framework which provides
sysfs temperature readings and other data and a method to shutdown the system
if it gets too hot.

A major change in this revision is that cooling device implementation
was removed as it is dependent on cpufreq and no imx6q cpufreq exist upstream
and there is no recent activity on efforts to do so.

Other changes in v6.
1.  Fixed several problems that were pointed out in v5 review.
2.  Re-worked the sensor reading code to better handle suspend/resume case
3.  Made various naming changes to make it more specific to the anatop
 peripheral instead of the imx6q platform.  This should reduce the changes
 required to use this driver in future platforms.
4.  Removed test support for imx6q parts without calibrated temperature
 sensor fuses.  These devices will simply exit thermal driver initialization
 with a warning message.
5.  Made the thermal driver and the anatopmfd driver defaulted to be enabled
 in the Kconfig files which will still dependend on imx6q_soc being enabled.
 Not sure if this is the right place to do this or if it should be done
 in the defconfig file.

Known future work will be to add back in cooling device support once cpufreq
is supported.  Improvements to coincide with that work may involve adding
the active cooling trip points into device tree for hardware application
specific settings.

link to previous submissions:
v5" https://lkml.org/lkml/2012/6/20/57
v4: http://comments.gmane.org/gmane.linux.acpi.devel/51779
v3: http://www.spinics.net/lists/arm-kernel/msg155955.html
v2: http://www.spinics.net/lists/arm-kernel/msg155790.html
v1: http://www.spinics.net/lists/arm-kernel/msg155111.html

Changes in v5:
1. Modified to use anatop mfd driver for accessing anatop registers
2. Made necessary changes to work with latest generic CPU cooling code.
3. Added Config changes and functionality to allow testing on parts without
 without programmed temperature sensor calibration values.
4. General cleanup and addition of comments.

Changes in v4:
1.  Removed bad suspend/resume assignment into thermal class.  After further
examination and discussion with SoC designers, a sequence is now used
for making measurements that is is unaffected by system suspendresumes.
Temp Sensor automatically powers off in hardware during the low power mode
caused by a system suspend.
2.  Moved some structures from static to dynamic allocation.
3.  Added some noise handling to temperatuer sensor readings.

Changes in v3:
1. Fixed the various issues pointed out in v2
2. Made other code cleanup and a bit of re-organizing
3. Removed unecessary platform driver and device.

Changes in v2:
1. Cleaned up some style issues pointed out in v1
2. Made various other code cleanup and re-organizing
3. Added temperature sensor calibration
4. Created platform driver and device to hook into pm suspend.


Performed some basic testing to ensure proper cooling operating.  If
you want to test this, full testing requires imx6q cpufreq
implementation (not yet in v3.5).

Robert Lee (1):
  ARM: imx: Add basic imx6q thermal driver

 arch/arm/boot/dts/imx6q.dtsi         |    5 +
 drivers/mfd/Kconfig                  |    1 +
 drivers/thermal/Kconfig              |    9 +
 drivers/thermal/Makefile             |    1 +
 drivers/thermal/imx_anatop_thermal.c |  465 ++++++++++++++++++++++++++++++++++
 5 files changed, 481 insertions(+)
 create mode 100644 drivers/thermal/imx_anatop_thermal.c

-- 
1.7.10

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

* [PATCH v6] ARM: imx: Add basic imx6q thermal driver
  2012-06-27  4:51 ` Robert Lee
@ 2012-06-27  4:51   ` Robert Lee
  -1 siblings, 0 replies; 10+ messages in thread
From: Robert Lee @ 2012-06-27  4:51 UTC (permalink / raw)
  To: kernel, shawn.guo
  Cc: linux, richard.zhao, dirk.behme, amit.kachhap, amit.kucheria,
	lenb, linux-arm-kernel, linux-kernel, linaro-dev, patches

Add imx anatop peripheral thermal driver and use it for imx6q builds.
This driver hooks into the linux thermal framework which provides a
sysfs interface for temperature readings and other information and
a mechanism to shutdown the system upon crossing a critical
temperature trip point.

Only the sysfs interface and a critcial trip are supported by this
patch and not any active trip points or cooling devices.

The thermal driver is defaulted to be enabled which required the
anatopmfd driver to be defaulted to enabled.

Signed-off-by: Robert Lee <rob.lee@linaro.org>
---
 arch/arm/boot/dts/imx6q.dtsi         |    5 +
 drivers/mfd/Kconfig                  |    1 +
 drivers/thermal/Kconfig              |    9 +
 drivers/thermal/Makefile             |    1 +
 drivers/thermal/imx_anatop_thermal.c |  465 ++++++++++++++++++++++++++++++++++
 5 files changed, 481 insertions(+)
 create mode 100644 drivers/thermal/imx_anatop_thermal.c

diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi
index d026f30..b53a16a 100644
--- a/arch/arm/boot/dts/imx6q.dtsi
+++ b/arch/arm/boot/dts/imx6q.dtsi
@@ -449,6 +449,10 @@
 					anatop-min-voltage = <725000>;
 					anatop-max-voltage = <1450000>;
 				};
+
+				thermal {
+					compatible ="fsl,anatop-thermal";
+				};
 			};
 
 			usbphy@020c9000 { /* USBPHY1 */
@@ -666,6 +670,7 @@
 			};
 
 			ocotp@021bc000 {
+				compatible = "fsl,imx6q-ocotp";
 				reg = <0x021bc000 0x4000>;
 			};
 
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index e129c82..552fae3 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -915,6 +915,7 @@ config MFD_STA2X11
 config MFD_ANATOP
 	bool "Support for Freescale i.MX on-chip ANATOP controller"
 	depends on SOC_IMX6Q
+	default y
 	help
 	  Select this option to enable Freescale i.MX on-chip ANATOP
 	  MFD controller. This controller embeds regulator and
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 04c6796..a35a35e 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -30,6 +30,15 @@ config CPU_THERMAL
 	  and not the ACPI interface.
 	  If you want this support, you should say Y or M here.
 
+config IMX_ANATOP_THERMAL
+	bool "imx anatop soc thermal driver"
+	depends on MFD_ANATOP && CPU_THERMAL
+	default y
+	help
+	  Enable the on-chip temperature sensor and register the linux thermal
+	  framework to provide SoC temperature data to sysfs and a basic
+	  shutdown mechanism to prevent the SoC from overheating.
+
 config SPEAR_THERMAL
 	bool "SPEAr thermal sensor driver"
 	depends on THERMAL
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 4636e35..4dd4570 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_THERMAL)		+= thermal_sys.o
 obj-$(CONFIG_CPU_THERMAL)       += cpu_cooling.o
 obj-$(CONFIG_SPEAR_THERMAL)		+= spear_thermal.o
 obj-$(CONFIG_EXYNOS_THERMAL)		+= exynos_thermal.o
+obj-$(CONFIG_IMX_ANATOP_THERMAL)	+= imx_anatop_thermal.o
diff --git a/drivers/thermal/imx_anatop_thermal.c b/drivers/thermal/imx_anatop_thermal.c
new file mode 100644
index 0000000..a932fe8
--- /dev/null
+++ b/drivers/thermal/imx_anatop_thermal.c
@@ -0,0 +1,465 @@
+/*
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ * Copyright 2012 Linaro Ltd.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*
+ * Thermal driver for i.MX anatop systems.  Implented by hooking in to the
+ * linux thermal framework.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dmi.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/anatop.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/smp.h>
+#include <linux/slab.h>
+#include <linux/syscalls.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+
+/* register define of anatop */
+#define HW_ANADIG_ANA_MISC0			0x00000150
+#define HW_ANADIG_ANA_MISC0_SET			0x00000154
+#define HW_ANADIG_ANA_MISC0_CLR			0x00000158
+#define HW_ANADIG_ANA_MISC0_TOG			0x0000015c
+#define BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF	0x00000008
+
+#define HW_ANADIG_TEMPSENSE0			0x00000180
+#define HW_ANADIG_TEMPSENSE0_SET		0x00000184
+#define HW_ANADIG_TEMPSENSE0_CLR		0x00000188
+#define HW_ANADIG_TEMPSENSE0_TOG		0x0000018c
+
+#define BP_ANADIG_TEMPSENSE0_TEMP_VALUE		8
+#define BM_ANADIG_TEMPSENSE0_TEMP_VALUE		0x000FFF00
+#define BM_ANADIG_TEMPSENSE0_FINISHED		0x00000004
+#define BM_ANADIG_TEMPSENSE0_MEASURE_TEMP	0x00000002
+#define BM_ANADIG_TEMPSENSE0_POWER_DOWN		0x00000001
+
+#define HW_ANADIG_TEMPSENSE1			0x00000190
+#define HW_ANADIG_TEMPSENSE1_SET		0x00000194
+#define HW_ANADIG_TEMPSENSE1_CLR		0x00000198
+#define BP_ANADIG_TEMPSENSE1_MEASURE_FREQ	0
+#define BM_ANADIG_TEMPSENSE1_MEASURE_FREQ	0x0000FFFF
+
+#define HW_OCOTP_ANA1				0x000004E0
+
+#define IMX_THERMAL_POLLING_FREQUENCY_MS 1000
+
+#define IMX_ANATOP_TS_NOISE_MARGIN 3000 /* in millicelsius */
+#define IMX_ANATOP_TS_NOISE_COUNT	3
+
+/* anatop temperature sensor data */
+struct imx_anatop_tsdata {
+	int	c1, c2;
+	u32	noise_margin;   /* in millicelsius */
+	int	last_temp;	/* in millicelsius */
+	/*
+	 * When filtering noise, if consecutive measurements are each higher
+	 * up to consec_high_limit times, assume changing temperature readings
+	 * to be valid and not noise.
+	 */
+	u32	consec_high_limit;
+	bool	handle_suspend;
+	struct anatop *anatopmfd;
+
+};
+
+struct imx_anatop_thdata {
+	struct thermal_zone_device	*tzdev;
+	struct imx_anatop_tsdata	sensor_data;
+	struct thermal_zone_device_ops	dev_ops;
+	unsigned int			crit_temp_level;
+	void __iomem			*ocotp_base;
+};
+
+static inline int anatop_get_temp(int *temp, struct thermal_zone_device *tzdev)
+{
+	unsigned int n_meas;
+	unsigned int reg;
+	struct imx_anatop_tsdata *sd;
+
+	sd = &((struct imx_anatop_thdata *)tzdev->devdata)->sensor_data;
+
+
+	do {
+		/*
+		 * 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.
+		 */
+		sd->handle_suspend = false;
+
+		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
+			BM_ANADIG_TEMPSENSE0_POWER_DOWN);
+		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
+			BM_ANADIG_TEMPSENSE0_MEASURE_TEMP,
+			BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
+		/*
+		 * According to the anatop temp sensor designers, it may require
+		 * up to ~17us to complete a measurement (imx6q).
+		 * 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);
+
+		reg = anatop_read_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0);
+
+		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
+			BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
+		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
+			BM_ANADIG_TEMPSENSE0_POWER_DOWN,
+			BM_ANADIG_TEMPSENSE0_POWER_DOWN);
+
+	/* if we had a suspend and resume event, we will re-take the reading */
+	} while (sd->handle_suspend);
+
+	if (!(reg & BM_ANADIG_TEMPSENSE0_FINISHED) &&
+		(reg & BM_ANADIG_TEMPSENSE0_TEMP_VALUE)) {
+		dev_dbg(&tzdev->device,
+			"Temp sensor error: reading never finished");
+		return -EAGAIN;
+	}
+
+	n_meas = (reg & BM_ANADIG_TEMPSENSE0_TEMP_VALUE)
+			>> BP_ANADIG_TEMPSENSE0_TEMP_VALUE;
+
+	/* See anatop_process_ts_fuse_data() for forumla derivation. */
+	*temp = sd->c2 + (sd->c1 * n_meas);
+
+	dev_dbg(&tzdev->device, "Temperature: %d\n", *temp / 1000);
+	return 0;
+}
+
+static int th_sys_get_temp(struct thermal_zone_device *tzdev,
+				  unsigned long *temp)
+{
+	int i, total = 0, tmp = 0;
+	const u8 loop = 5;
+	u32 consec_high = 0;
+
+	struct imx_anatop_tsdata *sd;
+
+	sd = &((struct imx_anatop_thdata *)tzdev->devdata)->sensor_data;
+
+	/*
+	 * Measure temperature and handle noise
+	 *
+	 * While the anatop temperature sensor is designed to minimize being
+	 * affected by system noise, it's safest to run sanity checks and
+	 * perform any necessary filtering, if a noise_margin has been defined.
+	 */
+	for (i = 0; (sd->noise_margin) && (i < loop); i++) {
+		/*
+		 * We expect the sensor reading to be successful, but if for
+		 * unknow reason it is not, use the data from the last
+		 * successful reading
+		 */
+		if (anatop_get_temp(&tmp, tzdev)) {
+			tmp = sd->last_temp;
+			continue;
+		}
+
+		if ((abs(tmp - sd->last_temp) <= sd->noise_margin) ||
+			(consec_high >= sd->consec_high_limit)) {
+			sd->last_temp = tmp;
+			i = 0;
+			break;
+		}
+		if (tmp > sd->last_temp)
+			consec_high++;
+
+		/*
+		 * ignore first measurement as the previous measurement was
+		 * a long time ago.
+		 */
+		if (i)
+			total += tmp;
+
+		sd->last_temp = tmp;
+	}
+
+	if (sd->noise_margin && i)
+		tmp = total / (loop - 1);
+
+	/*
+	 * The thermal framework code stores temperature in unsigned long. Also,
+	 * it has references to "millicelsius" which limits the lowest
+	 * temperature possible (compared to Kelvin).
+	 */
+	if (tmp > 0)
+		*temp = tmp;
+	else
+		*temp = 0;
+	return 0;
+}
+
+static int th_sys_get_mode(struct thermal_zone_device *tzdev,
+			    enum thermal_device_mode *mode)
+{
+	*mode = THERMAL_DEVICE_ENABLED;
+	return 0;
+}
+
+static int th_sys_get_trip_type(struct thermal_zone_device *tzdev, int trip,
+				 enum thermal_trip_type *type)
+{
+	*type = THERMAL_TRIP_CRITICAL;
+	return 0;
+}
+
+static int th_sys_get_crit_temp(struct thermal_zone_device *tzdev,
+				 unsigned long *temp)
+{
+	struct imx_anatop_thdata *p;
+
+	p = (struct imx_anatop_thdata *)tzdev->devdata;
+	*temp = p->crit_temp_level;
+	return 0;
+}
+
+static int th_sys_get_trip_temp(struct thermal_zone_device *tzdev, int trip,
+				 unsigned long *temp)
+{
+	/* only a critical trip point is supported, for now. */
+	return th_sys_get_crit_temp(tzdev, temp);
+}
+
+static int __devinit anatop_process_ts_fuse_data(unsigned int fuse_data,
+		 struct imx_anatop_thdata *thermal_data)
+{
+	int t1, t2, n1, n2;
+	struct imx_anatop_tsdata *sd = &thermal_data->sensor_data;
+	struct thermal_zone_device *tzdev = thermal_data->tzdev;
+
+
+	if (fuse_data == 0 || fuse_data == 0xffffffff) {
+		dev_warn(&tzdev->device, "WARNING: Disabling CPU thermal "
+			"protection (invalid calibration value of %x detected\n"
+			, fuse_data);
+		return -EINVAL;
+	}
+
+	/*
+	 * Fuse data layout:
+	 * [31:20] sensor value @ 25C
+	 * [19:8] sensor value of hot
+	 * [7:0] hot temperature value
+	 */
+	n1 = fuse_data >> 20;
+	n2 = (fuse_data & 0xfff00) >> 8;
+	t2 = fuse_data & 0xff;
+	t1 = 25; /* t1 always 25C */
+
+	dev_dbg(&tzdev->device, " -- temperature sensor calibration data --\n");
+	dev_dbg(&tzdev->device, "HW_OCOTP_ANA1: %x\n", fuse_data);
+	dev_dbg(&tzdev->device, "n1: %d\nn2: %d\nt1: %d\nt2: %d\n", n1, n2, t1,
+		t2);
+
+	/*
+	 * 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)
+	 */
+	sd->c1 = (1000 * (t1 - t2)) / (n1 - n2);
+	sd->c2 = (1000 * t2) - (sd->c1 * n2);
+
+	dev_dbg(&tzdev->device, "c1: %i\n", sd->c1);
+	dev_dbg(&tzdev->device, "c2: %i\n", sd->c2);
+	return 0;
+}
+
+static int anatop_thermal_suspend(struct platform_device *pdev,
+					pm_message_t state)
+{
+	struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
+	struct imx_anatop_tsdata *sd = &dd->sensor_data;
+
+	/* power off the sensor during suspend */
+	anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
+		BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
+
+	anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
+		BM_ANADIG_TEMPSENSE0_POWER_DOWN,
+		BM_ANADIG_TEMPSENSE0_POWER_DOWN);
+	return 0;
+}
+
+static int anatop_thermal_resume(struct platform_device *pdev)
+{
+	struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
+	struct imx_anatop_tsdata *sd = &dd->sensor_data;
+
+	sd->handle_suspend = true;
+	return 0;
+}
+
+static int __devexit anatop_thermal_remove(struct platform_device *pdev)
+{
+	struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
+
+	if (dd && dd->tzdev)
+		thermal_zone_device_unregister(dd->tzdev);
+
+	if (dd->ocotp_base)
+		iounmap(dd->ocotp_base);
+
+	dev_info(&pdev->dev, "imx thermal management unregistered\n");
+	return 0;
+}
+
+static int __devinit anatop_thermal_probe(struct platform_device *pdev)
+{
+	unsigned int fuse_data;
+	void __iomem *ocotp_base;
+	struct device_node *np_ocotp, *np_thermal;
+	static struct imx_anatop_thdata	*therm_data;
+	struct anatop *anatopmfd = dev_get_drvdata(pdev->dev.parent);
+
+	struct imx_anatop_tsdata *sd;
+	int ret;
+
+	np_ocotp = of_find_compatible_node(NULL, NULL, "fsl,imx6q-ocotp");
+	np_thermal = pdev->dev.of_node;
+
+	if (!(np_ocotp && np_thermal && anatopmfd))
+		return 0;
+
+	ocotp_base = of_iomap(np_ocotp, 0);
+
+	if (!ocotp_base) {
+		dev_err(&pdev->dev, "Could not retrieve ocotp-base\n");
+		ret = -ENXIO;
+		goto err_unregister;
+	}
+
+	fuse_data = readl_relaxed(ocotp_base + HW_OCOTP_ANA1);
+
+	therm_data = devm_kzalloc(&pdev->dev, sizeof(struct imx_anatop_thdata),
+		GFP_KERNEL);
+
+
+	if (!therm_data) {
+		ret = -ENOMEM;
+		goto err_unregister;
+	}
+
+	therm_data->dev_ops.get_temp = th_sys_get_temp;
+	therm_data->dev_ops.get_mode = th_sys_get_mode;
+	therm_data->dev_ops.get_trip_type = th_sys_get_trip_type;
+	therm_data->dev_ops.get_trip_temp = th_sys_get_trip_temp;
+	therm_data->dev_ops.get_crit_temp = th_sys_get_crit_temp;
+
+	/*
+	 * max die temp on imx parts using anatop is 105C, let's give some
+	 * cushion for noise and possible temperature rise between measurements.
+	 */
+	therm_data->crit_temp_level = 100000; /* in millicelsius */
+
+	sd = &therm_data->sensor_data;
+	sd->noise_margin = IMX_ANATOP_TS_NOISE_MARGIN;
+	sd->consec_high_limit = IMX_ANATOP_TS_NOISE_COUNT;
+	sd->anatopmfd = anatopmfd;
+
+	platform_set_drvdata(pdev, therm_data);
+
+	ret = anatop_process_ts_fuse_data(fuse_data, therm_data);
+
+	if (ret) {
+		dev_err(&pdev->dev, "Invalid temperature calibration data.\n");
+		goto err_unregister;
+	}
+
+	/* Make sure sensor is in known good state for measurements */
+	anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
+		BM_ANADIG_TEMPSENSE0_POWER_DOWN);
+
+	anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
+		BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
+
+	anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE1, 0,
+		BM_ANADIG_TEMPSENSE1_MEASURE_FREQ);
+
+	anatop_write_reg(anatopmfd, HW_ANADIG_ANA_MISC0_SET,
+		BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF,
+		BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF);
+
+	anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE0,
+		BM_ANADIG_TEMPSENSE0_POWER_DOWN,
+		BM_ANADIG_TEMPSENSE0_POWER_DOWN);
+
+	therm_data->tzdev = thermal_zone_device_register(
+		"Processor", 1,	therm_data, &therm_data->dev_ops, 0, 0, 0,
+		IMX_THERMAL_POLLING_FREQUENCY_MS);
+
+	if (IS_ERR(therm_data->tzdev)) {
+		dev_err(&pdev->dev,
+			"Failed to register thermal zone device\n");
+		ret = -EINVAL;
+		goto err_unregister;
+	}
+
+	dev_info(&pdev->dev, "imx thermal management registered\n");
+	return 0;
+
+err_unregister:
+	anatop_thermal_remove(pdev);
+	return ret;
+}
+
+static struct of_device_id __devinitdata of_anatop_thermal_match_tbl[] = {
+	{ .compatible = "fsl,anatop-thermal", },
+	{ /* end */ }
+};
+
+static struct platform_driver anatop_thermal = {
+	.driver = {
+		.name	= "anatop_thermal",
+		.owner  = THIS_MODULE,
+		.of_match_table = of_anatop_thermal_match_tbl,
+	},
+	.probe		= anatop_thermal_probe,
+	.remove		= anatop_thermal_remove,
+	.suspend	= anatop_thermal_suspend,
+	.resume		= anatop_thermal_resume,
+};
+
+static int __devinit anatop_thermal_init(void)
+{
+	return platform_driver_register(&anatop_thermal);
+}
+device_initcall(anatop_thermal_init);
+
+static void __exit anatop_thermal_exit(void)
+{
+	platform_driver_unregister(&anatop_thermal);
+}
+module_exit(anatop_thermal_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor");
+MODULE_DESCRIPTION("i.MX anatop thermal driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-anatop-thermal");
-- 
1.7.10


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

* [PATCH v6] ARM: imx: Add basic imx6q thermal driver
@ 2012-06-27  4:51   ` Robert Lee
  0 siblings, 0 replies; 10+ messages in thread
From: Robert Lee @ 2012-06-27  4:51 UTC (permalink / raw)
  To: linux-arm-kernel

Add imx anatop peripheral thermal driver and use it for imx6q builds.
This driver hooks into the linux thermal framework which provides a
sysfs interface for temperature readings and other information and
a mechanism to shutdown the system upon crossing a critical
temperature trip point.

Only the sysfs interface and a critcial trip are supported by this
patch and not any active trip points or cooling devices.

The thermal driver is defaulted to be enabled which required the
anatopmfd driver to be defaulted to enabled.

Signed-off-by: Robert Lee <rob.lee@linaro.org>
---
 arch/arm/boot/dts/imx6q.dtsi         |    5 +
 drivers/mfd/Kconfig                  |    1 +
 drivers/thermal/Kconfig              |    9 +
 drivers/thermal/Makefile             |    1 +
 drivers/thermal/imx_anatop_thermal.c |  465 ++++++++++++++++++++++++++++++++++
 5 files changed, 481 insertions(+)
 create mode 100644 drivers/thermal/imx_anatop_thermal.c

diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi
index d026f30..b53a16a 100644
--- a/arch/arm/boot/dts/imx6q.dtsi
+++ b/arch/arm/boot/dts/imx6q.dtsi
@@ -449,6 +449,10 @@
 					anatop-min-voltage = <725000>;
 					anatop-max-voltage = <1450000>;
 				};
+
+				thermal {
+					compatible ="fsl,anatop-thermal";
+				};
 			};
 
 			usbphy at 020c9000 { /* USBPHY1 */
@@ -666,6 +670,7 @@
 			};
 
 			ocotp at 021bc000 {
+				compatible = "fsl,imx6q-ocotp";
 				reg = <0x021bc000 0x4000>;
 			};
 
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index e129c82..552fae3 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -915,6 +915,7 @@ config MFD_STA2X11
 config MFD_ANATOP
 	bool "Support for Freescale i.MX on-chip ANATOP controller"
 	depends on SOC_IMX6Q
+	default y
 	help
 	  Select this option to enable Freescale i.MX on-chip ANATOP
 	  MFD controller. This controller embeds regulator and
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 04c6796..a35a35e 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -30,6 +30,15 @@ config CPU_THERMAL
 	  and not the ACPI interface.
 	  If you want this support, you should say Y or M here.
 
+config IMX_ANATOP_THERMAL
+	bool "imx anatop soc thermal driver"
+	depends on MFD_ANATOP && CPU_THERMAL
+	default y
+	help
+	  Enable the on-chip temperature sensor and register the linux thermal
+	  framework to provide SoC temperature data to sysfs and a basic
+	  shutdown mechanism to prevent the SoC from overheating.
+
 config SPEAR_THERMAL
 	bool "SPEAr thermal sensor driver"
 	depends on THERMAL
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 4636e35..4dd4570 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_THERMAL)		+= thermal_sys.o
 obj-$(CONFIG_CPU_THERMAL)       += cpu_cooling.o
 obj-$(CONFIG_SPEAR_THERMAL)		+= spear_thermal.o
 obj-$(CONFIG_EXYNOS_THERMAL)		+= exynos_thermal.o
+obj-$(CONFIG_IMX_ANATOP_THERMAL)	+= imx_anatop_thermal.o
diff --git a/drivers/thermal/imx_anatop_thermal.c b/drivers/thermal/imx_anatop_thermal.c
new file mode 100644
index 0000000..a932fe8
--- /dev/null
+++ b/drivers/thermal/imx_anatop_thermal.c
@@ -0,0 +1,465 @@
+/*
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ * Copyright 2012 Linaro Ltd.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*
+ * Thermal driver for i.MX anatop systems.  Implented by hooking in to the
+ * linux thermal framework.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dmi.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/anatop.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/smp.h>
+#include <linux/slab.h>
+#include <linux/syscalls.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+
+/* register define of anatop */
+#define HW_ANADIG_ANA_MISC0			0x00000150
+#define HW_ANADIG_ANA_MISC0_SET			0x00000154
+#define HW_ANADIG_ANA_MISC0_CLR			0x00000158
+#define HW_ANADIG_ANA_MISC0_TOG			0x0000015c
+#define BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF	0x00000008
+
+#define HW_ANADIG_TEMPSENSE0			0x00000180
+#define HW_ANADIG_TEMPSENSE0_SET		0x00000184
+#define HW_ANADIG_TEMPSENSE0_CLR		0x00000188
+#define HW_ANADIG_TEMPSENSE0_TOG		0x0000018c
+
+#define BP_ANADIG_TEMPSENSE0_TEMP_VALUE		8
+#define BM_ANADIG_TEMPSENSE0_TEMP_VALUE		0x000FFF00
+#define BM_ANADIG_TEMPSENSE0_FINISHED		0x00000004
+#define BM_ANADIG_TEMPSENSE0_MEASURE_TEMP	0x00000002
+#define BM_ANADIG_TEMPSENSE0_POWER_DOWN		0x00000001
+
+#define HW_ANADIG_TEMPSENSE1			0x00000190
+#define HW_ANADIG_TEMPSENSE1_SET		0x00000194
+#define HW_ANADIG_TEMPSENSE1_CLR		0x00000198
+#define BP_ANADIG_TEMPSENSE1_MEASURE_FREQ	0
+#define BM_ANADIG_TEMPSENSE1_MEASURE_FREQ	0x0000FFFF
+
+#define HW_OCOTP_ANA1				0x000004E0
+
+#define IMX_THERMAL_POLLING_FREQUENCY_MS 1000
+
+#define IMX_ANATOP_TS_NOISE_MARGIN 3000 /* in millicelsius */
+#define IMX_ANATOP_TS_NOISE_COUNT	3
+
+/* anatop temperature sensor data */
+struct imx_anatop_tsdata {
+	int	c1, c2;
+	u32	noise_margin;   /* in millicelsius */
+	int	last_temp;	/* in millicelsius */
+	/*
+	 * When filtering noise, if consecutive measurements are each higher
+	 * up to consec_high_limit times, assume changing temperature readings
+	 * to be valid and not noise.
+	 */
+	u32	consec_high_limit;
+	bool	handle_suspend;
+	struct anatop *anatopmfd;
+
+};
+
+struct imx_anatop_thdata {
+	struct thermal_zone_device	*tzdev;
+	struct imx_anatop_tsdata	sensor_data;
+	struct thermal_zone_device_ops	dev_ops;
+	unsigned int			crit_temp_level;
+	void __iomem			*ocotp_base;
+};
+
+static inline int anatop_get_temp(int *temp, struct thermal_zone_device *tzdev)
+{
+	unsigned int n_meas;
+	unsigned int reg;
+	struct imx_anatop_tsdata *sd;
+
+	sd = &((struct imx_anatop_thdata *)tzdev->devdata)->sensor_data;
+
+
+	do {
+		/*
+		 * 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.
+		 */
+		sd->handle_suspend = false;
+
+		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
+			BM_ANADIG_TEMPSENSE0_POWER_DOWN);
+		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
+			BM_ANADIG_TEMPSENSE0_MEASURE_TEMP,
+			BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
+		/*
+		 * According to the anatop temp sensor designers, it may require
+		 * up to ~17us to complete a measurement (imx6q).
+		 * 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);
+
+		reg = anatop_read_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0);
+
+		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
+			BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
+		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
+			BM_ANADIG_TEMPSENSE0_POWER_DOWN,
+			BM_ANADIG_TEMPSENSE0_POWER_DOWN);
+
+	/* if we had a suspend and resume event, we will re-take the reading */
+	} while (sd->handle_suspend);
+
+	if (!(reg & BM_ANADIG_TEMPSENSE0_FINISHED) &&
+		(reg & BM_ANADIG_TEMPSENSE0_TEMP_VALUE)) {
+		dev_dbg(&tzdev->device,
+			"Temp sensor error: reading never finished");
+		return -EAGAIN;
+	}
+
+	n_meas = (reg & BM_ANADIG_TEMPSENSE0_TEMP_VALUE)
+			>> BP_ANADIG_TEMPSENSE0_TEMP_VALUE;
+
+	/* See anatop_process_ts_fuse_data() for forumla derivation. */
+	*temp = sd->c2 + (sd->c1 * n_meas);
+
+	dev_dbg(&tzdev->device, "Temperature: %d\n", *temp / 1000);
+	return 0;
+}
+
+static int th_sys_get_temp(struct thermal_zone_device *tzdev,
+				  unsigned long *temp)
+{
+	int i, total = 0, tmp = 0;
+	const u8 loop = 5;
+	u32 consec_high = 0;
+
+	struct imx_anatop_tsdata *sd;
+
+	sd = &((struct imx_anatop_thdata *)tzdev->devdata)->sensor_data;
+
+	/*
+	 * Measure temperature and handle noise
+	 *
+	 * While the anatop temperature sensor is designed to minimize being
+	 * affected by system noise, it's safest to run sanity checks and
+	 * perform any necessary filtering, if a noise_margin has been defined.
+	 */
+	for (i = 0; (sd->noise_margin) && (i < loop); i++) {
+		/*
+		 * We expect the sensor reading to be successful, but if for
+		 * unknow reason it is not, use the data from the last
+		 * successful reading
+		 */
+		if (anatop_get_temp(&tmp, tzdev)) {
+			tmp = sd->last_temp;
+			continue;
+		}
+
+		if ((abs(tmp - sd->last_temp) <= sd->noise_margin) ||
+			(consec_high >= sd->consec_high_limit)) {
+			sd->last_temp = tmp;
+			i = 0;
+			break;
+		}
+		if (tmp > sd->last_temp)
+			consec_high++;
+
+		/*
+		 * ignore first measurement as the previous measurement was
+		 * a long time ago.
+		 */
+		if (i)
+			total += tmp;
+
+		sd->last_temp = tmp;
+	}
+
+	if (sd->noise_margin && i)
+		tmp = total / (loop - 1);
+
+	/*
+	 * The thermal framework code stores temperature in unsigned long. Also,
+	 * it has references to "millicelsius" which limits the lowest
+	 * temperature possible (compared to Kelvin).
+	 */
+	if (tmp > 0)
+		*temp = tmp;
+	else
+		*temp = 0;
+	return 0;
+}
+
+static int th_sys_get_mode(struct thermal_zone_device *tzdev,
+			    enum thermal_device_mode *mode)
+{
+	*mode = THERMAL_DEVICE_ENABLED;
+	return 0;
+}
+
+static int th_sys_get_trip_type(struct thermal_zone_device *tzdev, int trip,
+				 enum thermal_trip_type *type)
+{
+	*type = THERMAL_TRIP_CRITICAL;
+	return 0;
+}
+
+static int th_sys_get_crit_temp(struct thermal_zone_device *tzdev,
+				 unsigned long *temp)
+{
+	struct imx_anatop_thdata *p;
+
+	p = (struct imx_anatop_thdata *)tzdev->devdata;
+	*temp = p->crit_temp_level;
+	return 0;
+}
+
+static int th_sys_get_trip_temp(struct thermal_zone_device *tzdev, int trip,
+				 unsigned long *temp)
+{
+	/* only a critical trip point is supported, for now. */
+	return th_sys_get_crit_temp(tzdev, temp);
+}
+
+static int __devinit anatop_process_ts_fuse_data(unsigned int fuse_data,
+		 struct imx_anatop_thdata *thermal_data)
+{
+	int t1, t2, n1, n2;
+	struct imx_anatop_tsdata *sd = &thermal_data->sensor_data;
+	struct thermal_zone_device *tzdev = thermal_data->tzdev;
+
+
+	if (fuse_data == 0 || fuse_data == 0xffffffff) {
+		dev_warn(&tzdev->device, "WARNING: Disabling CPU thermal "
+			"protection (invalid calibration value of %x detected\n"
+			, fuse_data);
+		return -EINVAL;
+	}
+
+	/*
+	 * Fuse data layout:
+	 * [31:20] sensor value @ 25C
+	 * [19:8] sensor value of hot
+	 * [7:0] hot temperature value
+	 */
+	n1 = fuse_data >> 20;
+	n2 = (fuse_data & 0xfff00) >> 8;
+	t2 = fuse_data & 0xff;
+	t1 = 25; /* t1 always 25C */
+
+	dev_dbg(&tzdev->device, " -- temperature sensor calibration data --\n");
+	dev_dbg(&tzdev->device, "HW_OCOTP_ANA1: %x\n", fuse_data);
+	dev_dbg(&tzdev->device, "n1: %d\nn2: %d\nt1: %d\nt2: %d\n", n1, n2, t1,
+		t2);
+
+	/*
+	 * 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)
+	 */
+	sd->c1 = (1000 * (t1 - t2)) / (n1 - n2);
+	sd->c2 = (1000 * t2) - (sd->c1 * n2);
+
+	dev_dbg(&tzdev->device, "c1: %i\n", sd->c1);
+	dev_dbg(&tzdev->device, "c2: %i\n", sd->c2);
+	return 0;
+}
+
+static int anatop_thermal_suspend(struct platform_device *pdev,
+					pm_message_t state)
+{
+	struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
+	struct imx_anatop_tsdata *sd = &dd->sensor_data;
+
+	/* power off the sensor during suspend */
+	anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
+		BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
+
+	anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
+		BM_ANADIG_TEMPSENSE0_POWER_DOWN,
+		BM_ANADIG_TEMPSENSE0_POWER_DOWN);
+	return 0;
+}
+
+static int anatop_thermal_resume(struct platform_device *pdev)
+{
+	struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
+	struct imx_anatop_tsdata *sd = &dd->sensor_data;
+
+	sd->handle_suspend = true;
+	return 0;
+}
+
+static int __devexit anatop_thermal_remove(struct platform_device *pdev)
+{
+	struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
+
+	if (dd && dd->tzdev)
+		thermal_zone_device_unregister(dd->tzdev);
+
+	if (dd->ocotp_base)
+		iounmap(dd->ocotp_base);
+
+	dev_info(&pdev->dev, "imx thermal management unregistered\n");
+	return 0;
+}
+
+static int __devinit anatop_thermal_probe(struct platform_device *pdev)
+{
+	unsigned int fuse_data;
+	void __iomem *ocotp_base;
+	struct device_node *np_ocotp, *np_thermal;
+	static struct imx_anatop_thdata	*therm_data;
+	struct anatop *anatopmfd = dev_get_drvdata(pdev->dev.parent);
+
+	struct imx_anatop_tsdata *sd;
+	int ret;
+
+	np_ocotp = of_find_compatible_node(NULL, NULL, "fsl,imx6q-ocotp");
+	np_thermal = pdev->dev.of_node;
+
+	if (!(np_ocotp && np_thermal && anatopmfd))
+		return 0;
+
+	ocotp_base = of_iomap(np_ocotp, 0);
+
+	if (!ocotp_base) {
+		dev_err(&pdev->dev, "Could not retrieve ocotp-base\n");
+		ret = -ENXIO;
+		goto err_unregister;
+	}
+
+	fuse_data = readl_relaxed(ocotp_base + HW_OCOTP_ANA1);
+
+	therm_data = devm_kzalloc(&pdev->dev, sizeof(struct imx_anatop_thdata),
+		GFP_KERNEL);
+
+
+	if (!therm_data) {
+		ret = -ENOMEM;
+		goto err_unregister;
+	}
+
+	therm_data->dev_ops.get_temp = th_sys_get_temp;
+	therm_data->dev_ops.get_mode = th_sys_get_mode;
+	therm_data->dev_ops.get_trip_type = th_sys_get_trip_type;
+	therm_data->dev_ops.get_trip_temp = th_sys_get_trip_temp;
+	therm_data->dev_ops.get_crit_temp = th_sys_get_crit_temp;
+
+	/*
+	 * max die temp on imx parts using anatop is 105C, let's give some
+	 * cushion for noise and possible temperature rise between measurements.
+	 */
+	therm_data->crit_temp_level = 100000; /* in millicelsius */
+
+	sd = &therm_data->sensor_data;
+	sd->noise_margin = IMX_ANATOP_TS_NOISE_MARGIN;
+	sd->consec_high_limit = IMX_ANATOP_TS_NOISE_COUNT;
+	sd->anatopmfd = anatopmfd;
+
+	platform_set_drvdata(pdev, therm_data);
+
+	ret = anatop_process_ts_fuse_data(fuse_data, therm_data);
+
+	if (ret) {
+		dev_err(&pdev->dev, "Invalid temperature calibration data.\n");
+		goto err_unregister;
+	}
+
+	/* Make sure sensor is in known good state for measurements */
+	anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
+		BM_ANADIG_TEMPSENSE0_POWER_DOWN);
+
+	anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
+		BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
+
+	anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE1, 0,
+		BM_ANADIG_TEMPSENSE1_MEASURE_FREQ);
+
+	anatop_write_reg(anatopmfd, HW_ANADIG_ANA_MISC0_SET,
+		BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF,
+		BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF);
+
+	anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE0,
+		BM_ANADIG_TEMPSENSE0_POWER_DOWN,
+		BM_ANADIG_TEMPSENSE0_POWER_DOWN);
+
+	therm_data->tzdev = thermal_zone_device_register(
+		"Processor", 1,	therm_data, &therm_data->dev_ops, 0, 0, 0,
+		IMX_THERMAL_POLLING_FREQUENCY_MS);
+
+	if (IS_ERR(therm_data->tzdev)) {
+		dev_err(&pdev->dev,
+			"Failed to register thermal zone device\n");
+		ret = -EINVAL;
+		goto err_unregister;
+	}
+
+	dev_info(&pdev->dev, "imx thermal management registered\n");
+	return 0;
+
+err_unregister:
+	anatop_thermal_remove(pdev);
+	return ret;
+}
+
+static struct of_device_id __devinitdata of_anatop_thermal_match_tbl[] = {
+	{ .compatible = "fsl,anatop-thermal", },
+	{ /* end */ }
+};
+
+static struct platform_driver anatop_thermal = {
+	.driver = {
+		.name	= "anatop_thermal",
+		.owner  = THIS_MODULE,
+		.of_match_table = of_anatop_thermal_match_tbl,
+	},
+	.probe		= anatop_thermal_probe,
+	.remove		= anatop_thermal_remove,
+	.suspend	= anatop_thermal_suspend,
+	.resume		= anatop_thermal_resume,
+};
+
+static int __devinit anatop_thermal_init(void)
+{
+	return platform_driver_register(&anatop_thermal);
+}
+device_initcall(anatop_thermal_init);
+
+static void __exit anatop_thermal_exit(void)
+{
+	platform_driver_unregister(&anatop_thermal);
+}
+module_exit(anatop_thermal_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor");
+MODULE_DESCRIPTION("i.MX anatop thermal driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-anatop-thermal");
-- 
1.7.10

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

* Re: [PATCH v6] ARM: imx: Add basic imx6q thermal driver
  2012-06-27  4:51   ` Robert Lee
@ 2012-06-27 13:35     ` Rob Lee
  -1 siblings, 0 replies; 10+ messages in thread
From: Rob Lee @ 2012-06-27 13:35 UTC (permalink / raw)
  To: kernel, shawn.guo
  Cc: linux, richard.zhao, dirk.behme, amit.kachhap, amit.kucheria,
	lenb, linux-arm-kernel, linux-kernel, linaro-dev, patches

On Tue, Jun 26, 2012 at 11:51 PM, Robert Lee <rob.lee@linaro.org> wrote:
> Add imx anatop peripheral thermal driver and use it for imx6q builds.
> This driver hooks into the linux thermal framework which provides a
> sysfs interface for temperature readings and other information and
> a mechanism to shutdown the system upon crossing a critical
> temperature trip point.
>
> Only the sysfs interface and a critcial trip are supported by this
> patch and not any active trip points or cooling devices.
>
> The thermal driver is defaulted to be enabled which required the
> anatopmfd driver to be defaulted to enabled.
>
> Signed-off-by: Robert Lee <rob.lee@linaro.org>
> ---
>  arch/arm/boot/dts/imx6q.dtsi         |    5 +
>  drivers/mfd/Kconfig                  |    1 +
>  drivers/thermal/Kconfig              |    9 +
>  drivers/thermal/Makefile             |    1 +
>  drivers/thermal/imx_anatop_thermal.c |  465 ++++++++++++++++++++++++++++++++++
>  5 files changed, 481 insertions(+)
>  create mode 100644 drivers/thermal/imx_anatop_thermal.c
>
> diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi
> index d026f30..b53a16a 100644
> --- a/arch/arm/boot/dts/imx6q.dtsi
> +++ b/arch/arm/boot/dts/imx6q.dtsi
> @@ -449,6 +449,10 @@
>                                         anatop-min-voltage = <725000>;
>                                         anatop-max-voltage = <1450000>;
>                                 };
> +
> +                               thermal {
> +                                       compatible ="fsl,anatop-thermal";
> +                               };
>                         };
>
>                         usbphy@020c9000 { /* USBPHY1 */
> @@ -666,6 +670,7 @@
>                         };
>
>                         ocotp@021bc000 {
> +                               compatible = "fsl,imx6q-ocotp";
>                                 reg = <0x021bc000 0x4000>;
>                         };
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index e129c82..552fae3 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -915,6 +915,7 @@ config MFD_STA2X11
>  config MFD_ANATOP
>         bool "Support for Freescale i.MX on-chip ANATOP controller"
>         depends on SOC_IMX6Q
> +       default y
>         help
>           Select this option to enable Freescale i.MX on-chip ANATOP
>           MFD controller. This controller embeds regulator and
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 04c6796..a35a35e 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -30,6 +30,15 @@ config CPU_THERMAL
>           and not the ACPI interface.
>           If you want this support, you should say Y or M here.
>
> +config IMX_ANATOP_THERMAL
> +       bool "imx anatop soc thermal driver"
> +       depends on MFD_ANATOP && CPU_THERMAL

I forgot to replace "CPU_THERMAL" with "THERMAL"

> +       default y
> +       help
> +         Enable the on-chip temperature sensor and register the linux thermal
> +         framework to provide SoC temperature data to sysfs and a basic
> +         shutdown mechanism to prevent the SoC from overheating.
> +
>  config SPEAR_THERMAL
>         bool "SPEAr thermal sensor driver"
>         depends on THERMAL
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index 4636e35..4dd4570 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -6,3 +6,4 @@ obj-$(CONFIG_THERMAL)           += thermal_sys.o
>  obj-$(CONFIG_CPU_THERMAL)       += cpu_cooling.o
>  obj-$(CONFIG_SPEAR_THERMAL)            += spear_thermal.o
>  obj-$(CONFIG_EXYNOS_THERMAL)           += exynos_thermal.o
> +obj-$(CONFIG_IMX_ANATOP_THERMAL)       += imx_anatop_thermal.o
> diff --git a/drivers/thermal/imx_anatop_thermal.c b/drivers/thermal/imx_anatop_thermal.c
> new file mode 100644
> index 0000000..a932fe8
> --- /dev/null
> +++ b/drivers/thermal/imx_anatop_thermal.c
> @@ -0,0 +1,465 @@
> +/*
> + * Copyright 2012 Freescale Semiconductor, Inc.
> + * Copyright 2012 Linaro Ltd.
> + *
> + * The code contained herein is licensed under the GNU General Public
> + * License. You may obtain a copy of the GNU General Public License
> + * Version 2 or later at the following locations:
> + *
> + * http://www.opensource.org/licenses/gpl-license.html
> + * http://www.gnu.org/copyleft/gpl.html
> + */
> +
> +/*
> + * Thermal driver for i.MX anatop systems.  Implented by hooking in to the
> + * linux thermal framework.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/dmi.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/anatop.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.h>
> +#include <linux/smp.h>
> +#include <linux/slab.h>
> +#include <linux/syscalls.h>
> +#include <linux/thermal.h>
> +#include <linux/types.h>
> +
> +/* register define of anatop */
> +#define HW_ANADIG_ANA_MISC0                    0x00000150
> +#define HW_ANADIG_ANA_MISC0_SET                        0x00000154
> +#define HW_ANADIG_ANA_MISC0_CLR                        0x00000158
> +#define HW_ANADIG_ANA_MISC0_TOG                        0x0000015c
> +#define BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF  0x00000008
> +
> +#define HW_ANADIG_TEMPSENSE0                   0x00000180
> +#define HW_ANADIG_TEMPSENSE0_SET               0x00000184
> +#define HW_ANADIG_TEMPSENSE0_CLR               0x00000188
> +#define HW_ANADIG_TEMPSENSE0_TOG               0x0000018c
> +
> +#define BP_ANADIG_TEMPSENSE0_TEMP_VALUE                8
> +#define BM_ANADIG_TEMPSENSE0_TEMP_VALUE                0x000FFF00
> +#define BM_ANADIG_TEMPSENSE0_FINISHED          0x00000004
> +#define BM_ANADIG_TEMPSENSE0_MEASURE_TEMP      0x00000002
> +#define BM_ANADIG_TEMPSENSE0_POWER_DOWN                0x00000001
> +
> +#define HW_ANADIG_TEMPSENSE1                   0x00000190
> +#define HW_ANADIG_TEMPSENSE1_SET               0x00000194
> +#define HW_ANADIG_TEMPSENSE1_CLR               0x00000198
> +#define BP_ANADIG_TEMPSENSE1_MEASURE_FREQ      0
> +#define BM_ANADIG_TEMPSENSE1_MEASURE_FREQ      0x0000FFFF
> +
> +#define HW_OCOTP_ANA1                          0x000004E0
> +
> +#define IMX_THERMAL_POLLING_FREQUENCY_MS 1000
> +
> +#define IMX_ANATOP_TS_NOISE_MARGIN 3000 /* in millicelsius */
> +#define IMX_ANATOP_TS_NOISE_COUNT      3
> +
> +/* anatop temperature sensor data */
> +struct imx_anatop_tsdata {
> +       int     c1, c2;
> +       u32     noise_margin;   /* in millicelsius */
> +       int     last_temp;      /* in millicelsius */
> +       /*
> +        * When filtering noise, if consecutive measurements are each higher
> +        * up to consec_high_limit times, assume changing temperature readings
> +        * to be valid and not noise.
> +        */
> +       u32     consec_high_limit;
> +       bool    handle_suspend;
> +       struct anatop *anatopmfd;
> +
> +};
> +
> +struct imx_anatop_thdata {
> +       struct thermal_zone_device      *tzdev;
> +       struct imx_anatop_tsdata        sensor_data;
> +       struct thermal_zone_device_ops  dev_ops;
> +       unsigned int                    crit_temp_level;
> +       void __iomem                    *ocotp_base;
> +};
> +
> +static inline int anatop_get_temp(int *temp, struct thermal_zone_device *tzdev)
> +{
> +       unsigned int n_meas;
> +       unsigned int reg;
> +       struct imx_anatop_tsdata *sd;
> +
> +       sd = &((struct imx_anatop_thdata *)tzdev->devdata)->sensor_data;
> +
> +
> +       do {
> +               /*
> +                * 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.
> +                */
> +               sd->handle_suspend = false;
> +
> +               anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +                       BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +               anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
> +                       BM_ANADIG_TEMPSENSE0_MEASURE_TEMP,
> +                       BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
> +               /*
> +                * According to the anatop temp sensor designers, it may require
> +                * up to ~17us to complete a measurement (imx6q).
> +                * 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);
> +
> +               reg = anatop_read_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0);
> +
> +               anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +                       BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
> +               anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
> +                       BM_ANADIG_TEMPSENSE0_POWER_DOWN,
> +                       BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +
> +       /* if we had a suspend and resume event, we will re-take the reading */
> +       } while (sd->handle_suspend);
> +
> +       if (!(reg & BM_ANADIG_TEMPSENSE0_FINISHED) &&
> +               (reg & BM_ANADIG_TEMPSENSE0_TEMP_VALUE)) {
> +               dev_dbg(&tzdev->device,
> +                       "Temp sensor error: reading never finished");
> +               return -EAGAIN;
> +       }
> +
> +       n_meas = (reg & BM_ANADIG_TEMPSENSE0_TEMP_VALUE)
> +                       >> BP_ANADIG_TEMPSENSE0_TEMP_VALUE;
> +
> +       /* See anatop_process_ts_fuse_data() for forumla derivation. */
> +       *temp = sd->c2 + (sd->c1 * n_meas);
> +
> +       dev_dbg(&tzdev->device, "Temperature: %d\n", *temp / 1000);
> +       return 0;
> +}
> +
> +static int th_sys_get_temp(struct thermal_zone_device *tzdev,
> +                                 unsigned long *temp)
> +{
> +       int i, total = 0, tmp = 0;
> +       const u8 loop = 5;
> +       u32 consec_high = 0;
> +
> +       struct imx_anatop_tsdata *sd;
> +
> +       sd = &((struct imx_anatop_thdata *)tzdev->devdata)->sensor_data;
> +
> +       /*
> +        * Measure temperature and handle noise
> +        *
> +        * While the anatop temperature sensor is designed to minimize being
> +        * affected by system noise, it's safest to run sanity checks and
> +        * perform any necessary filtering, if a noise_margin has been defined.
> +        */
> +       for (i = 0; (sd->noise_margin) && (i < loop); i++) {
> +               /*
> +                * We expect the sensor reading to be successful, but if for
> +                * unknow reason it is not, use the data from the last
> +                * successful reading
> +                */
> +               if (anatop_get_temp(&tmp, tzdev)) {
> +                       tmp = sd->last_temp;
> +                       continue;
> +               }
> +
> +               if ((abs(tmp - sd->last_temp) <= sd->noise_margin) ||
> +                       (consec_high >= sd->consec_high_limit)) {
> +                       sd->last_temp = tmp;
> +                       i = 0;
> +                       break;
> +               }
> +               if (tmp > sd->last_temp)
> +                       consec_high++;
> +
> +               /*
> +                * ignore first measurement as the previous measurement was
> +                * a long time ago.
> +                */
> +               if (i)
> +                       total += tmp;
> +
> +               sd->last_temp = tmp;
> +       }
> +
> +       if (sd->noise_margin && i)
> +               tmp = total / (loop - 1);
> +
> +       /*
> +        * The thermal framework code stores temperature in unsigned long. Also,
> +        * it has references to "millicelsius" which limits the lowest
> +        * temperature possible (compared to Kelvin).
> +        */
> +       if (tmp > 0)
> +               *temp = tmp;
> +       else
> +               *temp = 0;
> +       return 0;
> +}
> +
> +static int th_sys_get_mode(struct thermal_zone_device *tzdev,
> +                           enum thermal_device_mode *mode)
> +{
> +       *mode = THERMAL_DEVICE_ENABLED;
> +       return 0;
> +}
> +
> +static int th_sys_get_trip_type(struct thermal_zone_device *tzdev, int trip,
> +                                enum thermal_trip_type *type)
> +{
> +       *type = THERMAL_TRIP_CRITICAL;
> +       return 0;
> +}
> +
> +static int th_sys_get_crit_temp(struct thermal_zone_device *tzdev,
> +                                unsigned long *temp)
> +{
> +       struct imx_anatop_thdata *p;
> +
> +       p = (struct imx_anatop_thdata *)tzdev->devdata;
> +       *temp = p->crit_temp_level;
> +       return 0;
> +}
> +
> +static int th_sys_get_trip_temp(struct thermal_zone_device *tzdev, int trip,
> +                                unsigned long *temp)
> +{
> +       /* only a critical trip point is supported, for now. */
> +       return th_sys_get_crit_temp(tzdev, temp);
> +}
> +
> +static int __devinit anatop_process_ts_fuse_data(unsigned int fuse_data,
> +                struct imx_anatop_thdata *thermal_data)
> +{
> +       int t1, t2, n1, n2;
> +       struct imx_anatop_tsdata *sd = &thermal_data->sensor_data;
> +       struct thermal_zone_device *tzdev = thermal_data->tzdev;
> +
> +
> +       if (fuse_data == 0 || fuse_data == 0xffffffff) {
> +               dev_warn(&tzdev->device, "WARNING: Disabling CPU thermal "
> +                       "protection (invalid calibration value of %x detected\n"
> +                       , fuse_data);
> +               return -EINVAL;
> +       }
> +
> +       /*
> +        * Fuse data layout:
> +        * [31:20] sensor value @ 25C
> +        * [19:8] sensor value of hot
> +        * [7:0] hot temperature value
> +        */
> +       n1 = fuse_data >> 20;
> +       n2 = (fuse_data & 0xfff00) >> 8;
> +       t2 = fuse_data & 0xff;
> +       t1 = 25; /* t1 always 25C */
> +
> +       dev_dbg(&tzdev->device, " -- temperature sensor calibration data --\n");
> +       dev_dbg(&tzdev->device, "HW_OCOTP_ANA1: %x\n", fuse_data);
> +       dev_dbg(&tzdev->device, "n1: %d\nn2: %d\nt1: %d\nt2: %d\n", n1, n2, t1,
> +               t2);
> +
> +       /*
> +        * 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)
> +        */
> +       sd->c1 = (1000 * (t1 - t2)) / (n1 - n2);
> +       sd->c2 = (1000 * t2) - (sd->c1 * n2);
> +
> +       dev_dbg(&tzdev->device, "c1: %i\n", sd->c1);
> +       dev_dbg(&tzdev->device, "c2: %i\n", sd->c2);
> +       return 0;
> +}
> +
> +static int anatop_thermal_suspend(struct platform_device *pdev,
> +                                       pm_message_t state)
> +{
> +       struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
> +       struct imx_anatop_tsdata *sd = &dd->sensor_data;
> +
> +       /* power off the sensor during suspend */
> +       anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +               BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
> +
> +       anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
> +               BM_ANADIG_TEMPSENSE0_POWER_DOWN,
> +               BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +       return 0;
> +}
> +
> +static int anatop_thermal_resume(struct platform_device *pdev)
> +{
> +       struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
> +       struct imx_anatop_tsdata *sd = &dd->sensor_data;
> +
> +       sd->handle_suspend = true;
> +       return 0;
> +}
> +
> +static int __devexit anatop_thermal_remove(struct platform_device *pdev)
> +{
> +       struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
> +
> +       if (dd && dd->tzdev)
> +               thermal_zone_device_unregister(dd->tzdev);
> +
> +       if (dd->ocotp_base)
> +               iounmap(dd->ocotp_base);
> +
> +       dev_info(&pdev->dev, "imx thermal management unregistered\n");
> +       return 0;
> +}
> +
> +static int __devinit anatop_thermal_probe(struct platform_device *pdev)
> +{
> +       unsigned int fuse_data;
> +       void __iomem *ocotp_base;
> +       struct device_node *np_ocotp, *np_thermal;
> +       static struct imx_anatop_thdata *therm_data;
> +       struct anatop *anatopmfd = dev_get_drvdata(pdev->dev.parent);
> +
> +       struct imx_anatop_tsdata *sd;
> +       int ret;
> +
> +       np_ocotp = of_find_compatible_node(NULL, NULL, "fsl,imx6q-ocotp");
> +       np_thermal = pdev->dev.of_node;
> +
> +       if (!(np_ocotp && np_thermal && anatopmfd))
> +               return 0;
> +
> +       ocotp_base = of_iomap(np_ocotp, 0);
> +
> +       if (!ocotp_base) {
> +               dev_err(&pdev->dev, "Could not retrieve ocotp-base\n");
> +               ret = -ENXIO;
> +               goto err_unregister;
> +       }
> +
> +       fuse_data = readl_relaxed(ocotp_base + HW_OCOTP_ANA1);
> +
> +       therm_data = devm_kzalloc(&pdev->dev, sizeof(struct imx_anatop_thdata),
> +               GFP_KERNEL);
> +
> +
> +       if (!therm_data) {
> +               ret = -ENOMEM;
> +               goto err_unregister;
> +       }
> +
> +       therm_data->dev_ops.get_temp = th_sys_get_temp;
> +       therm_data->dev_ops.get_mode = th_sys_get_mode;
> +       therm_data->dev_ops.get_trip_type = th_sys_get_trip_type;
> +       therm_data->dev_ops.get_trip_temp = th_sys_get_trip_temp;
> +       therm_data->dev_ops.get_crit_temp = th_sys_get_crit_temp;
> +
> +       /*
> +        * max die temp on imx parts using anatop is 105C, let's give some
> +        * cushion for noise and possible temperature rise between measurements.
> +        */
> +       therm_data->crit_temp_level = 100000; /* in millicelsius */
> +
> +       sd = &therm_data->sensor_data;
> +       sd->noise_margin = IMX_ANATOP_TS_NOISE_MARGIN;
> +       sd->consec_high_limit = IMX_ANATOP_TS_NOISE_COUNT;
> +       sd->anatopmfd = anatopmfd;
> +
> +       platform_set_drvdata(pdev, therm_data);
> +
> +       ret = anatop_process_ts_fuse_data(fuse_data, therm_data);
> +
> +       if (ret) {
> +               dev_err(&pdev->dev, "Invalid temperature calibration data.\n");
> +               goto err_unregister;
> +       }
> +
> +       /* Make sure sensor is in known good state for measurements */
> +       anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +               BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +
> +       anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +               BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
> +
> +       anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE1, 0,
> +               BM_ANADIG_TEMPSENSE1_MEASURE_FREQ);
> +
> +       anatop_write_reg(anatopmfd, HW_ANADIG_ANA_MISC0_SET,
> +               BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF,
> +               BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF);
> +
> +       anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE0,
> +               BM_ANADIG_TEMPSENSE0_POWER_DOWN,
> +               BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +
> +       therm_data->tzdev = thermal_zone_device_register(
> +               "Processor", 1, therm_data, &therm_data->dev_ops, 0, 0, 0,
> +               IMX_THERMAL_POLLING_FREQUENCY_MS);
> +
> +       if (IS_ERR(therm_data->tzdev)) {
> +               dev_err(&pdev->dev,
> +                       "Failed to register thermal zone device\n");
> +               ret = -EINVAL;
> +               goto err_unregister;
> +       }
> +
> +       dev_info(&pdev->dev, "imx thermal management registered\n");
> +       return 0;
> +
> +err_unregister:
> +       anatop_thermal_remove(pdev);
> +       return ret;
> +}
> +
> +static struct of_device_id __devinitdata of_anatop_thermal_match_tbl[] = {
> +       { .compatible = "fsl,anatop-thermal", },
> +       { /* end */ }
> +};
> +
> +static struct platform_driver anatop_thermal = {
> +       .driver = {
> +               .name   = "anatop_thermal",
> +               .owner  = THIS_MODULE,
> +               .of_match_table = of_anatop_thermal_match_tbl,
> +       },
> +       .probe          = anatop_thermal_probe,
> +       .remove         = anatop_thermal_remove,
> +       .suspend        = anatop_thermal_suspend,
> +       .resume         = anatop_thermal_resume,
> +};
> +
> +static int __devinit anatop_thermal_init(void)
> +{
> +       return platform_driver_register(&anatop_thermal);
> +}
> +device_initcall(anatop_thermal_init);
> +
> +static void __exit anatop_thermal_exit(void)
> +{
> +       platform_driver_unregister(&anatop_thermal);
> +}
> +module_exit(anatop_thermal_exit);
> +
> +MODULE_AUTHOR("Freescale Semiconductor");
> +MODULE_DESCRIPTION("i.MX anatop thermal driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:imx-anatop-thermal");
> --
> 1.7.10
>

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

* [PATCH v6] ARM: imx: Add basic imx6q thermal driver
@ 2012-06-27 13:35     ` Rob Lee
  0 siblings, 0 replies; 10+ messages in thread
From: Rob Lee @ 2012-06-27 13:35 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jun 26, 2012 at 11:51 PM, Robert Lee <rob.lee@linaro.org> wrote:
> Add imx anatop peripheral thermal driver and use it for imx6q builds.
> This driver hooks into the linux thermal framework which provides a
> sysfs interface for temperature readings and other information and
> a mechanism to shutdown the system upon crossing a critical
> temperature trip point.
>
> Only the sysfs interface and a critcial trip are supported by this
> patch and not any active trip points or cooling devices.
>
> The thermal driver is defaulted to be enabled which required the
> anatopmfd driver to be defaulted to enabled.
>
> Signed-off-by: Robert Lee <rob.lee@linaro.org>
> ---
>  arch/arm/boot/dts/imx6q.dtsi         |    5 +
>  drivers/mfd/Kconfig                  |    1 +
>  drivers/thermal/Kconfig              |    9 +
>  drivers/thermal/Makefile             |    1 +
>  drivers/thermal/imx_anatop_thermal.c |  465 ++++++++++++++++++++++++++++++++++
>  5 files changed, 481 insertions(+)
>  create mode 100644 drivers/thermal/imx_anatop_thermal.c
>
> diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi
> index d026f30..b53a16a 100644
> --- a/arch/arm/boot/dts/imx6q.dtsi
> +++ b/arch/arm/boot/dts/imx6q.dtsi
> @@ -449,6 +449,10 @@
>                                         anatop-min-voltage = <725000>;
>                                         anatop-max-voltage = <1450000>;
>                                 };
> +
> +                               thermal {
> +                                       compatible ="fsl,anatop-thermal";
> +                               };
>                         };
>
>                         usbphy at 020c9000 { /* USBPHY1 */
> @@ -666,6 +670,7 @@
>                         };
>
>                         ocotp at 021bc000 {
> +                               compatible = "fsl,imx6q-ocotp";
>                                 reg = <0x021bc000 0x4000>;
>                         };
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index e129c82..552fae3 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -915,6 +915,7 @@ config MFD_STA2X11
>  config MFD_ANATOP
>         bool "Support for Freescale i.MX on-chip ANATOP controller"
>         depends on SOC_IMX6Q
> +       default y
>         help
>           Select this option to enable Freescale i.MX on-chip ANATOP
>           MFD controller. This controller embeds regulator and
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 04c6796..a35a35e 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -30,6 +30,15 @@ config CPU_THERMAL
>           and not the ACPI interface.
>           If you want this support, you should say Y or M here.
>
> +config IMX_ANATOP_THERMAL
> +       bool "imx anatop soc thermal driver"
> +       depends on MFD_ANATOP && CPU_THERMAL

I forgot to replace "CPU_THERMAL" with "THERMAL"

> +       default y
> +       help
> +         Enable the on-chip temperature sensor and register the linux thermal
> +         framework to provide SoC temperature data to sysfs and a basic
> +         shutdown mechanism to prevent the SoC from overheating.
> +
>  config SPEAR_THERMAL
>         bool "SPEAr thermal sensor driver"
>         depends on THERMAL
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index 4636e35..4dd4570 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -6,3 +6,4 @@ obj-$(CONFIG_THERMAL)           += thermal_sys.o
>  obj-$(CONFIG_CPU_THERMAL)       += cpu_cooling.o
>  obj-$(CONFIG_SPEAR_THERMAL)            += spear_thermal.o
>  obj-$(CONFIG_EXYNOS_THERMAL)           += exynos_thermal.o
> +obj-$(CONFIG_IMX_ANATOP_THERMAL)       += imx_anatop_thermal.o
> diff --git a/drivers/thermal/imx_anatop_thermal.c b/drivers/thermal/imx_anatop_thermal.c
> new file mode 100644
> index 0000000..a932fe8
> --- /dev/null
> +++ b/drivers/thermal/imx_anatop_thermal.c
> @@ -0,0 +1,465 @@
> +/*
> + * Copyright 2012 Freescale Semiconductor, Inc.
> + * Copyright 2012 Linaro Ltd.
> + *
> + * The code contained herein is licensed under the GNU General Public
> + * License. You may obtain a copy of the GNU General Public License
> + * Version 2 or later at the following locations:
> + *
> + * http://www.opensource.org/licenses/gpl-license.html
> + * http://www.gnu.org/copyleft/gpl.html
> + */
> +
> +/*
> + * Thermal driver for i.MX anatop systems.  Implented by hooking in to the
> + * linux thermal framework.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/dmi.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/anatop.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.h>
> +#include <linux/smp.h>
> +#include <linux/slab.h>
> +#include <linux/syscalls.h>
> +#include <linux/thermal.h>
> +#include <linux/types.h>
> +
> +/* register define of anatop */
> +#define HW_ANADIG_ANA_MISC0                    0x00000150
> +#define HW_ANADIG_ANA_MISC0_SET                        0x00000154
> +#define HW_ANADIG_ANA_MISC0_CLR                        0x00000158
> +#define HW_ANADIG_ANA_MISC0_TOG                        0x0000015c
> +#define BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF  0x00000008
> +
> +#define HW_ANADIG_TEMPSENSE0                   0x00000180
> +#define HW_ANADIG_TEMPSENSE0_SET               0x00000184
> +#define HW_ANADIG_TEMPSENSE0_CLR               0x00000188
> +#define HW_ANADIG_TEMPSENSE0_TOG               0x0000018c
> +
> +#define BP_ANADIG_TEMPSENSE0_TEMP_VALUE                8
> +#define BM_ANADIG_TEMPSENSE0_TEMP_VALUE                0x000FFF00
> +#define BM_ANADIG_TEMPSENSE0_FINISHED          0x00000004
> +#define BM_ANADIG_TEMPSENSE0_MEASURE_TEMP      0x00000002
> +#define BM_ANADIG_TEMPSENSE0_POWER_DOWN                0x00000001
> +
> +#define HW_ANADIG_TEMPSENSE1                   0x00000190
> +#define HW_ANADIG_TEMPSENSE1_SET               0x00000194
> +#define HW_ANADIG_TEMPSENSE1_CLR               0x00000198
> +#define BP_ANADIG_TEMPSENSE1_MEASURE_FREQ      0
> +#define BM_ANADIG_TEMPSENSE1_MEASURE_FREQ      0x0000FFFF
> +
> +#define HW_OCOTP_ANA1                          0x000004E0
> +
> +#define IMX_THERMAL_POLLING_FREQUENCY_MS 1000
> +
> +#define IMX_ANATOP_TS_NOISE_MARGIN 3000 /* in millicelsius */
> +#define IMX_ANATOP_TS_NOISE_COUNT      3
> +
> +/* anatop temperature sensor data */
> +struct imx_anatop_tsdata {
> +       int     c1, c2;
> +       u32     noise_margin;   /* in millicelsius */
> +       int     last_temp;      /* in millicelsius */
> +       /*
> +        * When filtering noise, if consecutive measurements are each higher
> +        * up to consec_high_limit times, assume changing temperature readings
> +        * to be valid and not noise.
> +        */
> +       u32     consec_high_limit;
> +       bool    handle_suspend;
> +       struct anatop *anatopmfd;
> +
> +};
> +
> +struct imx_anatop_thdata {
> +       struct thermal_zone_device      *tzdev;
> +       struct imx_anatop_tsdata        sensor_data;
> +       struct thermal_zone_device_ops  dev_ops;
> +       unsigned int                    crit_temp_level;
> +       void __iomem                    *ocotp_base;
> +};
> +
> +static inline int anatop_get_temp(int *temp, struct thermal_zone_device *tzdev)
> +{
> +       unsigned int n_meas;
> +       unsigned int reg;
> +       struct imx_anatop_tsdata *sd;
> +
> +       sd = &((struct imx_anatop_thdata *)tzdev->devdata)->sensor_data;
> +
> +
> +       do {
> +               /*
> +                * 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.
> +                */
> +               sd->handle_suspend = false;
> +
> +               anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +                       BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +               anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
> +                       BM_ANADIG_TEMPSENSE0_MEASURE_TEMP,
> +                       BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
> +               /*
> +                * According to the anatop temp sensor designers, it may require
> +                * up to ~17us to complete a measurement (imx6q).
> +                * 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);
> +
> +               reg = anatop_read_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0);
> +
> +               anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +                       BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
> +               anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
> +                       BM_ANADIG_TEMPSENSE0_POWER_DOWN,
> +                       BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +
> +       /* if we had a suspend and resume event, we will re-take the reading */
> +       } while (sd->handle_suspend);
> +
> +       if (!(reg & BM_ANADIG_TEMPSENSE0_FINISHED) &&
> +               (reg & BM_ANADIG_TEMPSENSE0_TEMP_VALUE)) {
> +               dev_dbg(&tzdev->device,
> +                       "Temp sensor error: reading never finished");
> +               return -EAGAIN;
> +       }
> +
> +       n_meas = (reg & BM_ANADIG_TEMPSENSE0_TEMP_VALUE)
> +                       >> BP_ANADIG_TEMPSENSE0_TEMP_VALUE;
> +
> +       /* See anatop_process_ts_fuse_data() for forumla derivation. */
> +       *temp = sd->c2 + (sd->c1 * n_meas);
> +
> +       dev_dbg(&tzdev->device, "Temperature: %d\n", *temp / 1000);
> +       return 0;
> +}
> +
> +static int th_sys_get_temp(struct thermal_zone_device *tzdev,
> +                                 unsigned long *temp)
> +{
> +       int i, total = 0, tmp = 0;
> +       const u8 loop = 5;
> +       u32 consec_high = 0;
> +
> +       struct imx_anatop_tsdata *sd;
> +
> +       sd = &((struct imx_anatop_thdata *)tzdev->devdata)->sensor_data;
> +
> +       /*
> +        * Measure temperature and handle noise
> +        *
> +        * While the anatop temperature sensor is designed to minimize being
> +        * affected by system noise, it's safest to run sanity checks and
> +        * perform any necessary filtering, if a noise_margin has been defined.
> +        */
> +       for (i = 0; (sd->noise_margin) && (i < loop); i++) {
> +               /*
> +                * We expect the sensor reading to be successful, but if for
> +                * unknow reason it is not, use the data from the last
> +                * successful reading
> +                */
> +               if (anatop_get_temp(&tmp, tzdev)) {
> +                       tmp = sd->last_temp;
> +                       continue;
> +               }
> +
> +               if ((abs(tmp - sd->last_temp) <= sd->noise_margin) ||
> +                       (consec_high >= sd->consec_high_limit)) {
> +                       sd->last_temp = tmp;
> +                       i = 0;
> +                       break;
> +               }
> +               if (tmp > sd->last_temp)
> +                       consec_high++;
> +
> +               /*
> +                * ignore first measurement as the previous measurement was
> +                * a long time ago.
> +                */
> +               if (i)
> +                       total += tmp;
> +
> +               sd->last_temp = tmp;
> +       }
> +
> +       if (sd->noise_margin && i)
> +               tmp = total / (loop - 1);
> +
> +       /*
> +        * The thermal framework code stores temperature in unsigned long. Also,
> +        * it has references to "millicelsius" which limits the lowest
> +        * temperature possible (compared to Kelvin).
> +        */
> +       if (tmp > 0)
> +               *temp = tmp;
> +       else
> +               *temp = 0;
> +       return 0;
> +}
> +
> +static int th_sys_get_mode(struct thermal_zone_device *tzdev,
> +                           enum thermal_device_mode *mode)
> +{
> +       *mode = THERMAL_DEVICE_ENABLED;
> +       return 0;
> +}
> +
> +static int th_sys_get_trip_type(struct thermal_zone_device *tzdev, int trip,
> +                                enum thermal_trip_type *type)
> +{
> +       *type = THERMAL_TRIP_CRITICAL;
> +       return 0;
> +}
> +
> +static int th_sys_get_crit_temp(struct thermal_zone_device *tzdev,
> +                                unsigned long *temp)
> +{
> +       struct imx_anatop_thdata *p;
> +
> +       p = (struct imx_anatop_thdata *)tzdev->devdata;
> +       *temp = p->crit_temp_level;
> +       return 0;
> +}
> +
> +static int th_sys_get_trip_temp(struct thermal_zone_device *tzdev, int trip,
> +                                unsigned long *temp)
> +{
> +       /* only a critical trip point is supported, for now. */
> +       return th_sys_get_crit_temp(tzdev, temp);
> +}
> +
> +static int __devinit anatop_process_ts_fuse_data(unsigned int fuse_data,
> +                struct imx_anatop_thdata *thermal_data)
> +{
> +       int t1, t2, n1, n2;
> +       struct imx_anatop_tsdata *sd = &thermal_data->sensor_data;
> +       struct thermal_zone_device *tzdev = thermal_data->tzdev;
> +
> +
> +       if (fuse_data == 0 || fuse_data == 0xffffffff) {
> +               dev_warn(&tzdev->device, "WARNING: Disabling CPU thermal "
> +                       "protection (invalid calibration value of %x detected\n"
> +                       , fuse_data);
> +               return -EINVAL;
> +       }
> +
> +       /*
> +        * Fuse data layout:
> +        * [31:20] sensor value @ 25C
> +        * [19:8] sensor value of hot
> +        * [7:0] hot temperature value
> +        */
> +       n1 = fuse_data >> 20;
> +       n2 = (fuse_data & 0xfff00) >> 8;
> +       t2 = fuse_data & 0xff;
> +       t1 = 25; /* t1 always 25C */
> +
> +       dev_dbg(&tzdev->device, " -- temperature sensor calibration data --\n");
> +       dev_dbg(&tzdev->device, "HW_OCOTP_ANA1: %x\n", fuse_data);
> +       dev_dbg(&tzdev->device, "n1: %d\nn2: %d\nt1: %d\nt2: %d\n", n1, n2, t1,
> +               t2);
> +
> +       /*
> +        * 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)
> +        */
> +       sd->c1 = (1000 * (t1 - t2)) / (n1 - n2);
> +       sd->c2 = (1000 * t2) - (sd->c1 * n2);
> +
> +       dev_dbg(&tzdev->device, "c1: %i\n", sd->c1);
> +       dev_dbg(&tzdev->device, "c2: %i\n", sd->c2);
> +       return 0;
> +}
> +
> +static int anatop_thermal_suspend(struct platform_device *pdev,
> +                                       pm_message_t state)
> +{
> +       struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
> +       struct imx_anatop_tsdata *sd = &dd->sensor_data;
> +
> +       /* power off the sensor during suspend */
> +       anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +               BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
> +
> +       anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
> +               BM_ANADIG_TEMPSENSE0_POWER_DOWN,
> +               BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +       return 0;
> +}
> +
> +static int anatop_thermal_resume(struct platform_device *pdev)
> +{
> +       struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
> +       struct imx_anatop_tsdata *sd = &dd->sensor_data;
> +
> +       sd->handle_suspend = true;
> +       return 0;
> +}
> +
> +static int __devexit anatop_thermal_remove(struct platform_device *pdev)
> +{
> +       struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
> +
> +       if (dd && dd->tzdev)
> +               thermal_zone_device_unregister(dd->tzdev);
> +
> +       if (dd->ocotp_base)
> +               iounmap(dd->ocotp_base);
> +
> +       dev_info(&pdev->dev, "imx thermal management unregistered\n");
> +       return 0;
> +}
> +
> +static int __devinit anatop_thermal_probe(struct platform_device *pdev)
> +{
> +       unsigned int fuse_data;
> +       void __iomem *ocotp_base;
> +       struct device_node *np_ocotp, *np_thermal;
> +       static struct imx_anatop_thdata *therm_data;
> +       struct anatop *anatopmfd = dev_get_drvdata(pdev->dev.parent);
> +
> +       struct imx_anatop_tsdata *sd;
> +       int ret;
> +
> +       np_ocotp = of_find_compatible_node(NULL, NULL, "fsl,imx6q-ocotp");
> +       np_thermal = pdev->dev.of_node;
> +
> +       if (!(np_ocotp && np_thermal && anatopmfd))
> +               return 0;
> +
> +       ocotp_base = of_iomap(np_ocotp, 0);
> +
> +       if (!ocotp_base) {
> +               dev_err(&pdev->dev, "Could not retrieve ocotp-base\n");
> +               ret = -ENXIO;
> +               goto err_unregister;
> +       }
> +
> +       fuse_data = readl_relaxed(ocotp_base + HW_OCOTP_ANA1);
> +
> +       therm_data = devm_kzalloc(&pdev->dev, sizeof(struct imx_anatop_thdata),
> +               GFP_KERNEL);
> +
> +
> +       if (!therm_data) {
> +               ret = -ENOMEM;
> +               goto err_unregister;
> +       }
> +
> +       therm_data->dev_ops.get_temp = th_sys_get_temp;
> +       therm_data->dev_ops.get_mode = th_sys_get_mode;
> +       therm_data->dev_ops.get_trip_type = th_sys_get_trip_type;
> +       therm_data->dev_ops.get_trip_temp = th_sys_get_trip_temp;
> +       therm_data->dev_ops.get_crit_temp = th_sys_get_crit_temp;
> +
> +       /*
> +        * max die temp on imx parts using anatop is 105C, let's give some
> +        * cushion for noise and possible temperature rise between measurements.
> +        */
> +       therm_data->crit_temp_level = 100000; /* in millicelsius */
> +
> +       sd = &therm_data->sensor_data;
> +       sd->noise_margin = IMX_ANATOP_TS_NOISE_MARGIN;
> +       sd->consec_high_limit = IMX_ANATOP_TS_NOISE_COUNT;
> +       sd->anatopmfd = anatopmfd;
> +
> +       platform_set_drvdata(pdev, therm_data);
> +
> +       ret = anatop_process_ts_fuse_data(fuse_data, therm_data);
> +
> +       if (ret) {
> +               dev_err(&pdev->dev, "Invalid temperature calibration data.\n");
> +               goto err_unregister;
> +       }
> +
> +       /* Make sure sensor is in known good state for measurements */
> +       anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +               BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +
> +       anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +               BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
> +
> +       anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE1, 0,
> +               BM_ANADIG_TEMPSENSE1_MEASURE_FREQ);
> +
> +       anatop_write_reg(anatopmfd, HW_ANADIG_ANA_MISC0_SET,
> +               BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF,
> +               BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF);
> +
> +       anatop_write_reg(anatopmfd, HW_ANADIG_TEMPSENSE0,
> +               BM_ANADIG_TEMPSENSE0_POWER_DOWN,
> +               BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +
> +       therm_data->tzdev = thermal_zone_device_register(
> +               "Processor", 1, therm_data, &therm_data->dev_ops, 0, 0, 0,
> +               IMX_THERMAL_POLLING_FREQUENCY_MS);
> +
> +       if (IS_ERR(therm_data->tzdev)) {
> +               dev_err(&pdev->dev,
> +                       "Failed to register thermal zone device\n");
> +               ret = -EINVAL;
> +               goto err_unregister;
> +       }
> +
> +       dev_info(&pdev->dev, "imx thermal management registered\n");
> +       return 0;
> +
> +err_unregister:
> +       anatop_thermal_remove(pdev);
> +       return ret;
> +}
> +
> +static struct of_device_id __devinitdata of_anatop_thermal_match_tbl[] = {
> +       { .compatible = "fsl,anatop-thermal", },
> +       { /* end */ }
> +};
> +
> +static struct platform_driver anatop_thermal = {
> +       .driver = {
> +               .name   = "anatop_thermal",
> +               .owner  = THIS_MODULE,
> +               .of_match_table = of_anatop_thermal_match_tbl,
> +       },
> +       .probe          = anatop_thermal_probe,
> +       .remove         = anatop_thermal_remove,
> +       .suspend        = anatop_thermal_suspend,
> +       .resume         = anatop_thermal_resume,
> +};
> +
> +static int __devinit anatop_thermal_init(void)
> +{
> +       return platform_driver_register(&anatop_thermal);
> +}
> +device_initcall(anatop_thermal_init);
> +
> +static void __exit anatop_thermal_exit(void)
> +{
> +       platform_driver_unregister(&anatop_thermal);
> +}
> +module_exit(anatop_thermal_exit);
> +
> +MODULE_AUTHOR("Freescale Semiconductor");
> +MODULE_DESCRIPTION("i.MX anatop thermal driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:imx-anatop-thermal");
> --
> 1.7.10
>

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

* Re: [PATCH v6] ARM: imx: Add basic imx6q thermal driver
  2012-06-27  4:51   ` Robert Lee
@ 2012-06-27 13:48     ` Sascha Hauer
  -1 siblings, 0 replies; 10+ messages in thread
From: Sascha Hauer @ 2012-06-27 13:48 UTC (permalink / raw)
  To: Robert Lee
  Cc: kernel, shawn.guo, linux, richard.zhao, dirk.behme, amit.kachhap,
	amit.kucheria, lenb, linux-arm-kernel, linux-kernel, linaro-dev,
	patches

On Tue, Jun 26, 2012 at 11:51:55PM -0500, Robert Lee wrote:
> +static inline int anatop_get_temp(int *temp, struct thermal_zone_device *tzdev)
> +{
> +	unsigned int n_meas;
> +	unsigned int reg;
> +	struct imx_anatop_tsdata *sd;
> +
> +	sd = &((struct imx_anatop_thdata *)tzdev->devdata)->sensor_data;
> +
> +
> +	do {
> +		/*
> +		 * 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.
> +		 */
> +		sd->handle_suspend = false;
> +
> +		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +			BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
> +			BM_ANADIG_TEMPSENSE0_MEASURE_TEMP,
> +			BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
> +		/*
> +		 * According to the anatop temp sensor designers, it may require
> +		 * up to ~17us to complete a measurement (imx6q).
> +		 * 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);
> +
> +		reg = anatop_read_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0);
> +
> +		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +			BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
> +		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
> +			BM_ANADIG_TEMPSENSE0_POWER_DOWN,
> +			BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +
> +	/* if we had a suspend and resume event, we will re-take the reading */
> +	} while (sd->handle_suspend);

[...]

> +static int anatop_thermal_suspend(struct platform_device *pdev,
> +					pm_message_t state)
> +{
> +	struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
> +	struct imx_anatop_tsdata *sd = &dd->sensor_data;
> +
> +	/* power off the sensor during suspend */
> +	anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +		BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
> +
> +	anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
> +		BM_ANADIG_TEMPSENSE0_POWER_DOWN,
> +		BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +	return 0;
> +}
> +
> +static int anatop_thermal_resume(struct platform_device *pdev)
> +{
> +	struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
> +	struct imx_anatop_tsdata *sd = &dd->sensor_data;
> +
> +	sd->handle_suspend = true;
> +	return 0;
> +}

I don't know how to handle this properly or if it's really needed, but
the usage of this handle_suspend variable really looks suspicious. I've
never seen a driver looping around a while-suspended-in-between. Someone
else should have a look over this.

Sascha

-- 
Pengutronix e.K.                           |                             |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

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

* [PATCH v6] ARM: imx: Add basic imx6q thermal driver
@ 2012-06-27 13:48     ` Sascha Hauer
  0 siblings, 0 replies; 10+ messages in thread
From: Sascha Hauer @ 2012-06-27 13:48 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jun 26, 2012 at 11:51:55PM -0500, Robert Lee wrote:
> +static inline int anatop_get_temp(int *temp, struct thermal_zone_device *tzdev)
> +{
> +	unsigned int n_meas;
> +	unsigned int reg;
> +	struct imx_anatop_tsdata *sd;
> +
> +	sd = &((struct imx_anatop_thdata *)tzdev->devdata)->sensor_data;
> +
> +
> +	do {
> +		/*
> +		 * 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.
> +		 */
> +		sd->handle_suspend = false;
> +
> +		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +			BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
> +			BM_ANADIG_TEMPSENSE0_MEASURE_TEMP,
> +			BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
> +		/*
> +		 * According to the anatop temp sensor designers, it may require
> +		 * up to ~17us to complete a measurement (imx6q).
> +		 * 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);
> +
> +		reg = anatop_read_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0);
> +
> +		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +			BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
> +		anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
> +			BM_ANADIG_TEMPSENSE0_POWER_DOWN,
> +			BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +
> +	/* if we had a suspend and resume event, we will re-take the reading */
> +	} while (sd->handle_suspend);

[...]

> +static int anatop_thermal_suspend(struct platform_device *pdev,
> +					pm_message_t state)
> +{
> +	struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
> +	struct imx_anatop_tsdata *sd = &dd->sensor_data;
> +
> +	/* power off the sensor during suspend */
> +	anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
> +		BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
> +
> +	anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
> +		BM_ANADIG_TEMPSENSE0_POWER_DOWN,
> +		BM_ANADIG_TEMPSENSE0_POWER_DOWN);
> +	return 0;
> +}
> +
> +static int anatop_thermal_resume(struct platform_device *pdev)
> +{
> +	struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
> +	struct imx_anatop_tsdata *sd = &dd->sensor_data;
> +
> +	sd->handle_suspend = true;
> +	return 0;
> +}

I don't know how to handle this properly or if it's really needed, but
the usage of this handle_suspend variable really looks suspicious. I've
never seen a driver looping around a while-suspended-in-between. Someone
else should have a look over this.

Sascha

-- 
Pengutronix e.K.                           |                             |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

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

* Re: [PATCH v6] ARM: imx: Add basic imx6q thermal driver
  2012-06-27 13:48     ` Sascha Hauer
@ 2012-06-27 17:05       ` Rob Lee
  -1 siblings, 0 replies; 10+ messages in thread
From: Rob Lee @ 2012-06-27 17:05 UTC (permalink / raw)
  To: Sascha Hauer
  Cc: kernel, shawn.guo, linux, richard.zhao, dirk.behme, amit.kachhap,
	amit.kucheria, lenb, linux-arm-kernel, linux-kernel, linaro-dev,
	patches

On Wed, Jun 27, 2012 at 8:48 AM, Sascha Hauer <s.hauer@pengutronix.de> wrote:
> On Tue, Jun 26, 2012 at 11:51:55PM -0500, Robert Lee wrote:
>> +static inline int anatop_get_temp(int *temp, struct thermal_zone_device *tzdev)
>> +{
>> +     unsigned int n_meas;
>> +     unsigned int reg;
>> +     struct imx_anatop_tsdata *sd;
>> +
>> +     sd = &((struct imx_anatop_thdata *)tzdev->devdata)->sensor_data;
>> +
>> +
>> +     do {
>> +             /*
>> +              * 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.
>> +              */
>> +             sd->handle_suspend = false;
>> +
>> +             anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
>> +                     BM_ANADIG_TEMPSENSE0_POWER_DOWN);
>> +             anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
>> +                     BM_ANADIG_TEMPSENSE0_MEASURE_TEMP,
>> +                     BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
>> +             /*
>> +              * According to the anatop temp sensor designers, it may require
>> +              * up to ~17us to complete a measurement (imx6q).
>> +              * 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);
>> +
>> +             reg = anatop_read_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0);
>> +
>> +             anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
>> +                     BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
>> +             anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
>> +                     BM_ANADIG_TEMPSENSE0_POWER_DOWN,
>> +                     BM_ANADIG_TEMPSENSE0_POWER_DOWN);
>> +
>> +     /* if we had a suspend and resume event, we will re-take the reading */
>> +     } while (sd->handle_suspend);
>
> [...]
>
>> +static int anatop_thermal_suspend(struct platform_device *pdev,
>> +                                     pm_message_t state)
>> +{
>> +     struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
>> +     struct imx_anatop_tsdata *sd = &dd->sensor_data;
>> +
>> +     /* power off the sensor during suspend */
>> +     anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
>> +             BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
>> +
>> +     anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
>> +             BM_ANADIG_TEMPSENSE0_POWER_DOWN,
>> +             BM_ANADIG_TEMPSENSE0_POWER_DOWN);
>> +     return 0;
>> +}
>> +
>> +static int anatop_thermal_resume(struct platform_device *pdev)
>> +{
>> +     struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
>> +     struct imx_anatop_tsdata *sd = &dd->sensor_data;
>> +
>> +     sd->handle_suspend = true;
>> +     return 0;
>> +}
>
> I don't know how to handle this properly or if it's really needed, but
> the usage of this handle_suspend variable really looks suspicious. I've
> never seen a driver looping around a while-suspended-in-between. Someone
> else should have a look over this.

I'll add some more context that may help you and others in analyzing this.

As mentioned, this driver hooks into the linux thermal framework.
Upon registration with this is framework, the thermal framework
creates a kernel thread which periodically checks the temperature and
takes any necessary action.  So anatop_get_temp periodically gets
called.  Now if a suspend were to occur during the msleep() call of
this function for example, the temperature read afterword may not be
valid.  So the loop check can detect that a suspend had occurred and
that the temperature sensor value read was invalid so it will try
again.

Another option would be for the resume function to return the
temperature sensor to its previous state that it was in right before
suspend.  This would require the resume to re-enable the temperature
sensor and spin in a loop waiting for the temperature measurement to
complete (aka, checking a hardware bit for completion) which
theoretically may take up to ~17us.  In this case, a timeout could be
added to this loop if we don't trust the hardware, and the looping in
the anatop_get_temp could be removed.  This function already has some
handling if an invalid temp sensor reading is detected.

>
> Sascha
>
> --
> Pengutronix e.K.                           |                             |
> Industrial Linux Solutions                 | http://www.pengutronix.de/  |
> Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
> Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

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

* [PATCH v6] ARM: imx: Add basic imx6q thermal driver
@ 2012-06-27 17:05       ` Rob Lee
  0 siblings, 0 replies; 10+ messages in thread
From: Rob Lee @ 2012-06-27 17:05 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Jun 27, 2012 at 8:48 AM, Sascha Hauer <s.hauer@pengutronix.de> wrote:
> On Tue, Jun 26, 2012 at 11:51:55PM -0500, Robert Lee wrote:
>> +static inline int anatop_get_temp(int *temp, struct thermal_zone_device *tzdev)
>> +{
>> +     unsigned int n_meas;
>> +     unsigned int reg;
>> +     struct imx_anatop_tsdata *sd;
>> +
>> +     sd = &((struct imx_anatop_thdata *)tzdev->devdata)->sensor_data;
>> +
>> +
>> +     do {
>> +             /*
>> +              * 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.
>> +              */
>> +             sd->handle_suspend = false;
>> +
>> +             anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
>> +                     BM_ANADIG_TEMPSENSE0_POWER_DOWN);
>> +             anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
>> +                     BM_ANADIG_TEMPSENSE0_MEASURE_TEMP,
>> +                     BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
>> +             /*
>> +              * According to the anatop temp sensor designers, it may require
>> +              * up to ~17us to complete a measurement (imx6q).
>> +              * 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);
>> +
>> +             reg = anatop_read_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0);
>> +
>> +             anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
>> +                     BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
>> +             anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
>> +                     BM_ANADIG_TEMPSENSE0_POWER_DOWN,
>> +                     BM_ANADIG_TEMPSENSE0_POWER_DOWN);
>> +
>> +     /* if we had a suspend and resume event, we will re-take the reading */
>> +     } while (sd->handle_suspend);
>
> [...]
>
>> +static int anatop_thermal_suspend(struct platform_device *pdev,
>> +                                     pm_message_t state)
>> +{
>> +     struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
>> +     struct imx_anatop_tsdata *sd = &dd->sensor_data;
>> +
>> +     /* power off the sensor during suspend */
>> +     anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0, 0,
>> +             BM_ANADIG_TEMPSENSE0_MEASURE_TEMP);
>> +
>> +     anatop_write_reg(sd->anatopmfd, HW_ANADIG_TEMPSENSE0,
>> +             BM_ANADIG_TEMPSENSE0_POWER_DOWN,
>> +             BM_ANADIG_TEMPSENSE0_POWER_DOWN);
>> +     return 0;
>> +}
>> +
>> +static int anatop_thermal_resume(struct platform_device *pdev)
>> +{
>> +     struct imx_anatop_thdata *dd = platform_get_drvdata(pdev);
>> +     struct imx_anatop_tsdata *sd = &dd->sensor_data;
>> +
>> +     sd->handle_suspend = true;
>> +     return 0;
>> +}
>
> I don't know how to handle this properly or if it's really needed, but
> the usage of this handle_suspend variable really looks suspicious. I've
> never seen a driver looping around a while-suspended-in-between. Someone
> else should have a look over this.

I'll add some more context that may help you and others in analyzing this.

As mentioned, this driver hooks into the linux thermal framework.
Upon registration with this is framework, the thermal framework
creates a kernel thread which periodically checks the temperature and
takes any necessary action.  So anatop_get_temp periodically gets
called.  Now if a suspend were to occur during the msleep() call of
this function for example, the temperature read afterword may not be
valid.  So the loop check can detect that a suspend had occurred and
that the temperature sensor value read was invalid so it will try
again.

Another option would be for the resume function to return the
temperature sensor to its previous state that it was in right before
suspend.  This would require the resume to re-enable the temperature
sensor and spin in a loop waiting for the temperature measurement to
complete (aka, checking a hardware bit for completion) which
theoretically may take up to ~17us.  In this case, a timeout could be
added to this loop if we don't trust the hardware, and the looping in
the anatop_get_temp could be removed.  This function already has some
handling if an invalid temp sensor reading is detected.

>
> Sascha
>
> --
> Pengutronix e.K.                           |                             |
> Industrial Linux Solutions                 | http://www.pengutronix.de/  |
> Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
> Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

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

end of thread, other threads:[~2012-06-27 17:06 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-06-27  4:51 [PATCH v6] ARM: imx: Add basic imx6q thermal driver Robert Lee
2012-06-27  4:51 ` Robert Lee
2012-06-27  4:51 ` Robert Lee
2012-06-27  4:51   ` Robert Lee
2012-06-27 13:35   ` Rob Lee
2012-06-27 13:35     ` Rob Lee
2012-06-27 13:48   ` Sascha Hauer
2012-06-27 13:48     ` Sascha Hauer
2012-06-27 17:05     ` Rob Lee
2012-06-27 17:05       ` Rob Lee

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.