Browse Source

bsp: k230: add gpio driver

K230 supports 3 GPIO groups:
group0: GPIO0  ~ GPIO31, support i/o and irq
group1: GPIO32 ~ GPIO63, support i/o and irq
group2: GPIO64 ~ GPIO71, only support i/o.

Signed-off-by: Chen Wang <unicorn_wang@outlook.com>
Chen Wang 3 weeks ago
parent
commit
14c147cb00

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

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

+ 400 - 0
bsp/k230/drivers/interdrv/gpio/drv_gpio.c

@@ -0,0 +1,400 @@
+/* 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 <rthw.h>
+#include <rtdevice.h>
+#include <riscv_io.h>
+#include <rtdef.h>
+#include "ioremap.h"
+#include "drv_gpio.h"
+#include "drv_hardlock.h"
+#include "board.h"
+#include <dfs_posix.h>
+#include <lwp_user_mm.h>
+#include <sys/ioctl.h>
+#include <rtdbg.h>
+
+#define DBG_TAG "GPIO"
+#ifdef RT_DEBUG
+#define DBG_LVL DBG_LOG
+#else
+#define DBG_LVL DBG_WARNING
+#endif
+#define DBG_COLOR
+
+struct kd_gpio_device {
+    struct rt_device dev;
+    void* base[2];
+    int hardlock;
+};
+
+static struct kd_gpio_device gpio_dev;
+
+static struct
+{
+    void (*hdr)(void* args);
+    void* args;
+    gpio_pin_edge_t edge;
+    int debounce;
+    struct rt_work debounce_work;
+    struct rt_work send_sig_work;
+    struct rt_lwp* lwp;
+    int lwp_ref_cnt;
+    int signo;
+    void* sigval;
+} irq_table[GPIO_MAX_NUM];
+
+static void kd_gpio_reg_writel(void* reg, rt_size_t offset, rt_uint32_t value)
+{
+    while (0 != kd_hardlock_lock(gpio_dev.hardlock))
+        ;
+    rt_uint32_t val = readl(reg);
+    val &= ~(1 << offset);
+    val |= (value << offset);
+    writel(val, reg);
+    kd_hardlock_unlock(gpio_dev.hardlock);
+}
+
+static rt_uint32_t kd_gpio_reg_readl(void* reg, rt_size_t offset)
+{
+    rt_uint32_t val = readl(reg);
+    return (val & (1 << offset)) >> offset;
+}
+
+static int check_pin_valid(rt_base_t pin)
+{
+    if ((rt_uint16_t)pin < 0 || (rt_uint16_t)pin > GPIO_MAX_NUM)
+    {
+        LOG_E("pin %d is not valid\n", pin);
+        return -RT_EINVAL;
+    }
+    return pin;
+}
+
+rt_err_t kd_pin_mode(rt_base_t pin, rt_base_t mode)
+{
+    void* reg;
+    uint32_t dir;
+
+    if (check_pin_valid(pin) < 0)
+        return -RT_EINVAL;
+
+    switch (mode)
+    {
+    case GPIO_DM_INPUT:
+        dir = 0;
+        break;
+    case GPIO_DM_OUTPUT:
+        dir = 1;
+        break;
+    default:
+        LOG_E("GPIO drive mode is not supported.");
+        return -RT_EINVAL;
+    }
+
+    if (pin < 32)
+    {
+        reg = gpio_dev.base[0] + DIRECTION;
+    } else {
+        pin -= 32;
+        if (pin < 32)
+        {
+            reg = gpio_dev.base[1] + DIRECTION;
+        } else {
+            reg = gpio_dev.base[1] + DIRECTION + DIRECTION_STRIDE;
+            pin -= 32;
+        }
+    }
+
+    kd_gpio_reg_writel(reg, pin, dir);
+
+    return RT_EOK;
+}
+
+int kd_pin_mode_get(rt_base_t pin)
+{
+    void* reg;
+
+    if (check_pin_valid(pin) < 0)
+        return -RT_EINVAL;
+
+    if (pin < 32)
+    {
+        reg = gpio_dev.base[0] + DIRECTION;
+    } else {
+        pin -= 32;
+        if (pin < 32)
+        {
+            reg = gpio_dev.base[1] + DIRECTION;
+        } else {
+            reg = gpio_dev.base[1] + DIRECTION + DIRECTION_STRIDE;
+            pin -= 32;
+        }
+    }
+
+    return kd_gpio_reg_readl(reg, pin) ? GPIO_DM_OUTPUT : GPIO_DM_INPUT;
+}
+
+rt_err_t kd_pin_write(rt_base_t pin, rt_base_t value)
+{
+    void* reg;
+
+    if (check_pin_valid(pin) < 0)
+        return -RT_EINVAL;
+
+    if (pin < 32)
+    {
+        reg = gpio_dev.base[0] + DATA_OUTPUT;
+    } else {
+        pin -= 32;
+        if (pin < 32)
+        {
+            reg = gpio_dev.base[1] + DATA_OUTPUT;
+        } else {
+            reg = gpio_dev.base[1] + DATA_OUTPUT + DATA_INPUT_STRIDE;
+            pin -= 32;
+        }
+    }
+
+    kd_gpio_reg_writel(reg, pin, value ? GPIO_PV_HIGH : GPIO_PV_LOW);
+
+    return RT_EOK;
+}
+
+int kd_pin_read(rt_base_t pin)
+{
+    void* reg;
+
+    if (check_pin_valid(pin) < 0)
+        return -RT_EINVAL;
+
+    if (pin < 32)
+    {
+        reg = gpio_dev.base[0] + DATA_INPUT;
+    } else {
+        pin -= 32;
+        if (pin < 32)
+        {
+            reg = gpio_dev.base[1] + DATA_INPUT;
+        } else {
+            reg = gpio_dev.base[1] + DATA_INPUT + DATA_INPUT_STRIDE;
+            pin -= 32;
+        }
+    }
+
+    return kd_gpio_reg_readl(reg, pin) ? GPIO_PV_HIGH : GPIO_PV_LOW;
+}
+
+static int kd_set_pin_edge(rt_int32_t pin, gpio_pin_edge_t edge)
+{
+    void* reg;
+
+    reg = gpio_dev.base[pin >> 5];
+    pin = pin & 0x1f;
+
+    switch (edge)
+    {
+    case GPIO_PE_RISING:
+        kd_gpio_reg_writel(reg + INT_TYPE_LEVEL, pin, 0x1);
+        kd_gpio_reg_writel(reg + INT_POLARITY, pin, 0x1);
+        kd_gpio_reg_writel(reg + INT_BOTHEDGE, pin, 0x0);
+        break;
+    case GPIO_PE_FALLING:
+        kd_gpio_reg_writel(reg + INT_TYPE_LEVEL, pin, 0x1);
+        kd_gpio_reg_writel(reg + INT_POLARITY, pin, 0x0);
+        kd_gpio_reg_writel(reg + INT_BOTHEDGE, pin, 0x0);
+        break;
+    case GPIO_PE_BOTH:
+        kd_gpio_reg_writel(reg + INT_BOTHEDGE, pin, 0x1);
+        break;
+    case GPIO_PE_LOW:
+        kd_gpio_reg_writel(reg + INT_TYPE_LEVEL, pin, 0x0);
+        kd_gpio_reg_writel(reg + INT_POLARITY, pin, 0x0);
+        kd_gpio_reg_writel(reg + INT_BOTHEDGE, pin, 0x0);
+        break;
+    case GPIO_PE_HIGH:
+        kd_gpio_reg_writel(reg + INT_TYPE_LEVEL, pin, 0x0);
+        kd_gpio_reg_writel(reg + INT_POLARITY, pin, 0x1);
+        kd_gpio_reg_writel(reg + INT_BOTHEDGE, pin, 0x0);
+        break;
+    default:
+        break;
+    }
+
+    kd_gpio_reg_writel(reg + INT_ENABLE, pin, 0x1);
+
+    return RT_EOK;
+}
+
+static void debounce_work(struct rt_work* work, void* param)
+{
+    void* reg;
+    rt_size_t pin = (rt_size_t)param;
+
+    reg = gpio_dev.base[pin >> 5];
+    pin = pin & 0x1f;
+
+    rt_base_t level = rt_hw_interrupt_disable();
+    kd_gpio_reg_writel(reg + INT_MASK, pin, 0x0);
+    rt_hw_interrupt_enable(level);
+}
+
+static void pin_irq(int vector, void* param)
+{
+    void* reg;
+    long pin = vector - IRQN_GPIO0_INTERRUPT;
+    gpio_pin_edge_t edge = irq_table[pin].edge;
+    long pin_offset;
+
+    reg = gpio_dev.base[pin >> 5];
+    pin_offset = pin & 0x1f;
+
+    switch (edge)
+    {
+    case GPIO_PE_RISING:
+    case GPIO_PE_FALLING:
+    case GPIO_PE_BOTH:
+        kd_gpio_reg_writel(reg + INT_CLEAR, pin_offset, 0x1);
+        break;
+    case GPIO_PE_LOW:
+    case GPIO_PE_HIGH:
+        kd_gpio_reg_writel(reg + INT_MASK, pin_offset, 0x1);
+        rt_work_init(&irq_table[pin].debounce_work, debounce_work, (void *)pin);
+        rt_work_submit(&irq_table[pin].debounce_work, irq_table[pin].debounce);
+        break;
+    default:
+        break;
+    }
+
+    if (irq_table[pin].hdr)
+        irq_table[pin].hdr(irq_table[pin].args);
+}
+
+static void gpio_irq_to_user(void* args)
+{
+}
+
+rt_err_t kd_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void* args), void* args)
+{
+    char irq_name[10];
+
+    if (check_pin_valid(pin) < 0)
+        return -RT_EINVAL;
+    if (pin >= GPIO_IRQ_MAX_NUM)
+    {
+        LOG_E("pin %d not support interrupt", pin);
+        return -RT_EINVAL;
+    }
+
+    irq_table[pin].hdr = hdr;
+    irq_table[pin].args = args;
+    if (hdr != gpio_irq_to_user)
+    {
+        irq_table[pin].lwp = NULL;
+        irq_table[pin].lwp_ref_cnt = 0;
+    }
+
+    if (mode < 0 || mode > 4)
+        return -RT_EINVAL;
+    irq_table[pin].edge = mode;
+    irq_table[pin].debounce = rt_tick_from_millisecond(10);
+
+    kd_set_pin_edge(pin, irq_table[pin].edge);
+    rt_snprintf(irq_name, sizeof irq_name, "pin%d", pin);
+    rt_hw_interrupt_install(IRQN_GPIO0_INTERRUPT + pin, pin_irq, RT_NULL, irq_name);
+
+    return RT_EOK;
+}
+
+rt_err_t kd_pin_detach_irq(rt_int32_t pin)
+{
+    void* reg;
+
+    if (check_pin_valid(pin) < 0)
+        return -RT_EINVAL;
+    if (pin >= GPIO_IRQ_MAX_NUM)
+    {
+        LOG_E("pin %d not support interrupt", pin);
+        return -RT_EINVAL;
+    }
+
+    irq_table[pin].hdr = RT_NULL;
+    irq_table[pin].args = RT_NULL;
+    irq_table[pin].lwp = NULL;
+    irq_table[pin].lwp_ref_cnt = 0;
+    irq_table[pin].signo = 0;
+    irq_table[pin].sigval = 0;
+
+    reg = gpio_dev.base[pin >> 5];
+    pin = pin & 0x1f;
+    kd_gpio_reg_writel(reg + INT_ENABLE, pin, 0x0);
+
+    return RT_EOK;
+}
+
+rt_err_t kd_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled)
+{
+    if (check_pin_valid(pin) < 0)
+        return -RT_EINVAL;
+    if (pin >= GPIO_IRQ_MAX_NUM)
+    {
+        LOG_E("pin %d not support interrupt", pin);
+        return -RT_EINVAL;
+    }
+
+    if (enabled)
+        rt_hw_interrupt_umask(IRQN_GPIO0_INTERRUPT + pin);
+    else
+        rt_hw_interrupt_mask(IRQN_GPIO0_INTERRUPT + pin);
+
+    return RT_EOK;
+}
+
+int rt_hw_gpio_init(void)
+{
+    rt_err_t ret;
+
+    gpio_dev.base[0] = rt_ioremap((void*)GPIO0_BASE_ADDR, GPIO0_IO_SIZE);
+    gpio_dev.base[1] = rt_ioremap((void*)GPIO1_BASE_ADDR, GPIO1_IO_SIZE);
+
+    if (kd_request_lock(HARDLOCK_GPIO))
+    {
+        rt_kprintf("fail to request hardlock-%d\n", HARDLOCK_GPIO);
+        return -RT_ERROR;
+    }
+    gpio_dev.hardlock = HARDLOCK_GPIO;
+
+    ret = rt_device_register(&gpio_dev.dev, "gpio", RT_DEVICE_FLAG_RDWR);
+
+    return ret;
+}
+INIT_BOARD_EXPORT(rt_hw_gpio_init);

+ 132 - 0
bsp/k230/drivers/interdrv/gpio/drv_gpio.h

@@ -0,0 +1,132 @@
+/* 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_GPIO_H__
+#define DRV_GPIO_H__
+
+#define GPIO_IRQ_MAX_NUM            (64)
+#define GPIO_MAX_NUM                (64+8)
+#define IRQN_GPIO0_INTERRUPT        32
+
+/* k230 gpio register table */
+#define DATA_OUTPUT         0x0
+#define DIRECTION           0x04
+#define DATA_SOURCE         0x08
+#define INT_ENABLE          0x30
+#define INT_MASK            0x34
+#define INT_TYPE_LEVEL      0x38
+#define INT_POLARITY        0x3c
+#define INT_STATUS          0x40
+#define INT_STATUS_RAW      0x44
+#define INT_DEBOUNCE        0x48
+#define INT_CLEAR           0x4c
+#define DATA_INPUT          0x50
+#define VER_ID_CODE         0x64
+#define INT_BOTHEDGE        0x68
+
+#define DATA_INPUT_STRIDE   0x04 /* register stride 32 bits */
+#define DATA_OUTPUT_STRIDE  0x0c /* register stride 3*32 bits */
+#define DIRECTION_STRIDE    0x0c /* register stride 3*32 bits, */
+
+#define KD_GPIO_HIGH                1
+#define KD_GPIO_LOW                 0
+#define KD_GPIO_IRQ_DISABLE      0x00
+#define KD_GPIO_IRQ_ENABLE       0x01
+
+/* ioctl */
+
+#define KD_GPIO_DM_OUTPUT           _IOW('G', 0, int)
+#define KD_GPIO_DM_INPUT            _IOW('G', 1, int)
+#define KD_GPIO_DM_INPUT_PULL_UP    _IOW('G', 2, int)
+#define KD_GPIO_DM_INPUT_PULL_DOWN  _IOW('G', 3, int)
+#define KD_GPIO_WRITE_LOW           _IOW('G', 4, int)
+#define KD_GPIO_WRITE_HIGH          _IOW('G', 5, int)
+
+#define KD_GPIO_PE_RISING           _IOW('G', 7, int)
+#define KD_GPIO_PE_FALLING          _IOW('G', 8, int)
+#define KD_GPIO_PE_BOTH             _IOW('G', 9, int)
+#define KD_GPIO_PE_HIGH             _IOW('G', 10, int)
+#define KD_GPIO_PE_LOW              _IOW('G', 11, int)
+
+#define KD_GPIO_READ_VALUE          _IOW('G', 12, int)
+
+#define KD_GPIO_SET_MODE            _IOW('G', 20, int)
+#define KD_GPIO_GET_MODE            _IOWR('G', 21, int)
+#define KD_GPIO_SET_VALUE           _IOW('G', 22, int)
+#define KD_GPIO_GET_VALUE           _IOWR('G', 23, int)
+#define KD_GPIO_SET_IRQ             _IOW('G', 24, int)
+#define KD_GPIO_GET_IRQ             _IOWR('G', 25, int)
+
+typedef enum _gpio_pin_edge
+{
+    GPIO_PE_RISING,
+    GPIO_PE_FALLING,
+    GPIO_PE_BOTH,
+    GPIO_PE_HIGH,
+    GPIO_PE_LOW,
+} gpio_pin_edge_t;
+
+typedef enum _gpio_drive_mode
+{
+    GPIO_DM_OUTPUT,
+    GPIO_DM_INPUT,
+    GPIO_DM_INPUT_PULL_UP,
+    GPIO_DM_INPUT_PULL_DOWN,
+} gpio_drive_mode_t;
+
+typedef enum _gpio_pin_value
+{
+    GPIO_PV_LOW,
+    GPIO_PV_HIGH
+} gpio_pin_value_t;
+
+typedef struct {
+    rt_uint16_t pin;
+    rt_uint16_t value;
+} gpio_cfg_t;
+
+typedef struct {
+    rt_uint16_t pin;
+    rt_uint8_t enable;
+    rt_uint8_t mode;
+    rt_uint16_t debounce;
+    rt_uint8_t signo;
+    void *sigval;
+} gpio_irqcfg_t;
+
+rt_err_t kd_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled);
+rt_err_t kd_pin_detach_irq(rt_int32_t pin);
+rt_err_t kd_pin_attach_irq(rt_int32_t pin,rt_uint32_t mode, void (*hdr)(void *args), void *args);
+rt_err_t kd_pin_write(rt_base_t pin, rt_base_t value);
+rt_err_t kd_pin_mode(rt_base_t pin, rt_base_t mode);
+int kd_pin_read(rt_base_t pin);
+
+#endif

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

@@ -3,6 +3,8 @@ from building import *
 src = []
 
 if GetDepend('RT_UTEST_USING_ALL_CASES') or GetDepend('BSP_UTEST_DRIVERS'):
+    src += ['test_gpio.c']
+    src += ['test_gpio_irq.c']
 
     if GetDepend('BSP_USING_TIMERS'):
         src += ['test_timer.c']

+ 105 - 0
bsp/k230/drivers/utest/test_gpio.c

@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022, Canaan Bright Sight Co., Ltd
+ *
+ * All enquiries to https://www.canaan-creative.com/
+ */
+
+/*
+ * Copyright (c) 2006-2025 RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+ /*
+  * 本测试用例演示基于 01Studio Canmv-K230 开发板测试 gpio 的基本输入和输出功能。其中
+  * gpio_led_flashing 演示 LED 的跑马灯功能。LED 采用开发板上的 LED2(连接 LED_GPIO52)。
+  * gpio_io 演示用杜邦线将 40Pin GPIO 中的 GPIO33 和 GPIO32 连接起来,GPIO33 配
+  * 置为输出,GPIO32 配置为输入。分别用 GPIO33 产生输出给 GPIO32 读取并检查是否
+  * 和预期的输出一致。
+  */
+#include <unistd.h>
+#include <stdio.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+#include <ioremap.h>
+#include "board.h"
+#include "drv_pinctrl.h"
+#include "drv_gpio.h"
+#include "utest.h"
+
+#define LED_PIN_NUM 52
+
+#define OUT_PIN_NUM 33
+#define IN_PIN_NUM  32
+
+static void gpio_led_flashing(void)
+{
+    int cnt = 5;
+
+    /* Use default pinmux function */
+    kd_pin_mode(LED_PIN_NUM, GPIO_DM_OUTPUT);
+
+    while(cnt--)
+    {
+        LOG_I("led ON");
+        kd_pin_write(LED_PIN_NUM, GPIO_PV_HIGH);
+        rt_thread_mdelay(1000);
+
+        LOG_I("led OFF");
+        kd_pin_write(LED_PIN_NUM, GPIO_PV_LOW);
+        rt_thread_mdelay(1000);
+    }
+}
+
+static void gpio_io(void)
+{
+    int cnt = 5;
+    int level = 0xff;
+
+    rt_uint32_t val;
+
+    /* Set pinmux function */
+    k230_pinctrl_set_function(IN_PIN_NUM, IOMUX_FUNC1);
+    k230_pinctrl_set_ie(IN_PIN_NUM, 1);
+    k230_pinctrl_set_oe(IN_PIN_NUM, 0);
+
+    k230_pinctrl_set_function(OUT_PIN_NUM, IOMUX_FUNC1);
+    k230_pinctrl_set_ie(OUT_PIN_NUM, 0);
+    k230_pinctrl_set_oe(OUT_PIN_NUM, 1);
+
+    /* Set GPIO mode */
+    kd_pin_mode(OUT_PIN_NUM, GPIO_DM_OUTPUT);
+    kd_pin_mode(IN_PIN_NUM, GPIO_DM_INPUT);
+
+    while(cnt--)
+    {
+        kd_pin_write(OUT_PIN_NUM, GPIO_PV_LOW);
+        level = kd_pin_read(IN_PIN_NUM);
+        LOG_I("--> %d", level);
+        uassert_int_equal(level, GPIO_PV_LOW);
+
+        kd_pin_write(OUT_PIN_NUM, GPIO_PV_HIGH);
+        level = kd_pin_read(IN_PIN_NUM);
+        LOG_I("--> %d", level);
+        uassert_int_equal(level, GPIO_PV_HIGH);
+
+        rt_thread_mdelay(500);
+    }
+}
+
+static void testcase(void)
+{
+    UTEST_UNIT_RUN(gpio_led_flashing);
+    UTEST_UNIT_RUN(gpio_io);
+}
+
+static rt_err_t utest_tc_init(void)
+{
+    return RT_EOK;
+}
+static rt_err_t utest_tc_cleanup(void)
+{
+    return RT_EOK;
+}
+
+UTEST_TC_EXPORT(testcase, "gpio", utest_tc_init, utest_tc_cleanup, 100);

+ 133 - 0
bsp/k230/drivers/utest/test_gpio_irq.c

@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022, Canaan Bright Sight Co., Ltd
+ *
+ * All enquiries to https://www.canaan-creative.com/
+ */
+
+/*
+ * Copyright (c) 2006-2025 RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+ /*
+  * 本测试用例演示基于 01Studio Canmv-K230 开发板测试 gpio 的中断检测功能。其中
+  * 采用开发板上的按键 S3 以及其连接的 KEY_GPIO21 作为中断输入
+  * 在中断处理函数中控制 LED 的状态。LED2 采用开发板上的 LED2(连接 LED_GPIO52)。
+  * gpio_falling_edge_trigger_irq 将按键 S3 的中断触发方式设置为下降沿触发,测试
+  * 时应该会观察到在每次按下 S3 时收到中断。
+  * gpio_rising_edge_trigger_irq 将按键 S3 的中断触发方式设置为上升沿触发,测试
+  * 时应该会观察到在每次松开 S3 时收到中断。
+  * gpio_both_edge_trigger_irq 将按键 S3 的中断触发方式设置为双边沿触发。测试时
+  * 应该会观察到在每次按下或松开 S3 时都会收到中断。
+  */
+#include <unistd.h>
+#include <stdio.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+#include "drv_gpio.h"
+#include "utest.h"
+
+#define LED_PIN_NUM            52
+#define KEY_PIN_NUM            21
+
+/* It is best to use an even number, especially for testing both edges triggering. */
+#define MAX_LOOP_COUNT  6
+static volatile int cnt;
+
+#define LED_ON  1
+#define LED_OFF 0
+static volatile int led_status;
+
+static void led_on(void)
+{
+    LOG_W("led ON\n");
+    kd_pin_write(LED_PIN_NUM, GPIO_PV_HIGH);
+    led_status = LED_ON;
+}
+
+static void led_off(void)
+{
+    LOG_W("led OFF\n");
+    kd_pin_write(LED_PIN_NUM, GPIO_PV_LOW);
+    led_status = LED_OFF;
+}
+
+void key_irq(void *args)
+{
+    LOG_I("---> IRQ: %d\n", ++cnt);
+    if(LED_OFF == led_status)
+        led_on();
+    else
+        led_off();
+}
+
+static void gpio_falling_edge_trigger_irq(void)
+{
+    cnt = 0;
+
+    /* Use default pinmux function */
+    kd_pin_mode(LED_PIN_NUM, GPIO_DM_OUTPUT);
+    led_off();
+
+    /* Use default pinmux function */
+    kd_pin_mode(KEY_PIN_NUM, GPIO_DM_INPUT);
+    kd_pin_attach_irq(KEY_PIN_NUM, GPIO_PE_FALLING, key_irq, RT_NULL);
+    kd_pin_irq_enable(KEY_PIN_NUM, KD_GPIO_IRQ_ENABLE);
+
+    while (cnt < MAX_LOOP_COUNT);
+    kd_pin_detach_irq(KEY_PIN_NUM);
+}
+
+static void gpio_rising_edge_trigger_irq(void)
+{
+    cnt = 0;
+
+    /* Use default pinmux function */
+    kd_pin_mode(LED_PIN_NUM, GPIO_DM_OUTPUT);
+    led_off();
+
+    /* Use default pinmux function */
+    kd_pin_mode(KEY_PIN_NUM, GPIO_DM_INPUT);
+    kd_pin_attach_irq(KEY_PIN_NUM, GPIO_PE_RISING, key_irq, RT_NULL);
+    kd_pin_irq_enable(KEY_PIN_NUM, KD_GPIO_IRQ_ENABLE);
+
+    while (cnt < MAX_LOOP_COUNT);
+    kd_pin_detach_irq(KEY_PIN_NUM);
+}
+
+static void gpio_both_edge_trigger_irq(void)
+{
+    cnt = 0;
+
+    /* Use default pinmux function */
+    kd_pin_mode(LED_PIN_NUM, GPIO_DM_OUTPUT);
+    led_off();
+
+    /* Use default pinmux function */
+    kd_pin_mode(KEY_PIN_NUM, GPIO_DM_INPUT);
+    kd_pin_attach_irq(KEY_PIN_NUM, GPIO_PE_BOTH, key_irq, RT_NULL);
+    kd_pin_irq_enable(KEY_PIN_NUM, KD_GPIO_IRQ_ENABLE);
+
+    while (cnt < MAX_LOOP_COUNT);
+    kd_pin_detach_irq(KEY_PIN_NUM);
+}
+
+static void testcase(void)
+{
+    UTEST_UNIT_RUN(gpio_falling_edge_trigger_irq);
+    UTEST_UNIT_RUN(gpio_rising_edge_trigger_irq);
+    UTEST_UNIT_RUN(gpio_both_edge_trigger_irq);
+}
+
+static rt_err_t utest_tc_init(void)
+{
+    return RT_EOK;
+}
+
+static rt_err_t utest_tc_cleanup(void)
+{
+    return RT_EOK;
+}
+
+UTEST_TC_EXPORT(testcase, "gpio_irq", utest_tc_init, utest_tc_cleanup, 100);