diff mbox series

[3/5] thermal/drivers: Add TSU driver for RZ/G2L

Message ID 20211116151750.24857-4-biju.das.jz@bp.renesas.com (mailing list archive)
State New, archived
Delegated to: Daniel Lezcano
Headers show
Series Add Thermal support for RZ/G2L | expand

Commit Message

Biju Das Nov. 16, 2021, 3:17 p.m. UTC
Add Thermal Sensor Unit(TSU) driver for RZ/G2L SoC.

Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
Reviewed-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
---
 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

Comments

Niklas Söderlund Nov. 17, 2021, 8:40 a.m. UTC | #1
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@bp.renesas.com>
> Reviewed-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
> ---
>  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.

> +
> +	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@bp.renesas.com>");
> +MODULE_LICENSE("GPL v2");
> -- 
> 2.17.1
>
Biju Das Nov. 17, 2021, 8:55 a.m. UTC | #2
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@bp.renesas.com>
> > Reviewed-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
> > ---
> >  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@bp.renesas.com>");
> > +MODULE_LICENSE("GPL v2");
> > --
> > 2.17.1
> >
> 
> --
> Kind Regards,
> Niklas Söderlund
Niklas Söderlund Nov. 17, 2021, 2:12 p.m. UTC | #3
Hi Biju,

On 2021-11-17 08:55:15 +0000, Biju Das wrote:
> Another separate question, I have in my mind is how do we test trip 
> points
> for negative temperature? 

I think this is two different questions.

> This TSU doesn't have any interrupts and
> we need to use polling, The range is -40 to 125 degrees.

For tripping a trip-point polling or interrupt works. The only 
difference is in the worst case polling will detect that i past the 
trop-point <pollig-delay> later. For testing triggering of trippoints 
(high or low) I don't think we need to care about this.

To actually test it we have two options.

1. Put the SoC in a good freezer and, alternately open an office branch 
   in the arctics :-)

2. Use the thermal emulation framework.

    echo $temprature_in_mcelcius > /sys/class/thermal/$zone/emul_temp

  If the temperature set using this is above/below a trippoint the 
  associated action will happen. A good demo of this is to try on Gen3 
  where you can observe the cooling device state changes as the 
  different trip points are triggerd in:

      /sys/class/thermal/$cool/cur_state

  And of course if you set a temperature that trips the critical point 
  the system will shutdown.

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

I have not tested it but would it not be as simple as adding a second 
critical trippoint at -40 ?
Biju Das Nov. 17, 2021, 2:27 p.m. UTC | #4
Hi Niklas,

> Subject: Re: [PATCH 3/5] thermal/drivers: Add TSU driver for RZ/G2L
> 
> Hi Biju,
> 
> On 2021-11-17 08:55:15 +0000, Biju Das wrote:
> > Another separate question, I have in my mind is how do we test trip
> > points for negative temperature?
> 
> I think this is two different questions.
> 
> > This TSU doesn't have any interrupts and we need to use polling, The
> > range is -40 to 125 degrees.
> 
> For tripping a trip-point polling or interrupt works. The only difference
> is in the worst case polling will detect that i past the trop-point
> <pollig-delay> later. For testing triggering of trippoints (high or low) I
> don't think we need to care about this.
> 
> To actually test it we have two options.
> 
> 1. Put the SoC in a good freezer and, alternately open an office branch
>    in the arctics :-)
> 
> 2. Use the thermal emulation framework.
> 
>     echo $temprature_in_mcelcius > /sys/class/thermal/$zone/emul_temp
> 
>   If the temperature set using this is above/below a trippoint the
>   associated action will happen. A good demo of this is to try on Gen3
>   where you can observe the cooling device state changes as the
>   different trip points are triggerd in:
> 
>       /sys/class/thermal/$cool/cur_state
> 
>   And of course if you set a temperature that trips the critical point
>   the system will shutdown.

This I have tested with thermal IPA (writable_trips and thermal statistics) and
I can see the cooling state transitions around trip temperature.

> 
> > 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?
> 
> I have not tested it but would it not be as simple as adding a second
> critical trippoint at -40 ?

OK. This can happen only from a cold start(where the temp is below -40).
So at this point, I am not sure whether we should not rely on TSU values
as the value from TSU may not linear or it is beyond SoC characteristics
And so we should do a shutdown.

Regards,
Biju
Biju Das Nov. 20, 2021, 5:21 p.m. UTC | #5
Hi Niklas,

> -----Original Message-----
> From: Niklas Söderlund <niklas.soderlund@ragnatech.se>
> Sent: 17 November 2021 08:41
> To: Biju Das <biju.das.jz@bp.renesas.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>; Rafael J. Wysocki
> <rafael@kernel.org>; Daniel Lezcano <daniel.lezcano@linaro.org>; Amit
> Kucheria <amitk@kernel.org>; Zhang Rui <rui.zhang@intel.com>; linux-
> pm@vger.kernel.org; Geert Uytterhoeven <geert+renesas@glider.be>; Chris
> Paterson <Chris.Paterson2@renesas.com>; Biju Das
> <biju.das@bp.renesas.com>; Prabhakar Mahadev Lad <prabhakar.mahadev-
> lad.rj@bp.renesas.com>; linux-renesas-soc@vger.kernel.org
> 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@bp.renesas.com>
> > Reviewed-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
> > ---
> >  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_thermal.c#L561

For any error upto L561, any unregister blindly calls thermal_remove_hwmon_sysfs,
Which is wrong.

Looks like we need a fix on Gen2 drivers.

Please let me know, if I am missing anything here.

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@bp.renesas.com>");
> > +MODULE_LICENSE("GPL v2");
> > --
> > 2.17.1
> >
> 
> --
> Kind Regards,
> Niklas Söderlund
Biju Das Nov. 20, 2021, 7:39 p.m. UTC | #6
> -----Original Message-----
> From: Biju Das
> Sent: 20 November 2021 17:21
> To: 'Niklas Söderlund' <niklas.soderlund@ragnatech.se>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>; Rafael J. Wysocki
> <rafael@kernel.org>; Daniel Lezcano <daniel.lezcano@linaro.org>; Amit
> Kucheria <amitk@kernel.org>; Zhang Rui <rui.zhang@intel.com>; linux-
> pm@vger.kernel.org; Geert Uytterhoeven <geert+renesas@glider.be>; Chris
> Paterson <Chris.Paterson2@renesas.com>; Biju Das
> <biju.das@bp.renesas.com>; Prabhakar Mahadev Lad <prabhakar.mahadev-
> lad.rj@bp.renesas.com>; linux-renesas-soc@vger.kernel.org
> Subject: RE: [PATCH 3/5] thermal/drivers: Add TSU driver for RZ/G2L
> 
> Hi Niklas,
> 
> > -----Original Message-----
> > From: Niklas Söderlund <niklas.soderlund@ragnatech.se>
> > Sent: 17 November 2021 08:41
> > To: Biju Das <biju.das.jz@bp.renesas.com>
> > Cc: Philipp Zabel <p.zabel@pengutronix.de>; Rafael J. Wysocki
> > <rafael@kernel.org>; Daniel Lezcano <daniel.lezcano@linaro.org>; Amit
> > Kucheria <amitk@kernel.org>; Zhang Rui <rui.zhang@intel.com>; linux-
> > pm@vger.kernel.org; Geert Uytterhoeven <geert+renesas@glider.be>;
> > Chris Paterson <Chris.Paterson2@renesas.com>; Biju Das
> > <biju.das@bp.renesas.com>; Prabhakar Mahadev Lad <prabhakar.mahadev-
> > lad.rj@bp.renesas.com>; linux-renesas-soc@vger.kernel.org
> > 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@bp.renesas.com>
> > > Reviewed-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
> > > ---
> > >  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@bp.renesas.com>");
> > > +MODULE_LICENSE("GPL v2");
> > > --
> > > 2.17.1
> > >
> >
> > --
> > Kind Regards,
> > Niklas Söderlund
diff mbox series

Patch

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;
+
+	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@bp.renesas.com>");
+MODULE_LICENSE("GPL v2");