diff options
| -rw-r--r-- | MAINTAINERS | 5 | ||||
| -rw-r--r-- | arch/arm/cpu/armv8/fsl-layerscape/cpu.c | 21 | ||||
| -rw-r--r-- | arch/arm/dts/fsl-lx2160a.dtsi | 60 | ||||
| -rw-r--r-- | arch/arm/dts/fsl-lx2162a-qds.dts | 99 | ||||
| -rw-r--r-- | arch/arm/include/asm/arch-fsl-layerscape/sys_proto.h | 30 | ||||
| -rw-r--r-- | arch/arm/include/asm/arch-imx9/sys_proto.h | 1 | ||||
| -rw-r--r-- | arch/arm/mach-imx/imx9/Kconfig | 4 | ||||
| -rw-r--r-- | arch/arm/mach-imx/imx9/scmi/Makefile | 2 | ||||
| -rw-r--r-- | arch/arm/mach-imx/imx9/scmi/fdt.c | 644 | ||||
| -rw-r--r-- | arch/arm/mach-imx/imx9/scmi/soc.c | 105 | ||||
| -rw-r--r-- | board/nxp/imx94_evk/imx94_evk.c | 10 | ||||
| -rw-r--r-- | board/nxp/imx952_evk/imx952_evk.c | 8 | ||||
| -rw-r--r-- | board/nxp/imx95_evk/imx95_evk.c | 10 | ||||
| -rw-r--r-- | configs/imx943_evk_defconfig | 1 | ||||
| -rw-r--r-- | configs/imx952_evk_defconfig | 1 | ||||
| -rw-r--r-- | drivers/cpu/imx8_cpu.c | 12 | ||||
| -rw-r--r-- | drivers/thermal/Kconfig | 16 | ||||
| -rw-r--r-- | drivers/thermal/Makefile | 1 | ||||
| -rw-r--r-- | drivers/thermal/imx_tmu.c | 125 | ||||
| -rw-r--r-- | drivers/thermal/jc42.c | 93 |
20 files changed, 1116 insertions, 132 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index ec7217f39f4..56461129e89 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1297,6 +1297,11 @@ T: git https://source.denx.de/u-boot/u-boot.git F: cmd/i3c.c F: drivers/i3c/ +JEDEC JC-42.4 / TSE2004av TEMPERATURE SENSOR +M: Vincent Jardin <[email protected]> +S: Maintained +F: drivers/thermal/jc42.c + KWBIMAGE / KWBOOT TOOLS M: Pali Rohár <[email protected]> M: Marek Behún <[email protected]> diff --git a/arch/arm/cpu/armv8/fsl-layerscape/cpu.c b/arch/arm/cpu/armv8/fsl-layerscape/cpu.c index a047494b1fd..cbeac6d4383 100644 --- a/arch/arm/cpu/armv8/fsl-layerscape/cpu.c +++ b/arch/arm/cpu/armv8/fsl-layerscape/cpu.c @@ -986,6 +986,27 @@ uint get_svr(void) } #endif +/* + * Layerscape mirror of the i.MX get_cpu_temp_grade(). i.MX reads the + * OCOTP "CPU temp grade" fuses; Layerscape has no such fuse, so the + * limits come from the data sheet instead. LX2160A Reference Manual + * Rev. 1 (10/2021) section 1.12.1 specifies the maximum operating + * junction temperature at 105 degC for commercial / embedded parts; + * the lower bound is the standard -40 degC commercial low. + * + * The TMU itself is documented as accurate within +/- 3 degC (RM + * section 28.1), which the thermal driver clears by setting its + * alert threshold 10 degC below critical. + */ +u32 get_cpu_temp_grade(int *minc, int *maxc) +{ + if (minc) + *minc = -40; + if (maxc) + *maxc = 105; + return 0; /* commercial */ +} + #ifdef CONFIG_DISPLAY_CPUINFO int print_cpuinfo(void) { diff --git a/arch/arm/dts/fsl-lx2160a.dtsi b/arch/arm/dts/fsl-lx2160a.dtsi index 4b4b1c100b8..61d78b74f19 100644 --- a/arch/arm/dts/fsl-lx2160a.dtsi +++ b/arch/arm/dts/fsl-lx2160a.dtsi @@ -292,7 +292,7 @@ #interrupt-cells = <2>; }; - watchdog@23a0000 { + wdt0: watchdog@23a0000 { compatible = "arm,sbsa-gwdt"; reg = <0x0 0x23a0000 0 0x1000>, <0x0 0x2390000 0 0x1000>; @@ -594,6 +594,64 @@ }; }; + /* LX2160ARM Chapter 28 ("Thermal Monitoring Unit") */ + tmu: tmu@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@8b96000 { compatible = "fsl,ls-mdio"; diff --git a/arch/arm/dts/fsl-lx2162a-qds.dts b/arch/arm/dts/fsl-lx2162a-qds.dts index 4eaf982529d..35d80639958 100644 --- a/arch/arm/dts/fsl-lx2162a-qds.dts +++ b/arch/arm/dts/fsl-lx2162a-qds.dts @@ -37,105 +37,6 @@ status = "disabled"; }; -&dspi0 { - bus-num = <0>; - status = "okay"; - - dflash0: n25q128a@0 { - #address-cells = <1>; - #size-cells = <1>; - compatible = "jedec,spi-nor"; - spi-max-frequency = <3000000>; - spi-cpol; - spi-cpha; - reg = <0>; - }; - dflash1: sst25wf040b@1 { - #address-cells = <1>; - #size-cells = <1>; - compatible = "jedec,spi-nor"; - spi-max-frequency = <3000000>; - spi-cpol; - spi-cpha; - reg = <1>; - }; - dflash2: en25s64@2 { - #address-cells = <1>; - #size-cells = <1>; - compatible = "jedec,spi-nor"; - spi-max-frequency = <3000000>; - spi-cpol; - spi-cpha; - reg = <2>; - }; -}; - -&dspi1 { - bus-num = <0>; - status = "okay"; - - dflash3: n25q128a@0 { - #address-cells = <1>; - #size-cells = <1>; - compatible = "jedec,spi-nor"; - spi-max-frequency = <3000000>; - spi-cpol; - spi-cpha; - reg = <0>; - }; - dflash4: sst25wf040b@1 { - #address-cells = <1>; - #size-cells = <1>; - compatible = "jedec,spi-nor"; - spi-max-frequency = <3000000>; - spi-cpol; - spi-cpha; - reg = <1>; - }; - dflash5: en25s64@2 { - #address-cells = <1>; - #size-cells = <1>; - compatible = "jedec,spi-nor"; - spi-max-frequency = <3000000>; - spi-cpol; - spi-cpha; - reg = <2>; - }; -}; - -&dspi2 { - bus-num = <0>; - status = "okay"; - - dflash6: n25q128a@0 { - #address-cells = <1>; - #size-cells = <1>; - compatible = "jedec,spi-nor"; - spi-max-frequency = <3000000>; - spi-cpol; - spi-cpha; - reg = <0>; - }; - dflash7: sst25wf040b@1 { - #address-cells = <1>; - #size-cells = <1>; - compatible = "jedec,spi-nor"; - spi-max-frequency = <3000000>; - spi-cpol; - spi-cpha; - reg = <1>; - }; - dflash8: en25s64@2 { - #address-cells = <1>; - #size-cells = <1>; - compatible = "jedec,spi-nor"; - spi-max-frequency = <3000000>; - spi-cpol; - spi-cpha; - reg = <2>; - }; -}; - &esdhc1 { mmc-hs200-1_8v; mmc-hs400-1_8v; diff --git a/arch/arm/include/asm/arch-fsl-layerscape/sys_proto.h b/arch/arm/include/asm/arch-fsl-layerscape/sys_proto.h new file mode 100644 index 00000000000..3b78e73c726 --- /dev/null +++ b/arch/arm/include/asm/arch-fsl-layerscape/sys_proto.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2026 Free Mobile - Vincent Jardin + * + * Layerscape mirror of the i.MX <asm/mach-imx/sys_proto.h>: declares + * the SoC-personality helpers consumed by generic drivers that work on + * both i.MX and QorIQ/Layerscape parts (e.g. drivers/thermal/imx_tmu.c + * for the QorIQ TMU variant). + */ + +#ifndef _ASM_ARCH_FSL_LAYERSCAPE_SYS_PROTO_H +#define _ASM_ARCH_FSL_LAYERSCAPE_SYS_PROTO_H + +#include <linux/types.h> + +/* + * Per LX2160A Reference Manual, Rev. 1 (10/2021): + * - section 1.12.1: "NXP specs max power at 105 degC junction" for + * commercial / embedded operating conditions. + * - section 28.1: TMU "Accuracy within +/- 3 degC". + * + * Layerscape SoCs do not expose an OCOTP-style "CPU temp grade" fuse, + * so the implementation returns the documented junction-temperature + * limit from the data sheet (-40 .. 105 degC commercial range). The + * thermal driver subtracts 10 degC for its alert threshold, which + * comfortably clears the +/- 3 degC TMU accuracy in both directions. + */ +u32 get_cpu_temp_grade(int *minc, int *maxc); + +#endif /* _ASM_ARCH_FSL_LAYERSCAPE_SYS_PROTO_H */ diff --git a/arch/arm/include/asm/arch-imx9/sys_proto.h b/arch/arm/include/asm/arch-imx9/sys_proto.h index b5e7d7d6855..d43e54e72aa 100644 --- a/arch/arm/include/asm/arch-imx9/sys_proto.h +++ b/arch/arm/include/asm/arch-imx9/sys_proto.h @@ -22,6 +22,7 @@ int low_drive_freq_update(void *blob); enum imx9_soc_voltage_mode soc_target_voltage_mode(void); int get_reset_reason(bool sys, bool lm); +int imx9_uboot_fixup_by_fuse(void *fdt); int scmi_get_boot_device_offset(unsigned long *img_off); int scmi_get_boot_stage(u8 *stage); diff --git a/arch/arm/mach-imx/imx9/Kconfig b/arch/arm/mach-imx/imx9/Kconfig index 7fa6d6369e8..4bb6a87ce26 100644 --- a/arch/arm/mach-imx/imx9/Kconfig +++ b/arch/arm/mach-imx/imx9/Kconfig @@ -150,6 +150,7 @@ config TARGET_PHYCORE_IMX93 config TARGET_IMX95_19X19_EVK bool "imx95_19x19_evk" + select OF_BOARD_FIXUP select IMX95 imply BOOTSTD_BOOTCOMMAND imply BOOTSTD_FULL @@ -157,6 +158,7 @@ config TARGET_IMX95_19X19_EVK config TARGET_IMX95_15X15_EVK bool "imx95_15x15_evk" + select OF_BOARD_FIXUP select IMX95 imply BOOTSTD_BOOTCOMMAND imply BOOTSTD_FULL @@ -164,6 +166,7 @@ config TARGET_IMX95_15X15_EVK config TARGET_IMX943_EVK bool "imx943_evk" + select OF_BOARD_FIXUP select IMX94 imply BOOTSTD_BOOTCOMMAND imply BOOTSTD_FULL @@ -180,6 +183,7 @@ config TARGET_VERDIN_IMX95 config TARGET_IMX952_EVK bool "imx952_evk" + select OF_BOARD_FIXUP select IMX_SM_CPU select IMX_SM_LMM select IMX952 diff --git a/arch/arm/mach-imx/imx9/scmi/Makefile b/arch/arm/mach-imx/imx9/scmi/Makefile index b98744e1ecb..e83d27327cb 100644 --- a/arch/arm/mach-imx/imx9/scmi/Makefile +++ b/arch/arm/mach-imx/imx9/scmi/Makefile @@ -5,5 +5,5 @@ # Add include path for NXP device tree header files from Linux. ccflags-y += -I$(srctree)/dts/upstream/src/arm64/freescale/ -obj-y += soc.o +obj-y += soc.o fdt.o obj-y += clock_scmi.o clock.o diff --git a/arch/arm/mach-imx/imx9/scmi/fdt.c b/arch/arm/mach-imx/imx9/scmi/fdt.c new file mode 100644 index 00000000000..a1d9afbf69a --- /dev/null +++ b/arch/arm/mach-imx/imx9/scmi/fdt.c @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2026 NXP + * + */ + +#include <asm/arch/sys_proto.h> +#include <linux/bitfield.h> +#include <fuse.h> +#include <fdt_support.h> +#include <fdtdec.h> + +struct periph_fuse_info { + u32 bit_mask; + u32 soc_type; /* 0 means for all */ + bool of_board_fix; + int (*disable_func)(void *blob, u32 fuse_val); +}; + +int num_a55_cores_disabled; +int gpu_disabled; + +static int delete_fdt_nodes(void *blob, const char *const nodes_path[], int size_array) +{ + int i = 0; + int rc; + int nodeoff; + + for (i = 0; i < size_array; i++) { + nodeoff = fdt_path_offset(blob, nodes_path[i]); + if (nodeoff < 0) + continue; /* Not found, skip it */ + + debug("Found %s node\n", nodes_path[i]); + + rc = fdt_del_node(blob, nodeoff); + if (rc < 0) { + printf("Unable to delete node %s, err=%s\n", + nodes_path[i], fdt_strerror(rc)); + } else { + printf("Delete node %s\n", nodes_path[i]); + } + } + + return 0; +} + +static int disable_fdt_nodes(void *blob, const char *const nodes_path[], + int size_array, const char *prop, const char *value) +{ + int i = 0; + int rc; + int nodeoff; + const char *status = "disabled"; + const char *prop_str; + + for (i = 0; i < size_array; i++) { + nodeoff = fdt_path_offset(blob, nodes_path[i]); + if (nodeoff < 0) + continue; /* Not found, skip it */ + + debug("Found %s node\n", nodes_path[i]); + + if (prop && value) { + prop_str = fdt_stringlist_get(blob, nodeoff, prop, 0, NULL); + + if (!prop_str || strcmp(prop_str, value)) + continue; + } + +add_status: + rc = fdt_setprop(blob, nodeoff, "status", status, strlen(status) + 1); + if (rc) { + if (rc == -FDT_ERR_NOSPACE) { + rc = fdt_increase_size(blob, 512); + if (!rc) + goto add_status; + } + printf("Unable to update property %s:%s, err=%s\n", + nodes_path[i], "status", fdt_strerror(rc)); + } else { + debug("Modify %s:%s disabled\n", nodes_path[i], "status"); + } + } + + return 0; +} + +static int get_cooling_device_list(void *blob, u32 nodeoff, + const char *const path, u32 *cooling_dev, int max_cnt) +{ + int cnt, j; + + cnt = fdtdec_get_int_array_count(blob, nodeoff, "cooling-device", + cooling_dev, max_cnt); + if (cnt < 0) { + printf("cnt incorrect, path %s, cnt = %d\n", path, cnt); + return cnt; + } + if (cnt != max_cnt) + printf("Warning: %s, cooling-device count %d\n", path, cnt); + + for (j = 0; j < cnt; j++) + cooling_dev[j] = cpu_to_fdt32(cooling_dev[j]); + + return cnt; +} + +static void disable_thermal_vpu_node(void *blob, u32 disabled_cores, u32 gpu_disabled) +{ + static const char * const thermal_path[] = { + "/thermal-zones/ana/cooling-maps/map0", + "/thermal-zones/ana-thermal/cooling-maps/map0", + }; + int num_cpus = (is_imx94() || is_imx952()) ? 4 : 6; + u32 array_cnt = (num_cpus + 2) * 3 - (disabled_cores * 3) - (gpu_disabled * 3); + u32 cooling_dev[array_cnt]; + + int nodeoff, ret, i, cnt; + + for (i = 0; i < ARRAY_SIZE(thermal_path); i++) { + nodeoff = fdt_path_offset(blob, thermal_path[i]); + if (nodeoff < 0) { + printf("path not found %s\n", thermal_path[i]); + continue; /* Not found, skip it */ + } + + cnt = get_cooling_device_list(blob, nodeoff, + thermal_path[i], cooling_dev, array_cnt); + /* VPU map does not exist in cooling dev*/ + if (cnt <= ((num_cpus - disabled_cores) * 3 + (gpu_disabled ? 0 : 3))) + continue; + + /* Remove VPU it the last two nodes in the fdt ana blob */ + ret = fdt_setprop(blob, nodeoff, "cooling-device", &cooling_dev, + sizeof(u32) * (array_cnt - 3)); + + if (ret < 0) { + printf("Warning: %s, cooling-device setprop failed %d\n", + thermal_path[i], ret); + continue; + } + + printf("Update node %s, cooling-device prop\n", thermal_path[i]); + } +} + +static void disable_thermal_gpu_node(void *blob, u32 disabled_cores) +{ + static const char * const thermal_path[] = { + "/thermal-zones/ana/cooling-maps/map0", + "/thermal-zones/ana-thermal/cooling-maps/map0", + }; + int num_cpus = (is_imx94() || is_imx952()) ? 4 : 6; + u32 array_cnt = (num_cpus + 2) * 3 - (disabled_cores * 3); + u32 cooling_dev[array_cnt]; + int nodeoff, ret, i, cnt; + + for (i = 0; i < ARRAY_SIZE(thermal_path); i++) { + nodeoff = fdt_path_offset(blob, thermal_path[i]); + if (nodeoff < 0) { + printf("path not found %s\n", thermal_path[i]); + continue; /* Not found, skip it */ + } + + cnt = get_cooling_device_list(blob, nodeoff, thermal_path[i], + cooling_dev, array_cnt); + if (cnt <= (num_cpus - disabled_cores) * 3) + continue; /* GPU map does not exist in cooling dev*/ + + /* Remove GPU and VPU as these are the last two nodes in the fdt ana blob */ + ret = fdt_setprop(blob, nodeoff, "cooling-device", &cooling_dev, + sizeof(u32) * (array_cnt - 6)); + if (ret < 0) { + printf("Warning: %s, cooling-device setprop failed %d\n", + thermal_path[i], ret); + continue; + } + + if (cnt == array_cnt) { + /* Add VPU node back to ana thermal-zone. */ + ret = fdt_appendprop(blob, nodeoff, "cooling-device", + &cooling_dev[array_cnt - 3], sizeof(u32) * 3); + if (ret < 0) { + printf("Warning: %s, cooling-device appendprop failed %d\n", + thermal_path[i], ret); + continue; + } + } + + printf("Update node %s, cooling-device prop\n", thermal_path[i]); + } +} + +static void disable_thermal_cpu_nodes(void *blob, u32 disabled_cores) +{ + static const char * const thermal_path[] = { + "/thermal-zones/pf53_arm/cooling-maps/map0", + "/thermal-zones/ana/cooling-maps/map0", + "/thermal-zones/a55/cooling-maps/map0", + "/thermal-zones/a55-thermal/cooling-maps/map0", + "/thermal-zones/ana-thermal/cooling-maps/map0", + }; + u32 cooling_dev[24]; + int nodeoff, ret, i, cnt; + int prop_size = 3 * ((is_imx94() || is_imx952()) ? 4 : 6); + + for (i = 0; i < ARRAY_SIZE(thermal_path); i++) { + nodeoff = fdt_path_offset(blob, thermal_path[i]); + if (nodeoff < 0) { + printf("path not found %s\n", thermal_path[i]); + continue; /* Not found, skip it */ + } + cnt = get_cooling_device_list(blob, nodeoff, thermal_path[i], cooling_dev, 24); + + ret = fdt_setprop(blob, nodeoff, "cooling-device", &cooling_dev, + sizeof(u32) * (prop_size - disabled_cores * 3)); + + if (ret < 0) { + printf("Warning: %s, cooling-device setprop failed %d\n", + thermal_path[i], ret); + continue; + } + + /* Add GPU and VPU nodes back to ana thermal-zone. */ + if (cnt > prop_size) { + ret = fdt_appendprop(blob, nodeoff, "cooling-device", + &cooling_dev[prop_size], + sizeof(u32) * (cnt - prop_size)); + if (ret < 0) { + printf("Warning: %s, cooling-device appendprop failed %d\n", + thermal_path[i], ret); + continue; + } + } + + printf("Update node %s, cooling-device prop\n", thermal_path[i]); + } +} + +static int disable_ld_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_ld[] = { + "/remoteproc", + "/disp-mu", + "/soc/syscon@4b070000", + "/soc/mailbox@4b080000", + "/soc/mailbox@4b090000", + }; + + return delete_fdt_nodes(blob, nodes_path_ld, ARRAY_SIZE(nodes_path_ld)); +} + +static int disable_npu_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_npu[] = { + "/soc/imx95-neutron-remoteproc@4ab00000", + "/soc/imx95-neutron@4ab00004", + "/soc/neutron-remoteproc@4ab00000", + "/soc/neutron@4ab00004", + }; + + return delete_fdt_nodes(blob, nodes_path_npu, ARRAY_SIZE(nodes_path_npu)); +} + +static int disable_arm_cpu_nodes(void *blob, u32 fuse_val) +{ + u32 i = 0; + int rc; + int nodeoff; + char nodes_path[32]; + int num_cpus = (is_imx94() || is_imx952()) ? 4 : 6; + + num_a55_cores_disabled = 0; + + if (fuse_val & BIT(2)) /* A55C2 */ + num_a55_cores_disabled++; + + if (fuse_val & BIT(3)) /* A55C2 */ + num_a55_cores_disabled++; + + if (fuse_val & BIT(4)) /* A55C3 */ + num_a55_cores_disabled++; + + if (fuse_val & BIT(5)) /* A55C4 */ + num_a55_cores_disabled++; + + if (fuse_val & BIT(6)) /* A55C5 */ + num_a55_cores_disabled++; + + for (i = num_cpus; i > (num_cpus - num_a55_cores_disabled); i--) { + sprintf(nodes_path, "/cpus/cpu@%u00", i - 1); + + nodeoff = fdt_path_offset(blob, nodes_path); + if (nodeoff < 0) + continue; /* Not found, skip it */ + + debug("Found %s node\n", nodes_path); + + rc = fdt_del_node(blob, nodeoff); + if (rc < 0) { + printf("Unable to delete node %s, err=%s\n", + nodes_path, fdt_strerror(rc)); + } else { + printf("Delete node %s\n", nodes_path); + + /* Remove node from cpu-map/cluster0 */ + sprintf(nodes_path, "/cpus/cpu-map/cluster0/core%u", i - 1); + nodeoff = fdt_path_offset(blob, nodes_path); + if (nodeoff < 0) + continue; /* Not found, skip it */ + + rc = fdt_del_node(blob, nodeoff); + if (rc < 0) + printf("Unable to delete node %s, err=%s\n", + nodes_path, fdt_strerror(rc)); + } + } + + disable_thermal_cpu_nodes(blob, num_a55_cores_disabled); + + return 0; +} + +static int disable_jpegdec_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_jpegdec[] = { + "/soc/jpegdec@4c500000", + }; + + return delete_fdt_nodes(blob, nodes_path_jpegdec, ARRAY_SIZE(nodes_path_jpegdec)); +} + +static int disable_jpegenc_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_jpegenc[] = { + "/soc/jpegenc@4c550000", + }; + + return delete_fdt_nodes(blob, nodes_path_jpegenc, ARRAY_SIZE(nodes_path_jpegenc)); +} + +static int disable_mipicsi0_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_mipicsi0[] = { + "/soc/csi@4ad30000", + }; + + return delete_fdt_nodes(blob, nodes_path_mipicsi0, ARRAY_SIZE(nodes_path_mipicsi0)); +} + +static int disable_mipicsi1_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_mipicsi1[] = { + "/soc/csi@4ad40000", + }; + + return delete_fdt_nodes(blob, nodes_path_mipicsi1, ARRAY_SIZE(nodes_path_mipicsi1)); +} + +static int disable_isp_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_isp[] = { + "/soc@0/isp@4ae00000", + "/soc/isp@4ae00000", + }; + + return delete_fdt_nodes(blob, nodes_path_isp, ARRAY_SIZE(nodes_path_isp)); +} + +static int disable_vpu_node(void *blob, u32 fuse_val) +{ + int ret = 0; + + static const char * const nodes_path_vpu[] = { + "/soc/vpu-ctrl@4c4c0000", + "/soc/vpu-ctrl@4c4f0000", + "/soc/vpu@4c480000", + "/soc/vpu@4c490000", + "/soc/vpu@4c4a0000", + "/soc/vpu@4c4b0000", + "/soc/vpu@4c4c0000", + "/soc/vpu@4c4d0000", + "/soc/vpu@4c4e0000", + "/soc/jpegdec@4c500000", + "/soc/jpegenc@4c550000", + "/soc/vpuenc@4c460000", + "/soc/syscon@4c410000" + }; + + ret = delete_fdt_nodes(blob, nodes_path_vpu, ARRAY_SIZE(nodes_path_vpu)); + if (!ret) + disable_thermal_vpu_node(blob, num_a55_cores_disabled, gpu_disabled); + + return ret; +} + +static int disable_vpuenc_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_vpuenc[] = { + "/soc/vpuenc@4c460000", + }; + + return delete_fdt_nodes(blob, nodes_path_vpuenc, ARRAY_SIZE(nodes_path_vpuenc)); +} + +static int disable_vpuwave511_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_vpu511[] = { + "/soc/vpu-ctrl@4c4f0000", + "/soc/vpu@4c4b0000", + "/soc/vpu@4c4c0000", + "/soc/vpu@4c4d0000", + "/soc/vpu@4c4e0000", + }; + + return delete_fdt_nodes(blob, nodes_path_vpu511, ARRAY_SIZE(nodes_path_vpu511)); +} + +static int disable_gpu_node(void *blob, u32 fuse_val) +{ + int ret = 0; + + static const char * const nodes_path_gpu[] = { + "/soc/gpu@4d900000", + "/thermal-zones@1/ana/cooling-maps/map1/cooling-device/gpu@4d900000", + "/thermal-zones/ana/cooling-maps/map1/cooling-device/gpu@4d900000", + "/thermal-zones@1/ana/cooling-maps/map1/cooling-device", + "/thermal-zones/ana/cooling-maps/map1/cooling-device", + "/thermal-zones@1/ana/cooling-maps/map1", + "/thermal-zones/ana/cooling-maps/map1", + }; + + ret = delete_fdt_nodes(blob, nodes_path_gpu, ARRAY_SIZE(nodes_path_gpu)); + if (!ret) { + disable_thermal_gpu_node(blob, num_a55_cores_disabled); + gpu_disabled = 1; + } + return ret; +} + +static int disable_pciea_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_pciea[] = { + "/soc/pcie@4c300000", + "/soc/pcie-ep@4c300000" + }; + + return delete_fdt_nodes(blob, nodes_path_pciea, ARRAY_SIZE(nodes_path_pciea)); +} + +static int disable_pcieb_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_pcieb[] = { + "/soc/pcie@4c380000", + "/soc/pcie-ep@4c380000" + }; + + return delete_fdt_nodes(blob, nodes_path_pcieb, ARRAY_SIZE(nodes_path_pcieb)); +} + +int disable_enet10g_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_enet10g[] = { + "/pcie@4ca00000/ethernet@10,0", + "/soc/pcie@4ca00000/ethernet@10,0", + "/soc/syscon@4ca00000/ethernet@10,0", + "/soc/netc-blk-ctrl@4cde0000/pcie@4ca00000/ethernet@10,0", + }; + + return disable_fdt_nodes(blob, nodes_path_enet10g, ARRAY_SIZE(nodes_path_enet10g), + NULL, NULL); +} + +int disable_enet25g_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_enet25g[] = { + "/soc/system-controller@4ceb0000/pcie@4ca00000/ethernet-switch@0,2/ports/port@0", + "/soc/system-controller@4ceb0000/pcie@4ca00000/ethernet-switch@0,2/ports/port@1", + }; + + return disable_fdt_nodes(blob, nodes_path_enet25g, ARRAY_SIZE(nodes_path_enet25g), + "phy-mode", "sgmii"); +} + +int disable_mipidsi_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_mipidsi[] = { + "/soc/dsi@4acf0000", + "/soc/syscon@4acf0000", + "/soc/dsi@4b060000", + "/soc/phy@4b110000", + }; + + return delete_fdt_nodes(blob, nodes_path_mipidsi, ARRAY_SIZE(nodes_path_mipidsi)); +} + +int disable_dpu_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_dpu[] = { + "/soc/bridge@4b0d0000/channel@0/port@0/endpoint", + "/soc/bridge@4b0d0000/channel@0/port@1/endpoint", + "/soc/bridge@4b0d0000/channel@1/port@0/endpoint", + "/soc/bridge@4b0d0000/channel@1/port@1/endpoint", + "/soc/display-controller@4b400000/ports/port@0/endpoint", + "/soc/display-controller@4b400000/ports/port@1/endpoint", + "/soc/display-controller@4b400000", + "/soc/syscon@4b010000/bridge@8/ports/port@0/endpoint", + "/soc/syscon@4b010000/bridge@8/ports/port@1/endpoint", + "/soc/syscon@4b010000/bridge@8/ports/port@2/endpoint@0", + "/soc/syscon@4b010000/bridge@8/ports/port@2/endpoint@1", + "/soc/syscon@4b010000/bridge@8/ports/port@3/endpoint@0", + "/soc/syscon@4b010000/bridge@8/ports/port@3/endpoint@1", + "/soc/syscon@4b010000/bridge@8", + "/soc/syscon@4b010000", + "/soc/syscon@4b0a0000", + "/soc/interrupt-controller@4b0b0000", + "/soc/bridge@4b0d0000" + }; + + return delete_fdt_nodes(blob, nodes_path_dpu, ARRAY_SIZE(nodes_path_dpu)); +} + +int disable_lvds_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_lvds[] = { + "/soc/syscon@4b0c0000/ldb@4/channel@0", + "/soc/syscon@4b0c0000/phy@8", + "/soc/syscon@4b0c0000/ldb@4/channel@1", + "/soc/syscon@4b0c0000/phy@c", + "/soc/syscon@4b0c0000" + }; + + return delete_fdt_nodes(blob, nodes_path_lvds, ARRAY_SIZE(nodes_path_lvds)); +} + +int disable_cm70_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_cm70[] = { + "/reserved-memory/vdev0vring0@82000000", + "/reserved-memory/vdev0vring1@82008000", + "/reserved-memory/vdev1vring0@82010000", + "/reserved-memory/vdev1vring1@82018000", + "/reserved-memory/rsc-table@82220000", + "/reserved-memory/vdevbuffer@82020000", + "/imx943-cm70", + }; + + return delete_fdt_nodes(blob, nodes_path_cm70, ARRAY_SIZE(nodes_path_cm70)); +} + +int disable_cm71_node(void *blob, u32 fuse_val) +{ + static const char * const nodes_path_cm71[] = { + "/reserved-memory/vdev0vring0@84000000", + "/reserved-memory/vdev0vring1@84008000", + "/reserved-memory/vdev1vring0@84010000", + "/reserved-memory/vdev1vring1@84018000", + "/reserved-memory/rsc-table@84220000", + "/reserved-memory/vdevbuffer@84020000", + "/imx943-cm71", + }; + + return delete_fdt_nodes(blob, nodes_path_cm71, ARRAY_SIZE(nodes_path_cm71)); +} + +/* There is order dependency between cpu->gpu->vpu */ +struct periph_fuse_info f17_grp[] = { + { BIT(30), MXC_CPU_IMX952, false, disable_ld_node }, +}; + +struct periph_fuse_info f18_grp[] = { + { BIT(0), 0, false, disable_npu_node }, + { GENMASK(6, 2), 0, false, disable_arm_cpu_nodes }, + { BIT(9), 0, true, disable_cm70_node }, + { BIT(17), MXC_CPU_IMX94, true, disable_cm71_node }, + { BIT(22), 0, true, disable_dpu_node }, + { BIT(27), 0, true, disable_lvds_node }, + { BIT(29), 0, false, disable_isp_node }, +}; + +struct periph_fuse_info f19_grp[] = { + { BIT(6), 0, true, disable_pciea_node }, + { BIT(7), 0, true, disable_pcieb_node }, + { BIT(17), 0, false, disable_gpu_node }, + { BIT(18), 0, false, disable_vpu_node }, + { BIT(19), 0, false, disable_jpegenc_node }, + { BIT(20), 0, false, disable_jpegdec_node }, + { BIT(22), 0, false, disable_mipicsi0_node }, + { BIT(23), 0, false, disable_mipicsi1_node }, + { BIT(24), 0, true, disable_mipidsi_node }, + { BIT(26), 0, false, disable_vpuenc_node }, + { BIT(27), 0, false, disable_vpuwave511_node }, +}; + +struct periph_fuse_info f20_grp[] = { + { BIT(12), MXC_CPU_IMX95, true, disable_enet10g_node }, + { BIT(12), MXC_CPU_IMX94, true, disable_enet25g_node }, +}; + +static void ft_disable_periph(void *blob, u32 fuse_bank, u32 fuse_word, + struct periph_fuse_info *info, u32 info_num, + bool board_fix_fdt) +{ + int i, ret; + u32 val = 0; + + ret = fuse_read(fuse_bank, fuse_word, &val); + if (ret) + return; + + for (i = 0; i < info_num; i++) { + if (val & info[i].bit_mask) { + if (board_fix_fdt && !info[i].of_board_fix) + continue; + + if (!info[i].soc_type || is_cpu_type(info[i].soc_type)) + info[i].disable_func(blob, val); + } + } +} + +int ft_system_setup(void *blob, struct bd_info *bd) +{ + /* Common peripheral disable fuse process */ + ft_disable_periph(blob, 2, 1, f17_grp, ARRAY_SIZE(f17_grp), false); + ft_disable_periph(blob, 2, 2, f18_grp, ARRAY_SIZE(f18_grp), false); + ft_disable_periph(blob, 2, 3, f19_grp, ARRAY_SIZE(f19_grp), false); + ft_disable_periph(blob, 2, 4, f20_grp, ARRAY_SIZE(f20_grp), false); + + return 0; +} + +/* Fix uboot dtb based on fuses. */ +#if IS_ENABLED(CONFIG_OF_BOARD_FIXUP) && !IS_ENABLED(CONFIG_XPL_BUILD) +int imx9_uboot_fixup_by_fuse(void *fdt) +{ + ft_disable_periph(fdt, 2, 2, f18_grp, ARRAY_SIZE(f18_grp), true); + ft_disable_periph(fdt, 2, 3, f19_grp, ARRAY_SIZE(f19_grp), true); + ft_disable_periph(fdt, 2, 4, f20_grp, ARRAY_SIZE(f20_grp), true); + + return 0; +} +#endif diff --git a/arch/arm/mach-imx/imx9/scmi/soc.c b/arch/arm/mach-imx/imx9/scmi/soc.c index 8a295baf5a2..123c1d51a4d 100644 --- a/arch/arm/mach-imx/imx9/scmi/soc.c +++ b/arch/arm/mach-imx/imx9/scmi/soc.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Copyright 2025 NXP + * Copyright 2025-2026 NXP * * Peng Fan <[email protected]> */ @@ -186,8 +186,9 @@ u32 get_cpu_temp_grade(int *minc, int *maxc) *minc = -40; *maxc = 105; } else if (val == TEMP_EXTCOMMERCIAL) { - *minc = -20; - *maxc = 105; + /* Map to Ext industrial */ + *minc = -40; + *maxc = 125; } else { *minc = 0; *maxc = 95; @@ -715,14 +716,88 @@ int get_reset_reason(bool sys, bool lm) return 0; } -const char *get_imx_type(u32 imxtype) +const char *get_cpu_variant_type_name(u32 type) { - switch (imxtype) { - case SCMI_CPU: - return IMX_PLAT_STR; - default: - return "??"; + u32 val, core_num, part_num; + int ret; + + ret = fuse_read(2, 1, &val); + if (ret) + return NULL; + + /* Get part num */ + part_num = (val >> 4) & 0xff; + if (!part_num) + return NULL; + + if (type == MXC_CPU_IMX95 || type == MXC_CPU_IMX952) { + u32 segment; + static char name[8] = "95294"; + char pn[2]; + + core_num = part_num & 0x3; + segment = (part_num >> 2) & 0xf; + + switch (segment) { + case 0xa: + pn[0] = 'T'; + break; + case 0xb: + pn[0] = 'V'; + break; + case 0xc: + pn[0] = 'C'; + break; + case 0xd: + pn[0] = 'G'; + break; + case 0xe: + pn[0] = 'I'; + break; + case 0xf: + pn[0] = 'N'; + break; + default: + pn[0] = segment + '0'; + break; + } + + pn[1] = core_num * 2 + '0'; + + if (type == MXC_CPU_IMX95) + sprintf(name, "95%c%c", pn[0], pn[1]); + else + sprintf(name, "952%c%c", pn[0], pn[1]); + + return name; + } else if (type == MXC_CPU_IMX94) { + static char *name = "94398"; + + core_num = 8; + + ret = fuse_read(2, 2, &val); + if (ret) + return NULL; + + if (part_num > 30) { /* 943 */ + /* A55 2 & 3 disabled */ + if ((val & 0x18) == 0x18) + core_num = 6; + } else if (part_num > 20) { /* 942 */ + core_num = 5; + + /* m7_0 disabled */ + if ((val & 0x200) == 0x200) + core_num = 4; + } else if (part_num > 10) { /* 941 */ + core_num = 5; + } + sprintf(name, "94%u%u", part_num, core_num); + + return name; } + + return NULL; } void build_info(void) @@ -798,18 +873,6 @@ int arch_misc_init(void) return 0; } -#if defined(CONFIG_OF_BOARD_FIXUP) && !defined(CONFIG_SPL_BUILD) -int board_fix_fdt(void *fdt) -{ - return 0; -} -#endif - -int ft_system_setup(void *blob, struct bd_info *bd) -{ - return 0; -} - #if IS_ENABLED(CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG) void get_board_serial(struct tag_serialnr *serialnr) { diff --git a/board/nxp/imx94_evk/imx94_evk.c b/board/nxp/imx94_evk/imx94_evk.c index 4731b79b55d..02149afae87 100644 --- a/board/nxp/imx94_evk/imx94_evk.c +++ b/board/nxp/imx94_evk/imx94_evk.c @@ -7,7 +7,7 @@ #include <fdt_support.h> #include <asm/gpio.h> #include <asm/arch/clock.h> -#include <asm/mach-imx/sys_proto.h> +#include <asm/arch/sys_proto.h> int board_init(void) { @@ -26,3 +26,11 @@ int board_late_init(void) return 0; } + +#if IS_ENABLED(CONFIG_OF_BOARD_FIXUP) +int board_fix_fdt(void *fdt) +{ + /* Remove nodes based on fuses. */ + return imx9_uboot_fixup_by_fuse(fdt); +} +#endif diff --git a/board/nxp/imx952_evk/imx952_evk.c b/board/nxp/imx952_evk/imx952_evk.c index 2a61817939e..b5c2da032a8 100644 --- a/board/nxp/imx952_evk/imx952_evk.c +++ b/board/nxp/imx952_evk/imx952_evk.c @@ -24,3 +24,11 @@ int board_late_init(void) return 0; } + +#if IS_ENABLED(CONFIG_OF_BOARD_FIXUP) +int board_fix_fdt(void *fdt) +{ + /* Remove nodes based on fuses. */ + return imx9_uboot_fixup_by_fuse(fdt); +} +#endif diff --git a/board/nxp/imx95_evk/imx95_evk.c b/board/nxp/imx95_evk/imx95_evk.c index 99a37e0593f..394d6fd459c 100644 --- a/board/nxp/imx95_evk/imx95_evk.c +++ b/board/nxp/imx95_evk/imx95_evk.c @@ -5,7 +5,7 @@ #include <asm/gpio.h> #include <asm/arch/clock.h> -#include <asm/mach-imx/sys_proto.h> +#include <asm/arch/sys_proto.h> int board_late_init(void) { @@ -14,3 +14,11 @@ int board_late_init(void) return 0; } + +#if IS_ENABLED(CONFIG_OF_BOARD_FIXUP) +int board_fix_fdt(void *fdt) +{ + /* Remove nodes based on fuses. */ + return imx9_uboot_fixup_by_fuse(fdt); +} +#endif diff --git a/configs/imx943_evk_defconfig b/configs/imx943_evk_defconfig index b60d39a1fa2..4bc60e1d89f 100644 --- a/configs/imx943_evk_defconfig +++ b/configs/imx943_evk_defconfig @@ -26,7 +26,6 @@ CONFIG_SYS_LOAD_ADDR=0x90400000 CONFIG_SPL=y CONFIG_SPL_RECOVER_DATA_SECTION=y CONFIG_PCI=y -CONFIG_OF_BOARD_FIXUP=y CONFIG_SYS_MEMTEST_START=0x90000000 CONFIG_SYS_MEMTEST_END=0xA0000000 CONFIG_REMAKE_ELF=y diff --git a/configs/imx952_evk_defconfig b/configs/imx952_evk_defconfig index 66a56ddb879..b74df3a5d5f 100644 --- a/configs/imx952_evk_defconfig +++ b/configs/imx952_evk_defconfig @@ -28,7 +28,6 @@ CONFIG_SPL_OF_LIBFDT_ASSUME_MASK=0x0 CONFIG_SPL=y CONFIG_SPL_RECOVER_DATA_SECTION=y CONFIG_PCI=y -CONFIG_OF_BOARD_FIXUP=y CONFIG_SYS_MEMTEST_START=0x90000000 CONFIG_SYS_MEMTEST_END=0xA0000000 CONFIG_REMAKE_ELF=y diff --git a/drivers/cpu/imx8_cpu.c b/drivers/cpu/imx8_cpu.c index 2bd76ffa739..7bb7b420176 100644 --- a/drivers/cpu/imx8_cpu.c +++ b/drivers/cpu/imx8_cpu.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Copyright 2019, 2024 NXP + * Copyright 2019, 2024-2026 NXP */ #include <cpu.h> @@ -28,8 +28,18 @@ struct cpu_imx_plat { u32 mpidr; }; +__weak const char *get_cpu_variant_type_name(u32 type) +{ + return NULL; +} + static const char *get_imx_type_str(u32 imxtype) { + const char *name = get_cpu_variant_type_name(imxtype); + + if (name) + return name; + switch (imxtype) { case MXC_CPU_IMX8MM: return "8MMQ"; /* Quad-core version of the imx8mm */ diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 91c39aa4dee..33a82ca3bf1 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -27,11 +27,12 @@ config IMX_SCU_THERMAL trip is crossed config IMX_TMU - bool "Thermal Management Unit driver for NXP i.MX8M and iMX93" - depends on ARCH_IMX8M || IMX93 + bool "Thermal Management Unit driver for NXP i.MX8M / i.MX93 and QorIQ" + depends on ARCH_IMX8M || IMX93 || FSL_LAYERSCAPE help - Support for Temperature sensors on NXP i.MX8M and iMX93. - It supports one critical trip point and one passive trip point. + Support for the NXP Thermal Management Unit (TMU) sensors on + i.MX8M, i.MX93 and on QorIQ/Layerscape SoCs (LX2160A, + LS1028A, LS1088A, ...). The boot is hold to the cool device to throttle CPUs when the passive trip is crossed @@ -55,4 +56,11 @@ config TI_LM74_THERMAL Enable thermal support for the Texas Instruments LM74 chip. The driver supports reading CPU temperature. +config DM_THERMAL_JC42 + bool "JEDEC JC-42.4/TSE2004av SPD temperature sensor" + depends on DM_I2C + help + Enable support for the JEDEC JC-42.4 temperature sensor found + on the SPD bus of DDR3 and DDR4 DIMMs (TSE2004av and compatible). + endif # if DM_THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index b6f06c00ed9..c9fa7561b45 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_DM_THERMAL_JC42) += jc42.o diff --git a/drivers/thermal/imx_tmu.c b/drivers/thermal/imx_tmu.c index 1bde4d07f52..da0825ecd34 100644 --- a/drivers/thermal/imx_tmu.c +++ b/drivers/thermal/imx_tmu.c @@ -6,14 +6,17 @@ #include <config.h> #include <asm/io.h> -#include <asm/arch/clock.h> #include <asm/arch/sys_proto.h> +#if IS_ENABLED(CONFIG_ARCH_IMX8M) || IS_ENABLED(CONFIG_IMX93) +#include <asm/arch/clock.h> +#endif #include <dm.h> #include <dm/device_compat.h> #include <dm/device-internal.h> #include <dm/device.h> #include <errno.h> #include <fuse.h> +#include <linux/bitops.h> #include <linux/delay.h> #include <malloc.h> #include <thermal.h> @@ -22,10 +25,12 @@ #define FLAGS_VER2 0x1 #define FLAGS_VER3 0x2 #define FLAGS_VER4 0x4 +#define FLAGS_QORIQ 0x8 #define TMR_DISABLE 0x0 #define TMR_ME 0x80000000 #define TMR_ALPF 0x0c000000 +#define QORIQ_TMR_ALPF (0x3 << 24) /* QorIQ ALPF lives at bits[25:24] */ #define TMTMIR_DEFAULT 0x00000002 #define TIER_DISABLE 0x0 @@ -33,7 +38,19 @@ #define TER_ADC_PD 0x40000000 #define TER_ALPF 0x3 +/* default CPU delay time to cool down if over temperature */ #define IMX_TMU_POLLING_DELAY_MS 5000 + +/* TRITSR - QorIQ Immediate Temperature Site Register. + * + * Per LX2160A Reference Manual, Rev. 1 (10/2021) section 28.3.1.24 + * the calibrated reading lives in TEMP[8:0] and is reported in + * degrees Kelvin (integer). The QorIQ regs_v1 variant has no + * fractional 0.5 degC bit, unlike the i.MX regs_v2 / VER4 layouts. + */ +#define TRITSR_V BIT(31) /* reading valid */ +#define TRITSR_TEMP_MASK GENMASK(8, 0) /* degrees Kelvin */ +#define TRITSR_KELVIN_OFFSET 273 /* TEMP[8:0] - 273 = degC */ /* * i.MX TMU Registers */ @@ -148,11 +165,37 @@ struct imx_tmu_regs_v3 { u32 trim; }; +/* + * fsl,qoriq-tmu (LX2160A, LS1028A, LS1088A, ...). Same TMU IP family as + * the i.MX "regs_v1" layout but: site-enable is a discrete TMSR at 0x08 + * (TMTMIR moves to 0x0C), and the temperature range registers are + * variable-length at 0xF10 (a SoC may use fewer than 16). Calibration is + * taken from the DT (fsl,tmu-range / fsl,tmu-calibration) exactly like + * the i.MX regs_v1 path, so qoriq reuses imx_tmu_calibration()'s scheme. + */ +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]; + u32 tier; /* 0x020 interrupt enable */ + u32 tidr; /* 0x024 interrupt detect */ + u8 res1[0x58]; + u32 ttcfgr; /* 0x080 temperature config (cal walk) */ + u32 tscfgr; /* 0x084 sensor config (cal walk) */ + u8 res2[0x78]; + struct imx_tmu_site_regs site[SITES_MAX]; /* 0x100 */ + u8 res3[0xd10]; + u32 ttrcr[16]; /* 0xF10 temperature range control */ +}; + union tmu_regs { struct imx_tmu_regs regs_v1; struct imx_tmu_regs_v2 regs_v2; struct imx_tmu_regs_v3 regs_v3; struct imx_tmu_regs_v4 regs_v4; + struct qoriq_tmu_regs regs_qoriq; }; struct imx_tmu_plat { @@ -189,6 +232,9 @@ static int read_temperature(struct udevice *dev, int *temp) } else if (drv_data & FLAGS_VER4) { val = readl(&pdata->regs->regs_v4.tritsr0); valid = val & 0x80000000; + } else if (drv_data & FLAGS_QORIQ) { + val = readl(&pdata->regs->regs_qoriq.site[pdata->id].tritsr); + valid = val & TRITSR_V; } else { val = readl(&pdata->regs->regs_v1.site[pdata->id].tritsr); valid = val & 0x80000000; @@ -213,6 +259,19 @@ static int read_temperature(struct udevice *dev, int *temp) /* Convert Kelvin to Celsius */ *temp -= 273000; + } else if (drv_data & FLAGS_QORIQ) { + /* + * LX2160A Reference Manual, Rev. 1 (10/2021) + * section 28.3.1.24: TEMP[8:0] is the calibrated + * reading in degrees Kelvin (integer, no 0.5 degC + * bit on the regs_v1 variant). The calibration + * point examples in the same RM section 28.1.3 + * use the same Kelvin/Celsius offset: + * TTR0CR=0x800000E6 -> 230K (-43 degC) + * TTR1CR=0x8001017D -> 381K (108 degC) + */ + *temp = ((val & TRITSR_TEMP_MASK) - + TRITSR_KELVIN_OFFSET) * 1000; } else { *temp = (val & 0xff) * 1000; } @@ -265,6 +324,35 @@ static int imx_tmu_calibration(struct udevice *dev) if (drv_data & (FLAGS_VER2 | FLAGS_VER3)) return 0; + if (drv_data & FLAGS_QORIQ) { + const fdt32_t *ranges; + int n; + + ranges = dev_read_prop(dev, "fsl,tmu-range", &len); + if (!ranges || len % 4 || + len / 4 > (int)ARRAY_SIZE(pdata->regs->regs_qoriq.ttrcr)) { + dev_err(dev, "TMU: missing/invalid fsl,tmu-range\n"); + return -ENODEV; + } + n = len / 4; + for (i = 0; i < n; i++) + writel(fdt32_to_cpu(ranges[i]), + &pdata->regs->regs_qoriq.ttrcr[i]); + + calibration = dev_read_prop(dev, "fsl,tmu-calibration", &len); + if (!calibration || len % 8) { + dev_err(dev, "TMU: invalid calibration data.\n"); + return -ENODEV; + } + for (i = 0; i < len; i += 8, calibration += 2) { + writel(fdt32_to_cpu(*calibration), + &pdata->regs->regs_qoriq.ttcfgr); + writel(fdt32_to_cpu(*(calibration + 1)), + &pdata->regs->regs_qoriq.tscfgr); + } + return 0; + } + if (drv_data & FLAGS_VER4) { calibration = dev_read_prop(dev, "fsl,tmu-calibration", &len); if (!calibration || len % 8 || len > 128) { @@ -402,6 +490,18 @@ static inline void imx_tmu_mx8mq_init(struct udevice *dev) { } static void imx_tmu_arch_init(struct udevice *dev) { + /* + * QorIQ takes its calibration from the DT (fsl,tmu-calibration), + * not from OCOTP fuses, so it has no per-SoC arch init. The #if + * below is still required: the i.MX SoC-ID helpers and fuse API + * (<asm/arch/sys_proto.h>) do not exist in a Layerscape build, so + * the references must be removed at compile time, not merely + * skipped at runtime. + */ + if (dev_get_driver_data(dev) & FLAGS_QORIQ) + return; + +#if IS_ENABLED(CONFIG_ARCH_IMX8M) || IS_ENABLED(CONFIG_IMX93) if (is_imx8mm() || is_imx8mn()) imx_tmu_mx8mm_mx8mn_init(dev); else if (is_imx8mp()) @@ -412,6 +512,7 @@ static void imx_tmu_arch_init(struct udevice *dev) imx_tmu_mx8mq_init(dev); else dev_err(dev, "Unsupported SoC, TMU calibration not loaded!\n"); +#endif } static void imx_tmu_init(struct udevice *dev) @@ -443,6 +544,15 @@ static void imx_tmu_init(struct udevice *dev) /* Set update_interval */ writel(TMTMIR_DEFAULT, &pdata->regs->regs_v4.tmtmir); + } else if (drv_data & FLAGS_QORIQ) { + /* Disable monitoring */ + writel(TMR_DISABLE, &pdata->regs->regs_qoriq.tmr); + + /* Disable interrupt, using polling instead */ + writel(TIER_DISABLE, &pdata->regs->regs_qoriq.tier); + + /* Set update_interval */ + writel(TMTMIR_DEFAULT, &pdata->regs->regs_qoriq.tmtmir); } else { /* Disable monitoring */ writel(TMR_DISABLE, &pdata->regs->regs_v1.tmr); @@ -511,6 +621,18 @@ static int imx_tmu_enable_msite(struct udevice *dev) /* Enable ME */ reg |= TMR_ME; writel(reg, &pdata->regs->regs_v4.tmr); + } else if (drv_data & FLAGS_QORIQ) { + /* Clear ME, enable every site at once via the discrete TMSR */ + reg = readl(&pdata->regs->regs_qoriq.tmr); + reg &= ~TMR_ME; + writel(reg, &pdata->regs->regs_qoriq.tmr); + + writel(GENMASK(SITES_MAX - 1, 0), + &pdata->regs->regs_qoriq.tmsr); + + reg |= QORIQ_TMR_ALPF; + reg |= TMR_ME; + writel(reg, &pdata->regs->regs_qoriq.tmr); } else { /* Clear the ME before setting MSITE and ALPF*/ reg = readl(&pdata->regs->regs_v1.tmr); @@ -650,6 +772,7 @@ static const struct udevice_id imx_tmu_ids[] = { { .compatible = "fsl,imx8mm-tmu", .data = FLAGS_VER2, }, { .compatible = "fsl,imx8mp-tmu", .data = FLAGS_VER3, }, { .compatible = "fsl,imx93-tmu", .data = FLAGS_VER4, }, + { .compatible = "fsl,qoriq-tmu", .data = FLAGS_QORIQ, }, { } }; diff --git a/drivers/thermal/jc42.c b/drivers/thermal/jc42.c new file mode 100644 index 00000000000..6945260e8b0 --- /dev/null +++ b/drivers/thermal/jc42.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2026 Free Mobile - Vincent Jardin + * + * JEDEC JC-42.4 / TSE2004av Temperature Sensor driver. + * + * Generic I2C temperature sensor of the Serial Presence Detect (SPD) + * bus of DDR3 and DDR4 SO-DIMMs / UDIMMs / RDIMMs per the JEDEC + * JC-42.4 standard. The TSE2004av variant adds an integrated SPD + * EEPROM, but the thermal register interface is the same and is + * what this driver exposes. + * + * Register layout (subset): + * 0x05 Ambient temperature, 16-bit big-endian: + * bit 15 : T_CRIT alarm flag (read-only) + * bit 14 : T_HIGH alarm flag (read-only) + * bit 13 : T_LOW alarm flag (read-only) + * bit 12 : sign (two's-complement within bits[12:0]) + * bits[11:0]: magnitude * 16 (LSB = 0.0625 degC = 62.5 mC) + * 0x06 Manufacturer ID (16-bit BE, JEP-106 vendor code) + * 0x07 Device ID + Revision (upper byte = ID, lower = revision) + * ... + */ + +#include <dm.h> +#include <i2c.h> +#include <thermal.h> +#include <linux/bitops.h> + +#define JC42_REG_TEMP 0x05 + +#define JC42_TEMP_SIGN BIT(12) +#define JC42_TEMP_MAGNITUDE GENMASK(11, 0) + +static int jc42_get_temp(struct udevice *dev, int *temp) +{ + u8 buf[2]; + int ret; + int mag; + + ret = dm_i2c_read(dev, JC42_REG_TEMP, buf, sizeof(buf)); + if (ret) + return ret; + + mag = ((buf[0] << 8) | buf[1]) & (JC42_TEMP_SIGN | JC42_TEMP_MAGNITUDE); + if (mag & JC42_TEMP_SIGN) + mag -= (JC42_TEMP_SIGN << 1); + + /* + * mag is in units of 1/16 degC. Multiply first to keep one + * extra bit of precision before the divide. Worst-case range + * for a 13-bit signed value is +/-4096, so the product fits + * comfortably in an int (~4.1M mC). + */ + *temp = mag * 1000 / 16; + + return 0; +} + +static const struct dm_thermal_ops jc42_ops = { + .get_temp = jc42_get_temp, +}; + +/* + * Optional DT label property override: it replace the default DM + * device name (the ofnode name, eg "temp@18") so + * temperature list or temperature get commands + * show a human-meaningful identifier such as "ddr-top" or + * "ddr-bottom". + * It mirrors the Linux hwmon binding which uses label for the + * per-sensor display name. + */ +static int jc42_bind(struct udevice *dev) +{ + const char *label = dev_read_string(dev, "label"); + + if (label && *label) + return device_set_name(dev, label); + return 0; +} + +static const struct udevice_id jc42_match[] = { + { .compatible = "jedec,jc-42.4-temp" }, + { } +}; + +U_BOOT_DRIVER(jc42_thermal) = { + .name = "jc42_thermal", + .id = UCLASS_THERMAL, + .of_match = jc42_match, + .bind = jc42_bind, + .ops = &jc42_ops, +}; |
