[PATCH 1/2] thermal: rcar_gen3: Add Renesas R-Car Gen3/Gen4 and RZ/G2 thermal driver
Marek Vasut
marek.vasut+renesas at mailbox.org
Sat Sep 6 02:04:19 CEST 2025
Add support for R-Car Gen3/Gen4 and RZ/G2 thermal sensors.
Fuse readout support is present, including fallback trimming.
Example usage using the 'temperature' command on R-Car V4H Sparrow Hawk:
"
=> temperature list
| Device | Driver | Parent
| thermal-rcar-gen3-tsc.0 | thermal-rcar-gen3-tsc | thermal at e6198000
| thermal-rcar-gen3-tsc.1 | thermal-rcar-gen3-tsc | thermal at e6198000
| thermal-rcar-gen3-tsc.2 | thermal-rcar-gen3-tsc | thermal at e6198000
| thermal-rcar-gen3-tsc.3 | thermal-rcar-gen3-tsc | thermal at e6198000
=> for i in 0 1 2 3 ; do temperature get thermal-rcar-gen3-tsc.$i ; done
thermal-rcar-gen3-tsc.0: 56 C
thermal-rcar-gen3-tsc.1: 50 C
thermal-rcar-gen3-tsc.2: 48 C
thermal-rcar-gen3-tsc.3: 52 C
"
Ported from Linux 6.16-rc6 commit
9e1dc0360fcf ("thermal/drivers/rcar_gen3: Add support for R-Car V4H default trim values")
Signed-off-by: Marek Vasut <marek.vasut+renesas at mailbox.org>
---
Cc: Nobuhiro Iwamatsu <iwamatsu at nigauri.org>
Cc: Tom Rini <trini at konsulko.com>
Cc: u-boot at lists.denx.de
---
drivers/thermal/Kconfig | 8 +
drivers/thermal/Makefile | 1 +
drivers/thermal/rcar_gen3_thermal.c | 468 ++++++++++++++++++++++++++++
3 files changed, 477 insertions(+)
create mode 100644 drivers/thermal/rcar_gen3_thermal.c
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 440eb64a566..91c39aa4dee 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -35,6 +35,14 @@ config IMX_TMU
The boot is hold to the cool device to throttle CPUs when the
passive trip is crossed
+config RCAR_GEN3_THERMAL
+ bool "Renesas R-Car Gen3/Gen4 and RZ/G2 thermal driver"
+ depends on ARCH_RENESAS
+ depends on RCAR_64
+ help
+ Enable this to plug the R-Car Gen3/Gen4 or RZ/G2 thermal sensor
+ driver into the U-Boot thermal framework.
+
config TI_DRA7_THERMAL
bool "Temperature sensor driver for TI dra7xx SOCs"
help
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 18ad453f9b1..b6f06c00ed9 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_DM_THERMAL) += thermal-uclass.o
obj-$(CONFIG_IMX_SCU_THERMAL) += imx_scu_thermal.o
obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o
obj-$(CONFIG_IMX_TMU) += imx_tmu.o
+obj-$(CONFIG_RCAR_GEN3_THERMAL) += rcar_gen3_thermal.o
obj-$(CONFIG_SANDBOX) += thermal_sandbox.o
obj-$(CONFIG_TI_DRA7_THERMAL) += ti-bandgap.o
obj-$(CONFIG_TI_LM74_THERMAL) += ti-lm74.o
diff --git a/drivers/thermal/rcar_gen3_thermal.c b/drivers/thermal/rcar_gen3_thermal.c
new file mode 100644
index 00000000000..83433e8d8f0
--- /dev/null
+++ b/drivers/thermal/rcar_gen3_thermal.c
@@ -0,0 +1,468 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * R-Car Gen3/Gen4 and RZ/G2 THS thermal sensor driver
+ * Based on rcar_thermal.c and work from Hien Dang and Khiem Nguyen.
+ * Based on
+ *
+ * Copyright (C) 2016 Renesas Electronics Corporation.
+ * Copyright (C) 2016 Sang Engineering
+ */
+
+#include <asm/io.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <linux/delay.h>
+#include <thermal.h>
+
+/* Register offsets */
+#define REG_GEN3_IRQSTR 0x04
+#define REG_GEN3_IRQMSK 0x08
+#define REG_GEN3_IRQCTL 0x0c
+#define REG_GEN3_IRQEN 0x10
+#define REG_GEN3_IRQTEMP1 0x14
+#define REG_GEN3_IRQTEMP2 0x18
+#define REG_GEN3_IRQTEMP3 0x1c
+#define REG_GEN3_THCTR 0x20
+#define REG_GEN3_TEMP 0x28
+#define REG_GEN3_THCODE1 0x50
+#define REG_GEN3_THCODE2 0x54
+#define REG_GEN3_THCODE3 0x58
+#define REG_GEN3_PTAT1 0x5c
+#define REG_GEN3_PTAT2 0x60
+#define REG_GEN3_PTAT3 0x64
+#define REG_GEN3_THSCP 0x68
+#define REG_GEN4_THSFMON00 0x180
+#define REG_GEN4_THSFMON01 0x184
+#define REG_GEN4_THSFMON02 0x188
+#define REG_GEN4_THSFMON15 0x1bc
+#define REG_GEN4_THSFMON16 0x1c0
+#define REG_GEN4_THSFMON17 0x1c4
+
+/* IRQ{STR,MSK,EN} bits */
+#define IRQ_TEMP1 BIT(0)
+#define IRQ_TEMP2 BIT(1)
+#define IRQ_TEMP3 BIT(2)
+#define IRQ_TEMPD1 BIT(3)
+#define IRQ_TEMPD2 BIT(4)
+#define IRQ_TEMPD3 BIT(5)
+
+/* THCTR bits */
+#define THCTR_PONM BIT(6)
+#define THCTR_THSST BIT(0)
+
+/* THSCP bits */
+#define THSCP_COR_PARA_VLD (BIT(15) | BIT(14))
+
+#define CTEMP_MASK 0xfff
+
+#define MCELSIUS(temp) ((temp) * 1000)
+#define GEN3_FUSE_MASK 0xfff
+#define GEN4_FUSE_MASK 0xfff
+
+#define TSC_MAX_NUM 5
+
+struct rcar_gen3_thermal_priv;
+
+struct rcar_gen3_thermal_fuse_info {
+ u32 ptat[3];
+ u32 thcode[3];
+ u32 mask;
+};
+
+struct rcar_gen3_thermal_fuse_default {
+ u32 ptat[3];
+ u32 thcodes[TSC_MAX_NUM][3];
+};
+
+struct rcar_thermal_info {
+ int scale;
+ int adj_below;
+ int adj_above;
+ const struct rcar_gen3_thermal_fuse_info *fuses;
+ const struct rcar_gen3_thermal_fuse_default *fuse_defaults;
+};
+
+struct equation_set_coef {
+ int a;
+ int b;
+};
+
+struct rcar_gen3_thermal_tsc {
+ void __iomem *base;
+ /* Different coefficients are used depending on a threshold. */
+ struct {
+ struct equation_set_coef below;
+ struct equation_set_coef above;
+ } coef;
+ int thcode[3];
+};
+
+struct rcar_gen3_thermal_priv {
+ struct rcar_gen3_thermal_tsc tscs[TSC_MAX_NUM];
+ unsigned int num_tscs;
+ int ptat[3];
+};
+
+static inline u32 rcar_gen3_thermal_read(struct rcar_gen3_thermal_tsc *tsc,
+ u32 reg)
+{
+ return readl(tsc->base + reg);
+}
+
+static inline void rcar_gen3_thermal_write(struct rcar_gen3_thermal_tsc *tsc,
+ u32 reg, u32 data)
+{
+ writel(data, tsc->base + reg);
+}
+
+/*
+ * Linear approximation for temperature
+ *
+ * [temp] = ((thadj - [reg]) * a) / b + adj
+ * [reg] = thadj - ([temp] - adj) * b / a
+ *
+ * The constants a and b are calculated using two triplets of int values PTAT
+ * and THCODE. PTAT and THCODE can either be read from hardware or use hard
+ * coded values from the driver. The formula to calculate a and b are taken from
+ * the datasheet. Different calculations are needed for a and b depending on
+ * if the input variables ([temp] or [reg]) are above or below a threshold. The
+ * threshold is also calculated from PTAT and THCODE using formulas from the
+ * datasheet.
+ *
+ * The constant thadj is one of the THCODE values, which one to use depends on
+ * the threshold and input value.
+ *
+ * The constants adj is taken verbatim from the datasheet. Two values exists,
+ * which one to use depends on the input value and the calculated threshold.
+ * Furthermore different SoC models supported by the driver have different sets
+ * of values. The values for each model are stored in the device match data.
+ */
+static void rcar_gen3_thermal_tsc_coefs(struct udevice *dev,
+ struct rcar_gen3_thermal_tsc *tsc)
+{
+ struct rcar_thermal_info *info = (struct rcar_thermal_info *)dev_get_driver_data(dev);
+ struct rcar_gen3_thermal_priv *priv = dev_get_plat(dev);
+
+ tsc->coef.below.a = info->scale * (priv->ptat[2] - priv->ptat[1]);
+ tsc->coef.above.a = info->scale * (priv->ptat[0] - priv->ptat[1]);
+
+ tsc->coef.below.b = (priv->ptat[2] - priv->ptat[0]) * (tsc->thcode[2] - tsc->thcode[1]);
+ tsc->coef.above.b = (priv->ptat[0] - priv->ptat[2]) * (tsc->thcode[1] - tsc->thcode[0]);
+}
+
+static int rcar_gen3_thermal_get_temp(struct udevice *dev, int *temp)
+{
+ struct rcar_thermal_info *info = (struct rcar_thermal_info *)dev_get_driver_data(dev->parent);
+ struct rcar_gen3_thermal_priv *priv = dev_get_plat(dev->parent);
+ unsigned int tsc_id = dev_get_driver_data(dev);
+ struct rcar_gen3_thermal_tsc *tsc = &(priv->tscs[tsc_id]);
+ const struct equation_set_coef *coef;
+ int adj, decicelsius, reg, thcode;
+
+ /* Read register and convert to degree Celsius */
+ reg = rcar_gen3_thermal_read(tsc, REG_GEN3_TEMP) & CTEMP_MASK;
+
+ if (reg < tsc->thcode[1]) {
+ adj = info->adj_below;
+ coef = &tsc->coef.below;
+ thcode = tsc->thcode[2];
+ } else {
+ adj = info->adj_above;
+ coef = &tsc->coef.above;
+ thcode = tsc->thcode[0];
+ }
+
+ /*
+ * The dividend can't be grown as it might overflow, instead shorten the
+ * divisor to convert to decidegree Celsius. If we convert after the
+ * division precision is lost as we will scale up from whole degrees
+ * Celsius.
+ */
+ decicelsius = DIV_ROUND_CLOSEST(coef->a * (thcode - reg), coef->b / 10);
+
+ /* Guaranteed operating range is -40C to 125C. */
+
+ /* Reporting is done in degree Celsius */
+ *temp = (decicelsius * 100 + adj * 1000) / 1000;
+
+ return 0;
+}
+
+static const struct dm_thermal_ops rcar_gen3_thermal_ops = {
+ .get_temp = rcar_gen3_thermal_get_temp,
+};
+
+static void rcar_gen3_thermal_fetch_fuses(struct udevice *dev)
+{
+ struct rcar_thermal_info *info = (struct rcar_thermal_info *)dev_get_driver_data(dev);
+ struct rcar_gen3_thermal_priv *priv = dev_get_plat(dev);
+ const struct rcar_gen3_thermal_fuse_info *fuses = info->fuses;
+
+ /*
+ * Set the pseudo calibration points with fused values.
+ * PTAT is shared between all TSCs but only fused for the first
+ * TSC while THCODEs are fused for each TSC.
+ */
+ priv->ptat[0] = rcar_gen3_thermal_read(&(priv->tscs[0]), fuses->ptat[0])
+ & fuses->mask;
+ priv->ptat[1] = rcar_gen3_thermal_read(&(priv->tscs[0]), fuses->ptat[1])
+ & fuses->mask;
+ priv->ptat[2] = rcar_gen3_thermal_read(&(priv->tscs[0]), fuses->ptat[2])
+ & fuses->mask;
+
+ for (unsigned int i = 0; i < priv->num_tscs; i++) {
+ struct rcar_gen3_thermal_tsc *tsc = &(priv->tscs[i]);
+
+ tsc->thcode[0] = rcar_gen3_thermal_read(tsc, fuses->thcode[0])
+ & fuses->mask;
+ tsc->thcode[1] = rcar_gen3_thermal_read(tsc, fuses->thcode[1])
+ & fuses->mask;
+ tsc->thcode[2] = rcar_gen3_thermal_read(tsc, fuses->thcode[2])
+ & fuses->mask;
+ }
+}
+
+static bool rcar_gen3_thermal_read_fuses(struct udevice *dev)
+{
+ struct rcar_thermal_info *info = (struct rcar_thermal_info *)dev_get_driver_data(dev);
+ struct rcar_gen3_thermal_priv *priv = dev_get_plat(dev);
+ const struct rcar_gen3_thermal_fuse_default *fuse_defaults = info->fuse_defaults;
+ unsigned int i;
+ u32 thscp;
+
+ /* If fuses are not set, fallback to pseudo values. */
+ thscp = rcar_gen3_thermal_read(&(priv->tscs[0]), REG_GEN3_THSCP);
+ if (!info->fuses ||
+ (thscp & THSCP_COR_PARA_VLD) != THSCP_COR_PARA_VLD) {
+ /* Default THCODE values in case FUSEs are not set. */
+ priv->ptat[0] = fuse_defaults->ptat[0];
+ priv->ptat[1] = fuse_defaults->ptat[1];
+ priv->ptat[2] = fuse_defaults->ptat[2];
+
+ for (i = 0; i < priv->num_tscs; i++) {
+ struct rcar_gen3_thermal_tsc *tsc = &(priv->tscs[i]);
+
+ tsc->thcode[0] = fuse_defaults->thcodes[i][0];
+ tsc->thcode[1] = fuse_defaults->thcodes[i][1];
+ tsc->thcode[2] = fuse_defaults->thcodes[i][2];
+ }
+
+ return false;
+ }
+
+ rcar_gen3_thermal_fetch_fuses(dev);
+
+ return true;
+}
+
+static void rcar_gen3_thermal_init(struct rcar_gen3_thermal_priv *priv,
+ struct rcar_gen3_thermal_tsc *tsc)
+{
+ u32 reg_val;
+
+ reg_val = rcar_gen3_thermal_read(tsc, REG_GEN3_THCTR);
+ reg_val &= ~THCTR_PONM;
+ rcar_gen3_thermal_write(tsc, REG_GEN3_THCTR, reg_val);
+
+ udelay(1000);
+
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0);
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0);
+
+ reg_val = rcar_gen3_thermal_read(tsc, REG_GEN3_THCTR);
+ reg_val |= THCTR_THSST;
+ rcar_gen3_thermal_write(tsc, REG_GEN3_THCTR, reg_val);
+
+ udelay(1000);
+}
+
+static int rcar_gen3_thermal_probe(struct udevice *dev)
+{
+ struct rcar_gen3_thermal_priv *priv = dev_get_plat(dev);
+ unsigned int i;
+
+ if (!rcar_gen3_thermal_read_fuses(dev))
+ dev_dbg(dev, "No calibration values fused, fallback to driver values\n");
+
+ for (i = 0; i < priv->num_tscs; i++) {
+ struct rcar_gen3_thermal_tsc *tsc = &(priv->tscs[i]);
+
+ rcar_gen3_thermal_init(priv, tsc);
+ rcar_gen3_thermal_tsc_coefs(dev, tsc);
+ }
+
+ return 0;
+}
+
+static const struct rcar_gen3_thermal_fuse_info rcar_gen3_thermal_fuse_info_gen3 = {
+ .ptat = { REG_GEN3_PTAT1, REG_GEN3_PTAT2, REG_GEN3_PTAT3 },
+ .thcode = { REG_GEN3_THCODE1, REG_GEN3_THCODE2, REG_GEN3_THCODE3 },
+ .mask = GEN3_FUSE_MASK,
+};
+
+static const struct rcar_gen3_thermal_fuse_info rcar_gen3_thermal_fuse_info_gen4 = {
+ .ptat = { REG_GEN4_THSFMON16, REG_GEN4_THSFMON17, REG_GEN4_THSFMON15 },
+ .thcode = { REG_GEN4_THSFMON01, REG_GEN4_THSFMON02, REG_GEN4_THSFMON00 },
+ .mask = GEN4_FUSE_MASK,
+};
+
+static const struct rcar_gen3_thermal_fuse_default rcar_gen3_thermal_fuse_default_info_gen3 = {
+ .ptat = { 2631, 1509, 435 },
+ .thcodes = {
+ { 3397, 2800, 2221 },
+ { 3393, 2795, 2216 },
+ { 3389, 2805, 2237 },
+ { 3415, 2694, 2195 },
+ { 3356, 2724, 2244 },
+ },
+};
+
+static const struct rcar_gen3_thermal_fuse_default rcar_gen3_thermal_fuse_default_info_v4h = {
+ .ptat = { 3274, 2164, 985 },
+ .thcodes = { /* All four THS units share the same trimming */
+ { 3218, 2617, 1980 },
+ { 3218, 2617, 1980 },
+ { 3218, 2617, 1980 },
+ { 3218, 2617, 1980 },
+ }
+};
+
+static const struct rcar_thermal_info rcar_m3w_thermal_info = {
+ .scale = 157,
+ .adj_below = -41,
+ .adj_above = 116,
+ .fuses = &rcar_gen3_thermal_fuse_info_gen3,
+ .fuse_defaults = &rcar_gen3_thermal_fuse_default_info_gen3,
+};
+
+static const struct rcar_thermal_info rcar_gen3_thermal_info = {
+ .scale = 167,
+ .adj_below = -41,
+ .adj_above = 126,
+ .fuses = &rcar_gen3_thermal_fuse_info_gen3,
+ .fuse_defaults = &rcar_gen3_thermal_fuse_default_info_gen3,
+};
+
+static const struct rcar_thermal_info rcar_gen4_thermal_info = {
+ .scale = 167,
+ .adj_below = -41,
+ .adj_above = 126,
+ .fuses = &rcar_gen3_thermal_fuse_info_gen4,
+ .fuse_defaults = &rcar_gen3_thermal_fuse_default_info_gen3,
+};
+
+static const struct rcar_thermal_info rcar_v4h_thermal_info = {
+ .scale = 167,
+ .adj_below = -41,
+ .adj_above = 126,
+ .fuses = &rcar_gen3_thermal_fuse_info_gen4,
+ .fuse_defaults = &rcar_gen3_thermal_fuse_default_info_v4h,
+};
+
+static const struct udevice_id rcar_gen3_thermal_ids[] = {
+ {
+ .compatible = "renesas,r8a774a1-thermal",
+ .data = (ulong)&rcar_m3w_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a774b1-thermal",
+ .data = (ulong)&rcar_gen3_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a774e1-thermal",
+ .data = (ulong)&rcar_gen3_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a7795-thermal",
+ .data = (ulong)&rcar_gen3_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a7796-thermal",
+ .data = (ulong)&rcar_m3w_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a77961-thermal",
+ .data = (ulong)&rcar_m3w_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a77965-thermal",
+ .data = (ulong)&rcar_gen3_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a77980-thermal",
+ .data = (ulong)&rcar_gen3_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a779a0-thermal",
+ .data = (ulong)&rcar_gen3_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a779f0-thermal",
+ .data = (ulong)&rcar_gen4_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a779g0-thermal",
+ .data = (ulong)&rcar_v4h_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a779h0-thermal",
+ .data = (ulong)&rcar_gen4_thermal_info,
+ },
+ { }
+};
+
+U_BOOT_DRIVER(thermal_rcar_gen3_tsc) = {
+ .name = "thermal-rcar-gen3-tsc",
+ .id = UCLASS_THERMAL,
+ .ops = &rcar_gen3_thermal_ops,
+ .flags = DM_FLAG_PRE_RELOC,
+};
+
+static int rcar_gen3_thermal_bind(struct udevice *dev)
+{
+ /*
+ * We use dev_get_plat() here, because plat data are available
+ * in bind, while private data are not allocated yet.
+ */
+ struct rcar_gen3_thermal_priv *priv = dev_get_plat(dev);
+ struct udevice *tdev;
+ struct driver *tdrv;
+ char name[32];
+ void *addr;
+ int i, ret;
+
+ tdrv = lists_driver_lookup_name("thermal-rcar-gen3-tsc");
+ if (!tdrv)
+ return -ENOENT;
+
+ for (i = 0; i < TSC_MAX_NUM; i++) {
+ addr = dev_read_addr_index_ptr(dev, i);
+ if (!addr)
+ break;
+
+ priv->tscs[i].base = addr;
+
+ tdev = NULL;
+ snprintf(name, sizeof(name), "thermal-rcar-gen3-tsc.%d", i);
+ ret = device_bind_with_driver_data(dev, tdrv, strdup(name), i,
+ dev_ofnode(dev), &tdev);
+ if (ret)
+ return ret;
+ }
+
+ priv->num_tscs = i;
+
+ return 0;
+}
+
+U_BOOT_DRIVER(thermal_rcar_gen3) = {
+ .name = "thermal-rcar-gen3",
+ .id = UCLASS_NOP,
+ .of_match = rcar_gen3_thermal_ids,
+ .bind = rcar_gen3_thermal_bind,
+ .probe = rcar_gen3_thermal_probe,
+ .plat_auto = sizeof(struct rcar_gen3_thermal_priv),
+ .flags = DM_FLAG_PRE_RELOC,
+};
--
2.50.1
More information about the U-Boot
mailing list