> -----Original Message----- > From: Biju Das > Sent: 20 November 2021 17:21 > To: 'Niklas Söderlund' <niklas.soderlund@xxxxxxxxxxxx> > Cc: Philipp Zabel <p.zabel@xxxxxxxxxxxxxx>; Rafael J. Wysocki > <rafael@xxxxxxxxxx>; Daniel Lezcano <daniel.lezcano@xxxxxxxxxx>; Amit > Kucheria <amitk@xxxxxxxxxx>; Zhang Rui <rui.zhang@xxxxxxxxx>; linux- > pm@xxxxxxxxxxxxxxx; Geert Uytterhoeven <geert+renesas@xxxxxxxxx>; Chris > Paterson <Chris.Paterson2@xxxxxxxxxxx>; Biju Das > <biju.das@xxxxxxxxxxxxxx>; Prabhakar Mahadev Lad <prabhakar.mahadev- > lad.rj@xxxxxxxxxxxxxx>; linux-renesas-soc@xxxxxxxxxxxxxxx > Subject: RE: [PATCH 3/5] thermal/drivers: Add TSU driver for RZ/G2L > > Hi Niklas, > > > -----Original Message----- > > From: Niklas Söderlund <niklas.soderlund@xxxxxxxxxxxx> > > Sent: 17 November 2021 08:41 > > To: Biju Das <biju.das.jz@xxxxxxxxxxxxxx> > > Cc: Philipp Zabel <p.zabel@xxxxxxxxxxxxxx>; Rafael J. Wysocki > > <rafael@xxxxxxxxxx>; Daniel Lezcano <daniel.lezcano@xxxxxxxxxx>; Amit > > Kucheria <amitk@xxxxxxxxxx>; Zhang Rui <rui.zhang@xxxxxxxxx>; linux- > > pm@xxxxxxxxxxxxxxx; Geert Uytterhoeven <geert+renesas@xxxxxxxxx>; > > Chris Paterson <Chris.Paterson2@xxxxxxxxxxx>; Biju Das > > <biju.das@xxxxxxxxxxxxxx>; Prabhakar Mahadev Lad <prabhakar.mahadev- > > lad.rj@xxxxxxxxxxxxxx>; linux-renesas-soc@xxxxxxxxxxxxxxx > > Subject: Re: [PATCH 3/5] thermal/drivers: Add TSU driver for RZ/G2L > > > > HI Biju, > > > > Thanks for your work. > > > > On 2021-11-16 15:17:48 +0000, Biju Das wrote: > > > Add Thermal Sensor Unit(TSU) driver for RZ/G2L SoC. > > > > > > Signed-off-by: Biju Das <biju.das.jz@xxxxxxxxxxxxxx> > > > Reviewed-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx> > > > --- > > > drivers/thermal/Kconfig | 9 ++ > > > drivers/thermal/Makefile | 1 + > > > drivers/thermal/rzg2l_thermal.c | 239 > > > ++++++++++++++++++++++++++++++++ > > > 3 files changed, 249 insertions(+) > > > create mode 100644 drivers/thermal/rzg2l_thermal.c > > > > > > diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index > > > d7f44deab5b1..e37691e0bf20 100644 > > > --- a/drivers/thermal/Kconfig > > > +++ b/drivers/thermal/Kconfig > > > @@ -354,6 +354,15 @@ config RCAR_GEN3_THERMAL > > > Enable this to plug the R-Car Gen3 or RZ/G2 thermal sensor > > > driver > > into > > > the Linux thermal framework. > > > > > > +config RZG2L_THERMAL > > > + tristate "Renesas RZ/G2L thermal driver" > > > + depends on ARCH_RENESAS || COMPILE_TEST > > > + depends on HAS_IOMEM > > > + depends on OF > > > + help > > > + Enable this to plug the RZ/G2L thermal sensor driver into the > > Linux > > > + thermal framework. > > > + > > > config KIRKWOOD_THERMAL > > > tristate "Temperature sensor on Marvell Kirkwood SoCs" > > > depends on MACH_KIRKWOOD || COMPILE_TEST diff --git > > > a/drivers/thermal/Makefile b/drivers/thermal/Makefile index > > > 82fc3e616e54..f0c36a1530d5 100644 > > > --- a/drivers/thermal/Makefile > > > +++ b/drivers/thermal/Makefile > > > @@ -37,6 +37,7 @@ obj-$(CONFIG_SUN8I_THERMAL) += sun8i_thermal.o > > > obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o > > > obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o > > > obj-$(CONFIG_RCAR_GEN3_THERMAL) += rcar_gen3_thermal.o > > > +obj-$(CONFIG_RZG2L_THERMAL) += rzg2l_thermal.o > > > obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o > > > obj-y += samsung/ > > > obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o > > > diff --git a/drivers/thermal/rzg2l_thermal.c > > > b/drivers/thermal/rzg2l_thermal.c new file mode 100644 index > > > 000000000000..e551355cd4f6 > > > --- /dev/null > > > +++ b/drivers/thermal/rzg2l_thermal.c > > > @@ -0,0 +1,239 @@ > > > +// SPDX-License-Identifier: GPL-2.0 > > > +/* > > > + * Renesas RZ/G2L TSU Thermal Sensor Driver > > > + * > > > + * Copyright (C) 2021 Renesas Electronics Corporation */ #include > > > +<linux/delay.h> #include <linux/err.h> #include <linux/io.h> > > > +#include <linux/iopoll.h> #include <linux/module.h> #include > > > +<linux/of_device.h> #include <linux/platform_device.h> #include > > > +<linux/pm_runtime.h> #include <linux/reset.h> #include > > > +<linux/thermal.h> > > > + > > > +#include "thermal_hwmon.h" > > > + > > > +#define CTEMP_MASK 0xFFF > > > + > > > +/* default calibration values, if FUSE values are missing */ > > > +#define SW_CALIB0_VAL 3148 > > > +#define SW_CALIB1_VAL 503 > > > + > > > +/* Register offsets */ > > > +#define TSU_SM 0x00 > > > +#define TSU_ST 0x04 > > > +#define TSU_SAD 0x0C > > > +#define TSU_SS 0x10 > > > + > > > +#define OTPTSUTRIM_REG(n) (0x18 + ((n) * 0x4)) > > > + > > > +/* Sensor Mode Register(TSU_SM) */ > > > +#define TSU_SM_EN_TS BIT(0) > > > +#define TSU_SM_ADC_EN_TS BIT(1) > > > +#define TSU_SM_NORMAL_MODE (TSU_SM_EN_TS | TSU_SM_ADC_EN_TS) > > > + > > > +/* TSU_ST bits */ > > > +#define TSU_ST_START BIT(0) > > > + > > > +#define TSU_SS_CONV_RUNNING BIT(0) > > > + > > > +#define TS_CODE_AVE_SCALE(x) ((x) * 1000000) > > > +#define MCELSIUS(temp) ((temp) * 1000) > > > +#define TS_CODE_CAP_TIMES 8 /* Capture times */ > > > +#define RZG2L_THERMAL_GRAN 500 /* milli Celsius */ > > > + > > > +#define RZG2L_TSU_SS_TIMEOUT_US 1000 > > > + > > > +struct rzg2l_thermal_priv { > > > + struct device *dev; > > > + void __iomem *base; > > > + struct thermal_zone_device *zone; > > > + struct reset_control *rstc; > > > + u32 calib0, calib1; > > > +}; > > > + > > > +static inline u32 rzg2l_thermal_read(struct rzg2l_thermal_priv > > > +*priv, > > > +u32 reg) { > > > + return ioread32(priv->base + reg); } > > > + > > > +static inline void rzg2l_thermal_write(struct rzg2l_thermal_priv > > > +*priv, > > u32 reg, > > > + u32 data) > > > +{ > > > + iowrite32(data, priv->base + reg); } > > > + > > > +static int rzg2l_thermal_round(int temp) { > > > + int result, round_offs; > > > + > > > + round_offs = temp >= 0 ? RZG2L_THERMAL_GRAN / 2 : - > > RZG2L_THERMAL_GRAN / 2; > > > + result = (temp + round_offs) / RZG2L_THERMAL_GRAN; > > > + > > > + return result * RZG2L_THERMAL_GRAN; } > > > + > > > +static int rzg2l_thermal_get_temp(void *devdata, int *temp) { > > > + struct rzg2l_thermal_priv *priv = devdata; > > > + u32 result, dsensor, ts_code_ave; > > > + int val, i; > > > + > > > + result = 0; > > > + /* Read the ADC value 8 times with an interval of 20 microsecs */ > > > + for (i = 0; i < TS_CODE_CAP_TIMES ; i++) { > > > + result += rzg2l_thermal_read(priv, TSU_SAD) & CTEMP_MASK; > > > + usleep_range(20, 30); > > > + } > > > + > > > + /* Calculate the average value */ > > > + ts_code_ave = result / TS_CODE_CAP_TIMES; > > > + > > > + /* Curvature correction */ > > > + dsensor = TS_CODE_AVE_SCALE(ts_code_ave) / > > > + (TS_CODE_AVE_SCALE(1) + (ts_code_ave * 13)); > > > + > > > + /* Temperature calculation */ > > > + val = ((dsensor - priv->calib1) * (MCELSIUS(165) / > > > + (priv->calib0 - priv->calib1))) - MCELSIUS(40); > > > + > > > + /* Round value to device granularity setting */ > > > + *temp = rzg2l_thermal_round(val); > > > + > > > + return 0; > > > +} > > > + > > > +static const struct thermal_zone_of_device_ops rzg2l_tz_of_ops = { > > > + .get_temp = rzg2l_thermal_get_temp, }; > > > + > > > +static int rzg2l_thermal_init(struct rzg2l_thermal_priv *priv) { > > > + u32 reg_val; > > > + > > > + rzg2l_thermal_write(priv, TSU_SM, TSU_SM_NORMAL_MODE); > > > + rzg2l_thermal_write(priv, TSU_ST, 0); > > > + > > > + /* Before setting START bit, Wait for 60 µs */ > > > + usleep_range(60, 80); > > > + > > > + reg_val = rzg2l_thermal_read(priv, TSU_ST); > > > + reg_val |= TSU_ST_START; > > > + rzg2l_thermal_write(priv, TSU_ST, reg_val); > > > + > > > + return readl_poll_timeout(priv->base + TSU_SS, reg_val, > > > + reg_val == TSU_SS_CONV_RUNNING, 50, > > > + RZG2L_TSU_SS_TIMEOUT_US); > > > +} > > > + > > > +static int rzg2l_thermal_remove(struct platform_device *pdev) { > > > + struct rzg2l_thermal_priv *priv = dev_get_drvdata(&pdev->dev); > > > + > > > + pm_runtime_put(&pdev->dev); > > > + pm_runtime_disable(&pdev->dev); > > > + reset_control_assert(priv->rstc); > > > + > > > + return 0; > > > +} > > > + > > > +static void rzg2l_hwmon_action(void *data) { > > > + struct thermal_zone_device *zone = data; > > > + > > > + thermal_remove_hwmon_sysfs(zone); > > > +} > > > + > > > +static int rzg2l_thermal_probe(struct platform_device *pdev) { > > > + struct thermal_zone_device *zone; > > > + struct rzg2l_thermal_priv *priv; > > > + struct device *dev = &pdev->dev; > > > + int ret; > > > + > > > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > > > + if (!priv) > > > + return -ENOMEM; > > > + > > > + priv->base = devm_platform_ioremap_resource(pdev, 0); > > > + if (IS_ERR(priv->base)) > > > + return PTR_ERR(priv->base); > > > + > > > + priv->dev = dev; > > > + priv->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); > > > + if (IS_ERR(priv->rstc)) > > > + return dev_err_probe(dev, PTR_ERR(priv->rstc), > > > + "failed to get cpg reset"); > > > + > > > + reset_control_deassert(priv->rstc); > > > + > > > + pm_runtime_enable(dev); > > > + pm_runtime_get_sync(dev); > > > + > > > + priv->calib0 = rzg2l_thermal_read(priv, OTPTSUTRIM_REG(0)); > > > + if (!priv->calib0) > > > + priv->calib0 = SW_CALIB0_VAL; > > > + > > > + priv->calib1 = rzg2l_thermal_read(priv, OTPTSUTRIM_REG(1)); > > > + if (!priv->calib1) > > > + priv->calib1 = SW_CALIB1_VAL; > > > + > > > + platform_set_drvdata(pdev, priv); > > > + ret = rzg2l_thermal_init(priv); > > > + if (ret) { > > > + dev_err(dev, "Failed to start TSU"); > > > + goto error_unregister; > > > + } > > > + > > > + zone = devm_thermal_zone_of_sensor_register(dev, 0, priv, > > > + &rzg2l_tz_of_ops); > > > + if (IS_ERR(zone)) { > > > + dev_err(dev, "Can't register thermal zone"); > > > + ret = PTR_ERR(zone); > > > + goto error_unregister; > > > + } > > > + > > > + priv->zone = zone; > > > + priv->zone->tzp->no_hwmon = false; > > > + ret = thermal_add_hwmon_sysfs(priv->zone); > > > + if (ret) > > > + goto error_unregister; > > > + > > > + ret = devm_add_action_or_reset(dev, rzg2l_hwmon_action, zone); > > > + if (ret) > > > + goto error_unregister; > > > > > This driver only uses one zone would it be worth considering calling > > thermal_remove_hwmon_sysfs() directly from rzg2l_thermal_remove()? > > In fact this is correct. > > > Compare then Renesas Gen2 and Gen3 drivers. Where Gen3 uses devres to > > make it easier to deal with more then one zone wile Gen2 calls it > > directly from its remove function. > > If you see rcar-thermal.c there are unregister path (goto > error_unregister) Where we call thermal_remove_hwmon_sysfs without adding > hwmon_sys > > See [1] > [1] > https://elixir.bootlin.com/linux/latest/source/drivers/thermal/rcar_therma > l.c#L561 > > For any error upto L561, any unregister blindly calls > thermal_remove_hwmon_sysfs, Which is wrong. Oops, I am wrong, remove function is taking care. Sorry for the noise. Regards, Biju > > > > > > + > > > + dev_info(dev, "TSU probed with %s caliberation values", > > > + rzg2l_thermal_read(priv, OTPTSUTRIM_REG(0)) ? "hw" : "sw"); > > > + > > > + return 0; > > > + > > > +error_unregister: > > > + rzg2l_thermal_remove(pdev); > > > + > > > + return ret; > > > +} > > > + > > > +static const struct of_device_id rzg2l_thermal_dt_ids[] = { > > > + { .compatible = "renesas,rzg2l-tsu", }, > > > + { /* sentinel */ } > > > +}; > > > +MODULE_DEVICE_TABLE(of, rzg2l_thermal_dt_ids); > > > + > > > +static struct platform_driver rzg2l_thermal_driver = { > > > + .driver = { > > > + .name = "rzg2l_thermal", > > > + .of_match_table = rzg2l_thermal_dt_ids, > > > + }, > > > + .probe = rzg2l_thermal_probe, > > > + .remove = rzg2l_thermal_remove, > > > +}; > > > +module_platform_driver(rzg2l_thermal_driver); > > > + > > > +MODULE_DESCRIPTION("Renesas RZ/G2L TSU Thermal Sensor Driver"); > > > +MODULE_AUTHOR("Biju Das <biju.das.jz@xxxxxxxxxxxxxx>"); > > > +MODULE_LICENSE("GPL v2"); > > > -- > > > 2.17.1 > > > > > > > -- > > Kind Regards, > > Niklas Söderlund