[PATCH v5] ARM: imx: Add basic imx6q cpu thermal management

Sascha Hauer s.hauer at pengutronix.de
Wed Jun 20 06:11:27 EDT 2012


On Wed, Jun 20, 2012 at 02:06:04AM -0500, Robert Lee wrote:
> Add imx6q cpu thermal management driver using the new cpu cooling
> interface which limits system performance via cpufreq to reduce
> the cpu temperature.  Temperature readings are taken using
> the imx6q temperature sensor and this functionality was added
> as part of this cpu thermal management driver.
> 
> Signed-off-by: Robert Lee <rob.lee at linaro.org>
> ---
>  arch/arm/boot/dts/imx6q.dtsi    |    5 +
>  drivers/thermal/Kconfig         |   28 ++
>  drivers/thermal/Makefile        |    1 +
>  drivers/thermal/imx6q_thermal.c |  593 +++++++++++++++++++++++++++++++++++++++
>  4 files changed, 627 insertions(+)
>  create mode 100644 drivers/thermal/imx6q_thermal.c
> 
> diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi
> index 8c90cba..2650f65 100644
> --- a/arch/arm/boot/dts/imx6q.dtsi
> +++ b/arch/arm/boot/dts/imx6q.dtsi
> @@ -442,6 +442,10 @@
>  					anatop-min-voltage = <725000>;
>  					anatop-max-voltage = <1450000>;
>  				};
> +
> +				thermal {
> +					compatible ="fsl,anatop-thermal";
> +				};
>  			};
>  
>  			usbphy at 020c9000 { /* USBPHY1 */
> @@ -659,6 +663,7 @@
>  			};
>  
>  			ocotp at 021bc000 {
> +				compatible = "fsl,imx6q-ocotp";
>  				reg = <0x021bc000 0x4000>;
>  			};
>  
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 04c6796..b80c408 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -30,6 +30,34 @@ config CPU_THERMAL
>  	  and not the ACPI interface.
>  	  If you want this support, you should say Y or M here.
>  
> +config IMX6Q_THERMAL
> +	bool "IMX6Q Thermal interface support"
> +	depends on MFD_ANATOP && CPU_THERMAL
> +	help
> +	  Adds thermal management for IMX6Q.
> +
> +config IMX6Q_THERMAL_FAKE_CALIBRATION
> +	bool "IMX6Q fake temperature sensor calibration (FOR TESTING ONLY)"
> +	depends on IMX6Q_THERMAL
> +	help
> +	  This enables a fake temp sensor calibration value for parts without
> +	  the correction calibration values burned into OCOTP. If the part
> +	  already has the calibrated values burned into OCOTP, enabling this
> +	  does nothing.
> +	  WARNING: Use of this feature is for testing only as it will cause
> +	  incorrect temperature readings which will result in incorrect system
> +	  thermal limiting behavior such as premature system performance
> +	  limiting or lack of proper performance reduction to reduce cpu
> +	  temperature
> +
> +config IMX6Q_THERMAL_FAKE_CAL_VAL
> +	hex "IMX6Q fake temperature sensor calibration value"
> +	depends on IMX6Q_THERMAL_FAKE_CALIBRATION
> +	default 0x5704c67d
> +	help
> +	  Refer to the temperature sensor section of the imx6q reference manual
> +	  for more inforation on how this value is used.

Don't add such stuff to Kconfig. If during runtime you detect that there
is no calibration data, then issue a warning and fall back to a safe
value. If you really think this should be configurable, add a sysfs
entry for it. "FOR TESTING ONLY" seems to imply though that it shouldn't
be configurable.


> +
>  config SPEAR_THERMAL
>  	bool "SPEAr thermal sensor driver"
>  	depends on THERMAL
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index 4636e35..fc4004e 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_IMX6Q_THERMAL)	+= imx6q_thermal.o
> diff --git a/drivers/thermal/imx6q_thermal.c b/drivers/thermal/imx6q_thermal.c
> new file mode 100644
> index 0000000..255d646
> --- /dev/null
> +++ b/drivers/thermal/imx6q_thermal.c
> @@ -0,0 +1,593 @@
> +/*
> + * 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
> + */
> +
> +/* i.MX6Q Thermal Implementation */
> +
> +#include <linux/kernel.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/dmi.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include <linux/types.h>
> +#include <linux/thermal.h>
> +#include <linux/io.h>
> +#include <linux/syscalls.h>
> +#include <linux/cpufreq.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/smp.h>
> +#include <linux/cpu_cooling.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/anatop.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 IMX6Q_THERMAL_POLLING_FREQUENCY_MS 1000
> +
> +#define IMX6Q_THERMAL_ACTIVE_TRP_PTS 3
> +/* assumption: always one critical trip point */
> +#define IMX6Q_THERMAL_TOTAL_TRP_PTS (IMX6Q_THERMAL_ACTIVE_TRP_PTS + 1)
> +
> +struct imx6q_sensor_data {
> +	int	c1, c2;
> +	char	name[16];
> +	u8	meas_delay;	/* in milliseconds */
> +	u32	noise_margin;   /* in millicelcius */
> +	int	last_temp;	/* in millicelcius */

s/celcius/celsius/

> +	/*
> +	 * When noise filtering, 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;
> +	struct anatop *anatopmfd;
> +};
> +
> +struct imx6q_thermal_data {
> +	enum thermal_trip_type type[IMX6Q_THERMAL_TOTAL_TRP_PTS];
> +	struct freq_clip_table freq_tab[IMX6Q_THERMAL_ACTIVE_TRP_PTS];
> +	unsigned int crit_temp_level;
> +};
> +
> +struct imx6q_thermal_zone {
> +	struct thermal_zone_device	*therm_dev;
> +	struct thermal_cooling_device	*cool_dev;
> +	struct imx6q_sensor_data	*sensor_data;
> +	struct imx6q_thermal_data	*thermal_data;
> +	struct thermal_zone_device_ops	dev_ops;
> +};
> +
> +static struct imx6q_sensor_data g_sensor_data __devinitdata = {
> +	.name			= "imx6q-temp_sens",
> +	.meas_delay		= 1,	/* in milliseconds */

This is initialized here, once again in probe and never changed to any
other value.

> +	.noise_margin		= 3000,
> +	.consec_high_limit	= 3,
> +};

Also constant values. What's the purpose of collecting these in this
struct?

> +
> +/*
> + * This data defines the various temperature trip points that will trigger
> + * cooling action when crossed.
> + */
> +static struct imx6q_thermal_data g_thermal_data __devinitdata = {
> +	.type[0] = THERMAL_TRIP_ACTIVE,
> +	.freq_tab[0] = {
> +		.freq_clip_max = 800 * 1000,
> +		.temp_level = 85000,
> +	},
> +	.type[1] = THERMAL_TRIP_ACTIVE,
> +	.freq_tab[1] = {
> +		.freq_clip_max = 400 * 1000,
> +		.temp_level = 90000,
> +	},
> +	.type[2] = THERMAL_TRIP_ACTIVE,
> +	.freq_tab[2] = {
> +		.freq_clip_max = 200 * 1000,
> +		.temp_level = 95000,
> +	},
> +	.type[3] = THERMAL_TRIP_CRITICAL,
> +	.crit_temp_level = 100000,
> +};
> +
> +static int th_sys_get_temp(struct thermal_zone_device *thermal,
> +				  unsigned long *temp);
> +
> +static struct imx6q_thermal_zone	*th_zone;
> +static void __iomem			*ocotp_base;

This is a driver and drivers should generally be multi instance safe.

> +
> +static inline int imx6q_get_temp(int *temp, struct imx6q_sensor_data *sd)
> +{
> +	unsigned int n_meas;
> +	unsigned int reg;
> +
> +	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.
> +		 */
> +		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 imx6q temp sensor designers,
> +		 * it may require up to ~17us to complete
> +		 * a measurement.  But this timing isn't checked on every part
> +		 * nor is it specified in the datasheet, so we check the
> +		 * 'finished' status bit to be sure of completion of a valid
> +		 * measurement.
> +		 */
> +		msleep(sd->meas_delay);

The comment seems to have nothing to do with calling msleep, and it's
not clear to me why you have to put the argument to msleep into a
struct.

> +
> +		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);
> +
> +	} while (!(reg & BM_ANADIG_TEMPSENSE0_FINISHED) &&
> +		 (reg & BM_ANADIG_TEMPSENSE0_TEMP_VALUE));

You should never solely depend on hardware to break out of loops.

> +
> +	n_meas = (reg & BM_ANADIG_TEMPSENSE0_TEMP_VALUE)
> +			>> BP_ANADIG_TEMPSENSE0_TEMP_VALUE;
> +
> +	/* See imx6q_thermal_process_fuse_data() for forumla derivation. */
> +	*temp = sd->c2 + (sd->c1 * n_meas);
> +
> +	pr_debug("Temperature: %d\n", *temp / 1000);
> +
> +	return 0;
> +}
> +
> +static int th_sys_get_temp(struct thermal_zone_device *thermal,
> +				  unsigned long *temp)
> +{
> +	int i, total = 0, tmp = 0;
> +	const u8 loop = 5;
> +	u32 consec_high = 0;
> +
> +	struct imx6q_sensor_data *sd;
> +
> +	sd = ((struct imx6q_thermal_zone *)thermal->devdata)->sensor_data;
> +
> +	/*
> +	 * Measure temperature and handle noise
> +	 *
> +	 * While the imx6q temperature sensor is designed to minimize being
> +	 * affected by system noise, it's safest to run sanity checks and
> +	 * perform any necessary filitering.

s/filitering/filtering/

> +	 */
> +	for (i = 0; (sd->noise_margin) && (i < loop); i++) {
> +		imx6q_get_temp(&tmp, sd);
> +
> +		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 "millicelcius" which limits the lowest

s/celcius/celsius/

> +	 * 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 *thermal,
> +			    enum thermal_device_mode *mode)
> +{
> +	*mode = THERMAL_DEVICE_ENABLED;
> +	return 0;
> +}
> +
> +static int th_sys_get_trip_type(struct thermal_zone_device *thermal, int trip,
> +				 enum thermal_trip_type *type)
> +{
> +	struct imx6q_thermal_data *p;
> +
> +	if (trip >= thermal->trips)
> +		return -EINVAL;
> +
> +	p = ((struct imx6q_thermal_zone *)thermal->devdata)->thermal_data;
> +
> +	*type = p->type[trip];
> +
> +	return 0;
> +}
> +
> +static int th_sys_get_trip_temp(struct thermal_zone_device *thermal, int trip,
> +				 unsigned long *temp)
> +{
> +	struct imx6q_thermal_data *p;
> +	enum thermal_trip_type type;
> +
> +	if (trip >= thermal->trips)
> +		return -EINVAL;
> +
> +	p = ((struct imx6q_thermal_zone *)thermal->devdata)->thermal_data;
> +
> +	thermal->ops->get_trip_type(thermal, trip, &type);
> +
> +	if (type == THERMAL_TRIP_CRITICAL)
> +		*temp = p->crit_temp_level;
> +	else
> +		*temp = p->freq_tab[trip].temp_level;
> +	return 0;
> +}
> +
> +static int th_sys_get_crit_temp(struct thermal_zone_device *thermal,
> +				 unsigned long *temp)
> +{
> +	struct imx6q_thermal_data *p;
> +
> +	p = ((struct imx6q_thermal_zone *)thermal->devdata)->thermal_data;
> +	*temp = p->crit_temp_level;
> +	return 0;
> +}
> +
> +static int th_sys_bind(struct thermal_zone_device *thermal,
> +			struct thermal_cooling_device *cdev)
> +{
> +	int i;
> +	struct thermal_cooling_device *cd;
> +
> +	cd = ((struct imx6q_thermal_zone *)thermal->devdata)->cool_dev;
> +
> +	/* if the cooling device is the one from imx6 bind it */
> +	if (cdev != cd)
> +		return 0;
> +
> +	for (i = 0; i < IMX6Q_THERMAL_ACTIVE_TRP_PTS; i++) {
> +		if (thermal_zone_bind_cooling_device(thermal, i, cdev)) {
> +			pr_err("error binding cooling dev\n");
> +			return -EINVAL;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int th_sys_unbind(struct thermal_zone_device *thermal,
> +			  struct thermal_cooling_device *cdev)
> +{
> +	int i;
> +
> +	struct thermal_cooling_device *cd;
> +
> +	cd = ((struct imx6q_thermal_zone *)thermal->devdata)->cool_dev;
> +
> +	if (cdev != cd)
> +		return 0;
> +
> +	for (i = 0; i < IMX6Q_THERMAL_ACTIVE_TRP_PTS; i++) {
> +		if (thermal_zone_unbind_cooling_device(thermal, i, cdev)) {
> +			pr_err("error unbinding cooling dev\n");
> +			return -EINVAL;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static struct thermal_zone_device_ops g_dev_ops __devinitdata = {
> +	.bind = th_sys_bind,
> +	.unbind = th_sys_unbind,
> +	.get_temp = th_sys_get_temp,
> +	.get_mode = th_sys_get_mode,
> +	.get_trip_type = th_sys_get_trip_type,
> +	.get_trip_temp = th_sys_get_trip_temp,
> +	.get_crit_temp = th_sys_get_crit_temp,
> +};
> +
> +static int __devinit imx6q_thermal_process_fuse_data(unsigned int fuse_data,
> +		 struct imx6q_sensor_data *sd)
> +{
> +	int t1, t2, n1, n2;
> +
> +	if (fuse_data == 0 || fuse_data == 0xffffffff) {
> +		pr_warn("WARNING: Incorrect temperature sensor value of %x "
> +			"detected\n", fuse_data);
> +
> +#ifdef CONFIG_IMX6Q_THERMAL_FAKE_CALIBRATION
> +		pr_warn(
> +			"WARNING: Using fake calibration value of %x value. "
> +			"This will cause your system performance to be limited "
> +			"prematurely (compared to a using properly calibrated "
> +			"value) OR will cause the system to allow the "
> +			"temperature to exceed the maximum specified "
> +			"temperature without the proper performance "
> +			"limiting.\n", CONFIG_IMX6Q_THERMAL_FAKE_CAL_VAL);
> +
> +		fuse_data = CONFIG_IMX6Q_THERMAL_FAKE_CAL_VAL;
> +#else
> +		pr_warn("WARNING: Disabling CPU thermal protection\n");
> +		return -EINVAL;
> +#endif
> +	}
> +
> +	/*
> +	 * 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 */
> +
> +	pr_debug(" -- temperature sensor calibration data --\n");
> +	pr_debug("HW_OCOTP_ANA1: %x\n", fuse_data);
> +	pr_debug("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 millicelcius
> +	 * 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);
> +
> +	pr_debug("c1: %i\n", sd->c1);
> +	pr_debug("c2: %i\n", sd->c2);
> +
> +	return 0;
> +}
> +
> +static int anatop_thermal_suspend(struct platform_device *pdev,
> +					pm_message_t state)
> +{
> +	struct imx6q_sensor_data *sd = pdev->dev.platform_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 __devexit anatop_thermal_remove(struct platform_device *pdev)
> +{
> +	if (th_zone && th_zone->therm_dev)
> +		thermal_zone_device_unregister(th_zone->therm_dev);
> +
> +	if (th_zone && th_zone->cool_dev)
> +		cpufreq_cooling_unregister(th_zone->cool_dev);
> +
> +	kfree(th_zone->sensor_data);
> +	kfree(th_zone->thermal_data);
> +	kfree(th_zone);
> +
> +	if (ocotp_base)
> +		iounmap(ocotp_base);
> +
> +	pr_info("i.MX6Q: Kernel Thermal management unregistered\n");
> +
> +	return 0;
> +}
> +
> +static int __devinit anatop_thermal_probe(struct platform_device *pdev)
> +{
> +	struct device_node *np_ocotp, *np_thermal;
> +	unsigned int fuse_data;
> +	struct anatop *anatopmfd = dev_get_drvdata(pdev->dev.parent);
> +	int ret, i;
> +
> +	np_ocotp = of_find_compatible_node(NULL, NULL, "fsl,imx6q-ocotp");
> +	np_thermal = of_find_compatible_node(NULL, NULL, "fsl,anatop-thermal");

np_thermal = pdev->dev.of_node;

> +
> +	if (!(np_ocotp && np_thermal && anatopmfd))
> +		return -ENXIO; /* not a compatible platform */
> +
> +	ocotp_base = of_iomap(np_ocotp, 0);
> +
> +	if (!ocotp_base) {
> +		pr_err("Could not retrieve ocotp-base\n");
> +		ret = -ENXIO;
> +		goto err_unregister;
> +	}
> +
> +	fuse_data = readl_relaxed(ocotp_base + HW_OCOTP_ANA1);
> +
> +	th_zone = kzalloc(sizeof(struct imx6q_thermal_zone), GFP_KERNEL);

consider using devm_*

> +	if (!th_zone) {
> +		ret = -ENOMEM;
> +		goto err_unregister;
> +	}
> +
> +	th_zone->dev_ops = g_dev_ops;
> +
> +	th_zone->thermal_data =
> +		kzalloc(sizeof(struct imx6q_thermal_data), GFP_KERNEL);

Why do you allocate this seperately? You could embed a struct
imx6q_thermal_data in struct imx6q_thermal_zone.

> +
> +	if (!th_zone->thermal_data) {
> +		ret = -ENOMEM;
> +		goto err_unregister;
> +	}
> +
> +	memcpy(th_zone->thermal_data, &g_thermal_data,
> +				sizeof(struct imx6q_thermal_data));
> +
> +	for (i = 0; i < ARRAY_SIZE(g_thermal_data.freq_tab); i++)
> +		th_zone->thermal_data->freq_tab[i].mask_val = cpumask_of(0);
> +
> +
> +	th_zone->sensor_data =
> +		kzalloc(sizeof(struct imx6q_sensor_data), GFP_KERNEL);

This one also could be embedded in struct imx6q_thermal_zone.

> +
> +	if (!th_zone->sensor_data) {
> +		ret = -ENOMEM;
> +		goto err_unregister;
> +	}
> +
> +	memcpy(th_zone->sensor_data, &g_sensor_data,
> +				sizeof(struct imx6q_sensor_data));
> +
> +	th_zone->sensor_data->anatopmfd = anatopmfd;
> +
> +	ret = imx6q_thermal_process_fuse_data(fuse_data, th_zone->sensor_data);
> +
> +	if (ret) {
> +		pr_err("Invalid temperature calibration data.\n");

use dev_* functions for logging throughout the driver.

> +		goto err_unregister;
> +	}
> +
> +	if (!th_zone->sensor_data->meas_delay)
> +		th_zone->sensor_data->meas_delay = 1;
> +
> +	/* 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);
> +
> +	th_zone->cool_dev = cpufreq_cooling_register(
> +		(struct freq_clip_table *)th_zone->thermal_data->freq_tab,
> +		IMX6Q_THERMAL_ACTIVE_TRP_PTS);
> +
> +	if (IS_ERR(th_zone->cool_dev)) {
> +		pr_err("Failed to register cpufreq cooling device\n");
> +		ret = -EINVAL;
> +		goto err_unregister;
> +	}
> +
> +	th_zone->therm_dev = thermal_zone_device_register(
> +		th_zone->sensor_data->name, IMX6Q_THERMAL_TOTAL_TRP_PTS,
> +		th_zone, &th_zone->dev_ops, 0, 0, 0,
> +		IMX6Q_THERMAL_POLLING_FREQUENCY_MS);
> +
> +	if (IS_ERR(th_zone->therm_dev)) {
> +		pr_err("Failed to register thermal zone device\n");
> +		ret = -EINVAL;
> +		goto err_unregister;
> +	}
> +
> +	pdev->dev.platform_data = th_zone->sensor_data;

platform_data is for passing information from the one who registers the
platform_device to the driver. What you have to use here is
platform_set_drvdata(). Also you have to initialize this before
registering the thermal zone, not afterwards.

> +
> +	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,
> +	/* due to implentation of imx6q_get_temp , resume is unnecessary */
> +};
> +
> +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);

use module_platform_driver

> +
> +MODULE_AUTHOR("Freescale Semiconductor");
> +MODULE_DESCRIPTION("i.MX6Q SoC thermal driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:imx6q-thermal");
> -- 
> 1.7.10
> 
> 

-- 
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 |



More information about the linux-arm-kernel mailing list