[PATCH v1] thermal: qoriq: add Layerscape on-die TMU
Vincent Jardin
vjardin at free.fr
Wed May 27 15:46:45 CEST 2026
Add the support for lx2160 and other layerscape for:
=> temperature list
| cluster67-thermal | qoriq_thermal | tmu at 1f80000
| ddr1-cluster5-... | qoriq_thermal | tmu at 1f80000
...
=> temperature get cluster67-thermal
cluster67-thermal: 56000 mC
It is designed as a generic UCLASS_THERMAL driver for the
QorIQ/Layerscape on-die Thermal Monitoring Unit (TMU).
It is similar to the "regs_v1" variant already implemented in
drivers/thermal/imx_tmu.c, but the i.MX driver depends
on <asm/arch/clock.h>, is_imx8m*() arch helpers, and OCOTP fuse
reads that do not exist on Layerscape.
Rather than #ifdef the QorIQ bits, this driver is a clean Layerscape
counterpart binding the standard "fsl,qoriq-tmu" compatible used by
the Linux qoriq_thermal driver and by the existing fsl-ls10{28,88}a
DTSIs too!
The dtsi additions mirror the existing fsl-ls1028a.dtsi
For example, the LX2160A SoC dtsi gains the tmu at 1f80000 node plus a
thermal-zones hierarchy with 7 sites:
cluster67-thermal site 0 A72 clusters 6 + 7
ddr1-cluster5-thermal site 1 DDR1 + A72 cluster 5
wriop-thermal site 2 WRIOP
dce-qbman-hsio2-thermal site 3 DCE + QBMAN + HSIO2
ccn-dpaa-tbu-thermal site 4 CCN508 + DPAA + TBU
cluster4-hsio3-thermal site 5 A72 cluster 4 + HSIO3
cluster23-thermal site 6 A72 clusters 2 + 3
Signed-off-by: Vincent Jardin <vjardin at free.fr>
---
MAINTAINERS | 5 +
arch/arm/dts/fsl-lx2160a.dtsi | 58 +++++++
drivers/thermal/Kconfig | 8 +
drivers/thermal/Makefile | 1 +
drivers/thermal/qoriq_thermal.c | 283 ++++++++++++++++++++++++++++++++
5 files changed, 355 insertions(+)
create mode 100644 drivers/thermal/qoriq_thermal.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 056902f6ef2..fb2d834e432 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1521,6 +1521,11 @@ M: Radu Pirea <radu-nicolae.pirea at oss.nxp.com>
S: Maintained
F: drivers/net/phy/nxp-c45-tja11xx.c
+NXP QORIQ / LAYERSCAPE THERMAL
+M: Vincent Jardin <vjardin at free.fr>
+S: Maintained
+F: drivers/thermal/qoriq_thermal.c
+
ONENAND
#M: Lukasz Majewski <l.majewski at majess.pl>
S: Orphaned (Since 2017-01)
diff --git a/arch/arm/dts/fsl-lx2160a.dtsi b/arch/arm/dts/fsl-lx2160a.dtsi
index 680c69c7b73..e27839124f0 100644
--- a/arch/arm/dts/fsl-lx2160a.dtsi
+++ b/arch/arm/dts/fsl-lx2160a.dtsi
@@ -594,6 +594,64 @@
};
};
+ /* LX2160ARM Chapter 28 ("Thermal Monitoring Unit") */
+ tmu: tmu at 1f80000 {
+ compatible = "fsl,qoriq-tmu";
+ reg = <0x0 0x1f80000 0x0 0x10000>;
+ interrupts = <GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH>;
+ fsl,tmu-range = <0x800000e6 0x8001017d>;
+ fsl,tmu-calibration = <0x00000000 0x00000035
+ 0x00000001 0x00000154>;
+ little-endian;
+ #thermal-sensor-cells = <1>;
+ label = "lx2160a-tmu"; /* explicit naming */
+ };
+
+ /* explicit thermal-zones names per LX2160ARM Table 323 */
+ thermal-zones {
+ cluster67-thermal {
+ polling-delay-passive = <1000>;
+ polling-delay = <5000>;
+ thermal-sensors = <&tmu 0>;
+ };
+
+ ddr1-cluster5-thermal {
+ polling-delay-passive = <1000>;
+ polling-delay = <5000>;
+ thermal-sensors = <&tmu 1>;
+ };
+
+ wriop-thermal {
+ polling-delay-passive = <1000>;
+ polling-delay = <5000>;
+ thermal-sensors = <&tmu 2>;
+ };
+
+ dce-qbman-hsio2-thermal {
+ polling-delay-passive = <1000>;
+ polling-delay = <5000>;
+ thermal-sensors = <&tmu 3>;
+ };
+
+ ccn-dpaa-tbu-thermal {
+ polling-delay-passive = <1000>;
+ polling-delay = <5000>;
+ thermal-sensors = <&tmu 4>;
+ };
+
+ cluster4-hsio3-thermal {
+ polling-delay-passive = <1000>;
+ polling-delay = <5000>;
+ thermal-sensors = <&tmu 5>;
+ };
+
+ cluster23-thermal {
+ polling-delay-passive = <1000>;
+ polling-delay = <5000>;
+ thermal-sensors = <&tmu 6>;
+ };
+ };
+
/* WRIOP0: 0x8b8_0000, E-MDIO1: 0x1_6000 */
emdio1: mdio at 8b96000 {
compatible = "fsl,ls-mdio";
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 91c39aa4dee..1a2e2884591 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -55,4 +55,12 @@ config TI_LM74_THERMAL
Enable thermal support for the Texas Instruments LM74 chip.
The driver supports reading CPU temperature.
+config QORIQ_THERMAL
+ bool "NXP QorIQ / Layerscape on-die TMU"
+ depends on FSL_LAYERSCAPE || ARCH_LS1028A || ARCH_LS1088A
+ help
+ Enable support for the on-die Thermal Monitoring Unit (TMU)
+ found in NXP QorIQ / Layerscape SoCs (LX2160A, LS1088A,
+ LS1028A, etc.).
+
endif # if DM_THERMAL
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index b6f06c00ed9..5a6a8064e68 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -11,3 +11,4 @@ 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
+obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o
diff --git a/drivers/thermal/qoriq_thermal.c b/drivers/thermal/qoriq_thermal.c
new file mode 100644
index 00000000000..dce0f7d5d0f
--- /dev/null
+++ b/drivers/thermal/qoriq_thermal.c
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2026 Free Mobile - Vincent Jardin
+ *
+ * NXP QorIQ / Layerscape Thermal Monitoring Unit (TMU) driver.
+ *
+ * Same on-die TMU IP block shared by LX2160A, LS1088A, LS1028A and
+ * other QorIQ / Layerscape SoCs.
+ *
+ * DT binding (variable-length range/calibration so SoCs needing
+ * fewer than 4 ranges, e.g. the LX2160A only uses 2 -- bind
+ * cleanly):
+ *
+ * tmu: tmu at 1f80000 {
+ * compatible = "fsl,qoriq-tmu";
+ * reg = <0x0 0x1f80000 0x0 0x10000>;
+ * fsl,tmu-range = <range0 [range1 [range2 [range3]]]>;
+ * fsl,tmu-calibration = <cfg0 sensor0 cfg1 sensor1 ...>;
+ * #thermal-sensor-cells = <1>;
+ * little-endian;
+ * };
+ *
+ * thermal-zones {
+ * cluster67-thermal {
+ * thermal-sensors = <&tmu 0>;
+ * };
+ * ...
+ * };
+ */
+
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <dm/lists.h>
+#include <errno.h>
+#include <fdtdec.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <thermal.h>
+#include <asm/io.h>
+
+#define QORIQ_TMU_SITES_MAX 16
+
+#define TMR_DISABLE 0x0
+#define TMR_MODE_MONITOR BIT(31)
+#define TMR_ALPF (0x3 << 24) /* heaviest filter (0.125) */
+
+#define TMTMIR_DEFAULT 0x00000002
+#define TIER_DISABLE 0x0
+
+#define TRITSR_V BIT(31)
+#define TRITSR_TEMP_MASK GENMASK(7, 0)
+
+#define QORIQ_TMU_READ_RETRIES 10
+#define QORIQ_TMU_READ_DELAY_MS 100
+
+struct qoriq_tmu_site_regs {
+ u32 tritsr;
+ u32 tratsr;
+ u8 res[0x8];
+};
+
+struct qoriq_tmu_regs {
+ u32 tmr; /* 0x000 mode */
+ u32 tsr; /* 0x004 status */
+ u32 tmsr; /* 0x008 monitor-site enable (bit N = site N) */
+ u32 tmtmir; /* 0x00C measurement interval */
+ u8 res0[0x10]; /* 0x010..0x01F */
+ u32 tier; /* 0x020 interrupt enable */
+ u32 tidr; /* 0x024 interrupt detect (W1C) */
+ u8 res1[0x8]; /* 0x028..0x02F */
+ u32 tiiscr; /* 0x030 immediate site capture */
+ u32 tiascr; /* 0x034 average site capture */
+ u32 ticscr; /* 0x038 critical site capture */
+ u8 res2[0x4]; /* 0x03C */
+ u32 tmhtcr; /* 0x040 high-temp capture */
+ u32 tmltcr; /* 0x044 low-temp capture */
+ u32 tmrtrcr; /* 0x048 rising-rate capture */
+ u32 tmftrcr; /* 0x04C falling-rate capture */
+ u32 tmhtitr; /* 0x050 high immediate threshold */
+ u32 tmhtatr; /* 0x054 high average threshold */
+ u32 tmhtactr; /* 0x058 high average critical threshold */
+ u8 res3[0x4]; /* 0x05C */
+ u32 tmltitr; /* 0x060 low immediate threshold */
+ u32 tmltatr; /* 0x064 low average threshold */
+ u32 tmltactr; /* 0x068 low average critical threshold */
+ u8 res4[0x4]; /* 0x06C */
+ u32 tmrtrctr; /* 0x070 rising-rate critical threshold */
+ u32 tmftrctr; /* 0x074 falling-rate critical threshold */
+ u8 res5[0x8]; /* 0x078..0x07F */
+ u32 ttcfgr; /* 0x080 temperature config (cal walk) */
+ u32 tscfgr; /* 0x084 sensor config (cal walk) */
+ u8 res6[0x78]; /* 0x088..0x0FF */
+ struct qoriq_tmu_site_regs site[QORIQ_TMU_SITES_MAX]; /* 0x100..0x1FF */
+ u8 res7[0xd10]; /* 0x200..0xF0F */
+ u32 ttrcr[16]; /* 0xF10..0xF4C temperature range control */
+};
+
+struct qoriq_tmu_plat {
+ struct qoriq_tmu_regs *regs;
+ int id; /* zone children: TMU site index, eg 0..6 */
+};
+
+static int qoriq_tmu_get_temp(struct udevice *dev, int *temp)
+{
+ struct qoriq_tmu_plat *pdata = dev_get_plat(dev);
+ u32 val = 0;
+ int retry = QORIQ_TMU_READ_RETRIES;
+
+ do {
+ mdelay(QORIQ_TMU_READ_DELAY_MS);
+ val = readl(&pdata->regs->site[pdata->id].tritsr);
+ } while (!(val & TRITSR_V) && --retry > 0);
+
+ if (!(val & TRITSR_V))
+ return -EIO;
+
+ /* TRITSRn[7:0] is the temperature in degrees C. Convert to mC. */
+ *temp = (val & TRITSR_TEMP_MASK) * 1000;
+ return 0;
+}
+
+static const struct dm_thermal_ops qoriq_tmu_ops = {
+ .get_temp = qoriq_tmu_get_temp,
+};
+
+static int qoriq_tmu_calibration(struct udevice *dev)
+{
+ struct qoriq_tmu_plat *pdata = dev_get_plat(dev);
+ const fdt32_t *prop;
+ int len, i, n;
+
+ prop = dev_read_prop(dev, "fsl,tmu-range", &len);
+ if (!prop || len % 4 || len / 4 > ARRAY_SIZE(pdata->regs->ttrcr)) {
+ dev_err(dev, "TMU: missing or invalid fsl,tmu-range\n");
+ return -ENODEV;
+ }
+
+ n = len / 4;
+ for (i = 0; i < n; i++)
+ writel(fdt32_to_cpu(prop[i]), &pdata->regs->ttrcr[i]);
+
+ prop = dev_read_prop(dev, "fsl,tmu-calibration", &len);
+ if (!prop || len % 8) {
+ dev_err(dev, "TMU: missing or invalid fsl,tmu-calibration\n");
+ return -ENODEV;
+ }
+
+ for (i = 0; i < len / 4; i += 2) {
+ writel(fdt32_to_cpu(prop[i]), &pdata->regs->ttcfgr);
+ writel(fdt32_to_cpu(prop[i + 1]), &pdata->regs->tscfgr);
+ }
+
+ return 0;
+}
+
+static void qoriq_tmu_init(struct udevice *dev)
+{
+ struct qoriq_tmu_plat *pdata = dev_get_plat(dev);
+
+ writel(TMR_DISABLE, &pdata->regs->tmr);
+ writel(TIER_DISABLE, &pdata->regs->tier);
+ writel(TMTMIR_DEFAULT, &pdata->regs->tmtmir);
+}
+
+static void qoriq_tmu_enable(struct udevice *dev)
+{
+ struct qoriq_tmu_plat *pdata = dev_get_plat(dev);
+ u32 reg;
+
+ reg = readl(&pdata->regs->tmr);
+ reg &= ~TMR_MODE_MONITOR;
+ writel(reg, &pdata->regs->tmr);
+
+ writel(GENMASK(QORIQ_TMU_SITES_MAX - 1, 0), &pdata->regs->tmsr);
+
+ reg |= TMR_ALPF;
+ reg |= TMR_MODE_MONITOR;
+ writel(reg, &pdata->regs->tmr);
+}
+
+static int qoriq_tmu_mmio_bind(struct udevice *dev)
+{
+ ofnode node, offset;
+ const char *name, *label;
+ struct udevice *zone;
+ int ret;
+
+ label = dev_read_string(dev, "label");
+ if (label && *label)
+ device_set_name(dev, label);
+
+ node = ofnode_path("/thermal-zones");
+ if (!ofnode_valid(node))
+ return 0;
+
+ ofnode_for_each_subnode(offset, node) {
+ name = ofnode_get_name(offset);
+ ret = device_bind_driver_to_node(dev, "qoriq_thermal",
+ name, offset, &zone);
+ if (ret) {
+ dev_err(dev, "Error binding %s: %d\n", name, ret);
+ continue;
+ }
+
+ label = ofnode_read_string(offset, "label");
+ if (label && *label)
+ device_set_name(zone, label);
+ }
+
+ return 0;
+}
+
+static int qoriq_tmu_mmio_probe(struct udevice *dev)
+{
+ struct qoriq_tmu_plat *pdata = dev_get_plat(dev);
+ int ret;
+
+ pdata->regs = dev_read_addr_ptr(dev);
+ if (!pdata->regs)
+ return -EINVAL;
+
+ qoriq_tmu_init(dev);
+ ret = qoriq_tmu_calibration(dev);
+ if (ret)
+ return ret;
+ qoriq_tmu_enable(dev);
+ return 0;
+}
+
+static const struct udevice_id qoriq_tmu_ids[] = {
+ { .compatible = "fsl,qoriq-tmu" },
+ { }
+};
+
+U_BOOT_DRIVER(qoriq_thermal_tmu) = {
+ .name = "qoriq_thermal_tmu",
+ .id = UCLASS_NOP,
+ .of_match = qoriq_tmu_ids,
+ .bind = qoriq_tmu_mmio_bind,
+ .probe = qoriq_tmu_mmio_probe,
+ .plat_auto = sizeof(struct qoriq_tmu_plat),
+};
+
+/*
+ * Zone (/thermal-zones/<...>) driver: UCLASS_THERMAL.
+ *
+ * Bound exclusively by the parent's qoriq_tmu_mmio_bind() (no
+ * of_match)
+ */
+static int qoriq_tmu_zone_probe(struct udevice *dev)
+{
+ struct qoriq_tmu_plat *pdata = dev_get_plat(dev);
+ struct qoriq_tmu_plat *parent_plat = dev_get_plat(dev->parent);
+ struct ofnode_phandle_args args;
+ int ret;
+
+ if (!parent_plat || !parent_plat->regs)
+ return -ENODEV;
+ pdata->regs = parent_plat->regs;
+
+ ret = dev_read_phandle_with_args(dev, "thermal-sensors",
+ "#thermal-sensor-cells",
+ 0, 0, &args);
+ if (ret)
+ return ret;
+
+ if (!ofnode_equal(args.node, dev_ofnode(dev->parent)))
+ return -EFAULT;
+
+ pdata->id = args.args_count >= 1 ? args.args[0] : 0;
+ if (pdata->id < 0 || pdata->id >= QORIQ_TMU_SITES_MAX)
+ return -EINVAL;
+
+ return 0;
+}
+
+U_BOOT_DRIVER(qoriq_thermal) = {
+ .name = "qoriq_thermal",
+ .id = UCLASS_THERMAL,
+ .probe = qoriq_tmu_zone_probe,
+ .ops = &qoriq_tmu_ops,
+ .plat_auto = sizeof(struct qoriq_tmu_plat),
+};
--
2.43.0
base-commit: bfe90a308a94caa9d855440683521ff04122ae2a
branch: for-upstream/thermal-qoriq
More information about the U-Boot
mailing list