[PATCH v2 1/3] rockchip: Add initial RK3582 support

Quentin Schulz quentin.schulz at cherry.de
Mon Aug 11 14:11:21 CEST 2025


Hi Jonas,

On 8/4/25 8:16 PM, Jonas Karlman wrote:
> The RK3582 SoC is a variant of the RK3588S with some IP blocks disabled.
> What blocks are disabled/non-working is indicated by ip-state in OTP.
> 
> This add initial support for RK3582 by using ft_system_setup() to mark
> any cpu and/or gpu node with status=fail as indicated by ip-state.
> 
> This apply same policy as vendor U-Boot for RK3582, i.e. two big cpu
> cores and the gpu is always failed/disabled.
> 
> Enable Kconfig option OF_SYSTEM_SETUP in board defconfig to make use of
> the required DT fixups for RK3582 board variants.
> 
> Signed-off-by: Jonas Karlman <jonas at kwiboo.se>
> ---
> Changes in v2:
> - Refactor code to first apply policy to ip-state and then fail cores
>    based on the updated ip-state
> - Add support for failing rkvdec and rkvenc cores
> - Append soc compatible to board model
> - Not picking up t-b tag due to significant changes to patch
> ---
>   arch/arm/mach-rockchip/rk3588/rk3588.c | 221 +++++++++++++++++++++++++
>   1 file changed, 221 insertions(+)
> 
> diff --git a/arch/arm/mach-rockchip/rk3588/rk3588.c b/arch/arm/mach-rockchip/rk3588/rk3588.c
> index c01a40020896..3ab0afe0979e 100644
> --- a/arch/arm/mach-rockchip/rk3588/rk3588.c
> +++ b/arch/arm/mach-rockchip/rk3588/rk3588.c
> @@ -7,6 +7,7 @@
>   #define LOG_CATEGORY LOGC_ARCH
>   
>   #include <dm.h>
> +#include <fdt_support.h>
>   #include <misc.h>
>   #include <spl.h>
>   #include <asm/armv8/mmu.h>
> @@ -200,6 +201,16 @@ int arch_cpu_init(void)
>   
>   #define RK3588_OTP_CPU_CODE_OFFSET		0x02
>   #define RK3588_OTP_SPECIFICATION_OFFSET		0x06
> +#define RK3588_OTP_IP_STATE_OFFSET		0x1d
> +
> +#define FAIL_CPU_CLUSTER0		GENMASK(3, 0)
> +#define FAIL_CPU_CLUSTER1		GENMASK(5, 4)
> +#define FAIL_CPU_CLUSTER2		GENMASK(7, 6)
> +#define FAIL_GPU				GENMASK(4, 1)
> +#define FAIL_RKVDEC0			BIT(6)
> +#define FAIL_RKVDEC1			BIT(7)
> +#define FAIL_RKVENC0			BIT(0)
> +#define FAIL_RKVENC1			BIT(2)
>   
>   int checkboard(void)
>   {
> @@ -245,3 +256,213 @@ int checkboard(void)
>   
>   	return 0;
>   }
> +
> +static int fdt_path_del_node(void *fdt, const char *path)
> +{
> +	int nodeoffset;
> +
> +	nodeoffset = fdt_path_offset(fdt, path);
> +	if (nodeoffset < 0)
> +		return nodeoffset;
> +
> +	return fdt_del_node(fdt, nodeoffset);
> +}
> +
> +static int fdt_path_set_name(void *fdt, const char *path, const char *name)
> +{
> +	int nodeoffset;
> +
> +	nodeoffset = fdt_path_offset(fdt, path);
> +	if (nodeoffset < 0)
> +		return nodeoffset;
> +
> +	return fdt_set_name(fdt, nodeoffset, name);
> +}
> +


Wondering if this shouldn't be something to add to the fdt "subsystem"?

With tests and all?

> +/*
> + * RK3582 is a variant of the RK3588S with some IP blocks disabled. What blocks
> + * are disabled/non-working is indicated by ip-state in OTP. ft_system_setup()
> + * is used to mark any cpu and/or gpu node with status=fail as indicated by
> + * ip-state. Apply same policy as vendor U-Boot for RK3582, i.e. two big cpu
> + * cores and the gpu is always failed/disabled. Enable OF_SYSTEM_SETUP to make
> + * use of the required DT fixups for RK3582 board variants.
> + */
> +int ft_system_setup(void *blob, struct bd_info *bd)
> +{
> +	static const char * const cpu_node_names[] = {
> +		"cpu at 0", "cpu at 100", "cpu at 200", "cpu at 300",
> +		"cpu at 400", "cpu at 500", "cpu at 600", "cpu at 700",
> +	};
> +	int parent, node, i, comp_len, len, ret;
> +	bool cluster1_removed = false;
> +	u8 cpu_code[2], ip_state[3];
> +	struct udevice *dev;
> +	char soc_comp[16];
> +	const char *comp;
> +	void *data;
> +
> +	if (!IS_ENABLED(CONFIG_OF_SYSTEM_SETUP))
> +		return 0;
> +
> +	if (!IS_ENABLED(CONFIG_ROCKCHIP_OTP) || !CONFIG_IS_ENABLED(MISC))

Mmmmm we should probably think about adding xPL symbols for 
ROCKCHIP_OTP. No required for this patch as we already have code doing 
that same check.

> +		return -ENOSYS;
> +
> +	ret = uclass_get_device_by_driver(UCLASS_MISC,
> +					  DM_DRIVER_GET(rockchip_otp), &dev);
> +	if (ret) {
> +		log_debug("Could not find otp device, ret=%d\n", ret);
> +		return ret;
> +	}
> +
> +	/* cpu-code: SoC model, e.g. 0x35 0x82 or 0x35 0x88 */
> +	ret = misc_read(dev, RK3588_OTP_CPU_CODE_OFFSET, cpu_code, 2);
> +	if (ret < 0) {
> +		log_debug("Could not read cpu-code, ret=%d\n", ret);
> +		return ret;
> +	}
> +
> +	log_debug("cpu-code: %02x %02x\n", cpu_code[0], cpu_code[1]);
> +
> +	/* only fail devices on rk3582/rk3583 */
> +	if (!(cpu_code[0] == 0x35 && cpu_code[1] == 0x82) &&
> +	    !(cpu_code[0] == 0x35 && cpu_code[1] == 0x83))
> +		return 0;
> +
> +	ret = misc_read(dev, RK3588_OTP_IP_STATE_OFFSET, &ip_state, 3);
> +	if (ret < 0) {
> +		log_err("Could not read ip-state, ret=%d\n", ret);
> +		return ret;
> +	}
> +
> +	log_debug("ip-state: %02x %02x %02x (otp)\n",
> +		  ip_state[0], ip_state[1], ip_state[2]);
> +
> +	/* policy: fail entire big core cluster when one or more core is bad */
> +	if (ip_state[0] & FAIL_CPU_CLUSTER1)
> +		ip_state[0] |= FAIL_CPU_CLUSTER1;
> +	if (ip_state[0] & FAIL_CPU_CLUSTER2)
> +		ip_state[0] |= FAIL_CPU_CLUSTER2;
> +
> +	/* policy: always fail one big core cluster on rk3582/rk3583 */
> +	if (!(ip_state[0] & (FAIL_CPU_CLUSTER1 | FAIL_CPU_CLUSTER2)))
> +		ip_state[0] |= FAIL_CPU_CLUSTER2;
> +
> +	if (cpu_code[0] == 0x35 && cpu_code[1] == 0x82) {
> +		/* policy: always fail gpu on rk3582 */
> +		ip_state[1] |= FAIL_GPU;
> +
> +		/* policy: always fail rkvdec on rk3582 */
> +		ip_state[1] |= FAIL_RKVDEC0 | FAIL_RKVDEC1;
> +	} else if (cpu_code[0] == 0x35 && cpu_code[1] == 0x83) {

Technically, we could have a simple else here as the code path before 
the if is guaranteed to only be reached in two cases, 0x8235 and 0x8335, 
so if you test the former and it doesn't match, it's necessarily the 
latter. No strong opinion.

> +		/* policy: always fail one rkvdec core on rk3583 */
> +		if (!(ip_state[1] & (FAIL_RKVDEC0 | FAIL_RKVDEC1)))

You could want to only check FAIL_RKVDEC0 as FAIL_RKVDEC1 is a bit (and 
not a bitfield so we don't need to set the whole bitfield in the event 
it's only partially set like for CPU clusters). No strong opinion.

> +			ip_state[1] |= FAIL_RKVDEC1;
> +	}
> +
> +	/* policy: always fail one rkvenc core on rk3582/rk3583 */
> +	if (!(ip_state[2] & (FAIL_RKVENC0 | FAIL_RKVENC1)))
> +		ip_state[2] |= FAIL_RKVENC1;
> +
> +	log_debug("ip-state: %02x %02x %02x (policy)\n",
> +		  ip_state[0], ip_state[1], ip_state[2]);
> +
> +	/* cpu cluster1: ip_state[0]: bit4~5 */
> +	if ((ip_state[0] & FAIL_CPU_CLUSTER1) == FAIL_CPU_CLUSTER1) {

No need for the == FAIL_CPU_CLUSTER1 check as you'll have set the whole 
bitfield earlier even if only one is set in the original ip_state from 
the register. No strong opinion.

> +		log_debug("remove cpu-map cluster1\n");
> +		fdt_path_del_node(blob, "/cpus/cpu-map/cluster1");
> +		cluster1_removed = true;
> +	}
> +
> +	/* cpu cluster2: ip_state[0]: bit6~7 */
> +	if ((ip_state[0] & FAIL_CPU_CLUSTER2) == FAIL_CPU_CLUSTER2) {

Ditto. No strong opinion.

> +		log_debug("remove cpu-map cluster2\n");
> +		fdt_path_del_node(blob, "/cpus/cpu-map/cluster2");
> +	} else if (cluster1_removed) {
> +		/* cluster nodes must be named in a continuous series */
> +		log_debug("rename cpu-map cluster2\n");
> +		fdt_path_set_name(blob, "/cpus/cpu-map/cluster2", "cluster1");
> +	}
> +
> +	/* gpu: ip_state[1]: bit1~4 */
> +	if (ip_state[1] & FAIL_GPU) {
> +		log_debug("fail gpu\n");
> +		fdt_status_fail_by_pathf(blob, "/gpu at fb000000");
> +	}
> +
> +	/* rkvdec: ip_state[1]: bit6,7 */
> +	if (ip_state[1] & FAIL_RKVDEC0) {
> +		log_debug("fail rkvdec0\n");
> +		fdt_status_fail_by_pathf(blob, "/video-codec at fdc38000");
> +		fdt_status_fail_by_pathf(blob, "/iommu at fdc38700");
> +	}
> +	if (ip_state[1] & FAIL_RKVDEC1) {
> +		log_debug("fail rkvdec1\n");
> +		fdt_status_fail_by_pathf(blob, "/video-codec at fdc40000");
> +		fdt_status_fail_by_pathf(blob, "/iommu at fdc40700");
> +	}
> +
> +	/* rkvenc: ip_state[2]: bit0,2 */
> +	if (ip_state[2] & FAIL_RKVENC0) {
> +		log_debug("fail rkvenc0\n");
> +		fdt_status_fail_by_pathf(blob, "/video-codec at fdbd0000");
> +		fdt_status_fail_by_pathf(blob, "/iommu at fdbdf000");
> +	}
> +	if (ip_state[2] & FAIL_RKVENC1) {
> +		log_debug("fail rkvenc1\n");
> +		fdt_status_fail_by_pathf(blob, "/video-codec at fdbe0000");
> +		fdt_status_fail_by_pathf(blob, "/iommu at fdbef000");
> +	}
> +
> +	parent = fdt_path_offset(blob, "/cpus");
> +	if (parent < 0) {
> +		log_err("Could not find /cpus, parent=%d\n", parent);
> +		return parent;
> +	}
> +
> +	/* cpu: ip_state[0]: bit0~7 */
> +	for (i = 0; i < 8; i++) {
> +		/* fail any bad cpu core */
> +		if (!(ip_state[0] & BIT(i)))
> +			continue;
> +
> +		node = fdt_subnode_offset(blob, parent, cpu_node_names[i]);
> +		if (node >= 0) {
> +			log_debug("fail cpu %s\n", cpu_node_names[i]);
> +			fdt_status_fail(blob, node);
> +		} else {
> +			log_err("Could not find %s, node=%d\n",
> +				cpu_node_names[i], node);
> +			return node;
> +		}
> +	}

I would move this closer to the renaming/removing of cpu-map. No strong 
opinion though.

I'm wondering why you aren't using a direct path like for other 
controllers, e.g. fdt_status_fail_by_pathf(blob, cpu_node_names[i]) by 
simply prefixing each node_name with /cpus/ ?

> +
> +	node = fdt_path_offset(blob, "/");
> +	if (node < 0) {
> +		log_err("Could not find /, node=%d\n", node);
> +		return node;
> +	}
> +
> +	snprintf(soc_comp, sizeof(soc_comp), "rockchip,rk35%x", cpu_code[1]);
> +
> +	for (i = 0, comp_len = 0;
> +	     (comp = fdt_stringlist_get(blob, node, "compatible", i, &len));
> +	     i++) {
> +		/* stop at soc compatible */
> +		if (!strcmp(comp, soc_comp) ||
> +		    !strcmp(comp, "rockchip,rk3588s") ||
> +		    !strcmp(comp, "rockchip,rk3588"))
> +			break;
> +
> +		log_debug("compatible[%d]: %s\n", i, comp);
> +		comp_len += len + 1;
> +	}
> +

If it happens that none of the expected strings are found in the 
compatible property, we should probably return early.

> +	/* truncate to only include board compatible */
> +	fdt_setprop_placeholder(blob, node, "compatible", comp_len, &data);
> +
> +	/* append soc compatible */
> +	fdt_appendprop_string(blob, node, "compatible", soc_comp);
> +	fdt_appendprop_string(blob, node, "compatible", "rockchip,rk3588s");

I would rather insert rk3582/rk3583 before the rk3588/rk3588s and keep 
the rest untouched instead of truncating to rockchip,rk3588/rk3588s and 
then adding rockchip,rk3588s back only. I wouldn't want my device tree 
to be modified too much and we don't know what we'll get from downstream 
)I'm not necessarily talking about Rockchip's vendor kernel here) DTBs 
for example, so truncating doesn't seem to be the best plan? What do you 
think?

Logic-wise aside from the "compatible" changes, this seems fine. I do 
not own any RK3582/RK3583 so cannot test this.

Cheers,
Quentin


More information about the U-Boot mailing list