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

Rob Lee rob.lee at linaro.org
Wed Jun 20 10:12:51 EDT 2012


Sascha, thanks for the review.

On Wed, Jun 20, 2012 at 5:11 AM, Sascha Hauer <s.hauer at pengutronix.de> wrote:
> 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.
>

Ok, I'll remove this in v6.

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

Oops, will fix this in v6.

>> +     /*
>> +      * 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?
>

There's not a good purpose for doing it this way now.  It came from a
very early implementation that ended up changing before submitting.
I'll fix this in v6.

>> +
>> +/*
>> + * 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.
>

I don't understand what this comment is referring to.  Could you elaborate?

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

Agree, it should move below to where we actually check the status bit.
 There isn't a good reason why the argument must be in the sensor data
struct in the current implementation.  I will fix these issues in v6.

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

Ok, I will re-work the implementation to not require this.

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

Oops, will fix in v6.

>> +      */
>> +     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/
>

Oops, will fix in v6.

>> +      * 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->devI don't quite understand this..of_node;
>

I see, thanks.  In this case I was just using the np_thermal as a
check to be sure we are on the right platform, but it seems that the
platform driver has already done the check for "fsl,anatop-thermal",
so I can remove this line completely and remove the np_thermal check
below.

>> +
>> +     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_*
>

Ok, will look at converting to devm_kzalloc and removing the
respective kfree calls in v6.

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

In one implementation I did during development this made more sense,
but now I should embed it as you say.  I'll change this in v6.

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

Same. Will change in v6.

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

Will change in v6.

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

Will fix this in v6.

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

Will fix this in v6.

Thanks,
Rob

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