浏览代码

bsp: k230: add spi driver

Requirement: The BSP for the k230 platform in the RT-Thread repository does not yet have an spi driver.

Solution: Provide spi driver for the k230 platform in the RT-Thread repository.

- Supports SPI0(OSPI) controller with 1/2/4/8 data lines.
- Supports SPI1(QSPI0) and SPI2(QSPI1) controllers with 1/2/4 data lines.
- Implements DMA-based transfers for OSPI, QSPI, and DSPI modes.
- Falls back to standard IRQ-driven transfers for legacy SPI mode (single line).
- Updates documentation in bsp/README.md

Signed-off-by: ChuanN-sudo <fjchuanil@gmail.com>
Chuan 2 月之前
父节点
当前提交
ae2a5758bc

+ 3 - 3
bsp/README.md

@@ -760,9 +760,9 @@ This document is based on the RT-Thread mainline repository and categorizes the
 
 #### 🟢 K230 (RT-Smart)
 
-| BSP Name | GPIO | UART | I2C | RTC | ADC | PWM | SDIO | HWTimer | WDT |
-|----------|------|------|-----|-----|-----|-----|------|---------|-----|
-| [k230](k230) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| BSP Name | GPIO | UART | I2C | RTC | ADC | PWM | SDIO | HWTimer | WDT | SPI |
+|----------|------|------|-----|-----|-----|-----|------|---------|-----|-----|
+| [k230](k230) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
 
 #### 🟢 Xuantie (RT-Smart)
 

+ 6 - 0
bsp/k230/.ci/attachconfig/ci.attachconfig.yml

@@ -1,6 +1,12 @@
 scons.args: &scons
     scons_arg:
       - '--strict'
+devices.spi:
+    <<: *scons
+    kconfig:
+      - CONFIG_RT_USING_SPI=y
+      - CONFIG_BSP_USING_SPI=y
+      - CONFIG_BSP_USING_SPI0=y
 devices.i2c:
     <<: *scons
     kconfig:

+ 8 - 2
bsp/k230/.config

@@ -534,8 +534,6 @@ CONFIG_RT_USING_ADT_REF=y
 # CONFIG_RT_USING_RT_LINK is not set
 # end of Utilities
 
-# CONFIG_RT_USING_VBUS is not set
-
 #
 # Memory management
 #
@@ -943,6 +941,7 @@ CONFIG_RT_USING_VDSO=y
 # CONFIG_PKG_USING_R_RHEALSTONE is not set
 # CONFIG_PKG_USING_HEARTBEAT is not set
 # CONFIG_PKG_USING_MICRO_ROS_RTTHREAD_PACKAGE is not set
+# CONFIG_PKG_USING_CHERRYECAT is not set
 # end of system packages
 
 #
@@ -1100,6 +1099,12 @@ CONFIG_RT_USING_VDSO=y
 # CONFIG_PKG_USING_GD32_ARM_CMSIS_DRIVER is not set
 # CONFIG_PKG_USING_GD32_ARM_SERIES_DRIVER is not set
 # end of GD32 Drivers
+
+#
+# HPMicro SDK
+#
+# CONFIG_PKG_USING_HPM_SDK is not set
+# end of HPMicro SDK
 # end of HAL & SDK Drivers
 
 #
@@ -1619,6 +1624,7 @@ CONFIG_PKG_ZLIB_VER="latest"
 #
 # Drivers Configuration
 #
+# CONFIG_BSP_USING_SPI is not set
 # CONFIG_BSP_USING_I2C is not set
 # CONFIG_BSP_USING_RTC is not set
 # CONFIG_BSP_USING_ADC is not set

+ 27 - 0
bsp/k230/board/Kconfig

@@ -1,4 +1,31 @@
 menu "Drivers Configuration"
+
+    menuconfig BSP_USING_SPI
+        bool "Enable SPI"
+        select RT_USING_SPI
+        select RT_USING_QSPI
+        default n
+
+        if BSP_USING_SPI
+            config BSP_USING_SPI0
+                bool "Enable SPI0"
+                help
+                    Support 1, 2, 4 and 8 lines, Max clock frequency is 200 Mhz.
+                default n
+
+            config BSP_USING_SPI1
+                bool "Enable SPI1"
+                help
+                    Support 1, 2, and 4 lines, Max clock frequency is 100 Mhz.
+                default n
+
+            config BSP_USING_SPI2
+                bool "Enable SPI2"
+                help
+                    Support 1, 2, and 4 lines, Max clock frequency is 100 Mhz.
+                default n
+        endif
+
     menuconfig BSP_USING_I2C
         bool "Enable I2C"
         select RT_USING_I2C

+ 11 - 0
bsp/k230/drivers/interdrv/spi/SConscript

@@ -0,0 +1,11 @@
+# RT-Thread building script for SPI component
+
+from building import *
+
+cwd     = GetCurrentDir()
+src     = Glob('*.c')
+CPPPATH = [cwd]
+
+group = DefineGroup('SPI', src, depend = ['BSP_USING_SPI'], CPPPATH = CPPPATH)
+
+Return('group')

+ 734 - 0
bsp/k230/drivers/interdrv/spi/drv_spi.c

@@ -0,0 +1,734 @@
+/* Copyright (c) 2023, Canaan Bright Sight Co., Ltd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 2006-2025 RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#include "drv_spi.h"
+#include <drivers/dev_spi.h>
+#include <string.h>
+#include "drv_gpio.h"
+#include "riscv_io.h"
+#include "board.h"
+#include "ioremap.h"
+#include "sysctl_rst.h"
+#include "sysctl_clk.h"
+#include "cache.h"
+
+#define DBG_TAG "spi"
+#include <rtdbg.h>
+
+struct k230_spi_dev
+{
+    struct rt_spi_bus dev;
+    void *base;
+    const char *name;
+    const char *event_name;
+    rt_ubase_t pa_base;
+    rt_uint32_t size;
+    rt_uint8_t idx;
+    rt_uint8_t rdse;
+    rt_uint8_t rdsd;
+    rt_uint8_t max_line;
+    rt_uint32_t max_hz;
+    struct rt_event event;
+    void *send_buf;
+    void *recv_buf;
+    rt_size_t send_length;
+    rt_size_t recv_length;
+    rt_uint8_t cell_size;
+    int vector;
+};
+
+static rt_err_t k230_spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *configuration)
+{
+    RT_ASSERT(device != RT_NULL);
+    RT_ASSERT(configuration != RT_NULL);
+    struct k230_spi_dev *qspi_bus = (struct k230_spi_dev *)device->bus;
+    k230_spi_reg_t *qspi_reg = (k230_spi_reg_t *)qspi_bus->base;
+    struct rt_qspi_device *dev = (struct rt_qspi_device *)device;
+    struct rt_qspi_configuration *qspi_cfg = &dev->config;
+    struct rt_spi_configuration *qspi_cfg_parent = &dev->config.parent;
+
+    rt_uint8_t dfs, mode, spi_ff;
+    rt_uint32_t max_hz, qspi_clk;
+    if (qspi_cfg->qspi_dl_width > qspi_bus->max_line || qspi_cfg->qspi_dl_width == 0)
+    {
+        return -RT_EINVAL;
+    }
+    if (qspi_cfg_parent->data_width < 4 || qspi_cfg_parent->data_width > 32)
+    {
+        return -RT_EINVAL;
+    }
+
+    /* Check if the clock frequency exceeds the hardware limits */
+    max_hz = qspi_cfg_parent->max_hz;
+    if (max_hz > qspi_bus->max_hz)
+    {
+        max_hz = qspi_bus->max_hz;
+    }
+
+    /* Get QSPI Controller Clock Frequency */
+    if (qspi_bus->idx == 0)
+    {
+        qspi_clk = sysctl_clk_get_leaf_freq(SYSCTL_CLK_SSI0);
+    }
+    else if (qspi_bus->idx == 1)
+    {
+        qspi_clk = sysctl_clk_get_leaf_freq(SYSCTL_CLK_SSI1);
+    }
+    else if (qspi_bus->idx == 2)
+    {
+        qspi_clk = sysctl_clk_get_leaf_freq(SYSCTL_CLK_SSI2);
+    }
+    else
+    {
+        return -RT_EINVAL;
+    }
+
+    /* Set SPI_FRF to config SPI mode*/
+    if (qspi_cfg->qspi_dl_width == 1)
+    {
+        spi_ff = SPI_FRF_STD_SPI;
+    }
+    else if (qspi_cfg->qspi_dl_width == 2)
+    {
+        spi_ff = SPI_FRF_DUAL_SPI;
+    }
+    else if (qspi_cfg->qspi_dl_width == 4)
+    {
+        spi_ff = SPI_FRF_QUAD_SPI;
+    }
+    else if (qspi_cfg->qspi_dl_width == 8)
+    {
+        spi_ff = SPI_FRF_OCT_SPI;
+    }
+    else
+    {
+        return -RT_EINVAL;
+    }
+    /*
+     * dfs: Data Frame Size, Write dfs into bits 8-9 of the register ctrlr0
+     * mode: SPI mode(CPOL and CPHA),Write mode into bits 0-4 of the register ctrlr0
+     */
+    mode = qspi_cfg_parent->mode & RT_SPI_MODE_3;
+    dfs = qspi_cfg_parent->data_width - 1;
+
+    qspi_reg->ssienr = 0;
+    qspi_reg->ser = 0;
+    qspi_reg->baudr = qspi_clk / max_hz;
+    qspi_reg->rx_sample_delay = qspi_bus->rdse << 16 | qspi_bus->rdsd;
+    qspi_reg->axiawlen = SSIC_AXI_BLW << 8;
+    qspi_reg->axiarlen = SSIC_AXI_BLW << 8;
+    qspi_reg->ctrlr0 = (dfs) | (mode << 8) | (spi_ff << 22);
+    return RT_EOK;
+}
+
+static rt_ssize_t k230_spi_xfer(struct rt_spi_device *device, struct rt_spi_message *message)
+{
+    struct k230_spi_dev *qspi_bus = (struct k230_spi_dev *)device->bus;
+    k230_spi_reg_t *qspi_reg = (k230_spi_reg_t *)qspi_bus->base;
+    struct rt_qspi_device *dev = (struct rt_qspi_device *)device;
+    struct rt_qspi_configuration *qspi_cfg = &dev->config;
+    struct rt_spi_configuration *qspi_cfg_parent = &dev->config.parent;
+    struct rt_qspi_message *msg = (struct rt_qspi_message *)message;
+    struct rt_spi_message *msg_parent = message;
+    /* Multi-line SPI transfers (2, 4, or 8 lines) DSPI QSPI OSPI  */
+    if (msg->qspi_data_lines > 1)
+    {
+        rt_uint8_t trans_type = 0;
+        if (msg->qspi_data_lines > qspi_cfg->qspi_dl_width)
+        {
+            LOG_E("data line is invalid");
+            return 0;
+        }
+        /* Check other parameters */
+        if (qspi_cfg_parent->data_width & (msg->qspi_data_lines - 1))
+        {
+            LOG_E("data line and data width do not match");
+            return 0;
+        }
+        if (msg->instruction.qspi_lines != 1 && msg->instruction.qspi_lines != msg->qspi_data_lines)
+        {
+            LOG_E("instruction line is invalid");
+            return 0;
+        }
+        if (msg->address.size & 3 || msg->address.size > 32)
+        {
+            LOG_E("address size is invalid");
+            return 0;
+        }
+        if (msg->address.size && msg->address.qspi_lines != 1 && msg->address.qspi_lines != msg->qspi_data_lines)
+        {
+            LOG_E("address line is invalid");
+            return 0;
+        }
+        if (msg_parent->length > 0x10000)
+        {
+            LOG_E("data length is invalid, more than 0x10000");
+            return 0;
+        }
+        if (msg->instruction.qspi_lines != 1)
+        {
+            trans_type = 2;
+        }
+        if (msg->address.size)
+        {
+            if (msg->address.qspi_lines != 1)
+            {
+                trans_type = trans_type ? trans_type : 1;
+
+            }
+            else if (trans_type != 0)
+            {
+                LOG_E("instruction or address line is invalid");
+                return 0;
+            }
+        }
+        if (msg->dummy_cycles > 31)
+        {
+            LOG_E("dummy cycle is invalid");
+            return 0;
+        }
+
+        rt_uint8_t tmod = msg_parent->recv_buf ? SPI_TMOD_RO : SPI_TMOD_TO;
+        rt_size_t length = msg_parent->length;
+        rt_size_t txfthr = length > (SSIC_TX_ABW / 2) ? (SSIC_TX_ABW / 2) : length - 1;
+        rt_uint8_t cell_size = (qspi_cfg_parent->data_width + 7) >> 3;
+        rt_uint8_t *buf = RT_NULL;
+        /* Allocate buffer for DMA transfer */
+        if (length)
+        {
+            buf = rt_malloc_align(CACHE_ALIGN_TOP(length * cell_size), L1_CACHE_BYTES);
+            if (buf == RT_NULL)
+            {
+                LOG_E("alloc mem error");
+                return 0;
+            }
+        }
+        /* msg->address.size & ~0x03:  Round address.size down to the nearest multiple of 4 and write it to ADDR_L[5:2] */
+        qspi_reg->spi_ctrlr0 = trans_type | (msg->address.size & ~0x03) | (0x2 << 8) | (msg->dummy_cycles << 11);
+        qspi_reg->ctrlr0 &= ~((3 << 22) | (3 << 10));
+
+        /* Config SPI frame format and transmission mode */
+        if (length)
+        {
+            qspi_reg->ctrlr0 |= (tmod << 10);
+            qspi_reg->txftlr = (txfthr << 16) | (SSIC_TX_ABW / 2);
+            qspi_reg->rxftlr = (SSIC_RX_ABW - 1);
+            qspi_reg->imr = (1 << 11) | (1 << 8);
+            qspi_reg->dmacr = (1 << 6) | (3 << 3) | (1 << 2);
+            qspi_reg->ctrlr1 = length - 1;
+            qspi_reg->spidr = msg->instruction.content;
+            qspi_reg->spiar = msg->address.content;
+            if (tmod == SPI_TMOD_TO)
+            {
+                rt_memcpy(buf, msg_parent->send_buf, length * cell_size);
+                rt_hw_cpu_dcache_clean(buf, CACHE_ALIGN_TOP(length * cell_size));
+            }
+            qspi_reg->axiar0 = (rt_uint32_t)((uint64_t)buf);
+            qspi_reg->axiar1 = (rt_uint32_t)((uint64_t)buf >> 32);
+        }
+        else
+        {
+            tmod = SPI_TMOD_TO;
+            qspi_reg->ctrlr0 |= (tmod << 10);
+            qspi_reg->txftlr = ((SSIC_TX_ABW - 1) << 16) | (SSIC_TX_ABW - 1);
+            qspi_reg->rxftlr = (SSIC_RX_ABW - 1);
+            qspi_reg->imr = 0;
+            qspi_reg->dmacr = 0;
+        }
+        rt_event_control(&qspi_bus->event, RT_IPC_CMD_RESET, 0);
+
+        qspi_reg->ser = 1;
+        qspi_reg->ssienr = 1;
+        rt_uint32_t event;
+        rt_err_t err;
+
+        /*
+         * Config QSPI address and instruction,
+         * if data is empty, unable dma and send address and instruction by write data register.
+         */
+        if (length)
+        {
+            err = rt_event_recv(&qspi_bus->event, BIT(SSI_DONE) | BIT(SSI_AXIE),
+                                RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, 1000, &event);
+        }
+        else
+        {
+            err = RT_EOK;
+            event = 0;
+            qspi_reg->dr[0] = msg->instruction.content;
+            length++;
+            if (msg->address.size)
+            {
+                qspi_reg->dr[0] = msg->address.content;
+                length++;
+            }
+            qspi_reg->txftlr = 0;
+            /* Wait for SPI transfer to complete (busy-wait) */
+            while ((qspi_reg->sr & 0x5) != 0x4)
+            {
+                /* Busy wait */
+            }
+        }
+        qspi_reg->ser = 0;
+        qspi_reg->ssienr = 0;
+        if (err == -RT_ETIMEOUT)
+        {
+            LOG_E("qspi%d transfer data timeout", qspi_bus->idx);
+            if (buf)
+            {
+                rt_free_align(buf);
+            }
+            return 0;
+        }
+        if (event & BIT(SSI_AXIE))
+        {
+            LOG_E("qspi%d dma error", qspi_bus->idx);
+            if (buf)
+            {
+                rt_free_align(buf);
+            }
+            return 0;
+        }
+        /* Read data from FIFO */
+        if (tmod == SPI_TMOD_RO)
+        {
+            rt_hw_cpu_dcache_invalidate(buf, CACHE_ALIGN_TOP(length * cell_size));
+            rt_memcpy(msg_parent->recv_buf, buf, length * cell_size);
+        }
+        if (buf)
+        {
+            rt_free_align(buf);
+        }
+        return length;
+    }
+    /* Standard SPI transfers */
+    else
+    {
+        if (msg_parent->length == 0)
+        {
+            return 0;
+        }
+        rt_uint8_t cell_size = (qspi_cfg_parent->data_width + 7) >> 3;
+        rt_size_t length = msg_parent->length;
+        rt_size_t count = length > 0x10000 ? 0x10000 : length;
+        rt_size_t send_single = 0, send_length = 0, recv_single = 0, recv_length = 0;
+        void *send_buf = (void *)msg_parent->send_buf;
+        void *recv_buf = msg_parent->recv_buf;
+        rt_uint8_t tmod = send_buf ? SPI_TMOD_TO : SPI_TMOD_EPROMREAD;
+        tmod = recv_buf ? tmod & SPI_TMOD_RO : tmod;
+        /* Check Qspi Parameters */
+        if (tmod == SPI_TMOD_EPROMREAD)
+        {
+            LOG_E("send_buf and recv_buf cannot both be empty");
+            return 0;
+        }
+        if (tmod == SPI_TMOD_RO && qspi_cfg_parent->data_width == 8)
+        {
+            if ((msg->address.size & 7) || (msg->dummy_cycles & 7))
+            {
+                LOG_E("instruction, address, dummy_cycles invalid");
+                LOG_E("For read-only mode the instruction, address, dummy_cycles must be set to zero");
+                LOG_E("For eeprom-read mode the instruction, address, dummy_cycles must be set to multiples of 8");
+                return 0;
+            }
+            else if (msg->address.size)
+            {
+                if (length > 0x10000)
+                {
+                    LOG_E("For eeprom-read mode, data length cannot exceed 0x10000");
+                    return 0;
+                }
+                tmod = SPI_TMOD_EPROMREAD;
+            }
+        }
+        /* Prepare the send buffer*/
+        if (send_buf)
+        {
+            send_single = count;
+            send_buf = rt_malloc(count * cell_size);
+            if (send_buf == RT_NULL)
+            {
+                LOG_E("alloc mem error");
+                return 0;
+            }
+            rt_memcpy(send_buf, msg_parent->send_buf, count * cell_size);
+        }
+        else if (tmod == SPI_TMOD_EPROMREAD)
+        {
+            send_single = 1 + msg->address.size / 8 + msg->dummy_cycles / 8;
+            send_buf = rt_malloc(send_single);
+            if (send_buf == RT_NULL)
+            {
+                LOG_E("alloc mem error");
+                return 0;
+            }
+            rt_uint8_t *temp = send_buf;
+            *temp++ = msg->instruction.content;
+            for (int i = msg->address.size / 8; i; i--)
+            {
+                *temp++ = msg->address.content >> ((i - 1) * 8);
+            }
+            for (int i = msg->dummy_cycles / 8; i; i--)
+            {
+                *temp++ = 0xFF;
+            }
+        }
+        /* Prepare the receive buffer*/
+        if (recv_buf)
+        {
+            recv_single = count;
+            recv_buf = rt_malloc(count * cell_size);
+            if (recv_buf == RT_NULL)
+            {
+                LOG_E("alloc mem error");
+                if (send_buf)
+                {
+                    rt_free(send_buf);
+                }
+                return 0;
+            }
+        }
+        send_length = 0;
+        recv_length = 0;
+        qspi_bus->cell_size = cell_size;
+        qspi_bus->send_buf = send_buf;
+        qspi_bus->recv_buf = recv_buf;
+        qspi_bus->send_length = send_single;
+        qspi_bus->recv_length = recv_single;
+        qspi_reg->ctrlr0 &= ~((3 << 22) | (3 << 10));
+        qspi_reg->ctrlr0 |= (tmod << 10);
+        qspi_reg->ctrlr1 = count - 1;
+        qspi_reg->txftlr = ((SSIC_TX_ABW / 2) << 16) | (SSIC_TX_ABW / 2);
+        qspi_reg->rxftlr = count >= (SSIC_RX_ABW / 2) ? (SSIC_RX_ABW / 2 - 1) : count - 1;
+        qspi_reg->dmacr = 0;
+        /* Interrupt transmit or receive */
+        qspi_reg->imr = (1 << 4) | (1 << 0);
+        rt_event_control(&qspi_bus->event, RT_IPC_CMD_RESET, 0);
+        qspi_reg->ser = 1;
+        qspi_reg->ssienr = 1;
+        if (tmod == SPI_TMOD_RO)
+            qspi_reg->dr[0] = 0;
+        rt_uint32_t event;
+        rt_err_t err;
+        while (RT_TRUE)
+        {
+            /* Waiting for transfer events */
+            err = rt_event_recv(&qspi_bus->event, BIT(SSI_TXE) | BIT(SSI_RXF),
+                                RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, 10000, &event);
+            if (err == -RT_ETIMEOUT)
+            {
+                LOG_E("qspi%d transfer data timeout", qspi_bus->idx);
+                length = 0;
+                if (send_buf)
+                {
+                    rt_free(send_buf);
+                }
+                if (recv_buf)
+                {
+                    rt_free(recv_buf);
+                }
+                return 0;
+            }
+            /* Handle Transmit buffer empty */
+            if (event & BIT(SSI_TXE))
+            {
+                send_length += send_single;
+                if (send_length < length && tmod <= SPI_TMOD_TO)
+                {
+                    count = length - send_length;
+                    count = count > 0x10000 ? 0x10000 : count;
+                    rt_memcpy(send_buf, msg_parent->send_buf + send_length * cell_size, count * cell_size);
+                    qspi_bus->send_buf = send_buf;
+                    qspi_bus->send_length = count;
+                    send_single = count;
+                    qspi_reg->txftlr = ((SSIC_TX_ABW / 2) << 16) | (SSIC_TX_ABW / 2);
+                    if (tmod == SPI_TMOD_TO)
+                        qspi_reg->imr |= (1 << 0);
+                }
+                else if (tmod == SPI_TMOD_TO)
+                {
+                    /* Wait for SPI transfer to complete (busy-wait) */
+                    while ((qspi_reg->sr & 0x5) != 0x4)
+                    {
+                        /* Busy wait */
+                    }
+                    break;
+                }
+            }
+            /* Handle receive buffer full */
+            if (event & BIT(SSI_RXF))
+            {
+                rt_memcpy(msg_parent->recv_buf + recv_length * cell_size, recv_buf, recv_single * cell_size);
+                recv_length += recv_single;
+                if (recv_length >= length)
+                {
+                    break;
+                }
+                count = length - recv_length;
+                count = count > 0x10000 ? 0x10000 : count;
+                qspi_bus->recv_buf = recv_buf;
+                qspi_bus->recv_length = count;
+                recv_single = count;
+                qspi_reg->rxftlr = count >= (SSIC_RX_ABW / 2) ? (SSIC_RX_ABW / 2 - 1) : count - 1;
+                if (tmod == SPI_TMOD_TR)
+                {
+                    qspi_reg->imr |= (1 << 0) | (1 << 4);
+                }
+                else if (tmod == SPI_TMOD_RO)
+                {
+                    qspi_reg->imr |= (1 << 4);
+                    qspi_reg->ssienr = 0;
+                    qspi_reg->ctrlr1 = count - 1;
+                    qspi_reg->ssienr = 1;
+                    /* Trigger two dummy transfers to restart SPI read in read-only mode.
+                     * This is required by the hardware to ensure correct data reception.
+                     */
+                    qspi_reg->dr[0] = 0;
+                    qspi_reg->dr[0] = 0;
+                }
+            }
+        }
+        qspi_reg->ser = 0;
+        qspi_reg->ssienr = 0;
+        if (send_buf)
+        {
+            rt_free(send_buf);
+        }
+        if (recv_buf)
+        {
+            rt_free(recv_buf);
+        }
+        return length;
+    }
+    return 0;
+}
+
+static const struct rt_spi_ops k230_qspi_ops =
+{
+    .configure = k230_spi_configure,
+    .xfer = k230_spi_xfer,
+};
+
+static void k230_spi_irq(int vector, void *param)
+{
+    struct k230_spi_dev *qspi_bus = param;
+    k230_spi_reg_t *qspi_reg = (k230_spi_reg_t *)qspi_bus->base;
+    vector -= IRQN_SPI0;
+    vector %= (IRQN_SPI1 - IRQN_SPI0);
+
+    /* Handle transmit buffer empty interrupt */
+    if (vector == SSI_TXE)
+    {
+        if (qspi_bus->send_buf == RT_NULL)
+        {
+            qspi_reg->imr &= ~1;
+        }
+        else if (qspi_bus->cell_size == 1)
+        {
+            while ((qspi_bus->send_length) && (qspi_reg->sr & 2))
+            {
+                qspi_reg->dr[0] = *((rt_uint8_t *)qspi_bus->send_buf);
+                qspi_bus->send_buf++;
+                qspi_bus->send_length--;
+            }
+        }
+        else if (qspi_bus->cell_size == 2)
+        {
+            while ((qspi_bus->send_length) && (qspi_reg->sr & 2))
+            {
+                qspi_reg->dr[0] = *((rt_uint16_t *)qspi_bus->send_buf);
+                qspi_bus->send_buf += 2;
+                qspi_bus->send_length--;
+            }
+        }
+        else if (qspi_bus->cell_size == 4)
+        {
+            while ((qspi_bus->send_length) && (qspi_reg->sr & 2))
+            {
+                qspi_reg->dr[0] = *((rt_uint32_t *)qspi_bus->send_buf);
+                qspi_bus->send_buf += 4;
+                qspi_bus->send_length--;
+            }
+        }
+        else
+        {
+            LOG_E("qspi%d datawidth error", qspi_bus->idx);
+        }
+        if (qspi_bus->send_length == 0)
+        {
+            if (((qspi_reg->ctrlr0 >> 10) & SPI_TMOD_EPROMREAD) == SPI_TMOD_TO)
+            {
+                if (qspi_reg->txftlr)
+                    return;
+            }
+            qspi_reg->txftlr = 0;
+            qspi_reg->imr &= ~1;
+            rt_event_send(&qspi_bus->event, BIT(SSI_TXE));
+        }
+    }
+    /* Handle receive buffer full interrupt */
+    else if (vector == SSI_RXF)
+    {
+        if (qspi_bus->recv_buf == RT_NULL)
+        {
+            qspi_reg->imr &= ~0x10;
+        }
+        else if (qspi_bus->cell_size == 1)
+        {
+            while ((qspi_bus->recv_length) && (qspi_reg->sr & 8))
+            {
+                *((rt_uint8_t *)qspi_bus->recv_buf) = qspi_reg->dr[0];
+                qspi_bus->recv_buf++;
+                qspi_bus->recv_length--;
+            }
+        }
+        else if (qspi_bus->cell_size == 2)
+        {
+            while ((qspi_bus->recv_length) && (qspi_reg->sr & 8))
+            {
+                *((rt_uint16_t *)qspi_bus->recv_buf) = qspi_reg->dr[0];
+                qspi_bus->recv_buf += 2;
+                qspi_bus->recv_length--;
+            }
+        }
+        else if (qspi_bus->cell_size == 4)
+        {
+            while ((qspi_bus->recv_length) && (qspi_reg->sr & 8))
+            {
+                *((rt_uint32_t *)qspi_bus->recv_buf) = qspi_reg->dr[0];
+                qspi_bus->recv_buf += 4;
+                qspi_bus->recv_length--;
+            }
+        }
+        else
+        {
+            LOG_E("qspi%d datawidth error", qspi_bus->idx);
+        }
+        if (qspi_bus->recv_length == 0)
+        {
+            qspi_reg->imr &= ~0x10;
+            rt_event_send(&qspi_bus->event, BIT(SSI_RXF));
+        }
+        else if (qspi_bus->recv_length <= qspi_reg->rxftlr)
+        {
+            qspi_reg->rxftlr = qspi_bus->recv_length - 1;
+        }
+    }
+    /* Handle transfer complete interrupt */
+    else if (vector == SSI_DONE)
+    {
+        /* Clear transfer done interrupt by reading donecr register */
+        (void)qspi_reg->donecr;
+        rt_event_send(&qspi_bus->event, BIT(SSI_DONE));
+    }
+    /* Handle DMA error interrupt */
+    else if (vector == SSI_AXIE)
+    {
+        /* Clear AXI error interrupt by reading axiecr register */
+        (void)qspi_reg->axiecr;
+        rt_event_send(&qspi_bus->event, BIT(SSI_AXIE));
+    }
+}
+static struct k230_spi_dev k230_spi_devs[] =
+{
+#ifdef BSP_USING_SPI0
+    {
+        .name = "spi0",
+        .event_name = "spi0_event",
+        .pa_base = SPI_OPI_BASE_ADDR,
+        .size = SPI_OPI_IO_SIZE,
+        .vector = IRQN_SPI0,
+        .idx = 0,
+        .rdse = 0,
+        .rdsd = 0,
+        .max_line = 8,
+        .max_hz = 200000000,
+    },
+#endif
+#ifdef BSP_USING_SPI1
+    {
+        .name = "spi1",
+        .event_name = "spi1_event",
+        .pa_base = SPI_QOPI_BASE_ADDR,
+        .size = SPI_QOPI_IO_SIZE / 2,
+        .vector = IRQN_SPI1,
+        .idx = 1,
+        .rdse = 0,
+        .rdsd = 0,
+        .max_line = 4,
+        .max_hz = 100000000,
+    },
+#endif
+#ifdef BSP_USING_SPI2
+    {
+        .name = "spi2",
+        .event_name = "spi2_event",
+        .pa_base = SPI_QOPI_BASE_ADDR + SPI_QOPI_IO_SIZE / 2,
+        .size = SPI_QOPI_IO_SIZE / 2,
+        .vector = IRQN_SPI2,
+        .idx = 2,
+        .rdse = 0,
+        .rdsd = 0,
+        .max_line = 4,
+        .max_hz = 100000000,
+    },
+#endif
+};
+
+int rt_hw_qspi_bus_init(void)
+{
+    rt_err_t ret;
+    int i;
+    for (i = 0; i < sizeof(k230_spi_devs) / sizeof(k230_spi_devs[0]); i++)
+    {
+        k230_spi_devs[i].base = rt_ioremap((void *)k230_spi_devs[i].pa_base, k230_spi_devs[i].size);
+        ret = rt_qspi_bus_register(&k230_spi_devs[i].dev, k230_spi_devs[i].name, &k230_qspi_ops);
+        if (ret)
+        {
+            LOG_E("%s register fail", k230_spi_devs[i].name);
+            return ret;
+        }
+        rt_event_init(&k230_spi_devs[i].event, k230_spi_devs[i].event_name, RT_IPC_FLAG_PRIO);
+        rt_hw_interrupt_install(k230_spi_devs[i].vector + SSI_TXE, k230_spi_irq, &k230_spi_devs[i], k230_spi_devs[i].name);
+        rt_hw_interrupt_umask(k230_spi_devs[i].vector  + SSI_TXE);
+        rt_hw_interrupt_install(k230_spi_devs[i].vector  + SSI_RXF, k230_spi_irq, &k230_spi_devs[i], k230_spi_devs[i].name);
+        rt_hw_interrupt_umask(k230_spi_devs[i].vector  + SSI_RXF);
+        rt_hw_interrupt_install(k230_spi_devs[i].vector  + SSI_DONE, k230_spi_irq, &k230_spi_devs[i], k230_spi_devs[i].name);
+        rt_hw_interrupt_umask(k230_spi_devs[i].vector  + SSI_DONE);
+        rt_hw_interrupt_install(k230_spi_devs[i].vector  + SSI_AXIE, k230_spi_irq, &k230_spi_devs[i], k230_spi_devs[i].name);
+        rt_hw_interrupt_umask(k230_spi_devs[i].vector  + SSI_AXIE);
+    }
+    return RT_EOK;
+}
+INIT_DEVICE_EXPORT(rt_hw_qspi_bus_init);

+ 203 - 0
bsp/k230/drivers/interdrv/spi/drv_spi.h

@@ -0,0 +1,203 @@
+/* Copyright (c) 2023, Canaan Bright Sight Co., Ltd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 2006-2025 RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef __DRV_SPI_H__
+#define __DRV_SPI_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#define SSIC_HAS_DMA 2
+#define SSIC_AXI_BLW 8
+#define SSIC_TX_ABW 256
+#define SSIC_RX_ABW 256
+
+#define IRQN_SPI0 146
+#define IRQN_SPI1 155
+#define IRQN_SPI2 164
+
+#ifndef L1_CACHE_BYTES
+    #define L1_CACHE_BYTES 64
+#endif
+
+#define CACHE_ALIGN_TOP(x) (((x) + L1_CACHE_BYTES - 1) & ~(L1_CACHE_BYTES - 1))
+#define CACHE_ALIGN_BOTTOM(x) ((x) & ~(L1_CACHE_BYTES - 1))
+#define BIT(n) (1UL << (n))
+
+enum
+{
+    SSI_TXE = 0,
+    SSI_TXO,
+    SSI_RXF,
+    SSI_RXO,
+    SSI_TXU,
+    SSI_RXU,
+    SSI_MST,
+    SSI_DONE,
+    SSI_AXIE,
+};
+/* SPI mode */
+enum
+{
+    SPI_FRF_STD_SPI,
+    SPI_FRF_DUAL_SPI,
+    SPI_FRF_QUAD_SPI,
+    SPI_FRF_OCT_SPI,
+};
+
+/* SPI transmit mode */
+enum
+{
+    SPI_TMOD_TR,
+    SPI_TMOD_TO,
+    SPI_TMOD_RO,
+    SPI_TMOD_EPROMREAD,
+};
+
+/* Qspi register */
+typedef struct
+{
+    /* SPI Control Register 0                                    (0x00)*/
+    volatile uint32_t ctrlr0;
+    /* SPI Control Register 1                                    (0x04)*/
+    volatile uint32_t ctrlr1;
+    /* SPI Enable Register                                       (0x08)*/
+    volatile uint32_t ssienr;
+    /* SPI Microwire Control Register                            (0x0c)*/
+    volatile uint32_t mwcr;
+    /* SPI Slave Enable Register                                 (0x10)*/
+    volatile uint32_t ser;
+    /* SPI Baud Rate Select                                      (0x14)*/
+    volatile uint32_t baudr;
+    /* SPI Transmit FIFO Threshold Level                         (0x18)*/
+    volatile uint32_t txftlr;
+    /* SPI Receive FIFO Threshold Level                          (0x1c)*/
+    volatile uint32_t rxftlr;
+    /* SPI Transmit FIFO Level Register                          (0x20)*/
+    volatile uint32_t txflr;
+    /* SPI Receive FIFO Level Register                           (0x24)*/
+    volatile uint32_t rxflr;
+    /* SPI Status Register                                       (0x28)*/
+    volatile uint32_t sr;
+    /* SPI Interrupt Mask Register                               (0x2c)*/
+    volatile uint32_t imr;
+    /* SPI Interrupt Status Register                             (0x30)*/
+    volatile uint32_t isr;
+    /* SPI Raw Interrupt Status Register                         (0x34)*/
+    volatile uint32_t risr;
+    /* SPI Transmit FIFO Underflow Interrupt Clear Register      (0x38)*/
+    volatile uint32_t txeicr;
+    /* SPI Receive FIFO Overflow Interrupt Clear Register        (0x3c)*/
+    volatile uint32_t rxoicr;
+    /* SPI Receive FIFO Underflow Interrupt Clear Register       (0x40)*/
+    volatile uint32_t rxuicr;
+    /* SPI Multi-Master Interrupt Clear Register                 (0x44)*/
+    volatile uint32_t msticr;
+    /* SPI Interrupt Clear Register                              (0x48)*/
+    volatile uint32_t icr;
+    /* SPI DMA Control Register                                  (0x4c)*/
+    volatile uint32_t dmacr;
+#if SSIC_HAS_DMA == 1
+    /* SPI DMA Transmit Data Level                               (0x50)*/
+    volatile uint32_t dmatdlr;
+    /* SPI DMA Receive Data Level                                (0x54)*/
+    volatile uint32_t dmardlr;
+#elif SSIC_HAS_DMA == 2
+    /* SPI Destination Burst Length                              (0x50)*/
+    volatile uint32_t axiawlen;
+    /* SPI Source Burst Length                                   (0x54)*/
+    volatile uint32_t axiarlen;
+#else
+    uint32_t resv0[2];
+#endif
+    /* SPI Identification Register                               (0x58)*/
+    volatile const uint32_t idr;
+    /* SPI DWC_ssi component version                             (0x5c)*/
+    volatile uint32_t ssic_version_id;
+    /* SPI Data Register 0-36                                    (0x60 -- 0xec)*/
+    volatile uint32_t dr[36];
+    /* SPI RX Sample Delay Register                              (0xf0)*/
+    volatile uint32_t rx_sample_delay;
+    /* SPI SPI Control Register                                  (0xf4)*/
+    volatile uint32_t spi_ctrlr0;
+    /* SPI Transmit Drive Edge Register                          (0xf8)*/
+    volatile uint32_t ddr_drive_edge;
+    /* SPI XIP Mode bits                                         (0xfc)*/
+    volatile uint32_t xip_mode_bits;
+    /* SPI XIP INCR transfer opcode                              (0x100)*/
+    volatile uint32_t xip_incr_inst;
+    /* SPI XIP WRAP transfer opcode                              (0x104)*/
+    volatile uint32_t xip_wrap_inst;
+#if SSIC_CONCURRENT_XIP_EN
+    /* SPI XIP Control Register                                  (0x108)*/
+    volatile uint32_t xip_ctrl;
+    /* SPI XIP Slave Enable Register                             (0x10c)*/
+    volatile uint32_t xip_ser;
+    /* SPI XIP Receive FIFO Overflow Interrupt Clear Register    (0x110)*/
+    volatile uint32_t xrxoicr;
+    /* SPI XIP time out register for continuous transfers        (0x114)*/
+    volatile uint32_t xip_cnt_time_out;
+    /* not support dyn ws                                        (0x118)*/
+    uint32_t resv1[1];
+    /* SPI Transmit Error Interrupt Clear Register               (0x11c)*/
+    volatile uint32_t spitecr;
+#else
+    uint32_t resv1[6];
+#endif
+#if SSIC_HAS_DMA == 2
+    /* SPI Device Register                                       (0x120)*/
+    volatile uint32_t spidr;
+    /* SPI Device Address Register                               (0x124)*/
+    volatile uint32_t spiar;
+    /* AXI Address Register 0                                    (0x128)*/
+    volatile uint32_t axiar0;
+    /* AXI Address Register 1                                    (0x12c)*/
+    volatile uint32_t axiar1;
+    /* AXI Master Error Interrupt Clear Register                 (0x130)*/
+    volatile uint32_t axiecr;
+    /* Transfer Done Clear Interrupt Clear Register              (0x134)*/
+    volatile uint32_t donecr;
+#endif
+    /* This register will not be used and is reserved.           (0x138 ~ 0x13c)*/
+    uint32_t resv3[2];
+#if SSIC_XIP_WRITE_REG_EN
+    /* XIP_WRITE_INCR_INST - XIP Write INCR transfer opcode      (0x140)*/
+    volatile uint32_t xip_write_incr_inst;
+    /* XIP_WRITE_WRAP_INST - XIP Write WRAP transfer opcode      (0x144)*/
+    volatile uint32_t xip_write_wrap_inst;
+    /* XIP_WRITE_CTRL - XIP Write Control Register               (0x148)*/
+    volatile uint32_t xip_write_ctrl;
+#else
+    uint32_t resv4[3];
+#endif
+    // volatile uint32_t endian;
+} __attribute__((packed, aligned(4))) k230_spi_reg_t;
+
+
+#endif

+ 3 - 0
bsp/k230/drivers/utest/SConscript

@@ -33,6 +33,9 @@ if GetDepend('BSP_UTEST_DRIVERS'):
     if GetDepend('BSP_USING_I2C'):
         src += ['test_i2c.c']
 
+    if GetDepend('BSP_USING_SPI'):
+        src += ['test_spi.c']
+
 group = DefineGroup('utestcases', src, depend = [''])
 
 Return('group')

+ 222 - 0
bsp/k230/drivers/utest/test_spi.c

@@ -0,0 +1,222 @@
+/* Copyright (c) 2023, Canaan Bright Sight Co., Ltd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 2006-2025 RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <rtthread.h>
+#include <rtdevice.h>
+#include <rtdbg.h>
+#include <utest.h>
+#include "drv_spi.h"
+#include <string.h>
+#include "drv_pinctrl.h"
+#include "drv_gpio.h"
+/*
+ * 测试 SPI0 在标准SPI模式下的数据发送功能
+ *
+ * 功能说明:
+ *  - 查找名为 "spi0" 的SPI总线设备;
+ *  - 挂载SPI设备到总线;
+ *  - 配置SPI设备参数:
+ *      - 模式:标准SPI模式0 (RT_SPI_MODE_0)
+ *      - 数据位:8位
+ *      - 最大频率:1MHz
+ *      - 数据线宽度:1(标准SPI)
+ *  - 准备测试数据(递增序列);
+ *  - 创建SPI消息并发送16字节数据;
+ *  - 发送完成接收从机的16字节数据;
+ *  - 发送完成后卸载SPI设备。
+ *
+ * 硬件说明:
+ * - 本测试基于 K230 平台;
+ * - 测试SPI0(OSPI)的标准SPI模式TX和RX功能,使用硬件CS;
+ * - 对应的引脚配置为:
+ *      - CS: GPIO14
+ *      - CLK: GPIO15
+ *      - D0: GPIO16
+ *      - D1: GPIO17
+ * - 需要连接SPI从设备(如SPI调试器等)来验证数据传输,本测试文件使用一块stm32制作的SPI调试器;
+ * - 如果没有实际从设备,可以使用逻辑分析仪或示波器观察SPI波形(但是只能验证TX功能,接收到会是16bit的0xff);
+ */
+#define SPI0_BUS_NAME     "spi0"
+#define SPI0_DEV_NAME0     "spi00"
+#define TEST_DATA_LENGTH  16
+#define SPI0_CS_PIN      14
+#define SPI0_CLK_PIN     15
+#define SPI0_D0_PIN      16
+#define SPI0_D1_PIN      17
+#define SPI0_CS_PIN_AF  IOMUX_FUNC2
+#define SPI0_CLK_PIN_AF  IOMUX_FUNC2
+#define SPI0_D0_PIN_AF  IOMUX_FUNC2
+#define SPI0_D1_PIN_AF  IOMUX_FUNC2
+
+static void spi_gpio_init(void)
+{
+    LOG_I("SPI demo: initializing SPI0 GPIO...");
+    k230_pinctrl_set_function(SPI0_CS_PIN, SPI0_CS_PIN_AF);
+    k230_pinctrl_set_function(SPI0_CLK_PIN, SPI0_CLK_PIN_AF);
+    k230_pinctrl_set_function(SPI0_D0_PIN, SPI0_D0_PIN_AF);
+    k230_pinctrl_set_function(SPI0_D1_PIN, SPI0_D1_PIN_AF);
+
+    k230_pinctrl_set_oe(SPI0_CS_PIN, 1);
+    k230_pinctrl_set_oe(SPI0_CLK_PIN, 1);
+    k230_pinctrl_set_oe(SPI0_D0_PIN, 1);
+    k230_pinctrl_set_oe(SPI0_D1_PIN, 1);
+
+    k230_pinctrl_set_ie(SPI0_CS_PIN, 1);
+    k230_pinctrl_set_ie(SPI0_CLK_PIN, 1);
+    k230_pinctrl_set_ie(SPI0_D0_PIN, 1);
+    k230_pinctrl_set_ie(SPI0_D1_PIN, 1);
+}
+
+static void spi_device_demo(void)
+{
+    struct rt_qspi_device *qspi_dev;
+    LOG_I("Using rt_qspi_device to transmit");
+    rt_err_t ret;
+    uint8_t tx_data[TEST_DATA_LENGTH];
+    uint8_t rx_data[TEST_DATA_LENGTH];
+    for (int i = 0; i < TEST_DATA_LENGTH; i++)
+    {
+        tx_data[i] = i;
+    }
+    rt_memset(rx_data, 0, sizeof(rx_data));
+
+    /* Find QSPI Bus */
+    struct rt_spi_bus *spi_bus = (struct rt_spi_bus *)rt_device_find(SPI0_BUS_NAME);
+    if (!spi_bus)
+    {
+        LOG_E("Failed to find SPI bus: %s", SPI0_BUS_NAME);
+        return;
+    }
+    LOG_I("Success to find SPI bus: %s", SPI0_BUS_NAME);
+
+    qspi_dev = (struct rt_qspi_device *)rt_malloc(sizeof(struct rt_qspi_device));
+    if (!qspi_dev)
+    {
+        LOG_E("Failed to allocate SPI device memory");
+        return;
+    }
+    LOG_I("Success to allocate QSPI device memory");
+    /* Attach SPI Device */
+    ret = rt_spi_bus_attach_device(&(qspi_dev->parent), SPI0_DEV_NAME0, SPI0_BUS_NAME, RT_NULL);
+    if (ret != RT_EOK)
+    {
+        LOG_E("Failed to attach SPI device: %d", ret);
+        rt_free(qspi_dev);
+        return;
+    }
+    LOG_I("SPI device attached successfully");
+    /* SPI Device Config*/
+    struct rt_qspi_configuration qspi_cfg;
+    qspi_cfg.parent.mode = RT_SPI_MODE_0 | RT_SPI_MSB;
+    qspi_cfg.parent.data_width = 8;
+    qspi_cfg.parent.max_hz = 1000000;
+    qspi_cfg.parent.reserved = 0;
+    qspi_cfg.qspi_dl_width = 1;
+    qspi_cfg.medium_size = 0;
+    qspi_cfg.ddr_mode = 0;
+
+
+    ret = rt_qspi_configure(qspi_dev, &qspi_cfg);
+    if (ret != RT_EOK)
+    {
+        LOG_E("SPI configuration failed: %d", ret);
+        rt_free(qspi_dev);
+        return;
+    }
+
+    LOG_I("SPI configuration: Standard SPI, mode=0, data_width=8, max_hz=%d, data_lines=%d",
+          qspi_cfg.parent.max_hz, qspi_cfg.qspi_dl_width);
+    LOG_I("Sending test data (length=%d):", TEST_DATA_LENGTH);
+
+    for (int i = 0; i < TEST_DATA_LENGTH; i++)
+    {
+        rt_kprintf("%02X ", tx_data[i]);
+    }
+    rt_kprintf("\n");
+
+    /* Create SPI Message */
+    struct rt_qspi_message msg;
+    rt_memset(&msg, 0, sizeof(msg));
+    /*Using Standard SPI*/
+    msg.instruction.content = 0;
+    msg.instruction.qspi_lines = 1;
+    msg.address.content = 0;
+    msg.address.size = 0;
+    msg.address.qspi_lines = 1;
+    msg.qspi_data_lines = 1;
+    msg.dummy_cycles = 0;
+
+    /* SPI Message Config */
+    msg.parent.send_buf = tx_data;
+    msg.parent.recv_buf = rx_data;
+    msg.parent.length = TEST_DATA_LENGTH;
+    msg.parent.cs_take = 1;
+    msg.parent.cs_release = 1;
+    msg.parent.next = RT_NULL;
+
+    /* Transfer Data */
+    ret = rt_qspi_transfer_message(qspi_dev, &msg);
+    if (ret != TEST_DATA_LENGTH)
+    {
+        LOG_E("SPI transfer failed, returned: %d", ret);
+    }
+    uassert_int_equal(ret, TEST_DATA_LENGTH);
+
+    LOG_I("SPI TX demo: sent %d bytes successfully", ret);
+    LOG_I("Received data from slave (length=%d):", TEST_DATA_LENGTH);
+    for (int i = 0; i < TEST_DATA_LENGTH; i++)
+    {
+        rt_kprintf("%02X ", rx_data[i]);
+    }
+    rt_kprintf("\n");
+    /* Detach SPI Device */
+    ret = rt_spi_bus_detach_device(&(qspi_dev->parent));
+    uassert_int_equal(ret, RT_EOK);
+    rt_free(qspi_dev);
+}
+
+static void testcase(void)
+{
+    UTEST_UNIT_RUN(spi_gpio_init);
+    UTEST_UNIT_RUN(spi_device_demo);
+}
+
+static rt_err_t utest_tc_init(void)
+{
+    LOG_I("SPI test case initialization");
+    return RT_EOK;
+}
+
+static rt_err_t utest_tc_cleanup(void)
+{
+    LOG_I("SPI test case cleanup");
+    return RT_EOK;
+}
+
+UTEST_TC_EXPORT(testcase, "bsp.k230.drivers.spi", utest_tc_init, utest_tc_cleanup, 10);

+ 4 - 0
bsp/k230/rtconfig.h

@@ -503,6 +503,10 @@
 /* GD32 Drivers */
 
 /* end of GD32 Drivers */
+
+/* HPMicro SDK */
+
+/* end of HPMicro SDK */
 /* end of HAL & SDK Drivers */
 
 /* sensors drivers */