diff mbox series

[v2,2/2] thermal/drivers: Add TSU driver for RZ/G2L

Message ID 20211120194711.9073-3-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. 20, 2021, 7:47 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>
---
v1->v2:
 * Removed devm_add_action_or_reset from probe.
---
 drivers/thermal/Kconfig         |   9 ++
 drivers/thermal/Makefile        |   1 +
 drivers/thermal/rzg2l_thermal.c | 235 ++++++++++++++++++++++++++++++++
 3 files changed, 245 insertions(+)
 create mode 100644 drivers/thermal/rzg2l_thermal.c

Comments

Daniel Lezcano Nov. 30, 2021, 10:52 a.m. UTC | #1
On 20/11/2021 20:47, Biju Das wrote:
> Add Thermal Sensor Unit(TSU) driver for RZ/G2L SoC.

Please give a technical description of the sensor as it is the first
submission.

> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> Reviewed-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
> ---
> v1->v2:
>  * Removed devm_add_action_or_reset from probe.
> ---
>  drivers/thermal/Kconfig         |   9 ++
>  drivers/thermal/Makefile        |   1 +
>  drivers/thermal/rzg2l_thermal.c | 235 ++++++++++++++++++++++++++++++++
>  3 files changed, 245 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..ea828c15dd1e
> --- /dev/null
> +++ b/drivers/thermal/rzg2l_thermal.c
> @@ -0,0 +1,235 @@
> +// 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)

cf. include/linux/units.h

> +#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;
> +}

roundup() | rounddown() ? (cf math.h)

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

Init in the declaration

What unit is the value returned ?

> +	/*  Read the ADC value 8 times with an interval of 20 microsecs */

Why do you have to do that ? + comment format

/*
 *
 */


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

With a proper comment in the loop, no need to add this one

> +	ts_code_ave = result / TS_CODE_CAP_TIMES;

> +	/* Curvature correction */

What is it? + comment format

> +	dsensor = TS_CODE_AVE_SCALE(ts_code_ave) /
> +		(TS_CODE_AVE_SCALE(1) + (ts_code_ave * 13));
> +

No litteral, s/13/a_macro/

> +	/* Temperature calculation */
> +	val = ((dsensor - priv->calib1) * (MCELSIUS(165) /
> +		(priv->calib0 - priv->calib1))) - MCELSIUS(40);

Elaborate please + comment format

> +
> +	/* Round value to device granularity setting */

Comment format

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

Why + comment format. The comment should not describe what is doing the
code but *why*. It is obvious usleep(60) waits 60us

> +	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 void rzg2l_thermal_reset_assert_pm_disable_put(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);
> +}
> +
> +static int rzg2l_thermal_remove(struct platform_device *pdev)
> +{
> +	struct rzg2l_thermal_priv *priv = dev_get_drvdata(&pdev->dev);
> +
> +	thermal_remove_hwmon_sysfs(priv->zone);
> +	rzg2l_thermal_reset_assert_pm_disable_put(pdev);
> +
> +	return 0;
> +}
> +
> +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 err;
> +	}
> +
> +	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);

		dev_err(dev, "Can't register thermal zone: %d", ret);

> +		goto err;
> +	}
> +
> +	priv->zone = zone;
> +	priv->zone->tzp->no_hwmon = false;
> +	ret = thermal_add_hwmon_sysfs(priv->zone);
> +	if (ret)
> +		goto err;
> +
> +	dev_info(dev, "TSU probed with %s caliberation values",
> +		 rzg2l_thermal_read(priv, OTPTSUTRIM_REG(0)) ?  "hw" : "sw");

	s/dev_info/dev_debug/

> +	return 0;
> +
> +err:
> +	rzg2l_thermal_reset_assert_pm_disable_put(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");
>
Biju Das Nov. 30, 2021, 12:35 p.m. UTC | #2
Hi Daniel Lezcano,

Thanks for the review.

> Subject: Re: [PATCH v2 2/2] thermal/drivers: Add TSU driver for RZ/G2L
> 
> On 20/11/2021 20:47, Biju Das wrote:
> > Add Thermal Sensor Unit(TSU) driver for RZ/G2L SoC.
> 
> Please give a technical description of the sensor as it is the first
> submission.

Agreed. Will do.


> 
> > Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> > Reviewed-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
> > ---
> > v1->v2:
> >  * Removed devm_add_action_or_reset from probe.
> > ---
> >  drivers/thermal/Kconfig         |   9 ++
> >  drivers/thermal/Makefile        |   1 +
> >  drivers/thermal/rzg2l_thermal.c | 235
> > ++++++++++++++++++++++++++++++++
> >  3 files changed, 245 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..ea828c15dd1e
> > --- /dev/null
> > +++ b/drivers/thermal/rzg2l_thermal.c
> > @@ -0,0 +1,235 @@
> > +// 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)
> 
> cf. include/linux/units.h


The value we get is Celsius, which we are converting it to milli Celsius,
So that we can update with 0.5 degree(500 milli Celsius) granularity.

Ok will replace "1000" with "MILLIDEGREE_PER_DEGREE"
Defined in include/linux/units.h

> 
> > +#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;
> > +}
> 
> roundup() | rounddown() ? (cf math.h)

OK will use roundup operation.
> 
> > +
> > +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;
> 
> Init in the declaration

OK.

> What unit is the value returned ?

Value from the sensor is positive.
Actual sensor value is the value after applying curvature correction.

Then we apply the calibration constants to the above value to compute Tj, that value
can be negative.

Basically,
Tj Degree Celsius = curvature_correction(val) * caliberation constants - 40

> 
> > +	/*  Read the ADC value 8 times with an interval of 20 microsecs */
> 
> Why do you have to do that ? + comment format

TSU repeats measurement at 20 micro seconds intervals and automatically updates
the results of measurement.

As per HW manual procedure for measuring the temperature, it recommends to
Read 8 values consecutively every 20 micro seconds and take the average.

> 
> /*
>  *
>  */
> 
> 
> > +	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 */
> 
> With a proper comment in the loop, no need to add this one
OK.

> 
> > +	ts_code_ave = result / TS_CODE_CAP_TIMES;
> 
> > +	/* Curvature correction */
> 
> What is it? + comment format

Sensor value may have drifts, to compensate that apply curvature correction
Based on this formula.

> 
> > +	dsensor = TS_CODE_AVE_SCALE(ts_code_ave) /
> > +		(TS_CODE_AVE_SCALE(1) + (ts_code_ave * 13));
> > +
> 
> No litteral, s/13/a_macro/
OK

> 
> > +	/* Temperature calculation */
> > +	val = ((dsensor - priv->calib1) * (MCELSIUS(165) /
> > +		(priv->calib0 - priv->calib1))) - MCELSIUS(40);
> 
> Elaborate please + comment format
OK will do.
Basically,
Tj Degree Celsius = curvature_correction(val) * caliberation constants - 40

> 
> > +
> > +	/* Round value to device granularity setting */
> 
> Comment format

OK. Will update.

> 
> > +	*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 */
> 
> Why + comment format. The comment should not describe what is doing the
> code but *why*. It is obvious usleep(60) waits 60us

OK, it will take 60 micro seconds to place sensor in thermal mode.
After sensor in normal mode only we should set the start bit.

Will update comment with suggested format.

> 
> > +	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 void rzg2l_thermal_reset_assert_pm_disable_put(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);
> > +}
> > +
> > +static int rzg2l_thermal_remove(struct platform_device *pdev) {
> > +	struct rzg2l_thermal_priv *priv = dev_get_drvdata(&pdev->dev);
> > +
> > +	thermal_remove_hwmon_sysfs(priv->zone);
> > +	rzg2l_thermal_reset_assert_pm_disable_put(pdev);
> > +
> > +	return 0;
> > +}
> > +
> > +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 err;
> > +	}
> > +
> > +	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);
> 
> 		dev_err(dev, "Can't register thermal zone: %d", ret);
> 
> > +		goto err;
> > +	}
> > +
> > +	priv->zone = zone;
> > +	priv->zone->tzp->no_hwmon = false;
> > +	ret = thermal_add_hwmon_sysfs(priv->zone);
> > +	if (ret)
> > +		goto err;
> > +
> > +	dev_info(dev, "TSU probed with %s caliberation values",
> > +		 rzg2l_thermal_read(priv, OTPTSUTRIM_REG(0)) ?  "hw" : "sw");
> 
> 	s/dev_info/dev_debug/
OK.

Regards,
Biju

> 
> > +	return 0;
> > +
> > +err:
> > +	rzg2l_thermal_reset_assert_pm_disable_put(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");
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..ea828c15dd1e
--- /dev/null
+++ b/drivers/thermal/rzg2l_thermal.c
@@ -0,0 +1,235 @@ 
+// 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 void rzg2l_thermal_reset_assert_pm_disable_put(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);
+}
+
+static int rzg2l_thermal_remove(struct platform_device *pdev)
+{
+	struct rzg2l_thermal_priv *priv = dev_get_drvdata(&pdev->dev);
+
+	thermal_remove_hwmon_sysfs(priv->zone);
+	rzg2l_thermal_reset_assert_pm_disable_put(pdev);
+
+	return 0;
+}
+
+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 err;
+	}
+
+	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 err;
+	}
+
+	priv->zone = zone;
+	priv->zone->tzp->no_hwmon = false;
+	ret = thermal_add_hwmon_sysfs(priv->zone);
+	if (ret)
+		goto err;
+
+	dev_info(dev, "TSU probed with %s caliberation values",
+		 rzg2l_thermal_read(priv, OTPTSUTRIM_REG(0)) ?  "hw" : "sw");
+
+	return 0;
+
+err:
+	rzg2l_thermal_reset_assert_pm_disable_put(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");