From 5c7449b185a58fe8e9e290a31401908adefc2f2a Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Thu, 13 Aug 2026 14:02:43 -0300 Subject: mtd: spi-nor-ids: Add XMC XM25QH128C Add the JEDEC ID for the XMC XM25QH128C SPI NOR flash. This is a 128Mbit device using 64KiB sectors and supporting 4KiB erase, dual read and quad read, matching the other XM25QH128 variants. Link: https://www.xmcwh.com/uploads/435/XM25QH128C.pdf Cc: Vignesh Raghavendra Cc: Takahiro Kuwano Signed-off-by: Fabio Estevam Reviewed-by: Takahiro Kuwano Link: https://patch.msgid.link/20260813170246.110899-1-festevam@gmail.com Signed-off-by: Quentin Schulz --- drivers/mtd/spi/spi-nor-ids.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/spi/spi-nor-ids.c b/drivers/mtd/spi/spi-nor-ids.c index 31a2ba49a87..90aa1025176 100644 --- a/drivers/mtd/spi/spi-nor-ids.c +++ b/drivers/mtd/spi/spi-nor-ids.c @@ -673,6 +673,7 @@ const struct flash_info spi_nor_ids[] = { { INFO("XM25QH64A", 0x207017, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { INFO("XM25QH64C", 0x204017, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { INFO("XM25QH128A", 0x207018, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { INFO("XM25QH128C", 0x204018, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { INFO("XM25QU128C", 0x204118, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { INFO("XM25QH256C", 0x204019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) }, { INFO("XM25QU256C", 0x204119, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) }, -- cgit v1.3.1 From 042b5d91747405f6d15bf32f8aa9432acc37b355 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Thu, 13 Aug 2026 14:02:44 -0300 Subject: rockchip: rk3399: Remove ROC-PC regulator overrides The upstream devicetree already describes dc_12v as the output of the MP8859 regulator and drives the USB hub reset through the vcc5v0_host regulator pinctrl state. Remove the legacy fixed-regulator overrides and rely on the upstream regulator descriptions. Signed-off-by: Fabio Estevam Reviewed-by: Quentin Schulz Link: https://patch.msgid.link/20260813170246.110899-2-festevam@gmail.com Signed-off-by: Quentin Schulz --- arch/arm/dts/rk3399-roc-pc-u-boot.dtsi | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/arch/arm/dts/rk3399-roc-pc-u-boot.dtsi b/arch/arm/dts/rk3399-roc-pc-u-boot.dtsi index 883d399a06a..a85e9549c83 100644 --- a/arch/arm/dts/rk3399-roc-pc-u-boot.dtsi +++ b/arch/arm/dts/rk3399-roc-pc-u-boot.dtsi @@ -10,30 +10,6 @@ config { sysreset-gpio = <&gpio1 RK_PA6 GPIO_ACTIVE_HIGH>; }; - - vcc_hub_en: vcc_hub_en-regulator { - compatible = "regulator-fixed"; - enable-active-high; - gpio = <&gpio2 RK_PA4 GPIO_ACTIVE_HIGH>; - pinctrl-names = "default"; - pinctrl-0 = <&hub_rst>; - regulator-name = "vcc_hub_en"; - regulator-always-on; - }; -}; - -/* - * should be placed inside mp8859, but not until mp8859 has - * its own dt-binding. - */ -&dc_12v { - compatible = "regulator-fixed"; - regulator-name = "dc_12v"; - regulator-always-on; - regulator-boot-on; - regulator-min-microvolt = <12000000>; - regulator-max-microvolt = <12000000>; - vin-supply = <&vcc_vbus_typec0>; }; &gpio4 { -- cgit v1.3.1 From 66ecacc5e4a3afa547244ce9c68937023188340a Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Thu, 13 Aug 2026 14:02:45 -0300 Subject: rockchip: rk3399: Add ROC-PC-PLUS board detection The ROC-RK3399-PC has an MP8859 regulator on I2C7 at address 0x66, while the ROC-RK3399-PC-PLUS does not. Probe for the regulator in SPL and use the result to select the matching devicetree from the U-Boot FIT. Fall back to the original board if the I2C bus cannot be probed. Build both devicetrees from the existing roc-pc-rk3399_defconfig and enable the XMC SPI NOR driver used by the Plus variant. Set fdtfile from the selected U-Boot devicetree so the matching Linux devicetree is used as well. Signed-off-by: Fabio Estevam Reviewed-by: Quentin Schulz Link: https://patch.msgid.link/20260813170246.110899-3-festevam@gmail.com Signed-off-by: Quentin Schulz --- arch/arm/dts/rk3399-roc-pc-u-boot.dtsi | 8 ++++++ board/firefly/roc-pc-rk3399/roc-pc-rk3399.c | 42 +++++++++++++++++++++++++++++ configs/roc-pc-rk3399_defconfig | 3 +++ doc/board/rockchip/rockchip.rst | 2 +- 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/arch/arm/dts/rk3399-roc-pc-u-boot.dtsi b/arch/arm/dts/rk3399-roc-pc-u-boot.dtsi index a85e9549c83..e7e4a2c8907 100644 --- a/arch/arm/dts/rk3399-roc-pc-u-boot.dtsi +++ b/arch/arm/dts/rk3399-roc-pc-u-boot.dtsi @@ -12,6 +12,14 @@ }; }; +&i2c7 { + bootph-pre-ram; +}; + +&i2c7_xfer { + bootph-pre-ram; +}; + &gpio4 { bootph-pre-ram; }; diff --git a/board/firefly/roc-pc-rk3399/roc-pc-rk3399.c b/board/firefly/roc-pc-rk3399/roc-pc-rk3399.c index 6937a27176f..5a71e94ae1e 100644 --- a/board/firefly/roc-pc-rk3399/roc-pc-rk3399.c +++ b/board/firefly/roc-pc-rk3399/roc-pc-rk3399.c @@ -5,14 +5,24 @@ #include #include +#include +#include +#include #include #include +#include #include #include #include #include +#define ROC_PC_MP8859_BUS "i2c@ff160000" +#define ROC_PC_MP8859_ADDR 0x66 +#define ROC_PC_PLUS_FDTFILE "rockchip/rk3399-roc-pc-plus.dtb" + +DECLARE_GLOBAL_DATA_PTR; + #ifdef CONFIG_XPL_BUILD #define PMUGRF_BASE 0xff320000 @@ -54,4 +64,36 @@ void led_setup(void) spl_gpio_output(gpio0, GPIO(BANK_B, 5), 1); } +static bool is_roc_pc_plus(void) +{ + struct udevice *bus, *dev; + + if (!(CONFIG_IS_ENABLED(I2C) && CONFIG_IS_ENABLED(DM_I2C))) + return false; + + if (uclass_get_device_by_name(UCLASS_I2C, ROC_PC_MP8859_BUS, &bus)) + return false; + + return dm_i2c_probe(bus, ROC_PC_MP8859_ADDR, 0, &dev); +} + +int board_fit_config_name_match(const char *name) +{ + if (is_roc_pc_plus()) + return strcmp(name, ROC_PC_PLUS_FDTFILE); + + return strcmp(name, CONFIG_DEFAULT_FDT_FILE); +} + #endif + +int rk_board_late_init(void) +{ + if (!fdt_node_check_compatible(gd->fdt_blob, 0, + "firefly,roc-rk3399-pc-plus")) + env_set("fdtfile", ROC_PC_PLUS_FDTFILE); + else + env_set("fdtfile", CONFIG_DEFAULT_FDT_FILE); + + return 0; +} diff --git a/configs/roc-pc-rk3399_defconfig b/configs/roc-pc-rk3399_defconfig index 1df5cb03adb..53182dc8251 100644 --- a/configs/roc-pc-rk3399_defconfig +++ b/configs/roc-pc-rk3399_defconfig @@ -26,6 +26,7 @@ CONFIG_DISPLAY_BOARDINFO_LATE=y CONFIG_SPL_MAX_SIZE=0x40000 # CONFIG_SPL_RAW_IMAGE_SUPPORT is not set CONFIG_SPL_ENV_SUPPORT=y +CONFIG_SPL_I2C=y CONFIG_SPL_SPI_LOAD=y CONFIG_SYS_SPI_U_BOOT_OFFS=0xE0000 CONFIG_SPL_ATF_NO_PLATFORM_PARAM=y @@ -37,6 +38,7 @@ CONFIG_CMD_USB=y # CONFIG_CMD_SETEXPR is not set CONFIG_CMD_TIME=y CONFIG_SPL_OF_CONTROL=y +CONFIG_OF_LIST="rockchip/rk3399-roc-pc rockchip/rk3399-roc-pc-plus" CONFIG_OF_SPL_REMOVE_PROPS="clock-names interrupt-parent assigned-clocks assigned-clock-rates assigned-clock-parents" CONFIG_ENV_IS_IN_SPI_FLASH=y CONFIG_ENV_RELOC_GD_ENV_ADDR=y @@ -49,6 +51,7 @@ CONFIG_MMC_SDHCI_SDMA=y CONFIG_MMC_SDHCI_ROCKCHIP=y CONFIG_SPI_FLASH_SFDP_SUPPORT=y CONFIG_SPI_FLASH_WINBOND=y +CONFIG_SPI_FLASH_XMC=y CONFIG_DM_ETH_PHY=y CONFIG_ETH_DESIGNWARE=y CONFIG_GMAC_ROCKCHIP=y diff --git a/doc/board/rockchip/rockchip.rst b/doc/board/rockchip/rockchip.rst index de2df3046e2..d9c4b753949 100644 --- a/doc/board/rockchip/rockchip.rst +++ b/doc/board/rockchip/rockchip.rst @@ -81,7 +81,7 @@ List of mainline supported Rockchip boards: - 96boards RK3399 Ficus (ficus-rk3399) - 96boards Rock960 (rock960-rk3399) - Firefly-RK3399 (firefly_rk3399) - - Firefly ROC-RK3399-PC + - Firefly ROC-RK3399-PC/ROC-RK3399-PC-PLUS (roc-pc-rk3399) - FriendlyElec NanoPC-T4 (nanopc-t4-rk3399) - FriendlyElec NanoPi M4 (nanopi-m4-rk3399) - FriendlyElec NanoPi M4B (nanopi-m4b-rk3399) -- cgit v1.3.1 From bd9fe520badd5314558084d86e01afba34e4ef31 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Thu, 13 Aug 2026 14:02:46 -0300 Subject: rockchip: roc-pc-rk3399: Enable redundant environment Keep the primary U-Boot environment at its existing 0x3f8000 offset and use the preceding 32 KiB region at 0x3f0000 for the redundant copy. Both copies are aligned to the 4 KiB erase size and together occupy 0x3f0000 through 0x3fffff. Redundancy protects the environment against power loss or an interrupted update. Signed-off-by: Fabio Estevam Reviewed-by: Quentin Schulz Link: https://patch.msgid.link/20260813170246.110899-4-festevam@gmail.com Signed-off-by: Quentin Schulz --- configs/roc-pc-rk3399_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configs/roc-pc-rk3399_defconfig b/configs/roc-pc-rk3399_defconfig index 53182dc8251..4347bab598a 100644 --- a/configs/roc-pc-rk3399_defconfig +++ b/configs/roc-pc-rk3399_defconfig @@ -17,6 +17,7 @@ CONFIG_SYS_LOAD_ADDR=0x800800 CONFIG_SF_DEFAULT_BUS=1 CONFIG_DEBUG_UART_BASE=0xFF1A0000 CONFIG_DEBUG_UART_CLOCK=24000000 +CONFIG_ENV_OFFSET_REDUND=0x3F0000 CONFIG_SPL_SPI_FLASH_SUPPORT=y CONFIG_SPL_SPI=y CONFIG_DEBUG_UART=y @@ -41,6 +42,7 @@ CONFIG_SPL_OF_CONTROL=y CONFIG_OF_LIST="rockchip/rk3399-roc-pc rockchip/rk3399-roc-pc-plus" CONFIG_OF_SPL_REMOVE_PROPS="clock-names interrupt-parent assigned-clocks assigned-clock-rates assigned-clock-parents" CONFIG_ENV_IS_IN_SPI_FLASH=y +CONFIG_ENV_REDUNDANT=y CONFIG_ENV_RELOC_GD_ENV_ADDR=y CONFIG_ROCKCHIP_GPIO=y CONFIG_SYS_I2C_ROCKCHIP=y -- cgit v1.3.1 From 300cdfa08c53e89ff277b16a719e2796e9ffa4ad Mon Sep 17 00:00:00 2001 From: Cole Munz Date: Fri, 21 Aug 2026 11:55:02 +0000 Subject: spi: Handle spi-{tx, rx}-bus-width 0 as SPI_NO_TX/SPI_NO_RX The spi-peripheral-props binding shipped in dts/upstream allows a bus width of 0, meaning no RX or TX is possible on this device. The switches in spi_slave_of_to_plat() only handle 1/2/4/8, so a width of 0 falls through to the default case and warns "spi-rx-bus-width 0 not supported" on every boot, even though the devicetree is valid per the binding. The fact that the wire is missing is then dropped from plat->mode. Map 0 to new SPI_NO_TX/SPI_NO_RX mode bits, as Linux has done since v5.12 ("spi: Add SPI_NO_TX/RX support", mainline d962608ce218). Bits 16 and 17 are the first free mode bits. Mapping the bits is not enough on its own, as Quentin pointed out: nothing would stop a caller from asking for a transfer in a direction that has no wire, and every controller driver would need its own guard. Validate centrally in dm_spi_xfer() instead, matching Linux's __spi_validate(): a din on a SPI_NO_RX device or a dout on a SPI_NO_TX device fails with -EINVAL before it reaches the driver. A new sandbox test covers both rejections. This comes up on devices with no MISO line at all, such as a write-only SPI display described with spi-rx-bus-width = <0>. Signed-off-by: Cole Munz Acked-by: Quentin Schulz Link: https://patch.msgid.link/ae6a85d6f985b63c8f5a3fe8deeab89d79757b14.1787309754.git.Munzzyy1@proton.me Signed-off-by: Quentin Schulz --- drivers/spi/spi-uclass.c | 16 ++++++++++++++++ include/spi.h | 2 ++ test/dm/spi.c | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/drivers/spi/spi-uclass.c b/drivers/spi/spi-uclass.c index 120565df149..dd1843ffac1 100644 --- a/drivers/spi/spi-uclass.c +++ b/drivers/spi/spi-uclass.c @@ -105,6 +105,7 @@ int dm_spi_set_wordlen(struct udevice *dev, unsigned int wordlen) int dm_spi_xfer(struct udevice *dev, unsigned int bitlen, const void *dout, void *din, unsigned long flags) { + struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev); struct udevice *bus = dev->parent; struct dm_spi_ops *ops = spi_get_ops(bus); @@ -113,6 +114,15 @@ int dm_spi_xfer(struct udevice *dev, unsigned int bitlen, if (!ops->xfer) return -ENOSYS; + /* + * A device with no wire in one direction cannot transfer in it, + * so reject the request here rather than in every driver. + */ + if (din && (slave_plat->mode & SPI_NO_RX)) + return -EINVAL; + if (dout && (slave_plat->mode & SPI_NO_TX)) + return -EINVAL; + return ops->xfer(dev, bitlen, dout, din, flags); } @@ -229,6 +239,9 @@ static int spi_child_post_bind(struct udevice *dev) /* Device DUAL/QUAD mode */ value = dev_read_u32_default(dev, "spi-tx-bus-width", 1); switch (value) { + case 0: + mode |= SPI_NO_TX; + break; case 1: break; case 2: @@ -247,6 +260,9 @@ static int spi_child_post_bind(struct udevice *dev) value = dev_read_u32_default(dev, "spi-rx-bus-width", 1); switch (value) { + case 0: + mode |= SPI_NO_RX; + break; case 1: break; case 2: diff --git a/include/spi.h b/include/spi.h index 97096a77526..f477763bdc6 100644 --- a/include/spi.h +++ b/include/spi.h @@ -34,6 +34,8 @@ struct spinand_info; #define SPI_RX_QUAD BIT(13) /* receive with 4 wires */ #define SPI_TX_OCTAL BIT(14) /* transmit with 8 wires */ #define SPI_RX_OCTAL BIT(15) /* receive with 8 wires */ +#define SPI_NO_TX BIT(16) /* no transmit wire */ +#define SPI_NO_RX BIT(17) /* no receive wire */ /* Header byte that marks the start of the message */ #define SPI_PREAMBLE_END_BYTE 0xec diff --git a/test/dm/spi.c b/test/dm/spi.c index a89ba06274f..a0da12ab6bc 100644 --- a/test/dm/spi.c +++ b/test/dm/spi.c @@ -216,3 +216,42 @@ static int dm_test_spi_xfer(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_spi_xfer, UTF_SCAN_PDATA | UTF_SCAN_FDT); + +/* Test that a transfer is rejected when the device has no wire for it */ +static int dm_test_spi_xfer_no_rx_tx(struct unit_test_state *uts) +{ + struct dm_spi_slave_plat *plat; + struct spi_slave *slave; + struct udevice *bus; + const int busnum = 0, cs = 0; + const char dout[5] = {0x9f}; + unsigned char din[5]; + uint saved_mode; + + ut_assertok(spi_get_bus_and_cs(busnum, cs, &bus, &slave)); + ut_assertok(spi_claim_bus(slave)); + plat = dev_get_parent_plat(slave->dev); + saved_mode = plat->mode; + + plat->mode |= SPI_NO_RX; + ut_asserteq(-EINVAL, spi_xfer(slave, 40, dout, din, + SPI_XFER_BEGIN | SPI_XFER_END)); + + plat->mode = saved_mode | SPI_NO_TX; + ut_asserteq(-EINVAL, spi_xfer(slave, 40, dout, din, + SPI_XFER_BEGIN | SPI_XFER_END)); + + plat->mode = saved_mode; + spi_release_bus(slave); + + /* + * Since we are about to destroy all devices, we must tell sandbox + * to forget the emulation device + */ +#if CONFIG_IS_ENABLED(DM_SPI_FLASH) + sandbox_sf_unbind_emul(state_get_current(), busnum, cs); +#endif + + return 0; +} +DM_TEST(dm_test_spi_xfer_no_rx_tx, UTF_SCAN_PDATA | UTF_SCAN_FDT); -- cgit v1.3.1 From ed58bafd0b24e15b734c624bdaf00b876e5d32e6 Mon Sep 17 00:00:00 2001 From: Cole Munz Date: Fri, 21 Aug 2026 11:55:06 +0000 Subject: spi: rockchip: skip the unused FIFO direction on a one-wire device The controller has a transfer-mode field that can run transmit-only or receive-only instead of both, which leaves the unused FIFO out of the transfer entirely. The driver never used it for that: claim_bus always programmed TMOD_TR, and the only other mode came from an opportunistic switch to TMOD_RO for read-only transfers. A device described with spi-{tx,rx}-bus-width = <0> has no wire in that direction at all, so now that the width reaches plat->mode as SPI_NO_TX/SPI_NO_RX, pick the transfer mode from it. A write-only display stops clocking receive bytes nobody reads. The transmit-only case needs one more change. The 8-bit loop paces itself on the receive FIFO and sets toread unconditionally, so with no receive path it would wait on a FIFO that stays empty forever. Leave toread at zero there and let the existing wait_till_not_busy() at the end of the chunk handle completion, which is the same thing that already covers a transmit component today. The restore at the end of a read-only transfer went back to a hardcoded TMOD_TR, which would undo the device's own mode. Restore what the mode asks for instead. Signed-off-by: Cole Munz Tested-by: Alexey Charkov Reviewed-by: Quentin Schulz Link: https://patch.msgid.link/3cf8dc75d461caf6076c35275021bbcdc0943de1.1787309754.git.Munzzyy1@proton.me Signed-off-by: Quentin Schulz --- drivers/spi/rk_spi.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/drivers/spi/rk_spi.c b/drivers/spi/rk_spi.c index 2c3d70ba715..54befd54b09 100644 --- a/drivers/spi/rk_spi.c +++ b/drivers/spi/rk_spi.c @@ -283,6 +283,20 @@ static int rockchip_spi_probe(struct udevice *bus) return 0; } +/* + * A device that declares spi-{tx,rx}-bus-width = <0> has no wire in that + * direction, so the controller can drop the matching FIFO entirely instead + * of clocking bytes nobody reads. + */ +static u32 rkspi_base_tmod(struct rockchip_spi_priv *priv) +{ + if (priv->mode & SPI_NO_RX) + return TMOD_TO; + if (priv->mode & SPI_NO_TX) + return TMOD_RO; + return TMOD_TR; +} + static int rockchip_spi_claim_bus(struct udevice *dev) { struct udevice *bus = dev->parent; @@ -329,8 +343,8 @@ static int rockchip_spi_claim_bus(struct udevice *dev) /* Frame Format */ ctrlr0 |= FRF_SPI << FRF_SHIFT; - /* Tx and Rx mode */ - ctrlr0 |= TMOD_TR << TMOD_SHIFT; + /* Configure RX/TX mode */ + ctrlr0 |= rkspi_base_tmod(priv) << TMOD_SHIFT; writel(ctrlr0, ®s->ctrlr0); @@ -472,7 +486,11 @@ static int rockchip_spi_xfer(struct udevice *dev, unsigned int bitlen, writel(todo - 1, ®s->ctrlr1); rkspi_enable_chip(regs, true); - toread = todo; + /* + * When the RX wire is not routed, the RX FIFO never fills, + * so waiting on it would hang. + */ + toread = (priv->mode & SPI_NO_RX) ? 0 : todo; /* Only write if we have something to write */ towrite = out ? todo : 0; while (toread || towrite) { @@ -492,9 +510,10 @@ static int rockchip_spi_xfer(struct udevice *dev, unsigned int bitlen, } /* - * In case that there's a transmit-component, we need to wait - * until the control goes idle before we can disable the SPI - * control logic (as this will implicitly flush the FIFOs). + * With a transmit component the TX FIFO can still hold data + * that has not been shifted onto the wire. Wait until the + * controller goes idle before disabling it, as disabling + * clears the FIFOs. */ if (out) { ret = rkspi_wait_till_not_busy(regs); @@ -513,7 +532,7 @@ static int rockchip_spi_xfer(struct udevice *dev, unsigned int bitlen, if (!out) clrsetbits_le32(®s->ctrlr0, TMOD_MASK << TMOD_SHIFT, - TMOD_TR << TMOD_SHIFT); + rkspi_base_tmod(priv) << TMOD_SHIFT); return ret; } -- cgit v1.3.1