24bbbfb73e
The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Acked-by: Daniel Lezcano <daniel.lezcano@linaro.org> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
250 lines
6.5 KiB
C
250 lines
6.5 KiB
C
// 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 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))
|
||
#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_new = 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");
|