Hi Niklas, Thanks for the feedback. > 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()? > 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. OK, will use Gen2 style as it has 1 sensor on the next version. Another separate question, I have in my mind is how do we test trip points for negative temperature? This TSU doesn't have any interrupts and we need to use polling, The range is -40 to 125 degrees. If it goes below -40, I guess we need to shutdown the system. On device tree we normally specify only positive temperature. How do we handle this? 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