Merge tag 'fsl-qoriq-next-2026-06-09' of https://source.denx.de/u-boot/custodians/u-boot-fsl-qoriq into next

CI: https://source.denx.de/u-boot/custodians/u-boot-fsl-qoriq/-/pipelines/30397

- Add JEDEC JC_42.4 thermal driver
- Add QorIQ/Layerscape TMU support
- Misc dts fix for fsl-lx2160a
- Misc i.MX9 SCMI updates
This commit is contained in:
Tom Rini
2026-06-10 09:17:16 -06:00
20 changed files with 1116 additions and 132 deletions

View File

@@ -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 <vjardin@free.fr>
S: Maintained
F: drivers/thermal/jc42.c
KWBIMAGE / KWBOOT TOOLS
M: Pali Rohár <pali@kernel.org>
M: Marek Behún <kabel@kernel.org>

View File

@@ -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)
{

View File

@@ -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";

View File

@@ -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;

View File

@@ -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 */

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2025 NXP
* Copyright 2025-2026 NXP
*
* Peng Fan <peng.fan@nxp.com>
*/
@@ -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)
{

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 */

View File

@@ -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

View File

@@ -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

View File

@@ -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, },
{ }
};

93
drivers/thermal/jc42.c Normal file
View File

@@ -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,
};