// 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/math.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/thermal.h>
#include <linux/units.h>
#include "../thermal_hwmon.h"
#define CTEMP_MASK 0 xFFF
/* default calibration values, if FUSE values are missing */
#define SW_CALIB0_VAL 3148
#define SW_CALIB1_VAL 503
/* Register offsets */
#define TSU_SM 0 x00
#define TSU_ST 0 x04
#define TSU_SAD 0 x0C
#define TSU_SS 0 x10
#define OTPTSUTRIM_REG(n) (0 x18 + ((n) * 0 x4))
#define OTPTSUTRIM_EN_MASK BIT(31 )
#define OTPTSUTRIM_MASK GENMASK(11 , 0 )
/* 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) * MILLIDEGREE_PER_DEGREE)
#define TS_CODE_CAP_TIMES 8 /* Total number of ADC data samples */
#define RZG2L_THERMAL_GRAN 500 /* milli Celsius */
#define RZG2L_TSU_SS_TIMEOUT_US 1000
#define CURVATURE_CORRECTION_CONST 13
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_get_temp(struct thermal_zone_device *tz, int *temp)
{
struct rzg2l_thermal_priv *priv = thermal_zone_device_priv(tz);
u32 result = 0 , dsensor, ts_code_ave;
int val, i;
for (i = 0 ; i < TS_CODE_CAP_TIMES ; i++) {
/*
* TSU repeats measurement at 20 microseconds intervals and
* automatically updates the results of measurement . As per
* the HW manual for measuring temperature we need to read 8
* values consecutively and then take the average .
* ts_code_ave = ( ts_code [ 0 ] + ⋯ + ts_code [ 7 ] ) / 8
*/
result += rzg2l_thermal_read(priv, TSU_SAD) & CTEMP_MASK;
usleep_range(20 , 30 );
}
ts_code_ave = result / TS_CODE_CAP_TIMES;
/*
* Calculate actual sensor value by applying curvature correction formula
* dsensor = ts_code_ave / ( 1 + ts_code_ave * 0 . 000013 ) . Here we are doing
* integer calculation by scaling all the values by 1000000 .
*/
dsensor = TS_CODE_AVE_SCALE(ts_code_ave) /
(TS_CODE_AVE_SCALE(1 ) + (ts_code_ave * CURVATURE_CORRECTION_CONST));
/*
* The temperature Tj is calculated by the formula
* Tj = ( dsensor − calib1 ) * 165 / ( calib0 − calib1 ) − 40
* where calib0 and calib1 are the calibration values .
*/
val = ((dsensor - priv->calib1) * (MCELSIUS(165 ) /
(priv->calib0 - priv->calib1))) - MCELSIUS(40 );
*temp = roundup(val, RZG2L_THERMAL_GRAN);
return 0 ;
}
static const struct thermal_zone_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 the START bit , TSU should be in normal operating
* mode . As per the HW manual , it will take 60 µ s to place the TSU
* into normal operating mode .
*/
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 void 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);
}
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(dev, NULL);
if (IS_ERR(priv->rstc))
return dev_err_probe(dev, PTR_ERR(priv->rstc),
"failed to get cpg reset" );
ret = reset_control_deassert(priv->rstc);
if (ret)
return dev_err_probe(dev, ret, "failed to deassert" );
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
priv->calib0 = rzg2l_thermal_read(priv, OTPTSUTRIM_REG(0 ));
if (priv->calib0 & OTPTSUTRIM_EN_MASK)
priv->calib0 &= OTPTSUTRIM_MASK;
else
priv->calib0 = SW_CALIB0_VAL;
priv->calib1 = rzg2l_thermal_read(priv, OTPTSUTRIM_REG(1 ));
if (priv->calib1 & OTPTSUTRIM_EN_MASK)
priv->calib1 &= OTPTSUTRIM_MASK;
else
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_of_zone_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;
ret = thermal_add_hwmon_sysfs(priv->zone);
if (ret)
goto err;
dev_dbg(dev, "TSU probed with %s calibration 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" );
Messung V0.5 in Prozent C=95 H=90 G=92
¤ Dauer der Verarbeitung: 0.1 Sekunden
(vorverarbeitet am 2026-10-11)
¤
*© Formatika GbR, Deutschland