Rockchip platform reference for the RK RT-Thread SDK, covering RK2108/RK2118 (SPI2APB mainly RK2118). Matching ArmSoM board support is unconfirmed; no hardware validation was performed. Check SoC, SDK/BSP, RT-Thread version, controller type, IOMUX, wiring and voltage domains before adaptation. Commands and test results are source examples, not ArmSoM measurements; no hardware or flash operations were executed. Only a Chinese original is available; all four languages use Chinese source figures.
RK-KF-YF-498 · V1.2.0
1. Introduction
1.1 Important notes
SPI2APB overlays an RK proprietary protocol on standard SPI, unlike the more general RK SPI without this extra protocol. Take this distinction into account when reviewing schematics.
1.2 Controller features
Supports SPI modes 0/2 (mode 0 recommended), 8-bit transfers and interrupts; reads/writes internal controller registers. LSB/MSB and Little Endian/Big Endian are configurable. Only simplex transfers and slave mode are supported. The SPI master must send data according to SPI2APB. Basic protocol: <CMD-32bits> [ADDR-32bits] [DUMMY-32bits] [READ_BEGING-32bits] [DATA]. Specific protocols follow.
| Operation | Protocol | CMD-32bits Little Endian |
|---|---|---|
| Read data | read data | <0x00000077> <ADDR> <DUMMY> <READ_BEGING> <DATA> |
| Write data | write data | <0x00000011> <ADDR> <DATA> |
| Write internal REG0 | write msg reg0 | <0x00010011> <DATA-32bits> |
| Write internal REG1, trigger interrupt | write msg reg1 | <0x00020011> <DATA-32bits> |
| Previous transfer status | query state | <0x000000ff> <DATA-32bits> |
| Read internal REG2 | query msg reg2 | <0x000001ff> <DATA-32bits> |
[ADDR-32bits] is an SPI2APB-supported memory address, usually SRAM/DRAM. [DUMMY-32bits] is unrestricted and may be 0. [READ_BEGING-32bits] is 0x000000AA. For query state return values see “State Register Information” in the TRM SPI2APB chapter.
1.3 Interface characteristics
| SoC | Max rate |
|---|---|
| RK2118 | 50MHz |
The maximum rate is theoretical; routing and PCB quality affect the actual result, which must be measured.
2. Software
2.1 Source paths
Framework code:
components/drivers/include/drivers/spi.h
components/drivers/spi/spi_core.c
components/drivers/spi/spi_dev.c
Driver adaptation layer:
bsp/rockchip/common/drivers/drv_spi2apb.c
bsp/rockchip/common/hal/lib/hal/src/hal_spi2apb.c
Test commands; applications may follow this driver:
bsp/rockchip-common/tests/spi2apb_test.c
2.2 Configuration
Enable the option whose name includes SPI2APB.
RT-Thread bsp drivers --->
RT-Thread rockchip "project" drivers --->
[*] Enable SPI
[*] Enable SPI0 (SPI2APB)
[ ] Enable SPI1
[ ] Enable SPI2
Configure board-level iomux.
2.3 Usage configuration
Defaults: LSB, Little Endian. Example modification in drv_spi2apb.c:
diff --git a/bsp/rockchip/common/drivers/drv_spi2apb.c b/bsp/rockchip/common/drivers/drv_spi2apb.c
index 15045270fe..ef055d8d03 100644
--- a/bsp/rockchip/common/drivers/drv_spi2apb.c
+++ b/bsp/rockchip/common/drivers/drv_spi2apb.c
@@ -45,7 +45,7 @@ struct rockchip_spi2apb
static struct rockchip_spi2apb rk_spi2apb =
{
- .device.config.mode = (SPI2APB_LITTLE_ENDIAN | SPI2APB_LSB),
+ .device.config.mode = (SPI2APB_LITTLE_ENDIAN | SPI2APB_MSB),
.device.config.clock_polarity = SPI2APB_TXCP_INVERT,
.base = SPI2APB,
.irq = SPISLV0_IRQn,
Only LSB/MSB changes are recommended; keep the other defaults.
3. Testing
3.1 Configuration
RT-Thread bsp test case --->
[*] RT-Thread Common Test case --->
[*] Enable BSP Common TEST
[*] Enable BSP Common SPI2APB TEST
3.2 Test commands
spi2apb_test config spi2apb 1 2 # <mode> <polarity>
spi2apb_test cb spi2apb # 注册 SPI2APB->REG1 中断回调
spi2apb_test read spi2apb 0 # 读取 SPI2APB->REG0 信息,建议调试方式,先主 设备端参考 'write msg reg0' 协议写,后从设备端执行该命令读
spi2apb_test read spi2apb 1 # 读取 SPI2APB->REG1 信息,建议调试方式,先主 端参考 'write msg reg1' 协议写,后从端执行该命令读
spi2apb_test query spi2apb # 读取 SPI2APB->SR 信息
spi2apb_test write spi2apb 0x12345678 # 写入 SPI2APB->REG2 信息,建议调试方式,先从 端执行该命令,后主端参考 'query msg reg2' 协议读
Master = SPI master; slave = RK SPI2APB slave. Configurable mode: bit0 0-LSB/1-MSB; bit1 0-LittleEndian/1-BigEndian. Configurable polarity is for debugging only; retain TX invert/RX normal in actual use, i.e. polarity =
- bit0: 0-RX normal, 1-RX invert; bit1: 0-TX normal, 1-TX invert.
4. Application integration
Both peers must implement data flow and control information according to SPI2APB. Master-side application code and drivers may be developed from RK reference code. Source location cited by the original: SPI development materials — FAE project — Rockchip Redmine (rock-chips.com).
4.1 Basic architecture

Chinese source figure (p.7)
Figure API/sequence transcription (original identifiers and numbering; not executable code)
spidev_rkspi2apb_app -> spidev_rkspi2apb.c -> SPI driver -> spi master
spi2apb_test.c <-> SPI2APB RTOS driver -> SPI2APB IP -> spi2apb slave
SPI2APB IP -> SRAM
Example: RV1106 SPI master connected to RK2118 SPI2APB. Source components:
| Layer | Master (Linux 5.10) | Slave (HAL + RTOS) |
|---|---|---|
| Application | spidev_rkspi2apb_app: ioctl/read/write | test_spi2apb.c: REG |
| Kernel driver | spidev_rkspi2apb.c: SPI2APB ioctl/read/write | |
| Controller driver | spi-rockchip.c | drv_spi2apb.c hal_spi2apb.c |
| Controller | spi master | spi2apb slave |
spidev_rkspi2apb.c ioctl interface:
#define SPIDEV_RKSPI2APB_GET_DEV_STATE _IOR(SPIDEV_RKSPI2APB_BASE, 0, __u32)
/* 参考 'query msg reg2' 协议 */
#define SPIDEV_RKSPI2APB_SET_DST_ADDR _IOW(SPIDEV_RKSPI2APB_BASE, 1, __u32)
/* 设置传输的目标内存地址,指向从设备端内存地址 */
#define SPIDEV_RKSPI2APB_SEND_MSG _IOW(SPIDEV_RKSPI2APB_BASE, 2, __u32) /* 参考 'write msg reg0' 协议 */
#define SPIDEV_RKSPI2APB_INT_REQ _IOW(SPIDEV_RKSPI2APB_BASE, 3, __u32) /* 参考 'write msg reg1' 协议 */
Read/write in spidev_rkspi2apbc.c (source spelling):
write(spidev_fd, buffer_w, size); /* 参考 'write data' 协议,addr 由 ioctl SPIDEV_RKSPI2APB_SET_DST_ADDR 设定 */
read(spidev_fd, buffer_r, size); /* 参考 'read data' 协议,addr 由 ioctl SPIDEV_RKSPI2APB_SET_DST_ADDR 设定 */
Target-address agreement: presetting is recommended. Reserve a known SRAM address on the slave; set it on the master with ioctl SPIDEV_RKSPI2APB_SET_DST_ADDR. RT-Thread generally reserves the memory through spi2apb_buffer_start in the linker script. Alternatively allocate SRAM on the slave and implement a custom exchange to send its address to the master, as in the sequence below.

Chinese source figure (p.9)
Figure API/sequence transcription (original identifiers and numbering; not executable code)
SPI Master APP -> ioctl SPIDEV_RKSPI2APB_INT_REQ -> SPI master
SPI master -> write reg1 -> SPI2APB slave -> interrupt call back -> SPI2APB APP
SPI2APB APP -> write address info to reg2 -> SPI2APB slave
SPI Master APP -> ioctl SPIDEV_RKSPI2APB_GET_DEV_STATE -> read reg2
4.2 Flash Object service
Flash Object transports requests through spi/spi2apb so the SPI master can read/write the SPI NOR attached to the SPI2APB slave.
4.2.1 Basic architecture

Chinese source figure (p.9)
Figure API/sequence transcription (original identifiers and numbering; not executable code)
spi2apb_flash_obj_mst.c -> drv_spi.c -> spi master
spi2apb_test.c -> SRAM_SPI2APB_BASE
spi2apb slave <-> SRAM_SPI2APB_BASE <-> spinor
NOR flash read sequence:

Chinese source figure (p.10)
Figure API/sequence transcription (original identifiers and numbering; not executable code)
1. cmd (CMD_READ)
2. write reg1
3. set reg2 (SLAVE_BUSY)
4. rt_mtd_nor_read
5. set reg (SLAVE_SUCCESS)
6. wait status (SLAVE_SUCCESS)
7. read data
8. read data
NOR flash write sequence:

Chinese source figure (p.10)
Figure API/sequence transcription (original identifiers and numbering; not executable code)
1. write data
2. write data
3. cmd (CMD_PROG)
4. write reg1
5. set reg2 (SLAVE_BUSY)
6. rt_mtd_nor_erase/write
6. set reg (SLAVE_SUCCESS)
7. wait status (SLAVE_SUCCESS)
4.2.2 Interfaces
flash_obj master interface:
rt_err_t spi2apb_flash_obj_mst_init(struct rt_spi_device *spi_device);
/**
* @brief Write sector from remote spinor device.
* @param sec: pointer to spinor 4KB sector offset.
* @param buffer: data buffer.
* @return rt_err_t
*/
rt_err_t spi2apb_flash_obj_mst_write_sector(rt_uint32_t sec, void *buffer);
/**
* @brief Read sector from remote spinor device.
* @param sec: pointer to spinor 4KB sector offset.
* @param buffer: data buffer.
* @return rt_err_t
*/
rt_err_t spi2apb_flash_obj_mst_read_sector(rt_uint32_t sec, void *buffer);
flash_obj master SPI2APB wrapper interface:
/**
* @brief Write data to remote sram by spi2apb interface.
* @param addr: pointer to remote sram address.
* @param buffer: data buffer.
* @param size: data size.
* @param trigger: if valid write spi2apb reg1 to trigger a interrupt.
* @return rt_err_t
*/
static rt_err_t spi2apb_flash_obj_mst_spi2apb_progsram(uint32_t addr, uint8_t
*buffer, uint32_t size, uint32_t trigger);
/**
* @brief Read data from remote sram by spi2apb interface.
* @param addr: pointer to remote sram address.
* @param buffer: data buffer.
* @param size: data size.
* @param trigger: if valid write spi2apb reg1 to trigger a interrupt.
* @return rt_err_t
*/
static rt_err_t spi2apb_flash_obj_mst_spi2apb_readsram(uint32_t addr, uint8_t
*buffer, uint32_t size, uint32_t trigger);
/**
* @brief Write msg reg in spi2apb.
* @param index: reg index.
* @param val: reg value.
* @param status: if valid check write success.
* @return rt_err_t
*/
static rt_err_t spi2apb_flash_obj_mst_spi2apb_writemsg(uint8_t index, uint32_t
value, bool status);
/**
* @brief Query msg reg2 in spi2apb.
* @return rt_err_t
*/
static uint32_t spi2apb_flash_obj_mst_spi2apb_querymsg(void);
/**
* @brief Query state abort last transmission.
* @return uint32_t status.
*/
static uint32_t spi2apb_flash_obj_mst_spi2apb_querystatus(void);
flash_obj slave interface:
rt_err_t spi_test_spi2apb_flash_obj_init(void);
static void spi_test_spi2apb_flash_obj_work(struct rt_work *work, void
*work_data);
rt_err_t spi_test_spi2apb_flash_obj_write(rt_uint8_t index, rt_uint16_t addr,
void *buffer);
rt_err_t spi_test_spi2apb_flash_obj_read(rt_uint8_t index, rt_uint16_t addr, void
*buffer);
rt_err_t spi_test_spi2apb_flash_obj_set_status(rt_uint8_t index, rt_uint8_t
status);
After initialization, register the SPI2APB interrupt callback. The reg1 bottom-half workqueue parses transactions, performs spinor read/write and sets state-machine status bits.
4.2.3 Flash obj configuration
CONFIG_RT_USING_COMMON_TEST_SPI2APB_FLASH_OBJ = y
4.2.4 Flash obj test commands
flash_obj slave commands:
spi2apb_test flash_obj spi2apb
flash_obj master commands:
1. spi2apb_flash_obj_mst spi2apb_progsram <spi_dev> <size>
2. spi2apb_flash_obj_mst spi2apb_readsram <spi_dev> <size>
3. spi2apb_flash_obj_mst spi2apb_writemsg <spi_dev> <reg> <val>
4. spi2apb_flash_obj_mst spi2apb_querymsg <spi_dev>
5. spi2apb_flash_obj_mst write_sector <spi_dev> <sector> <val> <loops>
6. spi2apb_flash_obj_mst read_sector <spi_dev> <sector> <loops>
7. spi2apb_flash_obj_mst stress <spi_dev> <sector> <n_sec>
like:
spi2apb_flash_obj_mst spi2apb_progsram spi1_0 32
spi2apb_flash_obj_mst spi2apb_readsram spi1_0 32
spi2apb_flash_obj_mst spi2apb_writemsg spi1_0 1 55
spi2apb_flash_obj_mst spi2apb_querymsg spi1_0
spi2apb_flash_obj_mst write_sector spi1_0 512 1 10
spi2apb_flash_obj_mst read_sector spi1_0 512 10
spi2apb_flash_obj_mst stress spi1_0 512 32
spi2apb_progsram uses SPI2APB write data; after writing, write msg reg1 triggers the SPI2APB interrupt. spi2apb_readsram uses read data, first waiting for reg2 status SPI2APB_FLASH_OBJ_CMD_READ_SRAM_READY. spi2apb_writemsg uses write msg reg0/1; spi2apb_querymsg uses read msg reg2. write_sector writes NOR flash; read_sector reads NOR flash; stress writes then reads back for stress testing. All three use a 4KB sector unit.
5. Troubleshooting
5.1 SPI2APB transfer failure
Following “Testing”, configure SPI mode 0, lsb, little-endian and register the callback:
spi2apb_test config spi2apb 1 2
spi2apb_test cb spi2apb
The master must follow the data-transfer protocol. First try write reg1 to trigger the SPI2APB interrupt. Check common ground, valid master csn/clock on an oscilloscope, slave wiring and unpopulated 0Ω resistors. For incorrect received data, reduce speed. Check for LSB/MSB or endianness mismatch. If only some data is valid and the pattern correlates with cache line size, the slave probably omitted cache-coherency operations around data access: normally invalidate after read transfer and flush before write transfer. If the read buffer was cleared or written beforehand, flush before transfer too.
