[PATCH v2 06/12] rockchip: board: qnap-ts433: Support multiple models of the TSx33 family

Quentin Schulz quentin.schulz at cherry.de
Fri Feb 13 17:30:54 CET 2026


Hi Heiko,

On 1/14/26 9:28 AM, Heiko Stuebner wrote:
> The RK3568-based NAS systems share a common basic structure and mainly
> only differ in the number of available hard-drive slots.
> 
> They also contain EEPROMs on their mainboard and backplane, that contain
> identifying information and therefore make it possible to determine the
> model at runtime.
> 
> There are 3 different formats for identifiers found on TSx33 models in
> the wild, which can only be distinguised by the position of dashes ("-")

s/distinguised/distinguished/

> in them, so the code determines which format it is, and uses a dedicated
> decoder function to get the relevant information.
> 
> Add that EEPROM reading and model association, to set the correct
> devicetree filename for the OS that gets booted. This allows to just
> set fdtdir in extlinux and have u-boot then load the correct devicetree.
> 
> For u-boot itself, all models are similar enough up to the emmc that
> in theory we wouldn't necessarily need to diversify here, especially as
> reading the eeproms gets tricky at the stage where embedded_dtb_select
> would run. But especially the still to introduce TS133 does have a
> quite different USB setup, so definitly will need its own DTB.

s/definitly/definitely/

> 
> So for now, we'll still need separate configs per model, but can share
> the board-code. I do hope to consolidate this further in the future.
> 
> Signed-off-by: Heiko Stuebner <heiko at sntech.de>
> ---
>   board/qnap/ts433_rk3568/Makefile    |   7 +
>   board/qnap/ts433_rk3568/board.c     | 386 ++++++++++++++++++++++++++++
>   configs/qnap-ts433-rk3568_defconfig |   2 +
>   3 files changed, 395 insertions(+)
>   create mode 100644 board/qnap/ts433_rk3568/Makefile
>   create mode 100644 board/qnap/ts433_rk3568/board.c
> 
> diff --git a/board/qnap/ts433_rk3568/Makefile b/board/qnap/ts433_rk3568/Makefile
> new file mode 100644
> index 00000000000..d87f916d00b
> --- /dev/null
> +++ b/board/qnap/ts433_rk3568/Makefile
> @@ -0,0 +1,7 @@
> +#
> +# (C) Copyright 2026 Heiko Stuebner <heiko at sntech.de>
> +#
> +# SPDX-License-Identifier:     GPL-2.0+
> +#
> +
> +obj-y	+= board.o

Only build for non-SPL...

"""
ifneq ($(CONFIG_XPL_BUILD),y)
obj-y	+= board.o
endif
"""

> diff --git a/board/qnap/ts433_rk3568/board.c b/board/qnap/ts433_rk3568/board.c
> new file mode 100644
> index 00000000000..006add6fb30
> --- /dev/null
> +++ b/board/qnap/ts433_rk3568/board.c
> @@ -0,0 +1,386 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * (C) Copyright 2026 Heiko Stuebner <heiko at sntech.de>
> + */
> +
> +#include <dm.h>
> +#include <env.h>
> +#include <i2c_eeprom.h>
> +#include <init.h>
> +#include <net.h>
> +#include <netdev.h>
> +#include <vsprintf.h>
> +
> +#ifndef CONFIG_XPL_BUILD
> +

... to avoid this big ifdef.

> +DECLARE_GLOBAL_DATA_PTR;
> +
> +#define DTB_DIR			"rockchip/"
> +
> +struct tsx33_model {

Please document members (bp meaning wasn't so obvious, had to read a bit 
more to get that this was meant as backplane).

> +	const char *mb;
> +	const u8 mb_pcb;
> +	const char *bp;
> +	const u8 bp_pcb;
> +	const char *name;
> +	const char *fdtfile;
> +};
> +
> +/*
> + * gd->board_type is unsigned long, so start at 1 for actual found types

Don't understand why that justifies starting at 1?

> + * The numeric PCB ids should be matched against the highest available
> + * one for best feature matching. For example Q0AI0_13 gained per-disk
> + * gpios for presence detection and power-control. Similar changes
> + * for the other boards.
> + * So the list should be sorted higest to lowest pcb-id.

s/higest/highest/

> + */
> +enum tsx33_device_id {
> +	UNKNOWN_TSX33 = 0,
> +	Q0AI0_13,
> +	Q0AI0_11,
> +	Q0AJ0_Q0AM0_12_11,
> +	Q0AJ0_Q0AM0_11_10,
> +	Q0B20_Q0AW0_12_10,
> +	Q0B20_Q0B30_12_10,
> +	Q0B20_Q0B30_10_10,
> +	QA0110_10,
> +	TSX33_MODELS,

Because you start at 1, this means this represents n+1 supported models.

> +};
> +
> +/*
> + * All TSx33 devices consist of a mainboard and possible backplane.
> + * Each board has a model identifier and a pcb code written to an eeprom
> + * on it. Later board revisions got per-harddrive presence detection
> + * and power-supply switches, so might need an overlay applied later on
> + * to support those.

Maybe make the last sentence a TODO: ?

> + */
> +static const struct tsx33_model tsx33_models[TSX33_MODELS] = {
> +	[UNKNOWN_TSX33] = {
> +		"UNKNOWN",
> +		0,
> +		NULL,
> +		0,
> +		"Unknown TSx33",
> +		NULL,
> +	},
> +	[Q0AI0_13] = {
> +		"Q0AI0",
> +		13,
> +		NULL,
> +		0,
> +		"TS133",
> +		NULL, /* not yet supported */
> +	},
> +	[Q0AI0_11] = {
> +		"Q0AI0",
> +		11,
> +		NULL,
> +		0,
> +		"TS133",
> +		NULL, /* not yet supported */
> +	},
> +	[Q0AJ0_Q0AM0_12_11] = {
> +		"Q0AJ0",
> +		12,
> +		"Q0AM0",
> +		11,
> +		"TS233",
> +		NULL, /* not yet supported */
> +	},
> +	[Q0AJ0_Q0AM0_11_10] = {
> +		"Q0AJ0",
> +		11,
> +		"Q0AM0",
> +		10,
> +		"TS233",
> +		NULL, /* not yet supported */
> +	},
> +	[Q0B20_Q0AW0_12_10] = {
> +		"Q0B20",
> +		12,
> +		"Q0AW0",
> +		10,
> +		"TS216G",
> +		NULL, /* not yet supported */
> +	},
> +	[Q0B20_Q0B30_12_10] = {
> +		"Q0B20",
> +		12,
> +		"Q0B30",
> +		10,
> +		"TS433",
> +		DTB_DIR "rk3568-qnap-ts433.dtb",
> +	},
> +	[Q0B20_Q0B30_10_10] = {
> +		"Q0B20",
> +		10,
> +		"Q0B30",
> +		10,
> +		"TS433",
> +		DTB_DIR "rk3568-qnap-ts433.dtb",
> +	},
> +	[QA0110_10] = {
> +		/*
> +		 * The board ident in the original firmware is longer here.
> +		 * Likely the field order is different in the ident string.
> +		 * So this needs an EEPROM dump to figure out.
> +		 */
> +		"QA0110",
> +		11,
> +		NULL,
> +		0,
> +		"TS433eU",
> +		NULL, /* not yet supported */
> +	},
> +};
> +
> +enum tsx33_product_id_format {
> +	LEN_22 = 0,
> +	LEN_21,
> +	LEN_12, /* has the least amound of dashes in the string, so last */

s/amound/amount/

> +	TSX33_PRODUCT_FORMATS,
> +};
> +
> +/*
> + * We (only) identify the serial format via the different position of dashes.
> + * Because we read the product string from an eeprom there is no

there is no?

> + */
> +struct tsx33_product_format {
> +	int *dashes;
> +	int num_dashes;
> +	int (*decoder)(char *product, char *board, int *pcb);
> +};
> +
> +/*
> + * The longest board-name length known.
> + * Buffers using this need to be MAX-LEN + 1 for the final 0-termination.

s/MAX-LEN/MAX_BOARD_LEN/

But also, why? It seems the other _LEN variables do take this into 
account and you have - 1 wherever applicable, some consistency would be 
nice?

> + */
> +#define PRODUCT_MAX_BOARD_LEN		6
> +
> +#define LEN22_PRODUCT_BOARD_OFF		6
> +#define LEN22_PRODUCT_BOARD_OFF2	12
> +#define LEN22_PRODUCT_BOARD_LEN		6
> +#define LEN22_PRODUCT_PCB_OFF		16
> +#define LEN22_PRODUCT_PCB_LEN		2
> +#define LEN22_PRODUCT_BOM_OFF		14
> +#define LEN22_PRODUCT_BOM_LEN		1
> +
> +static int tsx33_decode_len22(char *product, char *board, int *pcb)
> +{

Please document this.

"We get this as input, we return this as output" such that we know 
what's supposed to happen without deciphering the implementation?

Ditto for other decode functions.

> +	strncpy(board, product + LEN22_PRODUCT_BOARD_OFF,
> +		LEN22_PRODUCT_BOARD_LEN - 1);
> +	board[LEN22_PRODUCT_BOARD_LEN - 1] = product[LEN22_PRODUCT_BOARD_OFF2];
> +
> +	/* add an artificial end to the PCB part for strtol */
> +	product[LEN22_PRODUCT_PCB_OFF + LEN22_PRODUCT_PCB_LEN] = '\0';
> +	*pcb = (int)simple_strtol(product + LEN22_PRODUCT_PCB_OFF, NULL, 0);
> +

Are you sure you want to auto-detect the base? If the string starts with 
0 it'll be understood as octal. I think you want 10 as base?

> +	return 0;
> +}
> +
> +#define LEN21_PRODUCT_BOARD_OFF		6
> +#define LEN21_PRODUCT_BOARD_OFF2	11
> +#define LEN21_PRODUCT_BOARD_LEN		5
> +#define LEN21_PRODUCT_PCB_OFF		15
> +#define LEN21_PRODUCT_PCB_LEN		2
> +#define LEN21_PRODUCT_BOM_OFF		13
> +#define LEN21_PRODUCT_BOM_LEN		1
> +
> +static int tsx33_decode_len21(char *product, char *board, int *pcb)
> +{
> +	strncpy(board, product + LEN21_PRODUCT_BOARD_OFF,
> +		LEN21_PRODUCT_BOARD_LEN - 1);
> +	board[LEN21_PRODUCT_BOARD_LEN - 1] = product[LEN21_PRODUCT_BOARD_OFF2];
> +
> +	/* add an artificial end to the PCB part for strtol */
> +	product[LEN21_PRODUCT_PCB_OFF + LEN21_PRODUCT_PCB_LEN] = '\0';
> +	*pcb = (int)simple_strtol(product + LEN21_PRODUCT_PCB_OFF, NULL, 0);
> +
> +	return 0;
> +}
> +
> +#define LEN12_PRODUCT_BOARD_OFF	4
> +#define LEN12_PRODUCT_BOARD_LEN	5
> +#define LEN12_PRODUCT_PCB_OFF	9
> +#define LEN12_PRODUCT_PCB_LEN	2
> +#define LEN12_PRODUCT_BOM_OFF	11
> +#define LEN12_PRODUCT_BOM_LEN	1
> +
> +static int tsx33_decode_len12(char *product, char *board, int *pcb)
> +{
> +	strncpy(board, product + LEN12_PRODUCT_BOARD_OFF,
> +		LEN12_PRODUCT_BOARD_LEN);
> +
> +	/* add an artificial end to the PCB part for strtol */
> +	product[LEN12_PRODUCT_PCB_OFF + LEN12_PRODUCT_PCB_LEN] = '\0';
> +	*pcb = (int)simple_strtol(product + LEN12_PRODUCT_PCB_OFF, NULL, 0);
> +
> +	return 0;
> +}

The logic seem to be awfully similar, would it make sense to have a 
single function with offsets passed as parameters?

> +
> +const struct tsx33_product_format product_formats[TSX33_PRODUCT_FORMATS] = {

Can be static I assume?

> +	[LEN_22] = {
> +		(int[]){ 2, 11, 15 },
> +		3,

If you'd declared an array, you could simply use ARRAY_SIZE(array) and 
never have to worry about them being unsynced.

> +		tsx33_decode_len22,
> +	},
> +	[LEN_21] = {
> +		(int[]){ 2, 10, 14 },
> +		3,
> +		tsx33_decode_len21,
> +	},
> +	[LEN_12] = {
> +		(int[]){ 2 },
> +		1,
> +		tsx33_decode_len12,
> +	},
> +};
> +
> +static int tsx33_decode_product(char *product, char *board, int *pcb)
> +{
> +	const struct tsx33_product_format *format;
> +	int i, j;
> +
> +	/* Find the correct serial variant */
> +	for (i = 0; i < TSX33_PRODUCT_FORMATS; i++) {

ARRAY_SIZE(product_formats)?

> +		format = &product_formats[i];
> +
> +		for (j = 0; j < format->num_dashes; j++) {
> +			int dash_char = format->dashes[j];
> +
> +			if (product[dash_char] != '-')
> +				goto next_format;
> +		}

You can use strstr() instead of looping on all characters manually.

Cheers,
Quentin


More information about the U-Boot mailing list