[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