* [PATCH v7 0/3] thermal: imx91: Add support for i.MX91 thermal monitoring unit
@ 2025-10-20 19:00 Frank Li
2025-10-20 19:00 ` [PATCH v7 1/3] dt-bindings: thermal: fsl,imx91-tmu: add bindings for NXP i.MX91 thermal module Frank Li
` (2 more replies)
0 siblings, 3 replies; 10+ messages in thread
From: Frank Li @ 2025-10-20 19:00 UTC (permalink / raw)
To: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Pengfei Li,
Marco Felsch
Cc: linux-pm, devicetree, imx, linux-arm-kernel, linux-kernel, ye.li,
joy.zou, Frank Li, Krzysztof Kozlowski, Peng Fan
- Add binding doc
- Add imx91 thermal driver
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
Changes in v7:
- Add dts change
- rework irq handle (see change logs in patch2)
- Link to v6: https://lore.kernel.org/r/20250219-imx91tmu-v6-0-7af2281bcada@nxp.com
Changes in v6:
- use set_trips
- remove mutex
- Link to v5: https://lore.kernel.org/r/20250218-imx91tmu-v5-0-76c0b27673e6@nxp.com
Changes in v5:
- Add irq support.
- change to period mode.
- Link to v4: https://lore.kernel.org/r/20241216-imx91tmu-v4-0-75caef7481b8@nxp.com
Changes in v4:
- Add Kryz's review tag for binding
- Add Marco's review tag for driver
- Use devm_add_action()
- Move pm_runtim_put before thermal_of_zone_register()
- Link to v3: https://lore.kernel.org/r/20241212-imx91tmu-v3-0-85e756b29437@nxp.com
Changes in v3:
- add ref thermal-sensor
- restrict #thermal-sensor-cells to 0 only
- Change to unevaluatedProperties
- add IMX91_TMU_ prefix for register define
- remove unused register define
- fix missed pm_runtime_put() at error path in imx91_tmu_get_temp()
- use dev variable in probe function
- use pm_runtime_set_active() in probe
- move START to imx91_tmu_get_temp()
- use DEFINE_RUNTIME_DEV_PM_OPS()
- keep set reset value because there are not sw "reset" bit in controller,
uboot may change and enable tmu.
- Link to v2: https://lore.kernel.org/r/20241210-imx91tmu-v2-0-5032aad4d88e@nxp.com
Changes in v2:
- use low case for hexvalue
- combine struct imx91_tmu and tmu_sensor
- simplify imx91_tmu_start() and imx91_tmu_enable()
- use s16 for imx91_tmu_get_temp(), which may negative value
- use reverse christmas tree style
- use run time pm
- use oneshot to sample temp
- register thermal zone after hardware init
- Link to v1: https://lore.kernel.org/r/20241209-imx91tmu-v1-0-7859c5387f31@nxp.com
---
Frank Li (1):
arm64: dts: imx91: Add thermal-sensor and thermal-zone support
Pengfei Li (2):
dt-bindings: thermal: fsl,imx91-tmu: add bindings for NXP i.MX91 thermal module
thermal: imx91: Add support for i.MX91 thermal monitoring unit
.../devicetree/bindings/thermal/fsl,imx91-tmu.yaml | 87 +++++
arch/arm64/boot/dts/freescale/imx91.dtsi | 58 ++++
drivers/thermal/Kconfig | 10 +
drivers/thermal/Makefile | 1 +
drivers/thermal/imx91_thermal.c | 384 +++++++++++++++++++++
5 files changed, 540 insertions(+)
---
base-commit: 2433b84761658ef123ae683508bc461b07c5b0f0
change-id: 20241209-imx91tmu-af2a7c042d8d
Best regards,
--
Frank Li <Frank.Li@nxp.com>
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v7 1/3] dt-bindings: thermal: fsl,imx91-tmu: add bindings for NXP i.MX91 thermal module 2025-10-20 19:00 [PATCH v7 0/3] thermal: imx91: Add support for i.MX91 thermal monitoring unit Frank Li @ 2025-10-20 19:00 ` Frank Li 2025-10-20 19:00 ` [PATCH v7 2/3] thermal: imx91: Add support for i.MX91 thermal monitoring unit Frank Li 2025-10-20 19:00 ` [PATCH v7 3/3] arm64: dts: imx91: Add thermal-sensor and thermal-zone support Frank Li 2 siblings, 0 replies; 10+ messages in thread From: Frank Li @ 2025-10-20 19:00 UTC (permalink / raw) To: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Pengfei Li, Marco Felsch Cc: linux-pm, devicetree, imx, linux-arm-kernel, linux-kernel, ye.li, joy.zou, Frank Li, Krzysztof Kozlowski From: Pengfei Li <pengfei.li_1@nxp.com> Add bindings documentation for i.MX91 thermal modules. Signed-off-by: Pengfei Li <pengfei.li_1@nxp.com> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org> Signed-off-by: Frank Li <Frank.Li@nxp.com> --- Change from v5 to v6 - add interrupt-names Krzysztof Kozlowski: Compared with acked version, add interrupts and interrupt-names property. If need drop review tag, let me know. change from v3 to v4 - add interrupts propperty Change from v2 to v3 - add ref thermal-sensor - restrict #thermal-sensor-cells to 0 only - Change to unevaluatedProperties --- .../devicetree/bindings/thermal/fsl,imx91-tmu.yaml | 87 ++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/Documentation/devicetree/bindings/thermal/fsl,imx91-tmu.yaml b/Documentation/devicetree/bindings/thermal/fsl,imx91-tmu.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7fd1a86d728702d1274dd8a73109689dd11f61b6 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/fsl,imx91-tmu.yaml @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/fsl,imx91-tmu.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP i.MX91 Thermal + +maintainers: + - Pengfei Li <pengfei.li_1@nxp.com> + +description: + i.MX91 features a new temperature sensor. It includes programmable + temperature threshold comparators for both normal and privileged + accesses and allows a programmable measurement frequency for the + Periodic One-Shot Measurement mode. Additionally, it provides + status registers for indicating the end of measurement and threshold + violation events. + +properties: + compatible: + items: + - const: fsl,imx91-tmu + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + interrupts: + items: + - description: Comparator 1 irq + - description: Comparator 2 irq + - description: Data ready irq + + interrupt-names: + items: + - const: thr1 + - const: thr2 + - const: ready + + nvmem-cells: + items: + - description: Phandle to the trim control 1 provided by ocotp + - description: Phandle to the trim control 2 provided by ocotp + + nvmem-cell-names: + items: + - const: trim1 + - const: trim2 + + "#thermal-sensor-cells": + const: 0 + +required: + - compatible + - reg + - clocks + - interrupts + - interrupt-names + +allOf: + - $ref: thermal-sensor.yaml + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/clock/imx93-clock.h> + + thermal-sensor@44482000 { + compatible = "fsl,imx91-tmu"; + reg = <0x44482000 0x1000>; + #thermal-sensor-cells = <0>; + clocks = <&clk IMX93_CLK_TMC_GATE>; + interrupt-parent = <&gic>; + interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "thr1", "thr2", "ready"; + nvmem-cells = <&tmu_trim1>, <&tmu_trim2>; + nvmem-cell-names = "trim1", "trim2"; + }; + +... -- 2.34.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 2/3] thermal: imx91: Add support for i.MX91 thermal monitoring unit 2025-10-20 19:00 [PATCH v7 0/3] thermal: imx91: Add support for i.MX91 thermal monitoring unit Frank Li 2025-10-20 19:00 ` [PATCH v7 1/3] dt-bindings: thermal: fsl,imx91-tmu: add bindings for NXP i.MX91 thermal module Frank Li @ 2025-10-20 19:00 ` Frank Li 2026-01-07 15:22 ` Daniel Lezcano 2025-10-20 19:00 ` [PATCH v7 3/3] arm64: dts: imx91: Add thermal-sensor and thermal-zone support Frank Li 2 siblings, 1 reply; 10+ messages in thread From: Frank Li @ 2025-10-20 19:00 UTC (permalink / raw) To: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Pengfei Li, Marco Felsch Cc: linux-pm, devicetree, imx, linux-arm-kernel, linux-kernel, ye.li, joy.zou, Frank Li, Peng Fan From: Pengfei Li <pengfei.li_1@nxp.com> Introduce support for the i.MX91 thermal monitoring unit, which features a single sensor for the CPU. The register layout differs from other chips, necessitating the creation of a dedicated file for this. This sensor provides a resolution of 1/64°C (6-bit fraction). For actual accuracy, refer to the datasheet, as it varies depending on the chip grade. Provide an interrupt for end of measurement and threshold violation and Contain temperature threshold comparators, in normal and secure address space, with direction and threshold programmability. Datasheet Link: https://www.nxp.com/docs/en/data-sheet/IMX91CEC.pdf Signed-off-by: Pengfei Li <pengfei.li_1@nxp.com> Signed-off-by: Peng Fan <peng.fan@nxp.com> Signed-off-by: Frank Li <Frank.Li@nxp.com> --- Change in v7 - remove enable field because framework already check mode before call get_temp(); - only call runtime_pm_get(put) in set_mode function - remove enable irq at get_temp. - Enable GE compare irq at set_trip() call back - Config GE irq at set_mode() when start measure to avoid issue unnecessary irq when clear Compare mask (generate one LE irq). Change from v5 to v6 - remove Macro's review tag - remove mutex lock - use set_trips callback Change from v4 to v5 - add irq support - use period mode - Marco, if need drop review tag, let me know Change from v3 to v4 - Add Macro's review tag - Use devm_add_action() - Move pm_runtim_put before thermal_of_zone_register() change from v2 to v3 - add IMX91_TMU_ prefix for register define - remove unused register define - fix missed pm_runtime_put() at error path in imx91_tmu_get_temp() - use dev variable in probe function - use pm_runtime_set_active() in probe - move START to imx91_tmu_get_temp() - use DEFINE_RUNTIME_DEV_PM_OPS() - keep set reset value because there are not sw "reset" bit in controller, uboot may change and enable tmu. change from v1 to v2 - use low case for hexvalue - combine struct imx91_tmu and tmu_sensor - simplify imx91_tmu_start() and imx91_tmu_enable() - use s16 for imx91_tmu_get_temp(), which may negative value - use reverse christmas tree style - use run time pm - use oneshot to sample temp - register thermal zone after hardware init --- drivers/thermal/Kconfig | 10 ++ drivers/thermal/Makefile | 1 + drivers/thermal/imx91_thermal.c | 384 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 395 insertions(+) diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index a09c188b9ad11377afe232d89c60504eb7000417..b10080d618604ddd90295bff973e337ae0509059 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -296,6 +296,16 @@ config IMX8MM_THERMAL cpufreq is used as the cooling device to throttle CPUs when the passive trip is crossed. +config IMX91_THERMAL + tristate "Temperature sensor driver for NXP i.MX91 SoC" + depends on ARCH_MXC || COMPILE_TEST + depends on OF + help + Include one sensor and six comparators. Each of them compares the + temperature value (from the sensor) against the programmable + threshold values. The direction of the comparison is configurable + (greater / lesser than). + config K3_THERMAL tristate "Texas Instruments K3 thermal support" depends on ARCH_K3 || COMPILE_TEST diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index d7718978db245faffba98ff95a07c7bcbc776fd2..bb21e7ea7fc6b70aa84e5fed7cfdc7096e3fb1f7 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o obj-$(CONFIG_IMX_SC_THERMAL) += imx_sc_thermal.o obj-$(CONFIG_IMX8MM_THERMAL) += imx8mm_thermal.o +obj-$(CONFIG_IMX91_THERMAL) += imx91_thermal.o obj-$(CONFIG_MAX77620_THERMAL) += max77620_thermal.o obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o obj-$(CONFIG_DA9062_THERMAL) += da9062-thermal.o diff --git a/drivers/thermal/imx91_thermal.c b/drivers/thermal/imx91_thermal.c new file mode 100644 index 0000000000000000000000000000000000000000..9b20be03d6dec18553967548d0ca31d1c1fb387c --- /dev/null +++ b/drivers/thermal/imx91_thermal.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 NXP. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <linux/nvmem-consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/thermal.h> +#include <linux/units.h> + +#define REG_SET 0x4 +#define REG_CLR 0x8 +#define REG_TOG 0xc + +#define IMX91_TMU_CTRL0 0x0 +#define IMX91_TMU_CTRL0_THR1_IE BIT(9) +#define IMX91_TMU_CTRL0_THR1_MASK GENMASK(3, 2) +#define IMX91_TMU_CTRL0_CLR_FLT1 BIT(21) + +#define IMX91_TMU_THR_MODE_LE 0 +#define IMX91_TMU_THR_MODE_GE 1 + +#define IMX91_TMU_STAT0 0x10 +#define IMX91_TMU_STAT0_THR1_IF BIT(9) +#define IMX91_TMU_STAT0_THR1_STAT BIT(13) +#define IMX91_TMU_STAT0_DRDY0_IF_MASK BIT(16) + +#define IMX91_TMU_DATA0 0x20 + +#define IMX91_TMU_CTRL1 0x200 +#define IMX91_TMU_CTRL1_EN BIT(31) +#define IMX91_TMU_CTRL1_START BIT(30) +#define IMX91_TMU_CTRL1_STOP BIT(29) +#define IMX91_TMU_CTRL1_RES_MASK GENMASK(19, 18) +#define IMX91_TMU_CTRL1_MEAS_MODE_MASK GENMASK(25, 24) +#define IMX91_TMU_CTRL1_MEAS_MODE_SINGLE 0 +#define IMX91_TMU_CTRL1_MEAS_MODE_CONTINUES 1 +#define IMX91_TMU_CTRL1_MEAS_MODE_PERIODIC 2 + +#define IMX91_TMU_THR_CTRL01 0x30 +#define IMX91_TMU_THR_CTRL01_THR1_MASK GENMASK(31, 16) + +#define IMX91_TMU_REF_DIV 0x280 +#define IMX91_TMU_DIV_EN BIT(31) +#define IMX91_TMU_DIV_MASK GENMASK(23, 16) +#define IMX91_TMU_DIV_MAX 255 + +#define IMX91_TMU_PUD_ST_CTRL 0x2b0 +#define IMX91_TMU_PUDL_MASK GENMASK(23, 16) + +#define IMX91_TMU_TRIM1 0x2e0 +#define IMX91_TMU_TRIM2 0x2f0 + +#define IMX91_TMU_TEMP_LOW_LIMIT -40000 +#define IMX91_TMU_TEMP_HIGH_LIMIT 125000 + +#define IMX91_TMU_DEFAULT_TRIM1_CONFIG 0xb561bc2d +#define IMX91_TMU_DEFAULT_TRIM2_CONFIG 0x65d4 + +#define IMX91_TMU_PERIOD_CTRL 0x270 +#define IMX91_TMU_PERIOD_CTRL_MEAS_MASK GENMASK(23, 0) + +#define IMX91_TMP_FRAC 64 + +struct imx91_tmu { + void __iomem *base; + struct clk *clk; + struct device *dev; + struct thermal_zone_device *tzd; +}; + +static void imx91_tmu_start(struct imx91_tmu *tmu, bool start) +{ + u32 val = start ? IMX91_TMU_CTRL1_START : IMX91_TMU_CTRL1_STOP; + + writel_relaxed(val, tmu->base + IMX91_TMU_CTRL1 + REG_SET); +} + +static void imx91_tmu_enable(struct imx91_tmu *tmu, bool enable) +{ + u32 reg = IMX91_TMU_CTRL1; + + reg += enable ? REG_SET : REG_CLR; + + writel_relaxed(IMX91_TMU_CTRL1_EN, tmu->base + reg); +} + +static int imx91_tmu_to_mcelsius(int x) +{ + return x * MILLIDEGREE_PER_DEGREE / IMX91_TMP_FRAC; +} + +static int imx91_tmu_from_mcelsius(int x) +{ + return x * IMX91_TMP_FRAC / MILLIDEGREE_PER_DEGREE; +} + +static int imx91_tmu_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct imx91_tmu *tmu = thermal_zone_device_priv(tz); + s16 data; + + /* DATA0 is 16bit signed number */ + data = readw_relaxed(tmu->base + IMX91_TMU_DATA0); + *temp = imx91_tmu_to_mcelsius(data); + + return 0; +} + +static int imx91_tmu_set_trips(struct thermal_zone_device *tz, int low, int high) +{ + struct imx91_tmu *tmu = thermal_zone_device_priv(tz); + int val; + + if (high >= IMX91_TMU_TEMP_HIGH_LIMIT) + return -EINVAL; + + writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_CLR); + + /* Comparator1 for temperature threshold */ + writel_relaxed(IMX91_TMU_THR_CTRL01_THR1_MASK, tmu->base + IMX91_TMU_THR_CTRL01 + REG_CLR); + val = FIELD_PREP(IMX91_TMU_THR_CTRL01_THR1_MASK, imx91_tmu_from_mcelsius(high)); + + writel_relaxed(val, tmu->base + IMX91_TMU_THR_CTRL01 + REG_SET); + + writel_relaxed(IMX91_TMU_STAT0_THR1_IF, tmu->base + IMX91_TMU_STAT0 + REG_CLR); + + writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_SET); + + return 0; +} + +static int imx91_init_from_nvmem_cells(struct imx91_tmu *tmu) +{ + struct device *dev = tmu->dev; + u32 trim1, trim2; + int ret; + + ret = nvmem_cell_read_u32(dev, "trim1", &trim1); + if (ret) + return ret; + + ret = nvmem_cell_read_u32(dev, "trim2", &trim2); + if (ret) + return ret; + + if (trim1 == 0 || trim2 == 0) + return -EINVAL; + + writel_relaxed(trim1, tmu->base + IMX91_TMU_TRIM1); + writel_relaxed(trim2, tmu->base + IMX91_TMU_TRIM2); + + return 0; +} + +static void imx91_tmu_action_remove(void *data) +{ + struct imx91_tmu *tmu = data; + + /* disable tmu */ + imx91_tmu_enable(tmu, false); +} + +static irqreturn_t imx91_tmu_alarm_irq(int irq, void *data) +{ + struct imx91_tmu *tmu = data; + u32 val; + + val = readl_relaxed(tmu->base + IMX91_TMU_STAT0); + + /* Check if comparison interrupt occurred */ + if (val & IMX91_TMU_STAT0_THR1_IF) { + /* Clear irq flag and disable interrupt until reconfigured */ + writel(IMX91_TMU_STAT0_THR1_IF, tmu->base + IMX91_TMU_STAT0 + REG_CLR); + writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_CLR); + + return IRQ_WAKE_THREAD; + } + + return IRQ_NONE; +} + +static irqreturn_t imx91_tmu_alarm_irq_thread(int irq, void *data) +{ + struct imx91_tmu *tmu = data; + + thermal_zone_device_update(tmu->tzd, THERMAL_EVENT_UNSPECIFIED); + + return IRQ_HANDLED; +} + +static int imx91_tmu_change_mode(struct thermal_zone_device *tz, enum thermal_device_mode mode) +{ + struct imx91_tmu *tmu = thermal_zone_device_priv(tz); + int ret; + + if (mode == THERMAL_DEVICE_ENABLED) { + ret = pm_runtime_get(tmu->dev); + if (ret < 0) + return ret; + + writel_relaxed(IMX91_TMU_CTRL0_THR1_IE | IMX91_TMU_CTRL0_THR1_MASK, + tmu->base + IMX91_TMU_CTRL0 + REG_CLR); + + writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL0_THR1_MASK, IMX91_TMU_THR_MODE_GE), + tmu->base + IMX91_TMU_CTRL0 + REG_SET); + imx91_tmu_start(tmu, true); + } else { + writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_CLR); + imx91_tmu_start(tmu, false); + pm_runtime_put(tmu->dev); + } + + return 0; +} + +static struct thermal_zone_device_ops tmu_tz_ops = { + .get_temp = imx91_tmu_get_temp, + .change_mode = imx91_tmu_change_mode, + .set_trips = imx91_tmu_set_trips, +}; + +static int imx91_tmu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx91_tmu *tmu; + unsigned long rate; + int irq, ret; + u32 div; + + tmu = devm_kzalloc(dev, sizeof(struct imx91_tmu), GFP_KERNEL); + if (!tmu) + return -ENOMEM; + + tmu->dev = dev; + + tmu->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(tmu->base)) + return dev_err_probe(dev, PTR_ERR(tmu->base), "failed to get io resource"); + + tmu->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(tmu->clk)) + return dev_err_probe(dev, PTR_ERR(tmu->clk), "failed to get tmu clock\n"); + + platform_set_drvdata(pdev, tmu); + + /* disable the monitor during initialization */ + imx91_tmu_enable(tmu, false); + imx91_tmu_start(tmu, false); + + ret = imx91_init_from_nvmem_cells(tmu); + if (ret) { + dev_warn(dev, "can't get trim value, use default settings\n"); + + writel_relaxed(IMX91_TMU_DEFAULT_TRIM1_CONFIG, tmu->base + IMX91_TMU_TRIM1); + writel_relaxed(IMX91_TMU_DEFAULT_TRIM2_CONFIG, tmu->base + IMX91_TMU_TRIM2); + } + + /* The typical conv clk is 4MHz, the output freq is 'rate / (div + 1)' */ + rate = clk_get_rate(tmu->clk); + div = (rate / (4 * HZ_PER_MHZ)) - 1; + if (div > IMX91_TMU_DIV_MAX) + return dev_err_probe(dev, -EINVAL, "clock divider exceed hardware limitation"); + + /* Set divider value and enable divider */ + writel_relaxed(IMX91_TMU_DIV_EN | FIELD_PREP(IMX91_TMU_DIV_MASK, div), + tmu->base + IMX91_TMU_REF_DIV); + + /* Set max power up delay: 'Tpud(ms) = 0xFF * 1000 / 4000000' */ + writel_relaxed(FIELD_PREP(IMX91_TMU_PUDL_MASK, 100U), tmu->base + IMX91_TMU_PUD_ST_CTRL); + + /* + * Set resolution mode + * 00b - Conversion time = 0.59325 ms + * 01b - Conversion time = 1.10525 ms + * 10b - Conversion time = 2.12925 ms + * 11b - Conversion time = 4.17725 ms + */ + writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL1_RES_MASK, 0x3), + tmu->base + IMX91_TMU_CTRL1 + REG_CLR); + writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL1_RES_MASK, 0x1), + tmu->base + IMX91_TMU_CTRL1 + REG_SET); + + writel_relaxed(IMX91_TMU_CTRL1_MEAS_MODE_MASK, tmu->base + IMX91_TMU_CTRL1 + REG_CLR); + writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL1_MEAS_MODE_MASK, + IMX91_TMU_CTRL1_MEAS_MODE_PERIODIC), + tmu->base + IMX91_TMU_CTRL1 + REG_SET); + + /* + * Set Periodic Measurement Frequency to 25Hz: + * tMEAS_FREQ = tCONV_CLK * PERIOD_CTRL[MEAS_FREQ] + */ + writel_relaxed(FIELD_PREP(IMX91_TMU_PERIOD_CTRL_MEAS_MASK, 4 * HZ_PER_MHZ / 25), + tmu->base + IMX91_TMU_PERIOD_CTRL); + + imx91_tmu_enable(tmu, true); + ret = devm_add_action(dev, imx91_tmu_action_remove, tmu); + if (ret) + return dev_err_probe(dev, ret, "Failure to add action imx91_tmu_action_remove()\n"); + + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + tmu->tzd = devm_thermal_of_zone_register(dev, 0, tmu, &tmu_tz_ops); + if (IS_ERR(tmu->tzd)) + return dev_err_probe(dev, PTR_ERR(tmu->tzd), + "failed to register thermal zone sensor\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, irq, imx91_tmu_alarm_irq, + imx91_tmu_alarm_irq_thread, + IRQF_ONESHOT, "imx91_thermal", tmu); + + if (ret < 0) + return dev_err_probe(dev, ret, "failed to request alarm irq\n"); + + pm_runtime_put(dev); + + return 0; +} + +static int imx91_tmu_runtime_suspend(struct device *dev) +{ + struct imx91_tmu *tmu = dev_get_drvdata(dev); + + /* disable tmu */ + imx91_tmu_enable(tmu, false); + + clk_disable_unprepare(tmu->clk); + + return 0; +} + +static int imx91_tmu_runtime_resume(struct device *dev) +{ + struct imx91_tmu *tmu = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(tmu->clk); + if (ret) + return ret; + + imx91_tmu_enable(tmu, true); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(imx91_tmu_pm_ops, imx91_tmu_runtime_suspend, + imx91_tmu_runtime_resume, NULL); + +static const struct of_device_id imx91_tmu_table[] = { + { .compatible = "fsl,imx91-tmu", }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx91_tmu_table); + +static struct platform_driver imx91_tmu = { + .driver = { + .name = "imx91_thermal", + .pm = pm_ptr(&imx91_tmu_pm_ops), + .of_match_table = imx91_tmu_table, + }, + .probe = imx91_tmu_probe, +}; +module_platform_driver(imx91_tmu); + +MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>"); +MODULE_DESCRIPTION("i.MX91 Thermal Monitor Unit driver"); +MODULE_LICENSE("GPL"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v7 2/3] thermal: imx91: Add support for i.MX91 thermal monitoring unit 2025-10-20 19:00 ` [PATCH v7 2/3] thermal: imx91: Add support for i.MX91 thermal monitoring unit Frank Li @ 2026-01-07 15:22 ` Daniel Lezcano 2026-01-08 16:24 ` Frank Li 0 siblings, 1 reply; 10+ messages in thread From: Daniel Lezcano @ 2026-01-07 15:22 UTC (permalink / raw) To: Frank Li, Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Pengfei Li, Marco Felsch Cc: linux-pm, devicetree, imx, linux-arm-kernel, linux-kernel, ye.li, joy.zou, Peng Fan On 10/20/25 21:00, Frank Li wrote: > From: Pengfei Li <pengfei.li_1@nxp.com> > > Introduce support for the i.MX91 thermal monitoring unit, which features a > single sensor for the CPU. The register layout differs from other chips, > necessitating the creation of a dedicated file for this. > > This sensor provides a resolution of 1/64°C (6-bit fraction). For actual > accuracy, refer to the datasheet, as it varies depending on the chip grade. > Provide an interrupt for end of measurement and threshold violation and > Contain temperature threshold comparators, in normal and secure address > space, with direction and threshold programmability. > > Datasheet Link: https://www.nxp.com/docs/en/data-sheet/IMX91CEC.pdf > > Signed-off-by: Pengfei Li <pengfei.li_1@nxp.com> > Signed-off-by: Peng Fan <peng.fan@nxp.com> > Signed-off-by: Frank Li <Frank.Li@nxp.com> > --- > Change in v7 > - remove enable field because framework already check mode before call > get_temp(); > - only call runtime_pm_get(put) in set_mode function > - remove enable irq at get_temp. > - Enable GE compare irq at set_trip() call back > - Config GE irq at set_mode() when start measure to avoid issue unnecessary > irq when clear Compare mask (generate one LE irq). > > Change from v5 to v6 > - remove Macro's review tag > - remove mutex lock > - use set_trips callback > > Change from v4 to v5 > - add irq support > - use period mode > - Marco, if need drop review tag, let me know > > Change from v3 to v4 > - Add Macro's review tag > - Use devm_add_action() > - Move pm_runtim_put before thermal_of_zone_register() > > change from v2 to v3 > - add IMX91_TMU_ prefix for register define > - remove unused register define > - fix missed pm_runtime_put() at error path in imx91_tmu_get_temp() > - use dev variable in probe function > - use pm_runtime_set_active() in probe > - move START to imx91_tmu_get_temp() > - use DEFINE_RUNTIME_DEV_PM_OPS() > - keep set reset value because there are not sw "reset" bit in controller, > uboot may change and enable tmu. > > change from v1 to v2 > - use low case for hexvalue > - combine struct imx91_tmu and tmu_sensor > - simplify imx91_tmu_start() and imx91_tmu_enable() > - use s16 for imx91_tmu_get_temp(), which may negative value > - use reverse christmas tree style > - use run time pm > - use oneshot to sample temp > - register thermal zone after hardware init > --- > drivers/thermal/Kconfig | 10 ++ > drivers/thermal/Makefile | 1 + > drivers/thermal/imx91_thermal.c | 384 ++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 395 insertions(+) > > diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig > index a09c188b9ad11377afe232d89c60504eb7000417..b10080d618604ddd90295bff973e337ae0509059 100644 > --- a/drivers/thermal/Kconfig > +++ b/drivers/thermal/Kconfig > @@ -296,6 +296,16 @@ config IMX8MM_THERMAL > cpufreq is used as the cooling device to throttle CPUs when the passive > trip is crossed. > > +config IMX91_THERMAL > + tristate "Temperature sensor driver for NXP i.MX91 SoC" > + depends on ARCH_MXC || COMPILE_TEST > + depends on OF > + help > + Include one sensor and six comparators. Each of them compares the > + temperature value (from the sensor) against the programmable > + threshold values. The direction of the comparison is configurable > + (greater / lesser than). > + > config K3_THERMAL > tristate "Texas Instruments K3 thermal support" > depends on ARCH_K3 || COMPILE_TEST > diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile > index d7718978db245faffba98ff95a07c7bcbc776fd2..bb21e7ea7fc6b70aa84e5fed7cfdc7096e3fb1f7 100644 > --- a/drivers/thermal/Makefile > +++ b/drivers/thermal/Makefile > @@ -51,6 +51,7 @@ obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o > obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o > obj-$(CONFIG_IMX_SC_THERMAL) += imx_sc_thermal.o > obj-$(CONFIG_IMX8MM_THERMAL) += imx8mm_thermal.o > +obj-$(CONFIG_IMX91_THERMAL) += imx91_thermal.o > obj-$(CONFIG_MAX77620_THERMAL) += max77620_thermal.o > obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o > obj-$(CONFIG_DA9062_THERMAL) += da9062-thermal.o > diff --git a/drivers/thermal/imx91_thermal.c b/drivers/thermal/imx91_thermal.c > new file mode 100644 > index 0000000000000000000000000000000000000000..9b20be03d6dec18553967548d0ca31d1c1fb387c > --- /dev/null > +++ b/drivers/thermal/imx91_thermal.c > @@ -0,0 +1,384 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright 2025 NXP. > + */ > + > +#include <linux/bitfield.h> > +#include <linux/clk.h> > +#include <linux/err.h> > +#include <linux/interrupt.h> > +#include <linux/iopoll.h> > +#include <linux/nvmem-consumer.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/of_device.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > +#include <linux/thermal.h> > +#include <linux/units.h> > + > +#define REG_SET 0x4 > +#define REG_CLR 0x8 > +#define REG_TOG 0xc > + > +#define IMX91_TMU_CTRL0 0x0 > +#define IMX91_TMU_CTRL0_THR1_IE BIT(9) > +#define IMX91_TMU_CTRL0_THR1_MASK GENMASK(3, 2) > +#define IMX91_TMU_CTRL0_CLR_FLT1 BIT(21) > + > +#define IMX91_TMU_THR_MODE_LE 0 > +#define IMX91_TMU_THR_MODE_GE 1 > + > +#define IMX91_TMU_STAT0 0x10 > +#define IMX91_TMU_STAT0_THR1_IF BIT(9) > +#define IMX91_TMU_STAT0_THR1_STAT BIT(13) > +#define IMX91_TMU_STAT0_DRDY0_IF_MASK BIT(16) > + > +#define IMX91_TMU_DATA0 0x20 > + > +#define IMX91_TMU_CTRL1 0x200 > +#define IMX91_TMU_CTRL1_EN BIT(31) > +#define IMX91_TMU_CTRL1_START BIT(30) > +#define IMX91_TMU_CTRL1_STOP BIT(29) > +#define IMX91_TMU_CTRL1_RES_MASK GENMASK(19, 18) > +#define IMX91_TMU_CTRL1_MEAS_MODE_MASK GENMASK(25, 24) > +#define IMX91_TMU_CTRL1_MEAS_MODE_SINGLE 0 > +#define IMX91_TMU_CTRL1_MEAS_MODE_CONTINUES 1 > +#define IMX91_TMU_CTRL1_MEAS_MODE_PERIODIC 2 > + > +#define IMX91_TMU_THR_CTRL01 0x30 > +#define IMX91_TMU_THR_CTRL01_THR1_MASK GENMASK(31, 16) > + > +#define IMX91_TMU_REF_DIV 0x280 > +#define IMX91_TMU_DIV_EN BIT(31) > +#define IMX91_TMU_DIV_MASK GENMASK(23, 16) > +#define IMX91_TMU_DIV_MAX 255 > + > +#define IMX91_TMU_PUD_ST_CTRL 0x2b0 > +#define IMX91_TMU_PUDL_MASK GENMASK(23, 16) > + > +#define IMX91_TMU_TRIM1 0x2e0 > +#define IMX91_TMU_TRIM2 0x2f0 > + > +#define IMX91_TMU_TEMP_LOW_LIMIT -40000 > +#define IMX91_TMU_TEMP_HIGH_LIMIT 125000 > + > +#define IMX91_TMU_DEFAULT_TRIM1_CONFIG 0xb561bc2d > +#define IMX91_TMU_DEFAULT_TRIM2_CONFIG 0x65d4 > + > +#define IMX91_TMU_PERIOD_CTRL 0x270 > +#define IMX91_TMU_PERIOD_CTRL_MEAS_MASK GENMASK(23, 0) > + > +#define IMX91_TMP_FRAC 64 > + > +struct imx91_tmu { > + void __iomem *base; > + struct clk *clk; > + struct device *dev; > + struct thermal_zone_device *tzd; > +}; > + > +static void imx91_tmu_start(struct imx91_tmu *tmu, bool start) > +{ > + u32 val = start ? IMX91_TMU_CTRL1_START : IMX91_TMU_CTRL1_STOP; > + > + writel_relaxed(val, tmu->base + IMX91_TMU_CTRL1 + REG_SET); Why do you need the 'relaxed' version of the readl / writel ? > +} > + > +static void imx91_tmu_enable(struct imx91_tmu *tmu, bool enable) > +{ > + u32 reg = IMX91_TMU_CTRL1; > + > + reg += enable ? REG_SET : REG_CLR; > + > + writel_relaxed(IMX91_TMU_CTRL1_EN, tmu->base + reg); > +} Can you explain the register layout ? It is unclear how it starts / stops / is enabled ... For instance doing reg += enable ? REG_SET : REG_CLR is confusing > +static int imx91_tmu_to_mcelsius(int x) (short x) > +{ > + return x * MILLIDEGREE_PER_DEGREE / IMX91_TMP_FRAC; > +} > + > +static int imx91_tmu_from_mcelsius(int x) > +{ > + return x * IMX91_TMP_FRAC / MILLIDEGREE_PER_DEGREE; > +}> +static int imx91_tmu_get_temp(struct thermal_zone_device *tz, int *temp) > +{ > + struct imx91_tmu *tmu = thermal_zone_device_priv(tz); > + s16 data; > + > + /* DATA0 is 16bit signed number */ > + data = readw_relaxed(tmu->base + IMX91_TMU_DATA0); Does the routine works for a negative value ? > + *temp = imx91_tmu_to_mcelsius(data); > + > + return 0; > +} > + > +static int imx91_tmu_set_trips(struct thermal_zone_device *tz, int low, int high) > +{ > + struct imx91_tmu *tmu = thermal_zone_device_priv(tz); > + int val; > + > + if (high >= IMX91_TMU_TEMP_HIGH_LIMIT) > + return -EINVAL; > + > + writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_CLR); > + > + /* Comparator1 for temperature threshold */ > + writel_relaxed(IMX91_TMU_THR_CTRL01_THR1_MASK, tmu->base + IMX91_TMU_THR_CTRL01 + REG_CLR); > + val = FIELD_PREP(IMX91_TMU_THR_CTRL01_THR1_MASK, imx91_tmu_from_mcelsius(high)); > + > + writel_relaxed(val, tmu->base + IMX91_TMU_THR_CTRL01 + REG_SET); > + > + writel_relaxed(IMX91_TMU_STAT0_THR1_IF, tmu->base + IMX91_TMU_STAT0 + REG_CLR); > + > + writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_SET); > + IMO, it is a good practice to wrap those calls into single line functions with a self-explanatory name > + return 0; > +} > + > +static int imx91_init_from_nvmem_cells(struct imx91_tmu *tmu) > +{ > + struct device *dev = tmu->dev; > + u32 trim1, trim2; > + int ret; > + > + ret = nvmem_cell_read_u32(dev, "trim1", &trim1); > + if (ret) > + return ret; > + > + ret = nvmem_cell_read_u32(dev, "trim2", &trim2); > + if (ret) > + return ret; > + > + if (trim1 == 0 || trim2 == 0) > + return -EINVAL; > + > + writel_relaxed(trim1, tmu->base + IMX91_TMU_TRIM1); > + writel_relaxed(trim2, tmu->base + IMX91_TMU_TRIM2); > + > + return 0; > +} > + > +static void imx91_tmu_action_remove(void *data) > +{ > + struct imx91_tmu *tmu = data; > + > + /* disable tmu */ > + imx91_tmu_enable(tmu, false); > +} > + > +static irqreturn_t imx91_tmu_alarm_irq(int irq, void *data) > +{ > + struct imx91_tmu *tmu = data; > + u32 val; > + > + val = readl_relaxed(tmu->base + IMX91_TMU_STAT0); > + > + /* Check if comparison interrupt occurred */ > + if (val & IMX91_TMU_STAT0_THR1_IF) { > + /* Clear irq flag and disable interrupt until reconfigured */ > + writel(IMX91_TMU_STAT0_THR1_IF, tmu->base + IMX91_TMU_STAT0 + REG_CLR); > + writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_CLR); > + > + return IRQ_WAKE_THREAD; > + } > + > + return IRQ_NONE; Why is this routine needed ? Is it a shared interrupt ? > +} > + > +static irqreturn_t imx91_tmu_alarm_irq_thread(int irq, void *data) > +{ > + struct imx91_tmu *tmu = data; > + > + thermal_zone_device_update(tmu->tzd, THERMAL_EVENT_UNSPECIFIED); > + > + return IRQ_HANDLED; > +} > + > +static int imx91_tmu_change_mode(struct thermal_zone_device *tz, enum thermal_device_mode mode) > +{ > + struct imx91_tmu *tmu = thermal_zone_device_priv(tz); > + int ret; > + > + if (mode == THERMAL_DEVICE_ENABLED) { > + ret = pm_runtime_get(tmu->dev); > + if (ret < 0) > + return ret; > + > + writel_relaxed(IMX91_TMU_CTRL0_THR1_IE | IMX91_TMU_CTRL0_THR1_MASK, > + tmu->base + IMX91_TMU_CTRL0 + REG_CLR); > + > + writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL0_THR1_MASK, IMX91_TMU_THR_MODE_GE), > + tmu->base + IMX91_TMU_CTRL0 + REG_SET); > + imx91_tmu_start(tmu, true); > + } else { > + writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_CLR); > + imx91_tmu_start(tmu, false); > + pm_runtime_put(tmu->dev); > + } > + > + return 0; > +} > + > +static struct thermal_zone_device_ops tmu_tz_ops = { > + .get_temp = imx91_tmu_get_temp, > + .change_mode = imx91_tmu_change_mode, > + .set_trips = imx91_tmu_set_trips, > +}; > + > +static int imx91_tmu_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct imx91_tmu *tmu; > + unsigned long rate; > + int irq, ret; > + u32 div; > + > + tmu = devm_kzalloc(dev, sizeof(struct imx91_tmu), GFP_KERNEL); > + if (!tmu) > + return -ENOMEM; > + > + tmu->dev = dev; > + > + tmu->base = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(tmu->base)) > + return dev_err_probe(dev, PTR_ERR(tmu->base), "failed to get io resource"); > + > + tmu->clk = devm_clk_get_enabled(dev, NULL); > + if (IS_ERR(tmu->clk)) > + return dev_err_probe(dev, PTR_ERR(tmu->clk), "failed to get tmu clock\n"); > + > + platform_set_drvdata(pdev, tmu); > + > + /* disable the monitor during initialization */ > + imx91_tmu_enable(tmu, false); > + imx91_tmu_start(tmu, false); > + > + ret = imx91_init_from_nvmem_cells(tmu); > + if (ret) { > + dev_warn(dev, "can't get trim value, use default settings\n"); > + > + writel_relaxed(IMX91_TMU_DEFAULT_TRIM1_CONFIG, tmu->base + IMX91_TMU_TRIM1); > + writel_relaxed(IMX91_TMU_DEFAULT_TRIM2_CONFIG, tmu->base + IMX91_TMU_TRIM2); > + } > + > + /* The typical conv clk is 4MHz, the output freq is 'rate / (div + 1)' */ > + rate = clk_get_rate(tmu->clk); > + div = (rate / (4 * HZ_PER_MHZ)) - 1; > + if (div > IMX91_TMU_DIV_MAX) > + return dev_err_probe(dev, -EINVAL, "clock divider exceed hardware limitation"); > + > + /* Set divider value and enable divider */ > + writel_relaxed(IMX91_TMU_DIV_EN | FIELD_PREP(IMX91_TMU_DIV_MASK, div), > + tmu->base + IMX91_TMU_REF_DIV); > + > + /* Set max power up delay: 'Tpud(ms) = 0xFF * 1000 / 4000000' */ > + writel_relaxed(FIELD_PREP(IMX91_TMU_PUDL_MASK, 100U), tmu->base + IMX91_TMU_PUD_ST_CTRL); > + > + /* > + * Set resolution mode > + * 00b - Conversion time = 0.59325 ms > + * 01b - Conversion time = 1.10525 ms > + * 10b - Conversion time = 2.12925 ms > + * 11b - Conversion time = 4.17725 ms > + */ > + writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL1_RES_MASK, 0x3), > + tmu->base + IMX91_TMU_CTRL1 + REG_CLR); > + writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL1_RES_MASK, 0x1), > + tmu->base + IMX91_TMU_CTRL1 + REG_SET); > + > + writel_relaxed(IMX91_TMU_CTRL1_MEAS_MODE_MASK, tmu->base + IMX91_TMU_CTRL1 + REG_CLR); > + writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL1_MEAS_MODE_MASK, > + IMX91_TMU_CTRL1_MEAS_MODE_PERIODIC), > + tmu->base + IMX91_TMU_CTRL1 + REG_SET); > + > + /* > + * Set Periodic Measurement Frequency to 25Hz: > + * tMEAS_FREQ = tCONV_CLK * PERIOD_CTRL[MEAS_FREQ] > + */ > + writel_relaxed(FIELD_PREP(IMX91_TMU_PERIOD_CTRL_MEAS_MASK, 4 * HZ_PER_MHZ / 25), > + tmu->base + IMX91_TMU_PERIOD_CTRL); > + > + imx91_tmu_enable(tmu, true); > + ret = devm_add_action(dev, imx91_tmu_action_remove, tmu); > + if (ret) > + return dev_err_probe(dev, ret, "Failure to add action imx91_tmu_action_remove()\n"); > + > + pm_runtime_set_active(dev); > + pm_runtime_get_noresume(dev); > + ret = devm_pm_runtime_enable(dev); > + if (ret) > + return ret; > + > + tmu->tzd = devm_thermal_of_zone_register(dev, 0, tmu, &tmu_tz_ops); > + if (IS_ERR(tmu->tzd)) > + return dev_err_probe(dev, PTR_ERR(tmu->tzd), > + "failed to register thermal zone sensor\n"); > + > + irq = platform_get_irq(pdev, 0); > + if (irq < 0) > + return irq; > + > + ret = devm_request_threaded_irq(dev, irq, imx91_tmu_alarm_irq, > + imx91_tmu_alarm_irq_thread, > + IRQF_ONESHOT, "imx91_thermal", tmu); > + > + if (ret < 0) > + return dev_err_probe(dev, ret, "failed to request alarm irq\n"); > + > + pm_runtime_put(dev); > + > + return 0; > +} > + > +static int imx91_tmu_runtime_suspend(struct device *dev) > +{ > + struct imx91_tmu *tmu = dev_get_drvdata(dev); > + > + /* disable tmu */ > + imx91_tmu_enable(tmu, false); > + > + clk_disable_unprepare(tmu->clk); > + > + return 0; > +} > + > +static int imx91_tmu_runtime_resume(struct device *dev) > +{ > + struct imx91_tmu *tmu = dev_get_drvdata(dev); > + int ret; > + > + ret = clk_prepare_enable(tmu->clk); > + if (ret) > + return ret; > + > + imx91_tmu_enable(tmu, true); > + > + return 0; > +} > + > +static DEFINE_RUNTIME_DEV_PM_OPS(imx91_tmu_pm_ops, imx91_tmu_runtime_suspend, > + imx91_tmu_runtime_resume, NULL); > + > +static const struct of_device_id imx91_tmu_table[] = { > + { .compatible = "fsl,imx91-tmu", }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, imx91_tmu_table); > + > +static struct platform_driver imx91_tmu = { > + .driver = { > + .name = "imx91_thermal", > + .pm = pm_ptr(&imx91_tmu_pm_ops), > + .of_match_table = imx91_tmu_table, > + }, > + .probe = imx91_tmu_probe, > +}; > +module_platform_driver(imx91_tmu); > + > +MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>"); > +MODULE_DESCRIPTION("i.MX91 Thermal Monitor Unit driver"); > +MODULE_LICENSE("GPL"); > -- <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs Follow Linaro: <http://www.facebook.com/pages/Linaro> Facebook | <http://twitter.com/#!/linaroorg> Twitter | <http://www.linaro.org/linaro-blog/> Blog ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 2/3] thermal: imx91: Add support for i.MX91 thermal monitoring unit 2026-01-07 15:22 ` Daniel Lezcano @ 2026-01-08 16:24 ` Frank Li 0 siblings, 0 replies; 10+ messages in thread From: Frank Li @ 2026-01-08 16:24 UTC (permalink / raw) To: Daniel Lezcano Cc: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Pengfei Li, Marco Felsch, linux-pm, devicetree, imx, linux-arm-kernel, linux-kernel, ye.li, joy.zou, Peng Fan On Wed, Jan 07, 2026 at 04:22:20PM +0100, Daniel Lezcano wrote: > On 10/20/25 21:00, Frank Li wrote: > > From: Pengfei Li <pengfei.li_1@nxp.com> > > > > Introduce support for the i.MX91 thermal monitoring unit, which features a > > single sensor for the CPU. The register layout differs from other chips, > > necessitating the creation of a dedicated file for this. > > > > This sensor provides a resolution of 1/64°C (6-bit fraction). For actual > > accuracy, refer to the datasheet, as it varies depending on the chip grade. > > Provide an interrupt for end of measurement and threshold violation and > > Contain temperature threshold comparators, in normal and secure address > > space, with direction and threshold programmability. > > > > Datasheet Link: https://www.nxp.com/docs/en/data-sheet/IMX91CEC.pdf > > > > Signed-off-by: Pengfei Li <pengfei.li_1@nxp.com> > > Signed-off-by: Peng Fan <peng.fan@nxp.com> > > Signed-off-by: Frank Li <Frank.Li@nxp.com> > > --- > > Change in v7 > > - remove enable field because framework already check mode before call > > get_temp(); > > - only call runtime_pm_get(put) in set_mode function > > - remove enable irq at get_temp. > > - Enable GE compare irq at set_trip() call back > > - Config GE irq at set_mode() when start measure to avoid issue unnecessary > > irq when clear Compare mask (generate one LE irq). > > > > Change from v5 to v6 > > - remove Macro's review tag > > - remove mutex lock > > - use set_trips callback > > > > Change from v4 to v5 > > - add irq support > > - use period mode > > - Marco, if need drop review tag, let me know > > > > Change from v3 to v4 > > - Add Macro's review tag > > - Use devm_add_action() > > - Move pm_runtim_put before thermal_of_zone_register() > > > > change from v2 to v3 > > - add IMX91_TMU_ prefix for register define > > - remove unused register define > > - fix missed pm_runtime_put() at error path in imx91_tmu_get_temp() > > - use dev variable in probe function > > - use pm_runtime_set_active() in probe > > - move START to imx91_tmu_get_temp() > > - use DEFINE_RUNTIME_DEV_PM_OPS() > > - keep set reset value because there are not sw "reset" bit in controller, > > uboot may change and enable tmu. > > > > change from v1 to v2 > > - use low case for hexvalue > > - combine struct imx91_tmu and tmu_sensor > > - simplify imx91_tmu_start() and imx91_tmu_enable() > > - use s16 for imx91_tmu_get_temp(), which may negative value > > - use reverse christmas tree style > > - use run time pm > > - use oneshot to sample temp > > - register thermal zone after hardware init > > --- > > drivers/thermal/Kconfig | 10 ++ > > drivers/thermal/Makefile | 1 + > > drivers/thermal/imx91_thermal.c | 384 ++++++++++++++++++++++++++++++++++++++++ > > 3 files changed, 395 insertions(+) > > > > diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig > > index a09c188b9ad11377afe232d89c60504eb7000417..b10080d618604ddd90295bff973e337ae0509059 100644 > > --- a/drivers/thermal/Kconfig > > +++ b/drivers/thermal/Kconfig > > @@ -296,6 +296,16 @@ config IMX8MM_THERMAL > > cpufreq is used as the cooling device to throttle CPUs when the passive > > trip is crossed. > > +config IMX91_THERMAL > > + tristate "Temperature sensor driver for NXP i.MX91 SoC" > > + depends on ARCH_MXC || COMPILE_TEST > > + depends on OF > > + help > > + Include one sensor and six comparators. Each of them compares the > > + temperature value (from the sensor) against the programmable > > + threshold values. The direction of the comparison is configurable > > + (greater / lesser than). > > + > > config K3_THERMAL > > tristate "Texas Instruments K3 thermal support" > > depends on ARCH_K3 || COMPILE_TEST > > diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile > > index d7718978db245faffba98ff95a07c7bcbc776fd2..bb21e7ea7fc6b70aa84e5fed7cfdc7096e3fb1f7 100644 > > --- a/drivers/thermal/Makefile > > +++ b/drivers/thermal/Makefile > > @@ -51,6 +51,7 @@ obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o > > obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o > > obj-$(CONFIG_IMX_SC_THERMAL) += imx_sc_thermal.o > > obj-$(CONFIG_IMX8MM_THERMAL) += imx8mm_thermal.o > > +obj-$(CONFIG_IMX91_THERMAL) += imx91_thermal.o > > obj-$(CONFIG_MAX77620_THERMAL) += max77620_thermal.o > > obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o > > obj-$(CONFIG_DA9062_THERMAL) += da9062-thermal.o > > diff --git a/drivers/thermal/imx91_thermal.c b/drivers/thermal/imx91_thermal.c > > new file mode 100644 > > index 0000000000000000000000000000000000000000..9b20be03d6dec18553967548d0ca31d1c1fb387c > > --- /dev/null > > +++ b/drivers/thermal/imx91_thermal.c > > @@ -0,0 +1,384 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * Copyright 2025 NXP. > > + */ > > + > > +#include <linux/bitfield.h> > > +#include <linux/clk.h> > > +#include <linux/err.h> > > +#include <linux/interrupt.h> > > +#include <linux/iopoll.h> > > +#include <linux/nvmem-consumer.h> > > +#include <linux/module.h> > > +#include <linux/of.h> > > +#include <linux/of_device.h> > > +#include <linux/platform_device.h> > > +#include <linux/pm_runtime.h> > > +#include <linux/thermal.h> > > +#include <linux/units.h> > > + > > +#define REG_SET 0x4 > > +#define REG_CLR 0x8 > > +#define REG_TOG 0xc > > + > > +#define IMX91_TMU_CTRL0 0x0 > > +#define IMX91_TMU_CTRL0_THR1_IE BIT(9) > > +#define IMX91_TMU_CTRL0_THR1_MASK GENMASK(3, 2) > > +#define IMX91_TMU_CTRL0_CLR_FLT1 BIT(21) > > + > > +#define IMX91_TMU_THR_MODE_LE 0 > > +#define IMX91_TMU_THR_MODE_GE 1 > > + > > +#define IMX91_TMU_STAT0 0x10 > > +#define IMX91_TMU_STAT0_THR1_IF BIT(9) > > +#define IMX91_TMU_STAT0_THR1_STAT BIT(13) > > +#define IMX91_TMU_STAT0_DRDY0_IF_MASK BIT(16) > > + > > +#define IMX91_TMU_DATA0 0x20 > > + > > +#define IMX91_TMU_CTRL1 0x200 > > +#define IMX91_TMU_CTRL1_EN BIT(31) > > +#define IMX91_TMU_CTRL1_START BIT(30) > > +#define IMX91_TMU_CTRL1_STOP BIT(29) > > +#define IMX91_TMU_CTRL1_RES_MASK GENMASK(19, 18) > > +#define IMX91_TMU_CTRL1_MEAS_MODE_MASK GENMASK(25, 24) > > +#define IMX91_TMU_CTRL1_MEAS_MODE_SINGLE 0 > > +#define IMX91_TMU_CTRL1_MEAS_MODE_CONTINUES 1 > > +#define IMX91_TMU_CTRL1_MEAS_MODE_PERIODIC 2 > > + > > +#define IMX91_TMU_THR_CTRL01 0x30 > > +#define IMX91_TMU_THR_CTRL01_THR1_MASK GENMASK(31, 16) > > + > > +#define IMX91_TMU_REF_DIV 0x280 > > +#define IMX91_TMU_DIV_EN BIT(31) > > +#define IMX91_TMU_DIV_MASK GENMASK(23, 16) > > +#define IMX91_TMU_DIV_MAX 255 > > + > > +#define IMX91_TMU_PUD_ST_CTRL 0x2b0 > > +#define IMX91_TMU_PUDL_MASK GENMASK(23, 16) > > + > > +#define IMX91_TMU_TRIM1 0x2e0 > > +#define IMX91_TMU_TRIM2 0x2f0 > > + > > +#define IMX91_TMU_TEMP_LOW_LIMIT -40000 > > +#define IMX91_TMU_TEMP_HIGH_LIMIT 125000 > > + > > +#define IMX91_TMU_DEFAULT_TRIM1_CONFIG 0xb561bc2d > > +#define IMX91_TMU_DEFAULT_TRIM2_CONFIG 0x65d4 > > + > > +#define IMX91_TMU_PERIOD_CTRL 0x270 > > +#define IMX91_TMU_PERIOD_CTRL_MEAS_MASK GENMASK(23, 0) > > + > > +#define IMX91_TMP_FRAC 64 > > + > > +struct imx91_tmu { > > + void __iomem *base; > > + struct clk *clk; > > + struct device *dev; > > + struct thermal_zone_device *tzd; > > +}; > > + > > +static void imx91_tmu_start(struct imx91_tmu *tmu, bool start) > > +{ > > + u32 val = start ? IMX91_TMU_CTRL1_START : IMX91_TMU_CTRL1_STOP; > > + > > + writel_relaxed(val, tmu->base + IMX91_TMU_CTRL1 + REG_SET); > > Why do you need the 'relaxed' version of the readl / writel ? Thank you for your review this patch. This already in linux-next tree commit c411d8bf06992dade7abb88690dc2d467a868cc4 Author: Pengfei Li <pengfei.li_1@nxp.com> Date: Mon Oct 20 15:00:41 2025 -0400 thermal/drivers/imx91: Add support for i.MX91 thermal monitoring unit I ping this just to reminder guo shanw to pick dts part. If anything need me to fix, let me know. Sorry for confuse you. use _relaxed version here is because it slice better performance when polling temp capture register, needn't dma(). Frank > > > +} > > + > > +static void imx91_tmu_enable(struct imx91_tmu *tmu, bool enable) > > +{ > > + u32 reg = IMX91_TMU_CTRL1; > > + > > + reg += enable ? REG_SET : REG_CLR; > > + > > + writel_relaxed(IMX91_TMU_CTRL1_EN, tmu->base + reg); > > +} > > Can you explain the register layout ? It is unclear how it starts / stops / > is enabled ... > > For instance doing reg += enable ? REG_SET : REG_CLR is confusing > > > > +static int imx91_tmu_to_mcelsius(int x) > > (short x) > > > +{ > > + return x * MILLIDEGREE_PER_DEGREE / IMX91_TMP_FRAC; > > +} > > + > > +static int imx91_tmu_from_mcelsius(int x) > > +{ > > + return x * IMX91_TMP_FRAC / MILLIDEGREE_PER_DEGREE; > > +}> +static int imx91_tmu_get_temp(struct thermal_zone_device *tz, int *temp) > > +{ > > + struct imx91_tmu *tmu = thermal_zone_device_priv(tz); > > + s16 data; > > + > > + /* DATA0 is 16bit signed number */ > > + data = readw_relaxed(tmu->base + IMX91_TMU_DATA0); > > Does the routine works for a negative value ? > > > + *temp = imx91_tmu_to_mcelsius(data); > > + > > + return 0; > > +} > > + > > +static int imx91_tmu_set_trips(struct thermal_zone_device *tz, int low, int high) > > +{ > > + struct imx91_tmu *tmu = thermal_zone_device_priv(tz); > > + int val; > > + > > + if (high >= IMX91_TMU_TEMP_HIGH_LIMIT) > > + return -EINVAL; > > + > > + writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_CLR); > > + > > + /* Comparator1 for temperature threshold */ > > + writel_relaxed(IMX91_TMU_THR_CTRL01_THR1_MASK, tmu->base + IMX91_TMU_THR_CTRL01 + REG_CLR); > > + val = FIELD_PREP(IMX91_TMU_THR_CTRL01_THR1_MASK, imx91_tmu_from_mcelsius(high)); > > + > > + writel_relaxed(val, tmu->base + IMX91_TMU_THR_CTRL01 + REG_SET); > > + > > + writel_relaxed(IMX91_TMU_STAT0_THR1_IF, tmu->base + IMX91_TMU_STAT0 + REG_CLR); > > + > > + writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_SET); > > + > > IMO, it is a good practice to wrap those calls into single line functions > with a self-explanatory name > > > + return 0; > > +} > > + > > +static int imx91_init_from_nvmem_cells(struct imx91_tmu *tmu) > > +{ > > + struct device *dev = tmu->dev; > > + u32 trim1, trim2; > > + int ret; > > + > > + ret = nvmem_cell_read_u32(dev, "trim1", &trim1); > > + if (ret) > > + return ret; > > + > > + ret = nvmem_cell_read_u32(dev, "trim2", &trim2); > > + if (ret) > > + return ret; > > + > > + if (trim1 == 0 || trim2 == 0) > > + return -EINVAL; > > + > > + writel_relaxed(trim1, tmu->base + IMX91_TMU_TRIM1); > > + writel_relaxed(trim2, tmu->base + IMX91_TMU_TRIM2); > > + > > + return 0; > > +} > > + > > +static void imx91_tmu_action_remove(void *data) > > +{ > > + struct imx91_tmu *tmu = data; > > + > > + /* disable tmu */ > > + imx91_tmu_enable(tmu, false); > > +} > > + > > +static irqreturn_t imx91_tmu_alarm_irq(int irq, void *data) > > +{ > > + struct imx91_tmu *tmu = data; > > + u32 val; > > + > > + val = readl_relaxed(tmu->base + IMX91_TMU_STAT0); > > + > > + /* Check if comparison interrupt occurred */ > > + if (val & IMX91_TMU_STAT0_THR1_IF) { > > + /* Clear irq flag and disable interrupt until reconfigured */ > > + writel(IMX91_TMU_STAT0_THR1_IF, tmu->base + IMX91_TMU_STAT0 + REG_CLR); > > + writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_CLR); > > + > > + return IRQ_WAKE_THREAD; > > + } > > + > > + return IRQ_NONE; > > Why is this routine needed ? Is it a shared interrupt ? > > > +} > > + > > +static irqreturn_t imx91_tmu_alarm_irq_thread(int irq, void *data) > > +{ > > + struct imx91_tmu *tmu = data; > > + > > + thermal_zone_device_update(tmu->tzd, THERMAL_EVENT_UNSPECIFIED); > > + > > + return IRQ_HANDLED; > > +} > > + > > +static int imx91_tmu_change_mode(struct thermal_zone_device *tz, enum thermal_device_mode mode) > > +{ > > + struct imx91_tmu *tmu = thermal_zone_device_priv(tz); > > + int ret; > > + > > + if (mode == THERMAL_DEVICE_ENABLED) { > > + ret = pm_runtime_get(tmu->dev); > > + if (ret < 0) > > + return ret; > > + > > + writel_relaxed(IMX91_TMU_CTRL0_THR1_IE | IMX91_TMU_CTRL0_THR1_MASK, > > + tmu->base + IMX91_TMU_CTRL0 + REG_CLR); > > + > > + writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL0_THR1_MASK, IMX91_TMU_THR_MODE_GE), > > + tmu->base + IMX91_TMU_CTRL0 + REG_SET); > > + imx91_tmu_start(tmu, true); > > + } else { > > + writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_CLR); > > + imx91_tmu_start(tmu, false); > > + pm_runtime_put(tmu->dev); > > + } > > + > > + return 0; > > +} > > + > > +static struct thermal_zone_device_ops tmu_tz_ops = { > > + .get_temp = imx91_tmu_get_temp, > > + .change_mode = imx91_tmu_change_mode, > > + .set_trips = imx91_tmu_set_trips, > > +}; > > + > > +static int imx91_tmu_probe(struct platform_device *pdev) > > +{ > > + struct device *dev = &pdev->dev; > > + struct imx91_tmu *tmu; > > + unsigned long rate; > > + int irq, ret; > > + u32 div; > > + > > + tmu = devm_kzalloc(dev, sizeof(struct imx91_tmu), GFP_KERNEL); > > + if (!tmu) > > + return -ENOMEM; > > + > > + tmu->dev = dev; > > + > > + tmu->base = devm_platform_ioremap_resource(pdev, 0); > > + if (IS_ERR(tmu->base)) > > + return dev_err_probe(dev, PTR_ERR(tmu->base), "failed to get io resource"); > > + > > + tmu->clk = devm_clk_get_enabled(dev, NULL); > > + if (IS_ERR(tmu->clk)) > > + return dev_err_probe(dev, PTR_ERR(tmu->clk), "failed to get tmu clock\n"); > > + > > + platform_set_drvdata(pdev, tmu); > > + > > + /* disable the monitor during initialization */ > > + imx91_tmu_enable(tmu, false); > > + imx91_tmu_start(tmu, false); > > + > > + ret = imx91_init_from_nvmem_cells(tmu); > > + if (ret) { > > + dev_warn(dev, "can't get trim value, use default settings\n"); > > + > > + writel_relaxed(IMX91_TMU_DEFAULT_TRIM1_CONFIG, tmu->base + IMX91_TMU_TRIM1); > > + writel_relaxed(IMX91_TMU_DEFAULT_TRIM2_CONFIG, tmu->base + IMX91_TMU_TRIM2); > > + } > > + > > + /* The typical conv clk is 4MHz, the output freq is 'rate / (div + 1)' */ > > + rate = clk_get_rate(tmu->clk); > > + div = (rate / (4 * HZ_PER_MHZ)) - 1; > > + if (div > IMX91_TMU_DIV_MAX) > > + return dev_err_probe(dev, -EINVAL, "clock divider exceed hardware limitation"); > > + > > + /* Set divider value and enable divider */ > > + writel_relaxed(IMX91_TMU_DIV_EN | FIELD_PREP(IMX91_TMU_DIV_MASK, div), > > + tmu->base + IMX91_TMU_REF_DIV); > > + > > + /* Set max power up delay: 'Tpud(ms) = 0xFF * 1000 / 4000000' */ > > + writel_relaxed(FIELD_PREP(IMX91_TMU_PUDL_MASK, 100U), tmu->base + IMX91_TMU_PUD_ST_CTRL); > > + > > + /* > > + * Set resolution mode > > + * 00b - Conversion time = 0.59325 ms > > + * 01b - Conversion time = 1.10525 ms > > + * 10b - Conversion time = 2.12925 ms > > + * 11b - Conversion time = 4.17725 ms > > + */ > > + writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL1_RES_MASK, 0x3), > > + tmu->base + IMX91_TMU_CTRL1 + REG_CLR); > > + writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL1_RES_MASK, 0x1), > > + tmu->base + IMX91_TMU_CTRL1 + REG_SET); > > + > > + writel_relaxed(IMX91_TMU_CTRL1_MEAS_MODE_MASK, tmu->base + IMX91_TMU_CTRL1 + REG_CLR); > > + writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL1_MEAS_MODE_MASK, > > + IMX91_TMU_CTRL1_MEAS_MODE_PERIODIC), > > + tmu->base + IMX91_TMU_CTRL1 + REG_SET); > > + > > + /* > > + * Set Periodic Measurement Frequency to 25Hz: > > + * tMEAS_FREQ = tCONV_CLK * PERIOD_CTRL[MEAS_FREQ] > > + */ > > + writel_relaxed(FIELD_PREP(IMX91_TMU_PERIOD_CTRL_MEAS_MASK, 4 * HZ_PER_MHZ / 25), > > + tmu->base + IMX91_TMU_PERIOD_CTRL); > > + > > + imx91_tmu_enable(tmu, true); > > + ret = devm_add_action(dev, imx91_tmu_action_remove, tmu); > > + if (ret) > > + return dev_err_probe(dev, ret, "Failure to add action imx91_tmu_action_remove()\n"); > > + > > + pm_runtime_set_active(dev); > > + pm_runtime_get_noresume(dev); > > + ret = devm_pm_runtime_enable(dev); > > + if (ret) > > + return ret; > > + > > + tmu->tzd = devm_thermal_of_zone_register(dev, 0, tmu, &tmu_tz_ops); > > + if (IS_ERR(tmu->tzd)) > > + return dev_err_probe(dev, PTR_ERR(tmu->tzd), > > + "failed to register thermal zone sensor\n"); > > + > > + irq = platform_get_irq(pdev, 0); > > + if (irq < 0) > > + return irq; > > + > > + ret = devm_request_threaded_irq(dev, irq, imx91_tmu_alarm_irq, > > + imx91_tmu_alarm_irq_thread, > > + IRQF_ONESHOT, "imx91_thermal", tmu); > > + > > + if (ret < 0) > > + return dev_err_probe(dev, ret, "failed to request alarm irq\n"); > > + > > + pm_runtime_put(dev); > > + > > + return 0; > > +} > > + > > +static int imx91_tmu_runtime_suspend(struct device *dev) > > +{ > > + struct imx91_tmu *tmu = dev_get_drvdata(dev); > > + > > + /* disable tmu */ > > + imx91_tmu_enable(tmu, false); > > + > > + clk_disable_unprepare(tmu->clk); > > + > > + return 0; > > +} > > + > > +static int imx91_tmu_runtime_resume(struct device *dev) > > +{ > > + struct imx91_tmu *tmu = dev_get_drvdata(dev); > > + int ret; > > + > > + ret = clk_prepare_enable(tmu->clk); > > + if (ret) > > + return ret; > > + > > + imx91_tmu_enable(tmu, true); > > + > > + return 0; > > +} > > + > > +static DEFINE_RUNTIME_DEV_PM_OPS(imx91_tmu_pm_ops, imx91_tmu_runtime_suspend, > > + imx91_tmu_runtime_resume, NULL); > > + > > +static const struct of_device_id imx91_tmu_table[] = { > > + { .compatible = "fsl,imx91-tmu", }, > > + { }, > > +}; > > +MODULE_DEVICE_TABLE(of, imx91_tmu_table); > > + > > +static struct platform_driver imx91_tmu = { > > + .driver = { > > + .name = "imx91_thermal", > > + .pm = pm_ptr(&imx91_tmu_pm_ops), > > + .of_match_table = imx91_tmu_table, > > + }, > > + .probe = imx91_tmu_probe, > > +}; > > +module_platform_driver(imx91_tmu); > > + > > +MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>"); > > +MODULE_DESCRIPTION("i.MX91 Thermal Monitor Unit driver"); > > +MODULE_LICENSE("GPL"); > > > > > -- > <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs > > Follow Linaro: <http://www.facebook.com/pages/Linaro> Facebook | > <http://twitter.com/#!/linaroorg> Twitter | > <http://www.linaro.org/linaro-blog/> Blog ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v7 3/3] arm64: dts: imx91: Add thermal-sensor and thermal-zone support 2025-10-20 19:00 [PATCH v7 0/3] thermal: imx91: Add support for i.MX91 thermal monitoring unit Frank Li 2025-10-20 19:00 ` [PATCH v7 1/3] dt-bindings: thermal: fsl,imx91-tmu: add bindings for NXP i.MX91 thermal module Frank Li 2025-10-20 19:00 ` [PATCH v7 2/3] thermal: imx91: Add support for i.MX91 thermal monitoring unit Frank Li @ 2025-10-20 19:00 ` Frank Li 2025-10-22 10:56 ` Alberto Merciai ` (2 more replies) 2 siblings, 3 replies; 10+ messages in thread From: Frank Li @ 2025-10-20 19:00 UTC (permalink / raw) To: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Pengfei Li, Marco Felsch Cc: linux-pm, devicetree, imx, linux-arm-kernel, linux-kernel, ye.li, joy.zou, Frank Li Add thermal-sensor and thermal-zone support. Signed-off-by: Frank Li <Frank.Li@nxp.com> --- changes in v7 - new patch --- arch/arm64/boot/dts/freescale/imx91.dtsi | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/arch/arm64/boot/dts/freescale/imx91.dtsi b/arch/arm64/boot/dts/freescale/imx91.dtsi index 4d8300b2a7bca33bd0613db9e79d2fba6b40c052..f075592bfc01f1eb94d2a2bd8eea907cc2aed090 100644 --- a/arch/arm64/boot/dts/freescale/imx91.dtsi +++ b/arch/arm64/boot/dts/freescale/imx91.dtsi @@ -6,6 +6,54 @@ #include "imx91-pinfunc.h" #include "imx91_93_common.dtsi" +/{ + thermal-zones { + cpu-thermal { + polling-delay-passive = <250>; + polling-delay = <2000>; + thermal-sensors = <&tmu 0>; + + trips { + cpu_alert: cpu-alert { + temperature = <80000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu_crit: cpu-crit { + temperature = <90000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&cpu_alert>; + cooling-device = + <&A55_0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + }; +}; + +&aips1 { + tmu: thermal-sensor@44482000 { + compatible = "fsl,imx91-tmu"; + reg = <0x44482000 0x1000>; + #thermal-sensor-cells = <0>; + clocks = <&clk IMX93_CLK_TMC_GATE>; + interrupt-parent = <&gic>; + interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "thr1", "thr2", "ready"; + nvmem-cells = <&tmu_trim1>, <&tmu_trim2>; + nvmem-cell-names = "trim1", "trim2"; + }; +}; + &clk { compatible = "fsl,imx91-ccm"; }; @@ -69,3 +117,13 @@ &media_blk_ctrl { clock-names = "apb", "axi", "nic", "disp", "cam", "lcdif", "isi", "csi"; }; + +&ocotp { + tmu_trim1: tmu-trim@a0 { + reg = <0xa0 0x4>; + }; + + tmu_trim2: tmu-trim@a4 { + reg = <0xa4 0x4>; + }; +}; -- 2.34.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v7 3/3] arm64: dts: imx91: Add thermal-sensor and thermal-zone support 2025-10-20 19:00 ` [PATCH v7 3/3] arm64: dts: imx91: Add thermal-sensor and thermal-zone support Frank Li @ 2025-10-22 10:56 ` Alberto Merciai 2025-10-28 16:32 ` Frank Li 2026-01-05 20:07 ` Frank Li 2026-01-17 1:55 ` Shawn Guo 2 siblings, 1 reply; 10+ messages in thread From: Alberto Merciai @ 2025-10-22 10:56 UTC (permalink / raw) To: Frank Li Cc: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Pengfei Li, Marco Felsch, linux-pm, devicetree, imx, linux-arm-kernel, linux-kernel, ye.li, joy.zou On Mon, Oct 20, 2025 at 03:00:42PM -0400, Frank Li wrote: > Add thermal-sensor and thermal-zone support. > > Signed-off-by: Frank Li <Frank.Li@nxp.com> > --- > changes in v7 > - new patch > --- > arch/arm64/boot/dts/freescale/imx91.dtsi | 58 ++++++++++++++++++++++++++++++++ > 1 file changed, 58 insertions(+) > > diff --git a/arch/arm64/boot/dts/freescale/imx91.dtsi b/arch/arm64/boot/dts/freescale/imx91.dtsi > index 4d8300b2a7bca33bd0613db9e79d2fba6b40c052..f075592bfc01f1eb94d2a2bd8eea907cc2aed090 100644 > --- a/arch/arm64/boot/dts/freescale/imx91.dtsi > +++ b/arch/arm64/boot/dts/freescale/imx91.dtsi > @@ -6,6 +6,54 @@ > #include "imx91-pinfunc.h" > #include "imx91_93_common.dtsi" > > +/{ > + thermal-zones { > + cpu-thermal { > + polling-delay-passive = <250>; > + polling-delay = <2000>; > + thermal-sensors = <&tmu 0>; > + > + trips { > + cpu_alert: cpu-alert { > + temperature = <80000>; > + hysteresis = <2000>; > + type = "passive"; > + }; > + > + cpu_crit: cpu-crit { > + temperature = <90000>; > + hysteresis = <2000>; > + type = "critical"; > + }; > + }; > + > + cooling-maps { > + map0 { > + trip = <&cpu_alert>; > + cooling-device = > + <&A55_0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; > + }; > + }; > + }; > + }; > +}; Here you define cooling-maps with passive and critical trip points. I was trying test that by enabling CONFIG_THERMAL_EMULATION and setting the emulated temp via echo 85000 > /sys/class/thermal/thermal_zone0/emul_temp By checking with mhz (from lmbench Yocto recipe) command I was expecting that the 91 start throttling but is not the case, Am I missing something? Thanks, Alberto ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 3/3] arm64: dts: imx91: Add thermal-sensor and thermal-zone support 2025-10-22 10:56 ` Alberto Merciai @ 2025-10-28 16:32 ` Frank Li 0 siblings, 0 replies; 10+ messages in thread From: Frank Li @ 2025-10-28 16:32 UTC (permalink / raw) To: Alberto Merciai Cc: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Pengfei Li, Marco Felsch, linux-pm, devicetree, imx, linux-arm-kernel, linux-kernel, ye.li, joy.zou On Wed, Oct 22, 2025 at 12:56:22PM +0200, Alberto Merciai wrote: > On Mon, Oct 20, 2025 at 03:00:42PM -0400, Frank Li wrote: > > Add thermal-sensor and thermal-zone support. > > > > Signed-off-by: Frank Li <Frank.Li@nxp.com> > > --- > > changes in v7 > > - new patch > > --- > > arch/arm64/boot/dts/freescale/imx91.dtsi | 58 ++++++++++++++++++++++++++++++++ > > 1 file changed, 58 insertions(+) > > > > diff --git a/arch/arm64/boot/dts/freescale/imx91.dtsi b/arch/arm64/boot/dts/freescale/imx91.dtsi > > index 4d8300b2a7bca33bd0613db9e79d2fba6b40c052..f075592bfc01f1eb94d2a2bd8eea907cc2aed090 100644 > > --- a/arch/arm64/boot/dts/freescale/imx91.dtsi > > +++ b/arch/arm64/boot/dts/freescale/imx91.dtsi > > @@ -6,6 +6,54 @@ > > #include "imx91-pinfunc.h" > > #include "imx91_93_common.dtsi" > > > > +/{ > > + thermal-zones { > > + cpu-thermal { > > + polling-delay-passive = <250>; > > + polling-delay = <2000>; > > + thermal-sensors = <&tmu 0>; > > + > > + trips { > > + cpu_alert: cpu-alert { > > + temperature = <80000>; > > + hysteresis = <2000>; > > + type = "passive"; > > + }; > > + > > + cpu_crit: cpu-crit { > > + temperature = <90000>; > > + hysteresis = <2000>; > > + type = "critical"; > > + }; > > + }; > > + > > + cooling-maps { > > + map0 { > > + trip = <&cpu_alert>; > > + cooling-device = > > + <&A55_0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; > > + }; > > + }; > > + }; > > + }; > > +}; > > Here you define cooling-maps with passive and critical trip points. > > I was trying test that by enabling CONFIG_THERMAL_EMULATION and setting > the emulated temp via > > echo 85000 > /sys/class/thermal/thermal_zone0/emul_temp > > By checking with mhz (from lmbench Yocto recipe) command I was expecting that the 91 start > throttling but is not the case, Am I missing something? CPUfreq driver have not been implemented in upstream kernel. This don't affect tmu driver, tmu driver focus on read CPU tempature. Frank > > Thanks, > Alberto ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 3/3] arm64: dts: imx91: Add thermal-sensor and thermal-zone support 2025-10-20 19:00 ` [PATCH v7 3/3] arm64: dts: imx91: Add thermal-sensor and thermal-zone support Frank Li 2025-10-22 10:56 ` Alberto Merciai @ 2026-01-05 20:07 ` Frank Li 2026-01-17 1:55 ` Shawn Guo 2 siblings, 0 replies; 10+ messages in thread From: Frank Li @ 2026-01-05 20:07 UTC (permalink / raw) To: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Pengfei Li, Marco Felsch Cc: linux-pm, devicetree, imx, linux-arm-kernel, linux-kernel, ye.li, joy.zou On Mon, Oct 20, 2025 at 03:00:42PM -0400, Frank Li wrote: > Add thermal-sensor and thermal-zone support. > > Signed-off-by: Frank Li <Frank.Li@nxp.com> > --- Shawn: binding already in linux-next. Alberto's comments is not related with thermal-sensor part. Frank > changes in v7 > - new patch > --- > arch/arm64/boot/dts/freescale/imx91.dtsi | 58 ++++++++++++++++++++++++++++++++ > 1 file changed, 58 insertions(+) > > diff --git a/arch/arm64/boot/dts/freescale/imx91.dtsi b/arch/arm64/boot/dts/freescale/imx91.dtsi > index 4d8300b2a7bca33bd0613db9e79d2fba6b40c052..f075592bfc01f1eb94d2a2bd8eea907cc2aed090 100644 > --- a/arch/arm64/boot/dts/freescale/imx91.dtsi > +++ b/arch/arm64/boot/dts/freescale/imx91.dtsi > @@ -6,6 +6,54 @@ > #include "imx91-pinfunc.h" > #include "imx91_93_common.dtsi" > > +/{ > + thermal-zones { > + cpu-thermal { > + polling-delay-passive = <250>; > + polling-delay = <2000>; > + thermal-sensors = <&tmu 0>; > + > + trips { > + cpu_alert: cpu-alert { > + temperature = <80000>; > + hysteresis = <2000>; > + type = "passive"; > + }; > + > + cpu_crit: cpu-crit { > + temperature = <90000>; > + hysteresis = <2000>; > + type = "critical"; > + }; > + }; > + > + cooling-maps { > + map0 { > + trip = <&cpu_alert>; > + cooling-device = > + <&A55_0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; > + }; > + }; > + }; > + }; > +}; > + > +&aips1 { > + tmu: thermal-sensor@44482000 { > + compatible = "fsl,imx91-tmu"; > + reg = <0x44482000 0x1000>; > + #thermal-sensor-cells = <0>; > + clocks = <&clk IMX93_CLK_TMC_GATE>; > + interrupt-parent = <&gic>; > + interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>, > + <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>, > + <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>; > + interrupt-names = "thr1", "thr2", "ready"; > + nvmem-cells = <&tmu_trim1>, <&tmu_trim2>; > + nvmem-cell-names = "trim1", "trim2"; > + }; > +}; > + > &clk { > compatible = "fsl,imx91-ccm"; > }; > @@ -69,3 +117,13 @@ &media_blk_ctrl { > clock-names = "apb", "axi", "nic", "disp", "cam", > "lcdif", "isi", "csi"; > }; > + > +&ocotp { > + tmu_trim1: tmu-trim@a0 { > + reg = <0xa0 0x4>; > + }; > + > + tmu_trim2: tmu-trim@a4 { > + reg = <0xa4 0x4>; > + }; > +}; > > -- > 2.34.1 > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 3/3] arm64: dts: imx91: Add thermal-sensor and thermal-zone support 2025-10-20 19:00 ` [PATCH v7 3/3] arm64: dts: imx91: Add thermal-sensor and thermal-zone support Frank Li 2025-10-22 10:56 ` Alberto Merciai 2026-01-05 20:07 ` Frank Li @ 2026-01-17 1:55 ` Shawn Guo 2 siblings, 0 replies; 10+ messages in thread From: Shawn Guo @ 2026-01-17 1:55 UTC (permalink / raw) To: Frank Li Cc: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Pengfei Li, Marco Felsch, linux-pm, devicetree, imx, linux-arm-kernel, linux-kernel, ye.li, joy.zou On Mon, Oct 20, 2025 at 03:00:42PM -0400, Frank Li wrote: > Add thermal-sensor and thermal-zone support. > > Signed-off-by: Frank Li <Frank.Li@nxp.com> Applied, thanks! ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-01-17 1:55 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-10-20 19:00 [PATCH v7 0/3] thermal: imx91: Add support for i.MX91 thermal monitoring unit Frank Li 2025-10-20 19:00 ` [PATCH v7 1/3] dt-bindings: thermal: fsl,imx91-tmu: add bindings for NXP i.MX91 thermal module Frank Li 2025-10-20 19:00 ` [PATCH v7 2/3] thermal: imx91: Add support for i.MX91 thermal monitoring unit Frank Li 2026-01-07 15:22 ` Daniel Lezcano 2026-01-08 16:24 ` Frank Li 2025-10-20 19:00 ` [PATCH v7 3/3] arm64: dts: imx91: Add thermal-sensor and thermal-zone support Frank Li 2025-10-22 10:56 ` Alberto Merciai 2025-10-28 16:32 ` Frank Li 2026-01-05 20:07 ` Frank Li 2026-01-17 1:55 ` Shawn Guo
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox