From 1e563e2b731d928942f43e1341c8c50b0faf1c01 Mon Sep 17 00:00:00 2001
From: guowenxue <guowenxue@gmail.com>
Date: Fri, 31 May 2024 11:39:31 +0800
Subject: [PATCH]  APPS:IGKBoard-IMX6ULL: Add test-apps source code:

---
 drivers/test-apps/hello.c           |   21 
 drivers/test-apps/sht20_fops.c      |  208 +++++
 drivers/test-apps/sht20_ioctl.c     |  289 +++++++
 drivers/test-apps/ttyS_test.c       |  106 ++
 drivers/test-apps/can_test.c        |  191 ++++
 drivers/test-apps/libgpiod/build.sh |  118 +++
 drivers/test-apps/makefile          |   50 +
 drivers/test-apps/spi_test.c        |  205 +++++
 drivers/test-apps/leds.c            |  332 ++++++++
 drivers/test-apps/ds18b20.c         |   38 
 drivers/test-apps/pwm_play.c        |  214 +++++
 drivers/test-apps/pwm.c             |  341 ++++++++
 drivers/test-apps/libgpiod/makefile |   11 
 drivers/test-apps/keypad.c          |  197 +++++
 14 files changed, 2,296 insertions(+), 25 deletions(-)

diff --git a/drivers/test-apps/can_test.c b/drivers/test-apps/can_test.c
new file mode 100644
index 0000000..7570d51
--- /dev/null
+++ b/drivers/test-apps/can_test.c
@@ -0,0 +1,191 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2024 LingYun IoT System Studio
+ *                  All rights reserved.
+ *
+ *       Filename:  can_test.c
+ *    Description:  This file is socket CAN loop test program
+ *
+ *        Version:  1.0.0(05/26/2024)
+ *         Author:  Guo Wenxue <guowenxue@gmail.com>
+ *      ChangeLog:  1, Release initial version on "05/26/2024 05:42:49 PM"
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <linux/can.h>
+#include <linux/can/raw.h>
+#include <getopt.h>
+
+// 打印使用帮助信息
+void print_usage(const char *progname)
+{
+    printf("Usage: %s -i <can_interface> -m <mode>\n", progname);
+    printf("Options:\n");
+    printf("  -i, --interface    CAN interface (e.g., can0)\n");
+    printf("  -m, --mode         Mode: send or receive\n");
+    printf("  -h, --help         Display this help message\n");
+}
+
+void send_can_message(const char *ifname)
+{
+    int                     fd;
+    struct sockaddr_can     addr;
+    struct ifreq            ifr;
+    struct can_frame        frame;
+
+    // 创建socket
+    fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
+    if (fd < 0)
+    {
+        perror("socket");
+        exit(1);
+    }
+
+    // 指定CAN接口
+    strcpy(ifr.ifr_name, ifname);
+    ioctl(fd, SIOCGIFINDEX, &ifr);
+    addr.can_family = AF_CAN;
+    addr.can_ifindex = ifr.ifr_ifindex;
+
+    // 绑定socket到CAN接口
+    if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
+    {
+        perror("bind");
+        exit(1);
+    }
+
+    // 构造CAN帧
+    frame.can_id = 0x123; // 设置CAN ID
+    frame.can_dlc = 2;    // 数据长度
+    frame.data[0] = 0x11; // 数据
+    frame.data[1] = 0x22; // 数据
+
+    // 发送CAN帧
+    if (write(fd, &frame, sizeof(struct can_frame)) != sizeof(struct can_frame))
+    {
+        perror("write");
+        exit(1);
+    }
+
+    // 关闭socket
+    close(fd);
+}
+
+void receive_can_message(const char *ifname)
+{
+    int                     fd;
+    struct sockaddr_can     addr;
+    struct ifreq            ifr;
+    struct can_frame        frame;
+
+    // 创建socket
+    fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
+    if (fd < 0) {
+        perror("socket");
+        exit(1);
+    }
+
+    // 指定CAN接口
+    strcpy(ifr.ifr_name, ifname);
+    ioctl(fd, SIOCGIFINDEX, &ifr);
+    addr.can_family = AF_CAN;
+    addr.can_ifindex = ifr.ifr_ifindex;
+
+    // 绑定socket到CAN接口
+    if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
+    {
+        perror("bind");
+        exit(1);
+    }
+
+    // 接收CAN帧
+    while (1)
+    {
+        int nbytes = read(fd, &frame, sizeof(struct can_frame));
+        if (nbytes < 0)
+        {
+            perror("read");
+            exit(1);
+        }
+
+        if (nbytes < sizeof(struct can_frame))
+        {
+            fprintf(stderr, "read: incomplete CAN frame\n");
+            exit(1);
+        }
+
+        // 打印接收到的CAN帧
+        printf("Received CAN frame: ID=0x%X DLC=%d data=", frame.can_id, frame.can_dlc);
+        for (int i = 0; i < frame.can_dlc; i++)
+            printf("%02X ", frame.data[i]);
+
+        printf("\n");
+    }
+
+    // 关闭socket
+    close(fd);
+}
+
+int main(int argc, char **argv)
+{
+    int              opt, index = 0;
+    const char      *ifname = NULL;
+    const char      *mode = NULL;
+
+    // 定义长选项
+    static struct option long_options[] =
+    {
+        {"interface", required_argument, 0, 'i'},
+        {"mode",      required_argument, 0, 'm'},
+        {"help",      no_argument,       0, 'h'},
+        {0, 0, 0, 0}
+    };
+
+    while ((opt = getopt_long(argc, argv, "i:m:h", long_options, &index)) != -1)
+    {
+        switch (opt) {
+            case 'i':
+                ifname = optarg;
+                break;
+            case 'm':
+                mode = optarg;
+                break;
+            case 'h':
+                print_usage(argv[0]);
+                return 0;
+            default:
+                print_usage(argv[0]);
+                return 1;
+        }
+    }
+
+    if (ifname == NULL || mode == NULL)
+    {
+        print_usage(argv[0]);
+        return 1;
+    }
+
+    if (strcmp(mode, "send") == 0)
+    {
+        send_can_message(ifname);
+    }
+    else if (strcmp(mode, "receive") == 0)
+    {
+        receive_can_message(ifname);
+    }
+    else
+    {
+        fprintf(stderr, "Invalid mode: %s\n", mode);
+        print_usage(argv[0]);
+        return 1;
+    }
+
+    return 0;
+}
+
diff --git a/drivers/test-apps/ds18b20.c b/drivers/test-apps/ds18b20.c
index c9da5f3..caf8933 100644
--- a/drivers/test-apps/ds18b20.c
+++ b/drivers/test-apps/ds18b20.c
@@ -23,7 +23,6 @@
  *
  ********************************************************************************/
 
-/* 在C语言编程时,一般系统的头文件用<xxx.h>,我们自己写的头文件则用"zzz.h" */
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -33,17 +32,12 @@
 #include <time.h>
 #include <errno.h>
 
-/* 在C语言编程中,函数应该先定义再使用,如果函数的定义在函数调用后面,应该前向声明。*/
 int ds18b20_get_temperature(float *temp);
 
 int main(int argc, char *argv[])
 {
     float       temp; /* 温度值有小数位,所以使用浮点数 */
 
-    /* 1,在Linux下做C语言编程时,函数返回值一般是0表示成功,<0表示失败,我们也遵循这个规约;
-     * 2,但函数调用只能有一个返回值,所以这里的采样函数只能通过指针来返回采样的温度值;
-     * 3,因为要在ds18b20_get_temperature()函数中修改main()中temp的值,所以这里传&temp;
-     */
     if( ds18b20_get_temperature(&temp) < 0 )
     {
         printf("ERROR: ds18b20 get temprature failure\n");
@@ -64,15 +58,15 @@
 int ds18b20_get_temperature(float *temp)
 {
     const char     *w1_path = "/sys/bus/w1/devices/";
-    char            ds_path[50]; /* DS18B20 采样文件路径 */
-    char            chip[20];    /* DS18B20 芯片序列号文件名 */
-    char            buf[128];    /* read() 读数据存储 buffer */
-    DIR            *dirp;        /* opendir()打开的文件夹句柄 */
-    struct dirent  *direntp;     /* readdir()读文件夹内容时的目录项*/
-    int             fd =-1;      /* open()打开文件的文件描述符 */
-    char           *ptr;         /* 一个字符指针,用来字符串处理 */
-    int             found = 0;   /* 是否找到DS18B20的标志,默认设置为没找到(0) */
-    int             rv = 0;      /* 函数返回值,默认设置为成功返回(0) */
+    char            ds_path[50];
+    char            chip[20];
+    char            buf[128];
+    DIR            *dirp;
+    struct dirent  *direntp;
+    int             fd =-1;
+    char           *ptr;
+    int             found = 0;
+    int             rv = 0;
 
 
     /* 在C语言编程时,进入函数的第一件事应该进行函数参数的合法性检测,检查参数非法输入。
@@ -110,21 +104,17 @@
     /* 文件夹打开用完后,要记得第一时间关闭 */
     closedir(dirp);
 
-    /* found在定义时初始化为0,如果上面的代码没有找到 "28-" 文件名则其值依然为0,否则将会被
-     * 设置为1。如果 found 的值为0的话,则打印错误信息并返回相应的错误码-3.
-     */
+    /* found在定义时初始化为0,如果上面没有找到 "28-" 文件则其值依然为0,否则将被置为1 */
     if( !found )
     {
         printf("Can not find ds18b20 in %s\n", w1_path);
         return -3;
     }
 
-    /* 使用snprintf()函数生成完整路径/sys/bus/w1/devices/28-xxxxx/w1_slave
-     * 并保存到 ds_path 中。
-     */
+    /* 使用snprintf() 生成完整路径/sys/bus/w1/devices/28-xxxxx/w1_slave */
     snprintf(ds_path, sizeof(ds_path), "%s/%s/w1_slave", w1_path, chip);
 
-    /* 接下来打开 DS18B20 的采样文件,如果失败则返回相应的错误码-4。 */
+    /* 接下来打开 DS18B20 的采样文件 */
     if( (fd=open(ds_path, O_RDONLY)) < 0 )
     {
         printf("open %s error: %s\n", ds_path, strerror(errno));
@@ -136,13 +126,11 @@
     {
         printf("read %s error: %s\n", ds_path, strerror(errno));
 
-        /* 1, 这里不能直接调用 return -5 直接返回,否则的话前面open()打开的文件描述符就没有关闭。
+        /* 1, 这里不能直接调用 return直接返回,否则的话前面open()打开的文件描述符就没有关闭。
          * 这里设置 rv 为错误码-5,通过 goto 语句跳转到函数后面统一进行错误处理。
          *
          * 2, 在C语言编程时我们应该慎用goto语句进行"随意"的跳转,因为它会降低代码的可读性。但这里是
          * goto语句的一个非常典型应用,我们经常会用它来对错误进行统一的处理。
-         *
-         * 3,goto后面的cleanup为标号,它在下面的代码中定义。
          */
         rv = -5;
         goto cleanup;
diff --git a/drivers/test-apps/hello.c b/drivers/test-apps/hello.c
new file mode 100644
index 0000000..279e45c
--- /dev/null
+++ b/drivers/test-apps/hello.c
@@ -0,0 +1,21 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2021 LingYun IoT System Studio
+ *                  All rights reserved.
+ *
+ *       Filename:  hello.c
+ *    Description:  This file is hello world test program.
+ *                 
+ *        Version:  1.0.0(2021-12-10)
+ *         Author:  Guo Wenxue <guowenxue@gmail.com>
+ *      ChangeLog:  1, Release initial version on "2021-12-10 22:41:49"
+ *                 
+ ********************************************************************************/
+
+#include <stdio.h>
+
+int main (int argc, char **argv)
+{
+    printf("Hello, LingYun IoT System Studio.\n");
+
+    return 0;
+} 
diff --git a/drivers/test-apps/keypad.c b/drivers/test-apps/keypad.c
new file mode 100644
index 0000000..3426c1b
--- /dev/null
+++ b/drivers/test-apps/keypad.c
@@ -0,0 +1,197 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2021 Guo Wenxue<Email:guowenxue@gmail.com QQ:281143292>
+ *                  All rights reserved.
+ *
+ *       Filename:  keypad.c
+ *    Description:  This file used to test GPIO button driver builtin Linux kernel
+ *                 
+ *        Version:  1.0.0(11/17/2021~)
+ *         Author:  Guo Wenxue <guowenxue@gmail.com>
+ *      ChangeLog:  1, Release initial version on "11/17/2021 02:46:18 PM"
+ *                 
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <linux/input.h>
+#include <linux/kd.h>
+#include <linux/keyboard.h>
+
+#if 0 /* Just for comment here, Reference to linux-3.3/include/linux/input.h */
+struct input_event 
+{
+    struct timeval time;
+    __u16 type;  /* 0x00:EV_SYN 0x01:EV_KEY 0x04:EV_MSC 0x11:EV_LED*/
+    __u16 code;  /* key value, which key */
+    __s32 value; /* 1: Pressed  0:Not pressed  2:Always Pressed */
+};  
+#endif
+
+#define EV_RELEASED        0
+#define EV_PRESSED         1
+
+#define BUTTON_CNT         10
+
+/* 在C语言编程中,函数应该先定义再使用,如果函数的定义在函数调用后面,应该前向声明。*/
+void usage(char *name);
+
+void display_button_event(struct input_event *ev, int cnt);
+
+int main(int argc, char **argv)
+{
+    char                  *kbd_dev = "/dev/input/event1";  //默认监听按键设备;
+    char                  kbd_name[256] = "Unknown"; //用于保存获取到的设备名称
+    int                   kbd_fd = -1;  //open()打开文件的文件描述符 
+    int                   rv=0;  // 函数返回值,默认返回0;
+    int                   opt;    // getopt_long 解析命令行参数返回值;
+    int                   size = sizeof (struct input_event);
+    fd_set                rds; //用于监听的事件的集合
+
+    struct input_event    ev[BUTTON_CNT]; 
+
+    /* getopt_long参数函数第四个参数的定义,二维数组,每个成员由四个元素组成 */
+    struct option long_options[] = {  
+         /* { 参数名称,是否带参数,flags指针(NULL时将val的数值从getopt_long的返回值返回出去),
+            函数找到该选项时的返回值(字符)}
+         */
+        {"device", required_argument, NULL, 'd'},
+        {"help", no_argument, NULL, 'h'},
+        {NULL, 0, NULL, 0}
+    };
+
+    //获取命令行参数的解析返回值
+    while ((opt = getopt_long(argc, argv, "d:h", long_options, NULL)) != -1) 
+    { 
+        switch (opt) 
+        {
+            case 'd':
+                kbd_dev = optarg; 
+                break;
+
+            case 'h':
+                usage(argv[0]);
+                return 0;
+
+            default:
+                break;
+        }
+    }
+
+    if(NULL == kbd_dev)
+    {
+        /* 命令行argv[0]是输入的命令,如 ./keypad */
+        usage(argv[0]); 
+        return -1;
+    }
+
+    /* 获取uid 建议以root权限运行确保可以正常运行 */
+    if ((getuid ()) != 0)  
+        printf ("You are not root! This may not work...\n");
+
+    /* 打开按键对应的设备节点,如果错误则返回负数 */
+    if ((kbd_fd = open(kbd_dev, O_RDONLY)) < 0)
+    {
+        printf("Open %s failure: %s", kbd_dev, strerror(errno));
+        return -1;
+    }
+
+    /* 使用ioctl获取 /dev/input/event*对应的设备名字 */
+    ioctl (kbd_fd, EVIOCGNAME (sizeof (kbd_name)), kbd_name);
+    printf ("Monitor input device %s (%s) event on poll mode:\n", kbd_dev, kbd_name);
+
+    /* 循环使用 select() 多路复用监听按键事件 */
+    while (1)
+    {
+        FD_ZERO(&rds); /* 清空 select() 的读事件集合 */
+        FD_SET(kbd_fd, &rds); /* 将按键设备的文件描述符加入到读事件集合中*/
+
+        /* 使用select开启监听并等待多个描述符发生变化,第一个参数最大描述符+1,
+           2、3、4参数分别是要监听读、写、异常三个事件的文军描述符集合;
+           最后一个参数是超时时间(NULL-->永不超时,会一直阻塞住)
+           
+           如果按键没有按下,则程序一直阻塞在这里。一旦按键按下,则按键设备有数据
+           可读,此时函数将返回。
+        */
+        rv = select(kbd_fd + 1, &rds, NULL, NULL, NULL);
+        if (rv < 0) 
+        {
+            printf("Select() system call failure: %s\n", strerror(errno));
+            goto CleanUp;
+        }
+        else if (FD_ISSET(kbd_fd, &rds)) /* 是按键设备发生了事件 */
+        { 
+            //read读取input设备的数据包,数据包为input_event结构体类型。
+            if ((rv = read (kbd_fd, ev, size*BUTTON_CNT )) < size) 
+            {
+                printf("Reading data from kbd_fd failure: %s\n", strerror(errno));
+                break;
+            }
+            else
+            {
+                display_button_event(ev, rv/size);
+            }
+        }
+    }
+
+CleanUp:
+    close(kbd_fd);
+
+    return 0;
+}
+
+/* 该函数用来打印程序的使用方法 */
+void usage(char *name)
+{
+    char *progname = NULL;
+    char *ptr = NULL;
+
+    /* 字符串拷贝函数,该函数内部将调用malloc()来动态分配内存,然后将$name
+       字符串内容拷贝到malloc分配的内存中,这样使用完之后需要free释放内存. */
+    ptr = strdup(name); 
+    progname = basename(ptr); //去除该可执行文件的路径名,获取其自身名称(即keypad)
+
+    printf("Usage: %s [-p] -d <device>\n", progname);
+    printf(" -d[device  ] button device name\n");
+    printf(" -p[poll    ] Use poll mode, or default use infinit loop.\n");
+    printf(" -h[help    ] Display this help information\n"); 
+
+    free(ptr);  //和strdup对应,释放该内存
+    return;
+}
+
+/* 该函数用来解析按键设备上报的数据,并答应按键按下的相关信息 */
+void display_button_event(struct input_event *ev, int cnt)
+{
+    int i;
+    static struct timeval pressed_time;  //该变量用来存放按键按下的时间,注意static的使用。
+    struct timeval        duration_time; //该变量用来存放按键按下持续时间 
+
+    for(i=0; i<cnt; i++)
+    {
+        /* 当上报的时间type为EV_KEY时候并且,value值为1或0 (1为按下,0为释放) */
+        if(EV_KEY==ev[i].type && EV_PRESSED==ev[i].value)
+        {
+            pressed_time = ev[i].time;
+            printf("Keypad[%d] pressed time: %ld.%ld\n", 
+                   ev[i].code, pressed_time.tv_sec, pressed_time.tv_usec);
+        }
+        if(EV_KEY==ev[i].type && EV_RELEASED==ev[i].value)
+        {
+            /* 计算时间差函数 将第一个参数减去第二个参数的值的结果 放到第三个参数之中 */
+            timersub(&ev[i].time, &pressed_time, &duration_time);
+            printf("keypad[%d] released time: %ld.%ld\n", 
+                   ev[i].code, ev[i].time.tv_sec, ev[i].time.tv_usec);
+            printf("keypad[%d] duration time: %ld.%ld\n", 
+                   ev[i].code, duration_time.tv_sec, duration_time.tv_usec);
+        }
+    }
+}
diff --git a/drivers/test-apps/leds.c b/drivers/test-apps/leds.c
new file mode 100644
index 0000000..1918a0b
--- /dev/null
+++ b/drivers/test-apps/leds.c
@@ -0,0 +1,332 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2024 LingYun IoT System Studio
+ *                  All rights reserved.
+ *
+ *       Filename:  led.c
+ *    Description:  This file is used to control RGB 3-colors LED
+ *
+ *
+ * Pin connection:
+ *               RGB Led Module           IGKBoard
+ *                   R        <----->      #Pin33
+ *                   G        <----->      #Pin35
+ *                   B        <----->      #Pin37
+ *                  GND       <----->      GND
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <gpiod.h>
+
+#define DELAY     300
+
+#define ON        1
+#define OFF       0
+
+/* Three LEDs number */
+enum
+{
+    LED_R = 0,
+    LED_G,
+    LED_B,
+    LEDCNT,
+};
+
+enum
+{
+    ACTIVE_HIGH, /* High level will turn led on */
+    ACTIVE_LOW,  /* Low level will turn led on */
+};
+
+/* Three LEDs hardware information */
+typedef struct led_s
+{
+    const char               *name;      /* RGB 3-color LED name  */
+    int                       chip_num;  /* RGB 3-color LED connect chip */
+    int                       gpio_num;  /* RGB 3-color LED connect line */
+    int                       active;    /* RGB 3-color LED active level */
+    struct gpiod_line_request *request;  /* libgpiod gpio request handler */
+} led_t;
+
+static led_t leds_info[LEDCNT] =
+{
+    {"red",   0, 23, ACTIVE_HIGH, NULL }, /* GPIO1_IO23 on chip0 line 23, active high */
+    {"green", 4, 1,  ACTIVE_HIGH, NULL }, /* GPIO5_IO01 on chip4 line 1, active high */
+    {"blue",  4, 8,  ACTIVE_HIGH, NULL }, /* GPIO5_IO08 on chip4 line 8, active high */
+};
+
+/* Three LEDs API context */
+typedef struct leds_s
+{
+    led_t               *leds;  /* led pointer to leds_info */
+    int                  count; /* led count */
+} leds_t;
+
+
+/* function declaration  */
+int init_led(leds_t *leds);
+int term_led(leds_t *leds);
+int turn_led(leds_t *leds, int which, int cmd);
+static inline void msleep(unsigned long ms);
+
+
+int g_stop = 0;
+
+void sig_handler(int signum)
+{
+    switch( signum )
+    {
+        case SIGINT:
+        case SIGTERM:
+            g_stop = 1;
+
+        default:
+            break;
+    }
+
+    return ;
+}
+
+int main(int argc, char *argv[])
+{
+    int                 rv;
+    leds_t              leds =
+    {
+        .leds  = leds_info,
+        .count = LEDCNT,
+    };
+
+    if( (rv=init_led(&leds)) < 0 )
+    {
+        printf("initial leds gpio failure, rv=%d\n", rv);
+        return 1;
+    }
+    printf("initial RGB Led gpios okay\n");
+
+    signal(SIGINT,  sig_handler);
+    signal(SIGTERM, sig_handler);
+
+    while( !g_stop )
+    {
+        turn_led(&leds, LED_R, ON);
+        msleep(DELAY);
+        turn_led(&leds, LED_R, OFF);
+        msleep(DELAY);
+
+        turn_led(&leds, LED_G, ON);
+        msleep(DELAY);
+        turn_led(&leds, LED_G, OFF);
+        msleep(DELAY);
+
+        turn_led(&leds, LED_B, ON);
+        msleep(DELAY);
+        turn_led(&leds, LED_B, OFF);
+        msleep(DELAY);
+    }
+
+    term_led(&leds);
+    return 0;
+}
+
+int term_led(leds_t *leds)
+{
+    int            i;
+    led_t         *led;
+
+    printf("terminate RGB Led gpios\n");
+
+    if( !leds )
+    {
+        printf("Invalid input arguments\n");
+        return -1;
+    }
+
+    for(i=0; i<leds->count; i++)
+    {
+        led = &leds->leds[i];
+
+        if( led->request )
+        {
+            turn_led(leds, i, OFF);
+            gpiod_line_request_release(led->request);
+        }
+    }
+
+    return 0;
+}
+
+
+int init_led(leds_t *leds)
+{
+    led_t                       *led;
+    int                          i, rv = 0;
+    char                         chip_dev[32];
+    struct gpiod_chip           *chip;      /* gpio chip */
+    struct gpiod_line_settings  *settings;  /* gpio direction, bias, active_low, value */
+    struct gpiod_line_config    *line_cfg;  /* gpio line */
+    struct gpiod_request_config *req_cfg;   /* gpio consumer, it can be NULL */
+
+
+    if( !leds )
+    {
+        printf("Invalid input arguments\n");
+        return -1;
+    }
+
+
+    /* defined in libgpiod-2.0/lib/line-settings.c:
+
+        struct gpiod_line_settings {
+            enum gpiod_line_direction direction;
+            enum gpiod_line_edge edge_detection;
+            enum gpiod_line_drive drive;
+            enum gpiod_line_bias bias;
+            bool active_low;
+            enum gpiod_line_clock event_clock;
+            long debounce_period_us;
+            enum gpiod_line_value output_value;
+        };
+     */
+    settings = gpiod_line_settings_new();
+    if (!settings)
+    {
+        printf("unable to allocate line settings\n");
+        rv = -2;
+        goto cleanup;
+    }
+
+    /* defined in libgpiod-2.0/lib/line-config.c
+
+        struct gpiod_line_config {
+            struct per_line_config line_configs[LINES_MAX];
+            size_t num_configs;
+            enum gpiod_line_value output_values[LINES_MAX];
+            size_t num_output_values;
+            struct settings_node *sref_list;
+        };
+    */
+
+    line_cfg = gpiod_line_config_new();
+    if (!line_cfg)
+    {
+        printf("unable to allocate the line config structure");
+        rv = -2;
+        goto cleanup;
+    }
+
+
+    /* defined in libgpiod-2.0/lib/request-config.c:
+
+        struct gpiod_request_config {
+            char consumer[GPIO_MAX_NAME_SIZE];
+            size_t event_buffer_size;
+        };
+     */
+    req_cfg = gpiod_request_config_new();
+    if (!req_cfg)
+    {
+        printf("unable to allocate the request config structure");
+        rv = -2;
+        goto cleanup;
+    }
+
+    for(i=0; i<leds->count; i++)
+    {
+        led = &leds->leds[i];
+
+        snprintf(chip_dev, sizeof(chip_dev), "/dev/gpiochip%d", led->chip_num);
+        chip = gpiod_chip_open(chip_dev);
+        if( !chip )
+        {
+            printf("open gpiochip failure, maybe you need running as root\n");
+            rv = -3;
+            goto cleanup;
+        }
+
+        /* Set as output direction, active low and default level as inactive */
+        gpiod_line_settings_reset(settings);
+        gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_OUTPUT);
+        gpiod_line_settings_set_active_low(settings, led->active);
+        gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_INACTIVE);
+
+        /* set gpio line */
+        gpiod_line_config_reset(line_cfg);
+        gpiod_line_config_add_line_settings(line_cfg, &led->gpio_num, 1, settings);
+
+        /* Can be NULL for default settings. */
+        gpiod_request_config_set_consumer(req_cfg, led->name);
+
+        /* Request a set of lines for exclusive usage. */
+        led->request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+
+        gpiod_chip_close(chip);
+        //printf("request %5s led[%d] for gpio output okay\n", led->name, led->gpio);
+    }
+
+cleanup:
+
+    if( rv< 0 )
+        term_led(leds);
+
+    if( line_cfg )
+        gpiod_line_config_free(line_cfg);
+
+    if( req_cfg )
+        gpiod_request_config_free(req_cfg);
+
+    if( settings )
+        gpiod_line_settings_free(settings);
+
+    return rv;
+}
+
+int turn_led(leds_t *leds, int which, int cmd)
+{
+    led_t         *led;
+    int            rv = 0;
+    int            value = 0;
+
+    if( !leds || which<0 || which>=leds->count )
+    {
+        printf("Invalid input arguments\n");
+        return -1;
+    }
+
+    led = &leds->leds[which];
+
+    value = OFF==cmd ? GPIOD_LINE_VALUE_INACTIVE : GPIOD_LINE_VALUE_ACTIVE;
+
+    gpiod_line_request_set_value(led->request, led->gpio_num, value);
+
+    return 0;
+}
+
+static inline void msleep(unsigned long ms)
+{
+    struct timespec cSleep;
+    unsigned long ulTmp;
+
+    cSleep.tv_sec = ms / 1000;
+    if (cSleep.tv_sec == 0)
+    {
+        ulTmp = ms * 10000;
+        cSleep.tv_nsec = ulTmp * 100;
+    }
+    else
+    {
+        cSleep.tv_nsec = 0;
+    }
+
+    nanosleep(&cSleep, 0);
+
+    return ;
+}
diff --git a/drivers/test-apps/libgpiod/build.sh b/drivers/test-apps/libgpiod/build.sh
new file mode 100755
index 0000000..6009e21
--- /dev/null
+++ b/drivers/test-apps/libgpiod/build.sh
@@ -0,0 +1,118 @@
+#!/bin/bash
+
+# library name and version
+# Official: https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git
+LIB_NAME=libgpiod-2.0
+PACK_SUFIX=tar.gz
+
+# Cross compiler for cross compile on Linux server
+CROSS_COMPILE=arm-linux-gnueabihf-
+
+# this project absolute path
+PRJ_PATH=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
+
+# binaries install path
+PREFIX_PATH=$PRJ_PATH/install
+
+# check installed or not file
+INST_FILE=$PREFIX_PATH/lib/libgpiod.so
+
+#+-------------------------+
+#| Shell script functions  |
+#+-------------------------+
+
+function pr_error() {
+    echo -e "\033[40;31m $1 \033[0m"
+}
+
+function pr_warn() {
+    echo -e "\033[40;33m $1 \033[0m"
+}
+
+function pr_info() {
+    echo -e "\033[40;32m $1 \033[0m"
+}
+
+function check_result()
+{
+    if [ $? != 0 ] ; then
+        pr_error $1
+    fi  
+}
+
+function do_export()
+{
+    pr_warn "set cross(${CROSS_COMPILE})"
+
+    # export cross toolchain
+    export CC=${CROSS_COMPILE}gcc
+    export CXX=${CROSS_COMPILE}g++
+    export AS=${CROSS_COMPILE}as
+    export AR=${CROSS_COMPILE}ar
+    export LD=${CROSS_COMPILE}ld
+    export NM=${CROSS_COMPILE}nm
+    export RANLIB=${CROSS_COMPILE}ranlib
+    export OBJDUMP=${CROSS_COMPILE}objdump
+    export STRIP=${CROSS_COMPILE}strip
+
+    # export cross configure
+    export CONFIG_CROSS=" --build=i686-pc-linux --host=arm-linux "
+
+    # Clear LDFLAGS and CFLAGS
+    export LDFLAGS=
+    export CFLAGS=
+}
+
+function do_fetch()
+{
+    if [ -e ${INST_FILE} ] ; then
+        pr_warn "$LIB_NAME compile and installed alredy"
+        exit ;
+    fi
+
+    if [ -d $LIB_NAME ] ; then
+        pr_warn "$LIB_NAME fetch already"
+        return ;
+    fi
+
+    if [ ! -f ${LIB_NAME}.${PACK_SUFIX} ] ; then
+        wget https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/snapshot/${LIB_NAME}.${PACK_SUFIX}
+        check_result "ERROR: download ${LIB_NAME} failure"
+    fi
+
+	pr_warn "$LIB_NAME download already, decompress it now"
+    tar -xzf ${LIB_NAME}.${PACK_SUFIX}
+}
+
+function do_build()
+{
+    cd $LIB_NAME
+
+    ./autogen.sh
+
+    do_export
+
+    echo "ac_cv_func_malloc_0_nonnull=yes" > arm-linux.cache
+    ./configure --prefix=${PREFIX_PATH} ${CONFIG_CROSS} --enable-static \
+        --cache-file=arm-linux.cache --enable-tools
+    check_result "ERROR: configure ${LIB_NAME} failure"
+
+    make -j ${JOBS} && make install
+    check_result "ERROR: compile ${LIB_NAME} failure"
+}
+
+function do_clean()
+{
+	rm -rf ${LIB_NAME}*
+	rm -rf install
+}
+
+if [[ $# == 1 && $1 == -c ]] ;then
+    pr_warn "start clean $LIB_NAME"
+    do_clean
+    exit;
+fi
+
+do_fetch
+
+do_build
diff --git a/drivers/test-apps/libgpiod/makefile b/drivers/test-apps/libgpiod/makefile
new file mode 100644
index 0000000..4cc8cac
--- /dev/null
+++ b/drivers/test-apps/libgpiod/makefile
@@ -0,0 +1,11 @@
+
+all: update_cross
+	@./build.sh
+
+clean:
+	@./build.sh -c
+
+update_cross:
+ifdef CROSS_COMPILE
+	sed -i 's|^CROSS_COMPILE=.*|CROSS_COMPILE=${CROSS_COMPILE}|g' build.sh
+endif
diff --git a/drivers/test-apps/makefile b/drivers/test-apps/makefile
new file mode 100644
index 0000000..94dc8b9
--- /dev/null
+++ b/drivers/test-apps/makefile
@@ -0,0 +1,50 @@
+
+# Cross compiler
+CROSS_COMPILE=arm-linux-gnueabihf-
+CC=${CROSS_COMPILE}gcc
+AR=${CROSS_COMPILE}ar
+
+# libgpiod compile install path
+LIBGPIOD_PATH=libgpiod/install
+
+# compile flags and link flags
+CFLAGS+=-I ${LIBGPIOD_PATH}/include
+LDFLAGS+=-L ${LIBGPIOD_PATH}/lib -lgpiod
+
+INSTALL_BINS=can_test
+
+all: libs
+	${CC} hello.c -o hello
+	${CC} ${CFLAGS} leds.c -o leds ${LDFLAGS}
+	${CC} keypad.c -o keypad
+	${CC} ds18b20.c -o ds18b20
+	${CC} pwm.c -o pwm
+	${CC} pwm_play.c -o pwm_play
+	${CC} sht20_fops.c -o sht20_fops
+	${CC} sht20_ioctl.c -o sht20_ioctl
+	${CC} spi_test.c -o spi_test
+	${CC} ttyS_test.c -o ttyS_test -lpthread
+	${CC} can_test.c -o can_test
+	@make install
+
+libs:
+	make -C libgpiod CROSS_COMPILE=${CROSS_COMPILE}
+
+clean:
+	@rm -f hello
+	@rm -f leds 
+	@rm -f keypad
+	@rm -f ds18b20
+	@rm -f pwm 
+	@rm -f pwm_play
+	@rm -f sht20_fops
+	@rm -f sht20_ioctl
+	@rm -f spi_test
+	@rm -f ttyS_test
+	@rm -f can_test
+
+distclean: clean
+	make clean -C libgpiod
+
+install:
+	cp ${INSTALL_BINS} /tftp
diff --git a/drivers/test-apps/pwm.c b/drivers/test-apps/pwm.c
new file mode 100644
index 0000000..c855da6
--- /dev/null
+++ b/drivers/test-apps/pwm.c
@@ -0,0 +1,341 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2021 LingYun IoT System Studio
+ *                  All rights reserved.
+ *
+ *       Filename:  pwm.c
+ *    Description:  This file is used to control PWM buzzer/Led
+ *
+ * Pin connection:
+ *               PWM Module              IGKBoard
+ *                  VCC       <----->      5V
+ *                  Led       <----->      #Pin28(PWM8)
+ *                  GND       <----->      GND
+ *
+ *
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+#include <getopt.h>
+#include <libgen.h>
+
+#define PWM_SYS_PATH      "/sys/class/pwm/pwmchip0"
+
+#define ENABLE            1
+#define DISABLE           0
+
+
+typedef struct pwm_s 
+{
+	char				pwm_path[64]; /* PWM path, such as /sys/class/pwm/pwmchip0 */
+	char				chn_path[64]; /* channel path, such as /sys/class/pwm/pwmchip0/pwm0 */
+	int                 pwm_num;      /* pwm number */
+	int                 chn_num;      /* channel number */
+} pwm_t; 
+
+
+int init_pwm(pwm_t *pwm, int pwm_num, int chn_num);
+int conf_pwm(pwm_t *pwm, int freq, int duty);
+int turn_pwm(pwm_t *pwm, int status);
+int term_pwm(pwm_t *pwm);
+
+static inline void msleep(unsigned long ms);
+
+static inline void banner(const char *progname)
+{
+    printf("%s program Version v1.0.0\n", progname);
+    printf("Copyright (C) 2023 LingYun IoT System Studio.\n");
+}
+
+static void program_usage(const char *progname)
+{
+
+    printf("Usage: %s [OPTION]...\n", progname);
+    printf(" This is pwm control program. \n");
+
+    printf(" -p[pwm     ]  Specify PWM chip, such as 1 for pwmchip1\n");
+    printf(" -c[channel ]  Specify PWM channel, such as 0\n");
+    printf(" -f[freq    ]  Specify PWM frequency, default 2500(Hz)\n");
+    printf(" -d[duty    ]  Specify PWM duty, default 50(50%%)\n");
+    printf(" -s[status  ]  Specify PWM status: 1->on(default), 0->off\n");
+    printf(" -h[help    ]  Display this help information\n");
+    printf(" -v[version ]  Display the program version\n");
+    printf("\n");
+
+	printf("Example buzzer : %s -p 1 -c 0 -f 10000 -d 50 -s 1\n", progname);
+	printf("Example Led    : %s -p 1 -c 1 -f 100 -d 50 -s 1\n", progname);
+	printf("Example disable: %s -p 1 -c 0 -s 0\n", progname);
+
+    printf("\n");
+    banner(progname);
+    return;
+}
+
+int main(int argc, char **argv)
+{
+    int             rv;
+    char           *progname=NULL;
+    int             chn_num = -1;
+    int             pwm_num = -1;
+    int             freq = 2500;
+    int             duty = 50;
+    int             status = ENABLE;
+	pwm_t           pwm;
+
+    struct option long_options[] = {
+        {"pwm", required_argument, NULL, 'p'},
+        {"channel", required_argument, NULL, 'c'},
+        {"freq", required_argument, NULL, 'f'},
+        {"duty", required_argument, NULL, 'd'},
+        {"status", required_argument, NULL, 's'},
+        {"version", no_argument, NULL, 'v'},
+        {"help", no_argument, NULL, 'h'},
+        {NULL, 0, NULL, 0}
+    };
+
+    progname = basename(argv[0]);
+
+    /* Parser the command line parameters */
+    while ((rv = getopt_long(argc, argv, "p:c:f:d:s:vh", long_options, NULL)) != -1)
+    {
+        switch (rv)
+        {
+            case 'p': /*  Set pwm chip, such as 1 for pwmchip1 */
+                pwm_num = atoi(optarg);
+                break;
+
+            case 'c': /*  Set pwm channel, such as 0,1 */
+                chn_num = atoi(optarg);
+                break;
+
+            case 'f': /*  Set pwm frequency */
+                freq = atoi(optarg);
+                break;
+
+            case 'd': /*  Set pwm duty cycle */
+                duty = atoi(optarg);
+                break;
+
+            case 's': /*  Set pwm status, 0 for disable and 1 for enable */
+                status = atoi(optarg);
+                break;
+
+            case 'v':  /* Get software version */
+                banner(progname);
+                return EXIT_SUCCESS;
+
+            case 'h':  /* Get help information */
+                program_usage(progname);
+                return 0;
+
+            default:
+                break;
+        }
+    }
+
+	if( pwm_num<0 || chn_num<0 )
+	{
+		program_usage(progname);
+		return 0;
+	}
+
+	init_pwm(&pwm, pwm_num, chn_num);
+
+
+	if( status )
+	{
+		if( (rv=conf_pwm(&pwm, freq, duty)) < 0 )
+		{
+			printf("Configure PWM failure, rv=%d\n", rv);
+			return 1;
+		}
+
+		turn_pwm(&pwm, ENABLE);
+	}
+	else
+	{
+		term_pwm(&pwm);
+	}
+
+    return 0;
+}
+
+int init_pwm(pwm_t *pwm, int pwm_num, int chn_num)
+{
+	if( !pwm || pwm_num<0 || chn_num<0 )
+	{
+		printf("Invalid input arguments\n");
+		return -1;
+	}
+
+	snprintf(pwm->pwm_path, sizeof(pwm->pwm_path), "/sys/class/pwm/pwmchip%d", pwm_num);
+	snprintf(pwm->chn_path, sizeof(pwm->chn_path), "/sys/class/pwm/pwmchip%d/pwm%d", pwm_num, chn_num);
+	pwm->pwm_num = pwm_num;
+	pwm->chn_num = chn_num;
+
+	return 0;
+}
+
+/* check PWM $channel export or not */
+int check_pwm(pwm_t *pwm)
+{
+	/* check /sys/class/pwm/pwmchipX/pwmY exist or not */
+	return access(pwm->chn_path, F_OK) ? 0 : 1;
+}
+
+/* export($export=1)/unexport($export=0) PWM $channel */
+int export_pwm(pwm_t *pwm, int export)
+{
+	int           fd;
+	char          buf[32];
+	char          path[256];
+
+	if( export && check_pwm(pwm) )
+		return 0; /* export already when export  */
+	else if( !export && !check_pwm(pwm) )
+		return 0; /* unexport already when unexport  */
+
+	/* export PWM channel by echo Y > /sys/class/pwm/pwmchipX/export */
+	snprintf(path, sizeof(path), "%s/%s", pwm->pwm_path, export?"export":"unexport");
+	if( (fd=open(path, O_WRONLY)) < 0 )
+	{
+		printf("open '%s' failed: %s\n", path, strerror(errno));
+		return -3;
+	}
+
+	snprintf(buf, sizeof(buf), "%d", pwm->chn_num);
+	write(fd, buf, strlen(buf));
+
+	msleep(100);
+
+	if( export && check_pwm(pwm) )
+		return 0; /* export already when export  */
+	else if( !export && !check_pwm(pwm) )
+		return 0; /* unexport already when unexport  */
+
+	return -4;
+}
+
+/* set PWM $channel */
+int set_pwm(pwm_t *pwm, int freq, int duty) 
+{
+	int           fd;
+	char          buf[32];
+	char          path[256];
+	int           period;
+	int           duty_cycle;
+
+	if( !check_pwm(pwm) )
+		return -2;
+
+	/* open PWM period file and write period in ns */
+	snprintf(path, sizeof(path), "%s/period", pwm->chn_path);
+	if( (fd=open(path, O_WRONLY)) < 0 )
+	{
+		printf("open '%s' failed: %s\n", path, strerror(errno));
+		return -3;
+	}
+	period = 1000000000/freq;
+	snprintf(buf, sizeof(buf), "%d", period);
+	write(fd, buf, strlen(buf));
+
+	/* open PWM duty_cycle file and write duty */
+	snprintf(path, sizeof(path), "%s/duty_cycle", pwm->chn_path);
+	if( (fd=open(path, O_WRONLY)) < 0 )
+	{
+		printf("open '%s' failed: %s\n", path, strerror(errno));
+		return -3;
+	}
+	duty_cycle = (period*duty) / 100;
+	snprintf(buf, sizeof(buf), "%d", duty_cycle);
+	write(fd, buf, strlen(buf));
+
+	return 0;
+}
+
+int conf_pwm(pwm_t *pwm, int freq, int duty)
+{
+	int           rv;
+	char          buf[32];
+	char          path[256];
+
+	if( (rv=export_pwm(pwm, 1)) )
+	{
+		printf("export PWM channel[%d] failed, rv=%d\n", pwm->chn_num, rv);
+		return rv; 
+	}
+
+	if( (rv=set_pwm(pwm, freq, duty)) )
+	{
+		printf("config PWM channel[%d] failed, rv=%d\n", pwm->chn_num, rv);
+		return rv;
+	}
+
+	printf("config pwm%d channel%d with freq[%d] duty[%d] okay\n", pwm->pwm_num, pwm->chn_num, freq, duty);
+
+	return 0;
+}
+
+int turn_pwm(pwm_t *pwm, int status)
+{
+	int           fd;
+	char          buf[32];
+	char          path[256];
+
+	if( !check_pwm(pwm) )
+		return -1;
+
+	/* open PWM enable file and enable(1)/disable(0) it */
+	snprintf(path, sizeof(path), "%s/enable", pwm->chn_path);
+	if( (fd=open(path, O_WRONLY)) < 0 )
+	{
+		printf("open '%s' failed: %s\n", path, strerror(errno));
+		return -3;
+	}
+	snprintf(buf, sizeof(buf), "%d", status?1:0);
+	write(fd, buf, strlen(buf));
+
+	printf("pwm[%d] channel[%d]%s\n", pwm->pwm_num, pwm->chn_num, status?"enable":"disable");
+
+	return 0;
+}
+
+int term_pwm(pwm_t *pwm)
+{
+	if( !check_pwm(pwm) )
+		return 0;
+
+	turn_pwm(pwm, DISABLE);
+	export_pwm(pwm, 0);
+
+	return 0;
+}
+
+static inline void msleep(unsigned long ms)
+{
+    struct timespec cSleep;
+    unsigned long ulTmp;
+
+    cSleep.tv_sec = ms / 1000;
+    if (cSleep.tv_sec == 0)
+    {
+        ulTmp = ms * 10000;
+        cSleep.tv_nsec = ulTmp * 100;
+    }
+    else
+    {
+        cSleep.tv_nsec = 0;
+    }
+
+    nanosleep(&cSleep, 0);
+
+    return ;
+}
diff --git a/drivers/test-apps/pwm_play.c b/drivers/test-apps/pwm_play.c
new file mode 100644
index 0000000..63cc028
--- /dev/null
+++ b/drivers/test-apps/pwm_play.c
@@ -0,0 +1,214 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2021 Guo Wenxue<Email:guowenxue@gmail.com QQ:281143292>
+ *                  All rights reserved.
+ *
+ *       Filename:  pwm_music.c
+ *    Description:  This file used to test PWM music
+ *                 
+ *        Version:  1.0.0(10/21/2022~)
+ *         Author:  Guo Wenxue <guowenxue@gmail.com>
+ *      ChangeLog:  1, Release initial version on "10/21/2022 17:46:18 PM"
+ *                 
+ ********************************************************************************/
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+#include <string.h>
+
+#define PWM_CHIP			1 /* buzzer on pwmchip1 */
+
+typedef struct pwm_note_s
+{
+    unsigned int    msec; //持续时间,毫秒
+    unsigned int    freq;//频率
+    unsigned char   duty;//相对占空比,百分比 * 100
+}pwm_note_t;
+
+/* 使用宏确定使用中音还是高、低音 */
+#define CX                      CM
+
+/* 低、中、高音频率*/
+static const unsigned short CL[8] = {0, 262, 294, 330, 349, 392, 440, 494};
+static const unsigned short CM[8] = {0, 523, 587, 659, 698, 784, 880, 988};
+static const unsigned short CH[8] = {0, 1046, 1175, 1318, 1397, 1568, 1760, 1976};
+
+/* 小星星曲子*/
+static unsigned short melody[] = {
+    CX[1], CX[1], CX[5], CX[5], CX[6], CX[6], CX[5], CX[0],
+    CX[4], CX[4], CX[3], CX[3], CX[2], CX[2], CX[1], CX[0],
+    CX[5], CX[5], CX[4], CX[4], CX[3], CX[3], CX[2], CX[0],
+    CX[5], CX[5], CX[4], CX[4], CX[3], CX[3], CX[2], CX[0],
+};
+
+static char pwm_path[64]; /* pwm file path buffer */
+
+static int pwm_export(int chip);
+static int pwm_config(const char *attr, const char *val);
+static int pwm_ring(pwm_note_t *note);
+static inline void msleep(unsigned long ms);
+
+int main(int argc, char *argv[])
+{
+    pwm_note_t    note;
+    int           i;
+
+	if( pwm_export(PWM_CHIP) < 0 ) 
+		return 1;
+
+    pwm_config("enable", "1");
+
+    for(i=0; i<sizeof(melody)/sizeof(melody[0]); i++)
+    {
+        if(melody[i] == 0)
+        {
+            note.duty = 0; 
+        }
+        else
+        {
+            note.duty = 15; //越大音量越大
+            note.freq = melody[i];
+        }
+        note.msec = 300;
+
+        pwm_ring(&note);
+    }
+
+    pwm_config("enable", "0");
+
+    return 0;
+}
+
+static int pwm_export(int chip)
+{
+    char file_path[100];
+    int  fd, rv=0;
+
+    /* 导出pwm 首先确定最终导出的文件夹路径*/
+    memset(pwm_path, 0, sizeof(pwm_path));
+    snprintf(pwm_path, sizeof(pwm_path), "/sys/class/pwm/pwmchip%d/pwm0", PWM_CHIP);
+    
+    //如果pwm0 目录已经存在了, 则直接返回
+    if ( !access(pwm_path, F_OK)) 
+		return 0;
+
+	//如果pwm0 目录不存在, 则开始导出
+	
+	memset(file_path, 0, sizeof(file_path));
+	snprintf(file_path, sizeof(file_path) , "/sys/class/pwm/pwmchip%d/export", PWM_CHIP);
+
+	if ( (fd = open(file_path, O_WRONLY) < 0))
+	{
+		printf("ERROR: open pwmchip%d error\n", PWM_CHIP);
+		return 1;
+	}
+
+	if ( write(fd, "0", 1) < 0 )
+	{
+		printf("write '0' to  pwmchip%d/export error\n", PWM_CHIP);
+		rv = 2;
+	}
+
+	close(fd); 
+	return rv;
+}
+
+/*pwm 配置函数 attr:属性文件名字 
+ *  val:属性的值(字符串)
+*/
+static int pwm_config(const char *attr, const char *val)
+{
+    char file_path[100];
+    int fd;
+
+    if( !attr || !val )
+    {
+        printf("[%s] argument error\n", __FUNCTION__);
+        return -1;
+    }
+
+    memset(file_path, 0, sizeof(file_path));
+    snprintf(file_path, sizeof(file_path), "%s/%s", pwm_path, attr);
+    if( (fd=open(file_path, O_WRONLY)) < 0 ) 
+	{
+        printf("[%s] open %s error\n", __FUNCTION__, file_path);
+        return fd;
+    }
+
+    if ( write(fd, val, strlen(val)) < 0) {
+        printf("[%s] write %s to %s error\n", __FUNCTION__, val, file_path);
+        close(fd);
+        return -2;
+    }
+
+    close(fd);  //关闭文件
+    return 0;
+}
+
+/* pwm蜂鸣器响一次声音 */
+static int pwm_ring(pwm_note_t *note)
+{
+    unsigned long period = 0;
+    unsigned long duty_cycle = 0;
+    char period_str[20] = {};
+    char duty_cycle_str[20] = {}; 
+
+    if( !note || note->duty > 100 )
+    {
+        printf("[INFO] %s argument error.\n", __FUNCTION__);
+        return -1;
+    }
+
+    period = (unsigned long)((1.f / (double)note->freq) * 1e9);//ns单位
+    duty_cycle = (unsigned long)(((double)note->duty / 100.f) * (double)period);//ns单位
+
+    snprintf(period_str, sizeof(period_str), "%lu", period);
+    snprintf(duty_cycle_str, sizeof(duty_cycle_str), "%lu", duty_cycle);
+
+    //设置pwm频率和周期
+    if (pwm_config("period", period_str))
+    {
+        printf("pwm_config period failure.\n");
+        return -1;
+    }
+    if (pwm_config("duty_cycle", duty_cycle_str))
+    {
+        printf("pwm_config duty_cycle failure.\n");
+        return -2;
+    }
+
+    msleep(note->msec);
+
+    /* 设置占空比为0 蜂鸣器无声 */
+    if (pwm_config("duty_cycle", "0"))
+    {
+        printf("pwm_config duty_cycle failure.\n");
+        return -3;
+    }
+    msleep(20);
+
+    return 0;
+}
+
+/* ms级休眠函数 */
+static inline void msleep(unsigned long ms)
+{
+    struct timespec cSleep;
+    unsigned long ulTmp;
+
+    cSleep.tv_sec = ms / 1000;
+    if (cSleep.tv_sec == 0)
+    {
+        ulTmp = ms * 10000;
+        cSleep.tv_nsec = ulTmp * 100;
+    }
+    else
+    {
+        cSleep.tv_nsec = 0;
+    }
+
+    nanosleep(&cSleep, 0);
+}
diff --git a/drivers/test-apps/sht20_fops.c b/drivers/test-apps/sht20_fops.c
new file mode 100644
index 0000000..af3c03e
--- /dev/null
+++ b/drivers/test-apps/sht20_fops.c
@@ -0,0 +1,208 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2024 LingYun IoT System Studio
+ *                  All rights reserved.
+ *
+ *       Filename:  sht20_fops.c
+ *    Description:  This file is temperature and relative humidity sensor SHT20 code
+ *
+ *        Version:  1.0.0(2024/05/08)
+ *         Author:  Guo Wenxue <guowenxue@gmail.com>
+ *      ChangeLog:  1, Release initial version on "2024/05/08 12:13:26"
+ *
+ * Pin connection:
+ *
+ *               SHT20 Module            IGKBoard
+ *                   VCC      <----->      3.3V
+ *                   SDA      <----->      #Pin3(I2C1_SDA)
+ *                   SCL      <----->      #Pin5(I2C1_SCL)
+ *                   GND      <----->      GND
+ *
+ * /run/media/mmcblk1p1/config.txt:
+ *
+ *          # Eanble I2C overlay
+ *          dtoverlay_i2c=1
+ **
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/types.h>
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+
+int sht20_checksum(uint8_t *data, int len, int8_t checksum);
+static inline void msleep(unsigned long ms);
+static inline void dump_buf(const char *prompt, uint8_t *buf, int size);
+
+int main(int argc, char **argv)
+{
+    int             fd, rv;
+    float           temp, rh;
+    char           *i2c_dev = NULL;
+    uint8_t         buf[4];
+
+
+    if( argc != 2)
+    {
+        printf("This program used to do I2C test by SHT20 sensor.\n");
+        printf("Usage: %s /dev/i2c-x\n", argv[0]);
+        return 0;
+    }
+
+    i2c_dev = argv[1];
+
+    /*+--------------------------------+
+     *|     open /dev/i2c-x device     |
+     *+--------------------------------+*/
+    if( (fd=open(i2c_dev, O_RDWR)) < 0)
+    {
+        printf("i2c device open failed: %s\n", strerror(errno));
+        return -1;
+    }
+
+    /*+--------------------------------+
+     *| set I2C mode and slave address |
+     *+--------------------------------+*/
+    ioctl(fd, I2C_TENBIT, 0);    /* Not 10-bit but 7-bit mode */
+    ioctl(fd, I2C_SLAVE, 0x40);  /* set SHT2x slava address 0x40 */
+
+    /*+--------------------------------+
+     *|   software reset SHT20 sensor  |
+     *+--------------------------------+*/
+
+    buf[0] = 0xFE;
+    write(fd, buf, 1);
+
+    msleep(50);
+
+
+    /*+--------------------------------+
+     *|   trigger temperature measure  |
+     *+--------------------------------+*/
+
+    buf[0] = 0xF3;
+    write(fd, buf, 1);
+
+    msleep(85); /* datasheet: typ=66, max=85 */
+
+    memset(buf, 0, sizeof(buf));
+    read(fd, buf, 3);
+    dump_buf("Temperature sample data: ", buf, 3);
+
+    if( !sht20_checksum(buf, 2, buf[2]) )
+    {
+        printf("Temperature sample data CRC checksum failure.\n");
+        goto cleanup;
+    }
+
+    temp = 175.72 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 46.85;
+
+
+    /*+--------------------------------+
+     *|    trigger humidity measure    |
+     *+--------------------------------+*/
+
+    buf[0] = 0xF5;
+    write(fd, buf, 1);
+
+    msleep(29); /* datasheet: typ=22, max=29 */
+
+    memset(buf, 0, sizeof(buf));
+    read(fd, buf, 3);
+    dump_buf("Relative humidity sample data: ", buf, 3);
+
+    if( !sht20_checksum(buf, 2, buf[2]) )
+    {
+        printf("Relative humidity sample data CRC checksum failure.\n");
+        goto cleanup;
+    }
+
+    rh = 125 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 6;
+
+    /*+--------------------------------+
+     *|    print the measure result    |
+     *+--------------------------------+*/
+
+    printf("Temperature=%lf 'C relative humidity=%lf%%\n", temp, rh);
+
+cleanup:
+    close(fd);
+    return 0;
+}
+
+int sht20_checksum(uint8_t *data, int len, int8_t checksum)
+{
+    int8_t crc = 0;
+    int8_t bit;
+    int8_t byteCtr;
+
+    //calculates 8-Bit checksum with given polynomial: x^8 + x^5 + x^4 + 1
+    for (byteCtr = 0; byteCtr < len; ++byteCtr)
+    {
+        crc ^= (data[byteCtr]);
+        for ( bit = 8; bit > 0; --bit)
+        {
+            /* x^8 + x^5 + x^4 + 1 = 0001 0011 0001 = 0x131 */
+            if (crc & 0x80) crc = (crc << 1) ^ 0x131;
+            else crc = (crc << 1);
+        }
+    }
+
+    if (crc != checksum)
+        return 0;
+    else
+        return 1;
+}
+
+static inline void msleep(unsigned long ms)
+{
+    struct timespec cSleep;
+    unsigned long ulTmp;
+
+    cSleep.tv_sec = ms / 1000;
+    if (cSleep.tv_sec == 0)
+    {
+        ulTmp = ms * 10000;
+        cSleep.tv_nsec = ulTmp * 100;
+    }
+    else
+    {
+        cSleep.tv_nsec = 0;
+    }
+
+    nanosleep(&cSleep, 0);
+    return ;
+}
+
+static inline void dump_buf(const char *prompt, uint8_t *buf, int size)
+{
+    int          i;
+
+    if( !buf )
+    {
+        return ;
+    }
+
+    if( prompt )
+    {
+        printf("%-32s ", prompt);
+    }
+
+    for(i=0; i<size; i++)
+    {
+        printf("%02x ", buf[i]);
+    }
+    printf("\n");
+
+    return ;
+}
diff --git a/drivers/test-apps/sht20_ioctl.c b/drivers/test-apps/sht20_ioctl.c
new file mode 100644
index 0000000..9f95d05
--- /dev/null
+++ b/drivers/test-apps/sht20_ioctl.c
@@ -0,0 +1,289 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2024 LingYun IoT System Studio
+ *                  All rights reserved.
+ *
+ *       Filename:  sht20_ioctl.c
+ *    Description:  This file is temperature and relative humidity sensor SHT20 code
+ *
+ *        Version:  1.0.0(2024/05/08)
+ *         Author:  Guo Wenxue <guowenxue@gmail.com>
+ *      ChangeLog:  1, Release initial version on "2024/05/08 12:13:26"
+ *
+ * Pin connection:
+ *
+ *               SHT20 Module            IGKBoard
+ *                   VCC      <----->      3.3V
+ *                   SDA      <----->      #Pin3(I2C1_SDA)
+ *                   SCL      <----->      #Pin5(I2C1_SCL)
+ *                   GND      <----->      GND
+ *
+ * /run/media/mmcblk1p1/config.txt:
+ *
+ *          # Eanble I2C overlay
+ *          dtoverlay_i2c=1
+ **
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/types.h>
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+
+int i2c_write(int fd, uint8_t slave_addr, uint8_t *data, int len);
+int i2c_read(int fd, uint8_t slave_addr, uint8_t *buf, int size);
+int sht20_checksum(uint8_t *data, int len, int8_t checksum);
+static inline void msleep(unsigned long ms);
+static inline void dump_buf(const char *prompt, uint8_t *buf, int size);
+
+int main(int argc, char **argv)
+{
+    int             fd, rv;
+    float           temp, rh;
+    char           *i2c_dev = NULL;
+    uint8_t         buf[4];
+
+
+    if( argc != 2)
+    {
+        printf("This program used to do I2C test by SHT20 sensor.\n");
+        printf("Usage: %s /dev/i2c-x\n", argv[0]);
+        return 0;
+    }
+
+    i2c_dev = argv[1];
+
+    /*+--------------------------------+
+     *|     open /dev/i2c-x device     |
+     *+--------------------------------+*/
+    if( (fd=open(i2c_dev, O_RDWR)) < 0)
+    {
+        printf("i2c device open failed: %s\n", strerror(errno));
+        return -1;
+    }
+
+    /*+--------------------------------+
+     *|   software reset SHT20 sensor  |
+     *+--------------------------------+*/
+
+    buf[0] = 0xFE;
+    i2c_write(fd, 0x40, buf, 1);
+
+    msleep(50);
+
+
+    /*+--------------------------------+
+     *|   trigger temperature measure  |
+     *+--------------------------------+*/
+
+    buf[0] = 0xF3;
+    i2c_write(fd, 0x40, buf, 1);
+
+    msleep(85); /* datasheet: typ=66, max=85 */
+
+    memset(buf, 0, sizeof(buf));
+    i2c_read(fd, 0x40, buf, 3);
+    dump_buf("Temperature sample data: ", buf, 3);
+
+    if( !sht20_checksum(buf, 2, buf[2]) )
+    {
+        printf("Temperature sample data CRC checksum failure.\n");
+        goto cleanup;
+    }
+
+    temp = 175.72 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 46.85;
+
+
+    /*+--------------------------------+
+     *|    trigger humidity measure    |
+     *+--------------------------------+*/
+
+    buf[0] = 0xF5;
+    i2c_write(fd, 0x40, buf, 1);
+
+    msleep(29); /* datasheet: typ=22, max=29 */
+
+    memset(buf, 0, sizeof(buf));
+    i2c_read(fd, 0x40, buf, 3);
+    dump_buf("Relative humidity sample data: ", buf, 3);
+
+    if( !sht20_checksum(buf, 2, buf[2]) )
+    {
+        printf("Relative humidity sample data CRC checksum failure.\n");
+        goto cleanup;
+    }
+
+    rh = 125 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 6;
+
+    /*+--------------------------------+
+     *|    print the measure result    |
+     *+--------------------------------+*/
+
+    printf("Temperature=%lf 'C relative humidity=%lf%%\n", temp, rh);
+
+cleanup:
+    close(fd);
+    return 0;
+}
+
+int sht20_checksum(uint8_t *data, int len, int8_t checksum)
+{
+    int8_t crc = 0;
+    int8_t bit;
+    int8_t byteCtr;
+
+    //calculates 8-Bit checksum with given polynomial: x^8 + x^5 + x^4 + 1
+    for (byteCtr = 0; byteCtr < len; ++byteCtr)
+    {
+        crc ^= (data[byteCtr]);
+        for ( bit = 8; bit > 0; --bit)
+        {
+            /* x^8 + x^5 + x^4 + 1 = 0001 0011 0001 = 0x131 */
+            if (crc & 0x80) crc = (crc << 1) ^ 0x131;
+            else crc = (crc << 1);
+        }
+    }
+
+    if (crc != checksum)
+        return 0;
+    else
+        return 1;
+}
+
+int i2c_write(int fd, uint8_t slave_addr, uint8_t *data, int len)
+{
+    struct i2c_rdwr_ioctl_data i2cdata;
+    int rv = 0;
+
+    if( !data || len<= 0)
+    {
+        printf("%s() invalid input arguments!\n", __func__);
+        return -1;
+    }
+
+    i2cdata.nmsgs = 1;
+    i2cdata.msgs = malloc( sizeof(struct i2c_msg)*i2cdata.nmsgs );
+    if ( !i2cdata.msgs )
+    {
+        printf("%s() msgs malloc failed!\n", __func__);
+        return -2;
+    }
+
+    i2cdata.msgs[0].addr = slave_addr;
+    i2cdata.msgs[0].flags = 0; //write
+    i2cdata.msgs[0].len = len;
+    i2cdata.msgs[0].buf = malloc(len);
+    if( !i2cdata.msgs[0].buf )
+    {
+        printf("%s() msgs malloc failed!\n", __func__);
+        rv = -3;
+        goto cleanup;
+    }
+    memcpy(i2cdata.msgs[0].buf, data, len);
+
+
+    if( ioctl(fd, I2C_RDWR, &i2cdata)<0 )
+    {
+        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+        rv = -4;
+        goto cleanup;
+    }
+
+cleanup:
+    if( i2cdata.msgs[0].buf )
+        free(i2cdata.msgs[0].buf);
+
+    if( i2cdata.msgs )
+        free(i2cdata.msgs);
+
+    return rv;
+}
+
+int i2c_read(int fd, uint8_t slave_addr, uint8_t *buf, int size)
+{
+    struct i2c_rdwr_ioctl_data i2cdata;
+    int rv = 0;
+
+    if( !buf || size<= 0)
+    {
+        printf("%s() invalid input arguments!\n", __func__);
+        return -1;
+    }
+
+    i2cdata.nmsgs = 1;
+    i2cdata.msgs = malloc( sizeof(struct i2c_msg)*i2cdata.nmsgs );
+    if ( !i2cdata.msgs )
+    {
+        printf("%s() msgs malloc failed!\n", __func__);
+        return -2;
+    }
+
+    i2cdata.msgs[0].addr = slave_addr;
+    i2cdata.msgs[0].flags = I2C_M_RD; //read
+    i2cdata.msgs[0].len = size;
+    i2cdata.msgs[0].buf = buf;
+    memset(buf, 0, size);
+
+    if( ioctl(fd, I2C_RDWR, &i2cdata)<0 )
+    {
+        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+        rv = -4;
+    }
+
+    free( i2cdata.msgs );
+    return rv;
+}
+
+
+static inline void msleep(unsigned long ms)
+{
+    struct timespec cSleep;
+    unsigned long ulTmp;
+
+    cSleep.tv_sec = ms / 1000;
+    if (cSleep.tv_sec == 0)
+    {
+        ulTmp = ms * 10000;
+        cSleep.tv_nsec = ulTmp * 100;
+    }
+    else
+    {
+        cSleep.tv_nsec = 0;
+    }
+
+    nanosleep(&cSleep, 0);
+    return ;
+}
+
+static inline void dump_buf(const char *prompt, uint8_t *buf, int size)
+{
+    int          i;
+
+    if( !buf )
+    {
+        return ;
+    }
+
+    if( prompt )
+    {
+        printf("%-32s ", prompt);
+    }
+
+    for(i=0; i<size; i++)
+    {
+        printf("%02x ", buf[i]);
+    }
+    printf("\n");
+
+    return ;
+}
diff --git a/drivers/test-apps/spi_test.c b/drivers/test-apps/spi_test.c
new file mode 100644
index 0000000..8d80e8a
--- /dev/null
+++ b/drivers/test-apps/spi_test.c
@@ -0,0 +1,205 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2024 LingYun IoT System Studio
+ *                  All rights reserved.
+ *
+ *       Filename:  spi_test.c
+ *    Description:  This file is SPI loop test program
+ *
+ *        Version:  1.0.0(05/23/2024)
+ *         Author:  Guo Wenxue <guowenxue@gmail.com>
+ *      ChangeLog:  1, Release initial version on "05/23/2024 07:51:06 PM"
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <linux/spi/spidev.h>
+
+typedef struct spi_ctx_s
+{
+    int         fd;
+    char        dev[64];
+    uint8_t     bits;
+    uint16_t    delay;
+    uint32_t    mode;
+    uint32_t    speed;
+} spi_ctx_t;
+
+static int spi_init(spi_ctx_t *spi_ctx);
+static int transfer(spi_ctx_t *spi_ctx, uint8_t const *tx, uint8_t const *rx, size_t len);
+
+static void program_usage(char *progname)
+{
+    printf("Usage: %s [OPTION]...\n", progname);
+    printf(" %s is a program to test IGKBoard loop spi\n", progname);
+
+    printf("\nMandatory arguments to long options are mandatory for short options too:\n");
+    printf(" -d[device  ]  Specify SPI device, such as: /dev/spidev0.0\n");
+    printf(" -s[speed   ]  max speed (Hz), such as: -s 500000\n");
+    printf(" -p[print   ]  Send data (such as: -p 1234/xde/xad)\n");
+
+    printf("\n%s version 1.0\n", progname);
+    return;
+}
+
+int main(int argc,char * argv[])
+{
+    spi_ctx_t          spi_ctx;
+    uint32_t           spi_speed = 500000;  // Default SPI speed 500KHz
+    char              *spi_dev = "/dev/spidev0.0"; // Default SPI device
+    char              *input_tx = "Hello LingYun"; // Default transfer data
+    uint8_t            rx_buffer[128];
+    int                opt;
+    char               *progname=NULL;
+
+    struct option long_options[] = {
+        {"device", required_argument, NULL, 'd'},
+        {"speed", required_argument, NULL, 's'},
+        {"print", required_argument, NULL, 'p'},
+        {"help", no_argument, NULL, 'h'},
+        {NULL, 0, NULL, 0}
+    };
+
+    progname = (char *)basename(argv[0]);
+
+    while((opt = getopt_long(argc, argv, "d:s:p:h", long_options, NULL)) != -1)
+    {
+        switch (opt)
+        {
+        case 'd':
+            spi_dev = optarg;
+            break;
+
+        case 's':
+            spi_speed = atoi(optarg);
+            break;
+
+        case 'p':
+            input_tx = optarg;
+            break;
+
+        case 'h':
+            program_usage(progname);
+            return 0;
+
+        default:
+            break;
+        }
+    }
+
+    memset(&spi_ctx, 0, sizeof(spi_ctx));
+    strncpy(spi_ctx.dev, spi_dev, sizeof(spi_ctx.dev));
+    spi_ctx.bits = 8;
+    spi_ctx.delay = 100;
+    spi_ctx.mode = SPI_MODE_2;
+    spi_ctx.speed = spi_speed;
+
+    if( spi_init(&spi_ctx) < 0 )
+    {
+        printf("Initial SPI device '%s' failed.\n", spi_ctx.dev);
+        return -1;
+    }
+    printf("Initial SPI device '%s' okay.\n", spi_ctx.dev);
+
+    if ( transfer(&spi_ctx, input_tx, rx_buffer, strlen(input_tx)) < 0 )
+    {
+        printf("spi transfer error\n");
+        return -2;
+    }
+
+    printf("tx_buffer: | %s |\n", input_tx);
+    printf("rx_buffer: | %s |\n", rx_buffer);
+
+    return 0;
+}
+
+int transfer(spi_ctx_t *spi_ctx, uint8_t const *tx, uint8_t const *rx, size_t len)
+{
+    struct spi_ioc_transfer tr = {
+        .tx_buf = (unsigned long )tx,
+        .rx_buf = (unsigned long )rx,
+        .len = len,
+        .delay_usecs = spi_ctx->delay,
+        .speed_hz = spi_ctx->speed,
+        .bits_per_word = spi_ctx->bits,
+    };
+
+    if( ioctl(spi_ctx->fd, SPI_IOC_MESSAGE(1), &tr) < 0)
+    {
+        printf("ERROR: SPI transfer failure: %s\n ", strerror(errno));
+        return -1;
+    }
+
+    return 0;
+}
+
+int spi_init(spi_ctx_t *spi_ctx)
+{
+    int ret;
+    spi_ctx->fd = open(spi_ctx->dev, O_RDWR);
+    if(spi_ctx->fd < 0)
+    {
+        printf("open %s error\n", spi_ctx->dev);
+        return -1;
+    }
+
+    ret = ioctl(spi_ctx->fd, SPI_IOC_RD_MODE, &spi_ctx->mode);
+    if( ret < 0 )
+    {
+        printf("ERROR: SPI set SPI_IOC_RD_MODE [0x%x] failure: %s\n ", spi_ctx->mode, strerror(errno));
+        goto cleanup;
+    }
+
+    ret = ioctl(spi_ctx->fd, SPI_IOC_WR_MODE, &spi_ctx->mode);
+    if( ret < 0 )
+    {
+        printf("ERROR: SPI set SPI_IOC_WR_MODE [0x%x] failure: %s\n ", spi_ctx->mode, strerror(errno));
+        goto cleanup;
+    }
+
+    ret = ioctl(spi_ctx->fd, SPI_IOC_RD_BITS_PER_WORD, &spi_ctx->bits);
+    if( ret < 0 )
+    {
+        printf("ERROR: SPI set SPI_IOC_RD_BITS_PER_WORD [%d] failure: %s\n ", spi_ctx->bits, strerror(errno));
+        goto cleanup;
+    }
+    ret = ioctl(spi_ctx->fd, SPI_IOC_WR_BITS_PER_WORD, &spi_ctx->bits);
+    if( ret < 0 )
+    {
+        printf("ERROR: SPI set SPI_IOC_WR_BITS_PER_WORD [%d] failure: %s\n ", spi_ctx->bits, strerror(errno));
+        goto cleanup;
+    }
+
+    ret = ioctl(spi_ctx->fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_ctx->speed);
+    if( ret == -1)
+    {
+        printf("ERROR: SPI set SPI_IOC_WR_MAX_SPEED_HZ [%d] failure: %s\n ", spi_ctx->speed, strerror(errno));
+        goto cleanup;
+    }
+    ret = ioctl(spi_ctx->fd, SPI_IOC_RD_MAX_SPEED_HZ, &spi_ctx->speed);
+    if( ret == -1)
+    {
+        printf("ERROR: SPI set SPI_IOC_RD_MAX_SPEED_HZ [%d] failure: %s\n ", spi_ctx->speed, strerror(errno));
+        goto cleanup;
+    }
+
+    printf("spi mode: 0x%x\n", spi_ctx->mode);
+    printf("bits per word: %d\n", spi_ctx->bits);
+    printf("max speed: %d Hz (%d KHz)\n", spi_ctx->speed, spi_ctx->speed / 1000);
+
+   return spi_ctx->fd;
+
+cleanup:
+   close(spi_ctx->fd);
+   return -1;
+}
diff --git a/drivers/test-apps/ttyS_test.c b/drivers/test-apps/ttyS_test.c
new file mode 100644
index 0000000..80ee223
--- /dev/null
+++ b/drivers/test-apps/ttyS_test.c
@@ -0,0 +1,106 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2024 LingYun IoT System Studio
+ *                  All rights reserved.
+ *
+ *       Filename:  ttyS_test.c
+ *    Description:  This file is comport loop back test program
+ *
+ *        Version:  1.0.0(05/24/2024)
+ *         Author:  Guo Wenxue <guowenxue@gmail.com>
+ *      ChangeLog:  1, Release initial version on "05/24/2024 07:38:43 PM"
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <termios.h>
+#include <pthread.h>
+
+#define DEV_NAME    "/dev/ttymxc1"
+#define MSG         "Hello, LingYun IoT System Studio!"
+
+int main(int argc, char **argv)
+{
+    struct termios      tty;
+    int                 serial_fd;
+    int                 bytes;
+    char                read_buf[64];
+
+    /*+-------------------------+
+      |    Open Serial Port     |
+      +-------------------------+*/
+    serial_fd = open(DEV_NAME, O_RDWR );
+    if (serial_fd == -1)
+    {
+        printf("Open '%s' failed: %s\n", DEV_NAME, strerror(errno));
+        return 0;
+    }
+
+    /*+-------------------------+
+      |  Configure Serial Port  |
+      +-------------------------+*/
+
+    tcgetattr(serial_fd, &tty);// read current serial port settings
+
+    tty.c_cflag &= ~PARENB;    // Clear parity bit, disabling parity (most common)
+    tty.c_cflag &= ~CSTOPB;    // Clear stop field, only one stop bit used in communication (most common)
+    tty.c_cflag &= ~CSIZE;     // Clear all bits that set the data size
+    tty.c_cflag |= CS8;        // 8 bits per byte (most common)
+    tty.c_cflag &= ~CRTSCTS;   // Disable RTS/CTS hardware flow control (most common)
+    tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)
+
+    tty.c_lflag &= ~ICANON;
+    tty.c_lflag &= ~ECHO;      // Disable echo
+    tty.c_lflag &= ~ECHOE;     // Disable erasure
+    tty.c_lflag &= ~ECHONL;    // Disable new-line echo
+    tty.c_lflag &= ~ISIG;      // Disable interpretation of INTR, QUIT and SUSP
+    tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
+    tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes
+
+    tty.c_oflag &= ~OPOST;     // Prevent special interpretation of output bytes (e.g. newline chars)
+    tty.c_oflag &= ~ONLCR;     // Prevent conversion of newline to carriage return/line feed
+
+    tty.c_cc[VTIME] = 10;      // Wait for up to 1s (10 deciseconds), returning as soon as any data is received.
+    tty.c_cc[VMIN] = 0;
+
+    /* Set in/out baud rate to be 115200 */
+    cfsetispeed(&tty, B115200);
+    cfsetospeed(&tty, B115200);
+
+    tcsetattr(serial_fd, TCSANOW, &tty);
+
+    /*+-------------------------+
+      |   Write to Serial Port  |
+      +-------------------------+*/
+    write(serial_fd, MSG, strlen(MSG));
+	printf("Send MSG    : %s\n", MSG);
+
+    /*+-------------------------+
+      |  Read from Serial Port  |
+      +-------------------------+*/
+    /* 
+	 * The behaviour of read() (e.g. does it block?, how long does it block for?)
+	 * depends on the configuration settings above, specifically VMIN and VTIME
+     */
+    memset(&read_buf, '\0', sizeof(read_buf));
+    bytes =  read(serial_fd, &read_buf, sizeof(read_buf));
+    if (bytes< 0)
+    {
+        printf("Error reading: %s", strerror(errno));
+		goto cleanup;
+    }
+
+    printf("Received MSG: %s\n", read_buf);
+
+    /*+-------------------------+
+      |   close Serial Port     |
+      +-------------------------+*/
+
+cleanup:
+    close(serial_fd);
+    return 0; // success
+}

--
Gitblit v1.9.1