From fe321182084c5da0a91fd253e601af41613b94c2 Mon Sep 17 00:00:00 2001
From: guowenxue <guowenxue@gmail.com>
Date: Mon, 19 Aug 2024 16:46:58 +0800
Subject: [PATCH] Add modules in hal

---
 hal/modules/sht20.c     |  516 +++++++++++
 hal/modules/ds18b20.c   |  134 ++
 hal/modules/at24c.c     |  616 +++++++++++++
 hal/modules/makefile    |   45 +
 hal/modules/tsl2561.c   |  337 +++++++
 hal/modules/w25qflash.h |  181 ++++
 hal/modules/w25qflash.c |  831 ++++++++++++++++++
 7 files changed, 2,660 insertions(+), 0 deletions(-)

diff --git a/hal/modules/at24c.c b/hal/modules/at24c.c
new file mode 100644
index 0000000..575bf55
--- /dev/null
+++ b/hal/modules/at24c.c
@@ -0,0 +1,616 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2023 LingYun IoT System Studio
+ *                  All rights reserved.
+ *
+ *       Filename:  at24c.c
+ *    Description:  This file is AT24Cxx EEPROM code
+ *
+ *        Version:  1.0.0(10/08/23)
+ *         Author:  Guo Wenxue <guowenxue@gmail.com>
+ *      ChangeLog:  1, Release initial version on "10/08/23 17:52:00"
+ *
+ * Pin connection:
+ *                 AT24Cxx                Raspberry Pi 40Pin
+ *                   VCC      <----->      #Pin1(3.3V)
+ *                   SDA      <----->      #Pin3(SDA, BCM GPIO2)
+ *                   SCL      <----->      #Pin5(SCL, BCM GPIO3)
+ *                   GND      <----->      GND
+ *
+ * /boot/config.txt:
+ *                  dtoverlay=i2c1,pins_2_3
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <getopt.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>
+
+/*+----------------------+
+ *|  EEPROM Information  |
+ *+----------------------+*/
+
+/* AT24Cxx 7-bit I2C slave address */
+#define AT24C_CHIPADDR                  0x50
+
+enum
+{
+    AT24C01  = 1,
+    AT24C02  = 2,
+    AT24C04  = 4,
+    AT24C08  = 8,
+    AT24C16  = 16,
+    AT24C32  = 32,
+    AT24C64  = 64,
+    AT24C128 = 128,
+    AT24C256 = 256,
+    AT24C512 = 512,
+} chipid_t;
+
+typedef struct i2c_s
+{
+    char           *dev;    /* I2C master device, /dev/i2c-N */
+    int             addr;   /* 7-bits slave address */
+    int             fd;     /* File description */
+} i2c_t;
+
+typedef struct eeprom_s
+{
+    int             chip;       /* Chip ID */
+    uint32_t        capacity;   /* Chip size in bytes */
+    int             pagesize;   /* Page size in bytes */
+} eeprom_t;
+
+#define EEP_INFO(_chip, _capacity, _pagesize)       \
+    .chip       = _chip,                            \
+    .capacity   = _capacity,                        \
+    .pagesize   = _pagesize,                        \
+
+static eeprom_t at24c_ids[] = {
+    { EEP_INFO(AT24C01,  128,   8)   },
+    { EEP_INFO(AT24C02,  256,   8)   },
+    { EEP_INFO(AT24C04,  512,   16)  },
+    { EEP_INFO(AT24C08,  1024,  16)  },
+    { EEP_INFO(AT24C16,  2048,  16)  },
+    { EEP_INFO(AT24C32,  4096,  32)  },
+    { EEP_INFO(AT24C64,  8192,  32)  },
+    { EEP_INFO(AT24C128, 16384, 64)  },
+    { EEP_INFO(AT24C256, 32768, 64)  },
+    { EEP_INFO(AT24C512, 65536, 128) },
+};
+
+typedef struct at24c_s
+{
+    i2c_t            i2c;
+    eeprom_t        *eeprom;
+} at24c_t;
+
+int at24c_init(at24c_t *at24c, int chip);
+int at24c_read(at24c_t *at24c, int offset, uint8_t *buf, int size);
+int at24c_write(at24c_t *at24c, int offset, uint8_t *data, int len);
+int at24c_test(at24c_t *at24c);
+
+int i2c_init(i2c_t *i2c, char *i2cdev, int addr);
+int i2c_write(i2c_t *i2c, uint8_t *data, int len);
+int i2c_read(i2c_t *i2c, uint8_t *buf, int size);
+void i2c_term(i2c_t *i2c);
+
+static inline void msleep(unsigned long ms);
+void dump_buf(const char *prompt, char *buf, size_t len);
+
+static inline void banner(const char *progname)
+{
+    printf("%s program Version v1.0.0\n", progname);
+    printf("Copyright (C) 2023 Avnet.\n");
+}
+
+static void program_usage(const char *progname)
+{
+    printf("Usage: %s [OPTION]...\n", progname);
+    printf(" %s is AT24Cxx EEPROM test program. \n", progname);
+
+    printf(" -c[chipid  ]  Specify EEPROM chipID: 1,2,4,8...512 \n");
+    printf(" -d[device  ]  Specify I2C device, such as /dev/i2c-1\n");
+    printf(" -h[help    ]  Display this help information\n");
+    printf(" -v[version ]  Display the program version\n");
+
+    printf("\n");
+    banner(progname);
+    return;
+}
+
+int main(int argc, char **argv)
+{
+    char           *progname=NULL;
+    char           *dev="/dev/i2c-1";
+    int             chipid = AT24C256; /* default */
+    at24c_t         at24c;
+    int             rv;
+
+    struct option long_options[] = {
+        {"chip", required_argument, NULL, 'c'},
+        {"device", required_argument, NULL, 'd'},
+        {"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, "c:d:vh", long_options, NULL)) != -1)
+    {
+        switch (rv)
+        {
+            case 'c': /*  Set chip ID: 1,2,4,8...512 */
+                chipid = atoi(optarg);
+                break;
+
+            case 'd': /*  Set I2C device path: /dev/i2c-1 */
+                dev = 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( at24c_init(&at24c, chipid) < 0 )
+    {
+        printf("at24c initial failed!\n");
+        return 1;
+    }
+
+    if( i2c_init(&at24c.i2c, dev, AT24C_CHIPADDR) < 0 )
+    {
+        printf("i2c initial failed!\n");
+        return 2;
+    }
+
+    if( at24c_test(&at24c) < 0 )
+    {
+        return 3;
+    }
+
+    i2c_term(&at24c.i2c);
+    return 0;
+}
+
+/*+----------------------+
+ *| EEPROM API functions |
+ *+----------------------+*/
+
+int at24c_init(at24c_t *at24c, int chip)
+{
+    int             i;
+
+    if( !at24c )
+        return -1;
+
+    for(i=0; i<sizeof(at24c_ids)/sizeof(at24c_ids[0]); i++)
+    {
+        if( at24c_ids[i].chip == chip )
+        {
+            at24c->eeprom = &at24c_ids[i];
+            printf("Detect EEPROM AT24C%02d capacity %d bytes, pagesize %d bytes.\r\n",
+                    chip, at24c->eeprom->capacity, at24c->eeprom->pagesize);
+            return 0;
+        }
+    }
+
+    printf("EEPROM: Can not found EEPROM by chip ID[%d]\r\n", chip);
+    return -2;
+}
+
+int at24c_test(at24c_t *at24c)
+{
+    eeprom_t       *eeprom;
+    uint8_t         buf[128];
+    int             i;
+    int             addr = 0;
+
+    if( !at24c )
+        return -1;
+
+    eeprom = at24c->eeprom;
+
+    /* Read data before write */
+    memset(buf, 0, sizeof(buf));
+    if( at24c_read(at24c, addr, buf, sizeof(buf)) < 0 )
+    {
+        return -2;
+    }
+    dump_buf("<<<EEPROM read data:\n", (char *)buf, sizeof(buf));
+
+    /* fill a page data */
+    for(i=0; i<eeprom->pagesize; i++)
+    {
+        buf[i] = i;
+    }
+
+    /* write a page data from the address not page alignment */
+    if( at24c_write(at24c, addr+8, buf, eeprom->pagesize) < 0 )
+    {
+        return -3;
+    }
+
+    /* Read data after write */
+    memset(buf, 0, sizeof(buf));
+    if( at24c_read(at24c, addr, buf, sizeof(buf)) < 0 )
+    {
+        return -2;
+    }
+    dump_buf("<<<EEPROM read data:\n", (char *)buf, sizeof(buf));
+
+    return 0;
+}
+
+/* Figure 9. Page Write */
+int at24c_write_page(at24c_t *at24c, int offset, uint8_t *data, int len)
+{
+    uint8_t         buf[256];
+    int             bytes, rv;
+    int             addrlen;
+    eeprom_t       *eeprom;
+
+    if(!at24c || offset<0 || !data || !len)
+        return -1;
+
+    eeprom = at24c->eeprom;
+
+    /* exceeding EEPROM size  */
+    if( offset+len > eeprom->capacity )
+        return -1;
+
+    if( len > eeprom->pagesize )
+        len = eeprom->pagesize;
+
+    /*
+     * A temporary write buffer must be used which contains both the address
+     * and the data to be written, put the address in first based upon the
+     * size of the address for the EEPROM.
+     */
+    if( eeprom->chip >= AT24C16)
+    {
+        buf[0]=(uint8_t)(offset>>8);
+        buf[1]=(uint8_t)(offset);
+        addrlen = 2;
+    }
+    else
+    {
+        buf[0]=(uint8_t)(offset);
+        addrlen = 1;
+    }
+
+    /* Put the data in the write buffer following the address */
+    memcpy(&buf[addrlen], data, len);
+
+    /* Write a page of data at the specified address to the EEPROM. */
+    rv = i2c_write(&at24c->i2c, buf, len+addrlen);
+    if( rv < 0 )
+        printf("%s() write data failed\n", __func__);
+
+    /* Must give a delay here to wait page write finish */
+    msleep(5);
+
+    return rv;
+}
+
+int at24c_write(at24c_t *at24c, int offset, uint8_t *data, int len)
+{
+    int             bytes;
+    eeprom_t       *eeprom;
+
+    if(!at24c || offset<0 || !data || len<0)
+        return -1;
+
+    eeprom = at24c->eeprom;
+
+    /* exceeding EEPROM size  */
+    if( offset+len > eeprom->capacity )
+        return -1;
+
+    /* The offset + write bytes shouldn't overflow that page,
+     * or it will over write the start bytes of this page  */
+    if( offset%eeprom->pagesize )
+        bytes = eeprom->pagesize - offset%eeprom->pagesize;
+    else
+        bytes = len>eeprom->pagesize? eeprom->pagesize : len;
+
+    /* Write max one page at a time */
+    while(len > 0)
+    {
+        if( at24c_write_page(at24c, offset, data, bytes) )
+        {
+            return -2;
+        }
+
+        len    -= bytes;
+        data   += bytes;
+        offset += bytes;
+
+        bytes = len>eeprom->pagesize? eeprom->pagesize : len;
+    }
+
+    return 0;
+}
+
+/* Figure 12. Sequential Read */
+int at24c_read(at24c_t *at24c, int offset, uint8_t *buf, int size)
+{
+    struct i2c_rdwr_ioctl_data      tr;
+    eeprom_t                       *eeprom;
+    uint8_t                         addr[2];
+    int                             addrlen;
+    int                             bytes;
+    int                             rv = 0;
+
+    if(!at24c || offset<0 || !buf || size<=0)
+        return -1;
+
+    eeprom = at24c->eeprom;
+
+    /* exceeding EEPROM size  */
+    if( offset+size > eeprom->capacity )
+        return -1;
+
+    memset(buf, 0, size);
+
+    if( eeprom->chip >= AT24C16)
+    {
+        addr[0]=(uint8_t)(offset>>8);
+        addr[1]=(uint8_t)(offset);
+        addrlen = 2;
+    }
+    else
+    {
+        addr[0]=(uint8_t)(offset);
+        addrlen = 1;
+    }
+
+    /* We can't call i2c_write() and i2c_read() because it will generate a
+     * stop condition after send the offset address, it doesn't compatible
+     * with EEPROM sequential read sequence;
+     */
+
+
+    /* use I2C userspace API to send two message without stop condition */
+
+    tr.nmsgs = 2;
+    tr.msgs = malloc( sizeof(struct i2c_msg)*tr.nmsgs );
+    if ( !tr.msgs )
+    {
+        printf("%s() msgs malloc failed!\n", __func__);
+        return -2;
+    }
+
+    /* Create the I2C message for writing the EEPROM offset address */
+    tr.msgs[0].addr = at24c->i2c.addr;
+    tr.msgs[0].flags = 0; //write
+    tr.msgs[0].len = addrlen;
+    tr.msgs[0].buf = addr;
+
+    /* Create the I2C message for reading data from EEPROM */
+    memset(buf, 0, size);
+    tr.msgs[1].addr = at24c->i2c.addr;
+    tr.msgs[1].flags = I2C_M_RD; //read
+    tr.msgs[1].len = size;
+    tr.msgs[1].buf = buf;
+
+    if( ioctl(at24c->i2c.fd, I2C_RDWR, &tr)<0 )
+    {
+        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+        rv = -4;
+    }
+
+    free( tr.msgs );
+    return rv;
+}
+
+
+/*+----------------------+
+ *|   I2C API functions  |
+ *+----------------------+*/
+
+int i2c_init(i2c_t *i2c, char *i2cdev, int addr)
+{
+    if( !i2c || !i2cdev || !addr )
+        return -1;
+
+    memset(i2c, 0, sizeof(*i2c));
+    i2c->addr = addr;
+    i2c->dev = i2cdev;
+
+    if( (i2c->fd=open(i2cdev, O_RDWR)) < 0)
+    {
+        printf("open i2c device %s failed: %s\n", i2cdev, strerror(errno));
+        return -1;
+    }
+
+    return 0;
+}
+
+void i2c_term(i2c_t *i2c)
+{
+    if( !i2c )
+        return;
+
+    if( i2c->fd > 0)
+        close(i2c->fd);
+
+    return ;
+}
+
+int i2c_write(i2c_t *i2c, uint8_t *data, int len)
+{
+    struct i2c_rdwr_ioctl_data      tr;
+    int                             rv = 0;
+
+    if( !data || len<= 0)
+    {
+        printf("%s() invalid input arguments!\n", __func__);
+        return -1;
+    }
+
+    /* I2C device program API: ioctl() */
+    tr.nmsgs = 1;
+    tr.msgs = malloc( sizeof(struct i2c_msg)*tr.nmsgs );
+    if ( !tr.msgs )
+    {
+        printf("%s() msgs malloc failed!\n", __func__);
+        return -2;
+    }
+
+    tr.msgs[0].addr = i2c->addr;
+    tr.msgs[0].flags = 0; //write
+    tr.msgs[0].len = len;
+    tr.msgs[0].buf = malloc(len);
+    if( !tr.msgs[0].buf )
+    {
+        printf("%s() msgs malloc failed!\n", __func__);
+        rv = -3;
+        goto cleanup;
+    }
+    memcpy(tr.msgs[0].buf, data, len);
+
+
+    if( ioctl(i2c->fd, I2C_RDWR, &tr)<0 )
+    {
+        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+        rv = -4;
+        goto cleanup;
+    }
+
+cleanup:
+    if( tr.msgs[0].buf )
+        free(tr.msgs[0].buf);
+
+    if( tr.msgs )
+        free(tr.msgs);
+
+    return rv;
+}
+
+int i2c_read(i2c_t *i2c, uint8_t *buf, int size)
+{
+    struct i2c_rdwr_ioctl_data      tr;
+    int                             rv = 0;
+
+    if( !buf || size<= 0)
+    {
+        printf("%s() invalid input arguments!\n", __func__);
+        return -1;
+    }
+
+    tr.nmsgs = 1;
+    tr.msgs = malloc( sizeof(struct i2c_msg)*tr.nmsgs );
+    if ( !tr.msgs )
+    {
+        printf("%s() msgs malloc failed!\n", __func__);
+        return -2;
+    }
+
+    tr.msgs[0].addr = i2c->addr;
+    tr.msgs[0].flags = I2C_M_RD; //read
+    tr.msgs[0].len = size;
+    tr.msgs[0].buf = buf;
+    memset(buf, 0, size);
+
+    if( ioctl(i2c->fd, I2C_RDWR, &tr)<0 )
+    {
+        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+        rv = -4;
+    }
+
+    free( tr.msgs );
+    return rv;
+}
+
+/*+----------------------+
+ *|    Misc functions    |
+ *+----------------------+*/
+
+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);
+}
+
+void dump_buf(const char *prompt, char *buf, size_t len)
+{
+    char        line[256];
+    size_t      i, j;
+    int         offset;
+
+    if( prompt )
+    {
+        printf("%s", prompt);
+    }
+
+    for(i = 0; i < len; i += 16)
+    {
+        offset = snprintf(line, sizeof(line), "%08zx: ", i);
+
+        /* Print hex representation */
+        for (j = 0; j < 16; j++)
+        {
+            if (i + j < len)
+                offset += snprintf(line + offset, sizeof(line) - offset, "%02x ", buf[i + j]);
+            else
+                offset += snprintf(line + offset, sizeof(line) - offset, "   ");
+        }
+
+        offset += snprintf(line + offset, sizeof(line) - offset, " ");
+
+        /* Print ASCII representation */
+        for (j = 0; j < 16; j++)
+        {
+            if (i + j < len)
+            {
+                unsigned char c = buf[i + j];
+                offset += snprintf(line + offset, sizeof(line) - offset, "%c", (c >= 32 && c <= 126) ? c : '.');
+            }
+            else
+            {
+                offset += snprintf(line + offset, sizeof(line) - offset, " ");
+            }
+        }
+
+        /* Print the line */
+        printf("%s\n", line);
+    }
+}
diff --git a/hal/modules/ds18b20.c b/hal/modules/ds18b20.c
new file mode 100644
index 0000000..cb895a4
--- /dev/null
+++ b/hal/modules/ds18b20.c
@@ -0,0 +1,134 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2023 LingYun IoT System Studio
+ *                  All rights reserved.
+ *
+ *       Filename:  ds18b20.c
+ *    Description:  This file is temperature sensor DS18B20 code
+ *
+ *        Version:  1.0.0(2023/8/10)
+ *         Author:  Guo Wenxue <guowenxue@gmail.com>
+ *      ChangeLog:  1, Release initial version on "2023/8/10 12:13:26"
+ *
+ * Pin connection:
+ *
+ *               DS18B20 Module          Raspberry Pi Board
+ *                   VCC      <----->      #Pin1(3.3V)
+ *                   DQ       <----->      #Pin7(BCM GPIO4)
+ *                   GND      <----->      GND
+ *
+ * /boot/config.txt:
+ *
+ *          dtoverlay=w1-gpio-pullup,gpiopin=4
+ *
+ ********************************************************************************/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+
+int ds18b20_get_temperature(float *temp);
+
+int main(int argc, char *argv[])
+{
+    float       temp;
+
+    if( ds18b20_get_temperature(&temp) < 0 )
+    {
+        printf("ERROR: ds18b20 get temprature failure\n");
+        return 1;
+    }
+
+    printf("DS18B20 get temperature: %f ℃\n", temp);
+    return 0;
+}
+
+
+/* File Content:
+   pi@raspberrypi:~/guowenxue $ cat /sys/bus/w1/devices/28-041731f7c0ff/w1_slave
+   3a 01 4b 46 7f ff 0c 10 a5 : crc=a5 YES
+   3a 01 4b 46 7f ff 0c 10 a5 t=19625
+   */
+
+int ds18b20_get_temperature(float *temp)
+{
+    char            w1_path[50] = "/sys/bus/w1/devices/";
+    char            chip[20];
+    char            buf[128];
+    DIR            *dirp;
+    struct dirent  *direntp;
+    int             fd =-1;
+    char           *ptr;
+    float           value;
+    int             found = 0;
+
+    if( !temp )
+    {
+        return -1;
+    }
+
+    /*+-------------------------------------------------------------------+
+     *|  open dierectory /sys/bus/w1/devices to get chipset Serial Number |
+     *+-------------------------------------------------------------------+*/
+    if((dirp = opendir(w1_path)) == NULL)
+    {
+        printf("opendir error: %s\n", strerror(errno));
+        return -2;
+    }
+
+    while((direntp = readdir(dirp)) != NULL)
+    {
+        if(strstr(direntp->d_name,"28-"))
+        {
+            /* find and get the chipset SN filename */
+            strcpy(chip,direntp->d_name);
+            found = 1;
+            break;
+        }
+    }
+    closedir(dirp);
+
+    if( !found )
+    {
+        printf("Can not find ds18b20 in %s\n", w1_path);
+        return -3;
+    }
+
+    /* get DS18B20 sample file full path: /sys/bus/w1/devices/28-xxxx/w1_slave */
+    strncat(w1_path, chip, sizeof(w1_path)-strlen(w1_path));
+    strncat(w1_path, "/w1_slave", sizeof(w1_path)-strlen(w1_path));
+
+    /* open file /sys/bus/w1/devices/28-xxxx/w1_slave to get temperature */
+    if( (fd=open(w1_path, O_RDONLY)) < 0 )
+    {
+        printf("open %s error: %s\n", w1_path, strerror(errno));
+        return -4;
+    }
+
+    if(read(fd, buf, sizeof(buf)) < 0)
+    {
+        printf("read %s error: %s\n", w1_path, strerror(errno));
+        return -5;
+    }
+
+    ptr = strstr(buf, "t=");
+    if( !ptr )
+    {
+        printf("ERROR: Can not get temperature\n");
+        return -6;
+    }
+
+    ptr+=2;
+
+    /* convert string value to float value */
+    *temp = atof(ptr)/1000;
+
+    close(fd);
+
+    return 0;
+}
diff --git a/hal/modules/makefile b/hal/modules/makefile
new file mode 100644
index 0000000..433e220
--- /dev/null
+++ b/hal/modules/makefile
@@ -0,0 +1,45 @@
+#********************************************************************************
+#      Copyright:  (C) 2023 LingYun IoT System Studio
+#                  All rights reserved.
+#
+#       Filename:  Makefile
+#    Description:  This file used to compile all the C file to respective binary,
+#                  and it will auto detect cross compile or local compile.
+#
+#        Version:  1.0.0(11/08/23)
+#         Author:  Guo Wenxue <guowenxue@gmail.com>
+#      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
+#
+#*******************************************************************************
+
+PWD=$(shell pwd)
+INSTPATH=/tftp
+
+CROSS_COMPILE=arm-linux-gnueabihf-
+CC=${CROSS_COMPILE}gcc
+
+LDFLAGS += -lm
+
+SRCS = $(wildcard ${VPATH}/*.c)
+OBJS = $(patsubst %.c,%.o,$(SRCS))
+
+SRCFILES = $(wildcard *.c)
+BINARIES=$(SRCFILES:%.c=%)
+
+all: binaries install
+
+binaries:  ${BINARIES}
+
+%:  %.c
+	$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+
+install:
+	cp $(BINARIES) ${INSTPATH}
+
+clean:
+	@rm -f *.o *.lo $(BINARIES)
+
+distclean: clean
+	@rm -f  tags cscope*
+
+.PHONY: clean entry
diff --git a/hal/modules/sht20.c b/hal/modules/sht20.c
new file mode 100644
index 0000000..5aaa82d
--- /dev/null
+++ b/hal/modules/sht20.c
@@ -0,0 +1,516 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2023 LingYun IoT System Studio
+ *                  All rights reserved.
+ *
+ *       Filename:  sht20.c
+ *    Description:  This file is temperature and relative humidity sensor SHT20 code
+ *
+ *        Version:  1.0.0(10/08/23)
+ *         Author:  Guo Wenxue <guowenxue@gmail.com>
+ *      ChangeLog:  1, Release initial version on "10/08/23 17:52:00"
+ *
+ * Pin connection:
+ *                  SHT20                 Raspberry Pi 40Pin
+ *                   VCC      <----->      #Pin1(3.3V)
+ *                   SDA      <----->      #Pin3(SDA, BCM GPIO2)
+ *                   SCL      <----->      #Pin5(SCL, BCM GPIO3)
+ *                   GND      <----->      GND
+ *
+ * /boot/config.txt:
+ *                  dtoverlay=i2c1,pins_2_3
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <getopt.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>
+
+/* SHT2X 7-bit I2C slave address */
+#define SHT2X_CHIPADDR                  0x40
+
+/* SHT2X trigger command */
+#define SOFTRESET                       0xFE
+#define TRIGGER_TEMPERATURE_NO_HOLD     0xF3
+#define TRIGGER_HUMIDITY_NO_HOLD        0xF5
+
+/* Linux /dev/i2c-x device program API mode */
+enum
+{
+    MODE_IOCTL, /* I2C device API use ioctl() */
+    MODE_RDWR,  /* I2C device API use read()/write() */
+};
+
+typedef struct i2c_s
+{
+    char           *dev;    /* I2C master device, /dev/i2c-N */
+    int             addr;   /* 7-bits slave address */
+    int             fd;     /* File description */
+    int             mode;   /* I2C device API use read/write or ioctl mode */
+} i2c_t;
+
+int sht2x_softreset(i2c_t *i2c);
+int sht2x_get_serialnumber(i2c_t *i2c, uint8_t *serialnumber, int size);
+int sht2x_get_temp_humidity(i2c_t *i2c, float *temp, float *rh);
+
+int i2c_init(i2c_t *i2c, int mode, char *i2cdev, int addr);
+int i2c_write(i2c_t *i2c, uint8_t *data, int len);
+int i2c_read(i2c_t *i2c, uint8_t *buf, int size);
+void i2c_term(i2c_t *i2c);
+
+static inline void msleep(unsigned long ms);
+void print_buf(const char *prompt, uint8_t *buf, int size);
+void dump_buf(const char *prompt, char *buf, size_t len);
+
+static inline void banner(const char *progname)
+{
+    printf("%s program Version v1.0.0\n", progname);
+    printf("Copyright (C) 2023 Avnet.\n");
+}
+
+static void program_usage(const char *progname)
+{
+
+    printf("Usage: %s [OPTION]...\n", progname);
+    printf(" %s is SHT20 temperature and humidity sensor program. \n", progname);
+
+    printf(" -d[device  ]  Specify I2C device, such as /dev/i2c-1\n");
+    printf(" -m[mode    ]  Specify API mode, 0 for ioctl and 1 for read()/write()\n");
+    printf(" -h[help    ]  Display this help information\n");
+    printf(" -v[version ]  Display the program version\n");
+
+    printf("\n");
+    banner(progname);
+    return;
+}
+
+int main(int argc, char **argv)
+{
+    char           *dev="/dev/i2c-1";
+    char           *progname=NULL;
+    int             mode = MODE_IOCTL;
+    int             rv;
+    float           temp, rh;
+    uint8_t         serialnumber[8];
+    i2c_t           i2c;
+
+    struct option long_options[] = {
+        {"device", required_argument, NULL, 'd'},
+        {"mode", required_argument, NULL, 'm'},
+        {"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, "d:m:vh", long_options, NULL)) != -1)
+    {
+        switch (rv)
+        {
+            case 'd': /*  Set I2C device path: /dev/i2c-1 */
+                dev = optarg;
+                break;
+
+            case 'm': /*  Set I2C API mode: 0,1 */
+                mode = atoi(optarg) ? MODE_RDWR : MODE_IOCTL;
+                break;
+
+            case 'v':  /*  Get software version */
+                banner(progname);
+                return EXIT_SUCCESS;
+
+            case 'h':  /*  Get help information */
+                program_usage(progname);
+                return 0;
+
+            default:
+                break;
+        }
+    }
+
+    if( i2c_init(&i2c, mode, dev, SHT2X_CHIPADDR) < 0 )
+    {
+        printf("I2C master device initial failed.\n");
+        return 1;
+    }
+
+    if( sht2x_softreset(&i2c) < 0 )
+    {
+        printf("SHT2x initialize failure, maybe device not present!\n");
+        return 1;
+    }
+
+    if( sht2x_get_serialnumber(&i2c, serialnumber, 8) < 0)
+    {
+        printf("SHT2x get serial number failure\n");
+        return 3;
+    }
+
+    if( sht2x_get_temp_humidity(&i2c, &temp, &rh) < 0 )
+    {
+        printf("SHT2x get get temperature and relative humidity failure\n");
+        return 3;
+    }
+
+    printf("Temperature=%lf ℃ relative humidity=%lf.\n", temp, rh);
+
+    i2c_term(&i2c);
+
+    return 0;
+}
+
+int sht2x_softreset(i2c_t *i2c)
+{
+    uint8_t           buf[1];
+
+    if( !i2c || i2c->fd<0 )
+    {
+        printf("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__ );
+        return -1;
+    }
+
+    /* software reset SHT2x */
+    buf[0] = SOFTRESET;
+    if( i2c_write(i2c, buf, 1) < 0 )
+    {
+        return -2;
+    }
+
+    msleep(50);
+
+    return 0;
+}
+
+int sht2x_get_temp_humidity(i2c_t *i2c, float *temp, float *rh)
+{
+    uint8_t           buf[4];
+
+    if( !i2c || !temp || !rh || i2c->fd<0 )
+    {
+        printf("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__ );
+        return -1;
+    }
+
+    /* send trigger temperature measure command and read the data */
+    memset(buf, 0, sizeof(buf));
+    buf[0]=TRIGGER_TEMPERATURE_NO_HOLD;
+    i2c_write(i2c, buf, 1);
+
+    msleep(85); /* datasheet: typ=66, max=85 */
+
+    memset(buf, 0, sizeof(buf));
+    i2c_read(i2c, buf, 3);
+    print_buf("Temperature sample data: ", buf, 3);
+    *temp = 175.72 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 46.85;
+
+    /* send trigger humidity measure command and read the data */
+    memset(buf, 0, sizeof(buf));
+    buf[0] = TRIGGER_HUMIDITY_NO_HOLD;
+    i2c_write(i2c, buf, 1);
+
+    msleep(29); /* datasheet: typ=22, max=29 */
+    memset(buf, 0, sizeof(buf));
+
+    i2c_read(i2c, buf, 3);
+    print_buf("Relative humidity sample data: ", buf, 3);
+    *rh = 125 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 6;
+
+    return 0;
+}
+
+int sht2x_get_serialnumber(i2c_t *i2c, uint8_t *serialnumber, int size)
+{
+    uint8_t           buf[4]={0x0};
+
+    if( !i2c || i2c->fd<0 || !serialnumber || size!=8 )
+    {
+        printf("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__ );
+        return -1;
+    }
+
+    /* Read SerialNumber from Location 1 */
+    memset(buf, 0, sizeof(buf));
+    buf[0] = 0xfa;  /* command for readout on-chip memory */
+    buf[1] = 0x0f;  /* on-chip memory address */
+    i2c_write(i2c, buf, 2);
+
+    memset(buf, 0, sizeof(buf));
+    i2c_read(i2c, buf, 4);
+
+    if( !buf[0] && !buf[1] && !buf[2] && !buf[3] )
+    {
+        printf("ERROR: SHT2X device not detected!\n");
+        return -2;
+    }
+
+    serialnumber[5]=buf[0]; /* Read SNB_3 */
+    serialnumber[4]=buf[1]; /* Read SNB_2 */
+    serialnumber[3]=buf[2]; /* Read SNB_1 */
+    serialnumber[2]=buf[3]; /* Read SNB_0 */
+
+    /* Read SerialNumber from Location 2 */
+    memset(buf, 0, sizeof(buf) );
+    buf[0]=0xfc;  /* command for readout on-chip memory */
+    buf[1]=0xc9;  /* on-chip memory address */
+    i2c_write(i2c, buf, 2);
+
+    memset(buf, 0, sizeof(buf) );
+    i2c_read(i2c, buf, 4);
+
+    serialnumber[1]=buf[0]; /* Read SNC_1 */
+    serialnumber[0]=buf[1]; /* Read SNC_0 */
+    serialnumber[7]=buf[2]; /* Read SNA_1 */
+    serialnumber[6]=buf[3]; /* Read SNA_0 */
+
+    print_buf("SHT2x Serial number: ", serialnumber, 8);
+
+    return 0;
+}
+
+/*+----------------------+
+ *|   I2C API functions  |
+ *+----------------------+*/
+
+int i2c_init(i2c_t *i2c, int mode, char *i2cdev, int addr)
+{
+    if( !i2c || !i2cdev || !addr )
+        return -1;
+
+    memset(i2c, 0, sizeof(*i2c));
+    i2c->addr = addr;
+    i2c->dev = i2cdev;
+    i2c->mode = mode;
+
+    if( (i2c->fd=open(i2cdev, O_RDWR)) < 0)
+    {
+        printf("open i2c device %s failed: %s\n", i2cdev, strerror(errno));
+        return -1;
+    }
+
+    if( MODE_RDWR == i2c->mode )
+    {
+        /* set I2C mode and SHT2x slave address */
+        ioctl(i2c->fd, I2C_TENBIT, 0);          /* Not 10-bit but 7-bit mode */
+        ioctl(i2c->fd, I2C_SLAVE, i2c->addr);   /* set SHT2x slave address */
+    }
+
+    return 0;
+}
+
+void i2c_term(i2c_t *i2c)
+{
+    if( !i2c )
+        return;
+
+    if( i2c->fd > 0)
+        close(i2c->fd);
+
+    return ;
+}
+
+int i2c_write(i2c_t *i2c, uint8_t *data, int len)
+{
+    struct i2c_rdwr_ioctl_data      tr;
+    int                             rv = 0;
+
+    if( !data || len<= 0)
+    {
+        printf("%s() invalid input arguments!\n", __func__);
+        return -1;
+    }
+
+    /* I2C device program API: read()/write() */
+    if( MODE_RDWR == i2c->mode )
+    {
+        write(i2c->fd, data, len);
+        return 0;
+    }
+
+    /* I2C device program API: ioctl() */
+    tr.nmsgs = 1;
+    tr.msgs = malloc( sizeof(struct i2c_msg)*tr.nmsgs );
+    if ( !tr.msgs )
+    {
+        printf("%s() msgs malloc failed!\n", __func__);
+        return -2;
+    }
+
+    tr.msgs[0].addr = i2c->addr;
+    tr.msgs[0].flags = 0; //write
+    tr.msgs[0].len = len;
+    tr.msgs[0].buf = malloc(len);
+    if( !tr.msgs[0].buf )
+    {
+        printf("%s() msgs malloc failed!\n", __func__);
+        rv = -3;
+        goto cleanup;
+    }
+    memcpy(tr.msgs[0].buf, data, len);
+
+
+    if( ioctl(i2c->fd, I2C_RDWR, &tr)<0 )
+    {
+        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+        rv = -4;
+        goto cleanup;
+    }
+
+cleanup:
+    if( tr.msgs[0].buf )
+        free(tr.msgs[0].buf);
+
+    if( tr.msgs )
+        free(tr.msgs);
+
+    return rv;
+}
+
+int i2c_read(i2c_t *i2c, uint8_t *buf, int size)
+{
+    struct i2c_rdwr_ioctl_data      tr;
+    int                             rv = 0;
+
+    if( !buf || size<= 0)
+    {
+        printf("%s() invalid input arguments!\n", __func__);
+        return -1;
+    }
+
+    /* I2C device program API: read()/write() */
+    if( MODE_RDWR == i2c->mode )
+    {
+        read(i2c->fd, buf, size);
+        return 0;
+    }
+
+    /* I2C device program API: ioctl() */
+    tr.nmsgs = 1;
+    tr.msgs = malloc( sizeof(struct i2c_msg)*tr.nmsgs );
+    if ( !tr.msgs )
+    {
+        printf("%s() msgs malloc failed!\n", __func__);
+        return -2;
+    }
+
+    tr.msgs[0].addr = i2c->addr;
+    tr.msgs[0].flags = I2C_M_RD; //read
+    tr.msgs[0].len = size;
+    tr.msgs[0].buf = buf;
+    memset(buf, 0, size);
+
+    if( ioctl(i2c->fd, I2C_RDWR, &tr)<0 )
+    {
+        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+        rv = -4;
+    }
+
+    free( tr.msgs );
+    return rv;
+}
+
+/*+----------------------+
+ *|    Misc functions    |
+ *+----------------------+*/
+
+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);
+}
+
+void print_buf(const char *prompt, uint8_t *buf, int size)
+{
+    int          i;
+
+    if( !buf )
+    {
+        return ;
+    }
+
+    if( prompt )
+    {
+        printf("%s ", prompt);
+    }
+
+    for(i=0; i<size; i++)
+    {
+        printf("%02x ", buf[i]);
+    }
+    printf("\n");
+
+    return ;
+}
+
+void dump_buf(const char *prompt, char *buf, size_t len)
+{
+    char        line[256];
+    size_t      i, j;
+    int         offset;
+
+    if( prompt )
+    {
+        printf("%s\n", prompt);
+    }
+
+    for(i = 0; i < len; i += 16)
+    {
+        offset = snprintf(line, sizeof(line), "%08zx: ", i);
+
+        /* Print hex representation */
+        for (j = 0; j < 16; j++)
+        {
+            if (i + j < len)
+                offset += snprintf(line + offset, sizeof(line) - offset, "%02x ", buf[i + j]);
+            else
+                offset += snprintf(line + offset, sizeof(line) - offset, "   ");
+        }
+
+        offset += snprintf(line + offset, sizeof(line) - offset, " ");
+
+        /* Print ASCII representation */
+        for (j = 0; j < 16; j++)
+        {
+            if (i + j < len)
+            {
+                unsigned char c = buf[i + j];
+                offset += snprintf(line + offset, sizeof(line) - offset, "%c", (c >= 32 && c <= 126) ? c : '.');
+            }
+            else
+            {
+                offset += snprintf(line + offset, sizeof(line) - offset, " ");
+            }
+        }
+
+        /* Print the line */
+        printf("%s\n", line);
+    }
+}
+
diff --git a/hal/modules/tsl2561.c b/hal/modules/tsl2561.c
new file mode 100644
index 0000000..20bd0c2
--- /dev/null
+++ b/hal/modules/tsl2561.c
@@ -0,0 +1,337 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2023 LingYun IoT System Studio
+ *                  All rights reserved.
+ *
+ *       Filename:  sht20.c
+ *    Description:  This file is the Lux sensor TSL2561 code
+ *
+ *        Version:  1.0.0(10/08/23)
+ *         Author:  Guo Wenxue <guowenxue@gmail.com>
+ *      ChangeLog:  1, Release initial version on "10/08/23 17:52:00"
+ *
+ * Pin connection:
+ *               STH20 Module            Raspberry Pi Board
+ *                   VCC      <----->      #Pin1(3.3V)
+ *                   SDA      <----->      #Pin27(SDA, BCM GPIO0)
+ *                   SCL      <----->      #Pin28(SCL, BCM GPIO1)
+ *                   GND      <----->      GND
+ *
+ * /boot/config.txt:
+ *                  dtoverlay=i2c0,pins_0_1
+ *
+ ********************************************************************************/
+
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <math.h>
+#include <time.h>
+#include <errno.h>
+#include <libgen.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define TSL2561_I2C_ADDR                0x39
+
+#define CONTROL_REG                     0x80
+#define REG_COUNT                       4
+
+#define POWER_UP                        0x03
+#define POWER_DOWN                      0x00
+
+#define OFF                             0
+#define ON                              1
+
+/* Register Address  */
+enum
+{
+    /* Channel_0 = DATA0HIGH<<8 + DATA0LOW */
+    DATA0LOW = 0x8C,
+    DATA0HIGH,
+
+    /* Channel_1 = DATA1HIGH<<8 + DATA1LOW */
+    DATA1LOW,
+    DATA1HIGH,
+};
+
+static const int  regs_addr[REG_COUNT]={DATA0LOW, DATA0HIGH, DATA1LOW, DATA1HIGH};
+
+float tsl2561_get_lux(int fd);
+static inline void print_datime(void);
+
+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(" %s is TSL2561 Lux sensor program.\n", progname);
+
+    printf(" -d[device  ]  Specify I2C device, such as /dev/i2c-0\n");
+    printf(" -h[help    ]  Display this help information\n");
+    printf(" -v[version ]  Display the program version\n");
+
+    printf("\n");
+    banner(progname);
+    return;
+}
+
+int main(int argc, char **argv)
+{
+    int             fd, rv;
+    float           lux;
+    char           *dev = "/dev/i2c-0";
+    char           *progname=NULL;
+
+    struct option long_options[] = {
+        {"device", required_argument, NULL, 'd'},
+        {"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, "d:vh", long_options, NULL)) != -1)
+    {
+        switch (rv)
+        {
+            case 'd': /*  Set I2C device path: /dev/i2c-1 */
+                dev = optarg;
+                break;
+
+            case 'v':  /*  Get software version */
+                banner(progname);
+                return EXIT_SUCCESS;
+
+            case 'h':  /*  Get help information */
+                program_usage(progname);
+                return 0;
+
+            default:
+                break;
+        }
+    }
+
+    /*+--------------------------------+
+     *|     open /dev/i2c-x device     |
+     *+--------------------------------+*/
+    if( (fd=open(dev, O_RDWR)) < 0)
+    {
+        printf("i2c device '%s' open failed: %s\n", dev, strerror(errno));
+        return -1;
+    }
+
+    while(1)
+    {
+        lux = tsl2561_get_lux(fd);
+
+        print_datime();
+        printf("TSLl2561 get lux: %.3f\n", lux);
+
+        sleep(1);
+    }
+
+    close(fd);
+    return 0;
+}
+
+static inline void print_datime(void)
+{
+    time_t         tmp;
+    struct tm     *p;
+
+    time(&tmp);
+
+    p=localtime(&tmp);
+
+
+    printf("%d-%02d-%02d %02d:%02d:%02d\t", (p->tm_year+1900),(p->tm_mon+1),
+            p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec);
+
+}
+
+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 ;
+}
+
+void tsl2561_power(int fd, int cmd)
+{
+    struct i2c_msg               msg;
+    struct i2c_rdwr_ioctl_data   data;
+    unsigned char                buf[2];
+
+    msg.addr= TSL2561_I2C_ADDR;
+    msg.flags=0;  /* write */
+    msg.len= 1;
+    msg.buf= buf;
+
+    data.nmsgs= 1;
+    data.msgs= &msg;
+
+    msg.buf[0]=CONTROL_REG;
+    if( ioctl(fd, I2C_RDWR, &data) < 0 )
+    {
+        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+        return ;
+    }
+
+
+    if( cmd )
+        msg.buf[0]=POWER_UP;
+    else
+        msg.buf[0]=POWER_DOWN;
+
+    if( ioctl(fd, I2C_RDWR, &data) < 0 )
+    {
+        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+        return ;
+    }
+
+    return ;
+}
+
+int tsl2561_readreg(int fd, unsigned char regaddr, unsigned char *regval)
+{
+    struct i2c_msg               msg;
+    struct i2c_rdwr_ioctl_data   data;
+    unsigned char                buf[2];
+
+    msg.addr= TSL2561_I2C_ADDR;
+    msg.flags=0;  /* write */
+    msg.len= 1;
+    msg.buf= buf;
+    msg.buf[0] = regaddr;
+
+    data.nmsgs= 1;
+    data.msgs= &msg;
+
+    if( ioctl(fd, I2C_RDWR, &data) < 0 )
+    {
+        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+        return -1;
+    }
+
+    memset(buf, 0, sizeof(buf));
+
+    msg.addr= TSL2561_I2C_ADDR;
+    msg.flags=I2C_M_RD;  /* read */
+    msg.len= 1;
+    msg.buf= buf;
+
+    data.nmsgs= 1;
+    data.msgs= &msg;
+
+    if( ioctl(fd, I2C_RDWR, &data) < 0 )
+    {
+        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+        return -1;
+    }
+
+    *regval = msg.buf[0];
+    return 0;
+}
+
+float tsl2561_get_lux(int fd)
+{
+    int                 i;
+    unsigned char       reg_data[REG_COUNT];
+    unsigned char       buf;
+
+    int                 chn0_data = 0;
+    int                 chn1_data = 0;
+
+    float               div = 0.0;
+    float               lux = 0.0;
+
+    tsl2561_power(fd, ON);
+
+    msleep(410);  /* t(CONV) MAX 400ms */
+
+    /* Read register Channel0 and channel1 data from register */
+    for(i=0; i<REG_COUNT; i++)
+    {
+        tsl2561_readreg(fd, regs_addr[i], &reg_data[i]);
+    }
+
+    chn0_data = reg_data[1]*256 + reg_data[0]; /* Channel0 = DATA0HIGH<<8 + DATA0LOW  */
+    chn1_data = reg_data[3]*256 + reg_data[2]; /* channel1 = DATA1HIGH<<8 +  DATA1LOW */
+
+    if( chn0_data<=0 || chn1_data<0 )
+    {
+        lux = 0.0;
+        goto OUT;
+    }
+
+    div = (float)chn1_data / (float)chn0_data;
+
+    if( div>0 && div<=0.5 )
+        lux = 0.304*chn0_data-0.062*chn0_data*pow(div,1.4);
+
+    else if( div>0.5 && div<=0.61 )
+        lux = 0.0224*chn0_data-0.031*chn1_data;
+
+    else if( div>0.61 && div<=0.8 )
+        lux = 0.0128*chn0_data-0.0153*chn1_data;
+
+    else if( div>0.8 && div<=1.3 )
+        lux = 0.00146*chn0_data-0.00112*chn1_data;
+
+    else if( div>1.3 )
+        lux = 0.0;
+
+OUT:
+    tsl2561_power(fd, OFF);
+    return lux;
+}
+
+static inline void dump_buf(const char *prompt, char *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/hal/modules/w25qflash.c b/hal/modules/w25qflash.c
new file mode 100644
index 0000000..5623718
--- /dev/null
+++ b/hal/modules/w25qflash.c
@@ -0,0 +1,831 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2023 LingYun IoT System Studio
+ *                  All rights reserved.
+ *
+ *       Filename:  at24c.c
+ *    Description:  This file is AT24Cxx EEPROM code
+ *
+ *        Version:  1.0.0(10/08/23)
+ *         Author:  Guo Wenxue <guowenxue@gmail.com>
+ *      ChangeLog:  1, Release initial version on "10/08/23 17:52:00"
+ *
+ * Pin connection:
+ *                 W25QXX       Raspberry Pi 40Pin
+ *                   VCC   <--->   Pin#1 (3.3V)
+ *                   CS    <--->   Pin#24(CS)
+ *                   DO    <--->   Pin#21(MISO)
+ *                   GND   <--->   Pin#9 (GND)
+ *                   CLK   <--->   Pin#23(SCLK)
+ *                   DI    <--->   Pin#19(MOSI)
+ *
+ * /boot/config.txt:
+ *                  dtparam=spi=on
+ *
+ ********************************************************************************/
+
+
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <time.h>
+#include <sys/ioctl.h>
+#include <linux/ioctl.h>
+#include <sys/stat.h>
+#include <linux/types.h>
+#include <linux/spi/spidev.h>
+
+#include "w25qflash.h"
+
+#ifdef _W25QXX_DEBUG
+#define spinor_print(format,args...)    printf(format, ##args)
+#else
+#define spinor_print(format,args...)    do{} while(0)
+#endif
+
+#define spinor_Delay(delay)             usleep(delay*1000)
+
+/*+-----------------------+
+ *|   Entry Functions     |
+ *+-----------------------+*/
+
+void dump_buf(const char *prompt, char *buf, size_t len);
+
+int main (int argc, char **argv)
+{
+    spinor_test();
+
+    return 0;
+}
+
+
+/*+-----------------------+
+ *|   SPI API Functions   |
+ *+-----------------------+*/
+
+#define SPI_DEV                         "/dev/spidev0" /* SPI device 0 */
+#define SPI_CS0                         0 /* SPI CS0 */
+#define SPI_CS1                         1 /* SPI CS1 */
+#define SPI_BITS                        8
+#define SPI_MODE                        0 /* (SPI_CPHA|SPI_CPOL) */
+#define SPI_SPEED                       500000
+
+#define SPI_DUMMY_BYTE                  0xA5
+
+int spi_lowlevel_init(spi_bus_t *spi)
+{
+    uint8_t             bits = SPI_BITS;
+    uint32_t            speed = SPI_SPEED;
+    uint32_t            mode = SPI_MODE;
+    uint32_t            request;
+    char                dev[32];
+    int                 rv = 0;
+
+    snprintf(dev, sizeof(dev), "%s.%d", SPI_DEV, spi->cs);
+
+    spi->hspi = open(dev, O_RDWR);
+    if (spi->hspi < 0)
+    {
+        printf("ERROR: open device %s failure: %s\r\n", dev, strerror(errno));
+        return -2;
+    }
+    printf("Open '%s' for W25Q SPI norflash\n", dev);
+
+    /*
+     * spi mode
+     */
+    request = mode;
+    if( ioctl(spi->hspi, SPI_IOC_WR_MODE32, &mode) < 0 )
+    {
+        printf("ERROR: can't set spi mode\n");
+        rv = -3;
+        goto cleanup;
+    }
+
+    if( ioctl(spi->hspi, SPI_IOC_RD_MODE32, &mode) < 0 )
+    {
+        printf("ERROR: can't get spi mode\n");
+        rv = -3;
+        goto cleanup;
+    }
+
+    if (request != mode)
+    {
+        printf("WARNING: device does not support requested mode 0x%x\n", request);
+    }
+
+    /*
+     * bits per word
+     */
+    if( ioctl(spi->hspi, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0 )
+    {
+        printf("ERROR: can't set bits per word\n");
+        rv = -3;
+        goto cleanup;
+    }
+
+    if( ioctl(spi->hspi, SPI_IOC_RD_BITS_PER_WORD, &bits) < 0 )
+    {
+        printf("ERROR: can't get bits per word\n");
+        rv = -3;
+        goto cleanup;
+    }
+
+    /*
+     * max speed hz
+     */
+    if( ioctl(spi->hspi, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0 )
+    {
+        printf("ERROR: can't set max speed hz\n");
+        rv = -3;
+        goto cleanup;
+    }
+
+    if( ioctl(spi->hspi, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0 )
+    {
+        printf("ERROR: can't get max speed hz\n");
+        rv = -3;
+        goto cleanup;
+    }
+
+    printf("spi mode: 0x%x\n", mode);
+    printf("bits per word: %u\n", bits);
+    printf("max speed: %u Hz (%u kHz)\n", speed, speed/1000);
+
+cleanup:
+    if( rv < 0 )
+    {
+        close(spi->hspi);
+        spi->hspi = -1;
+    }
+
+    return rv;
+}
+
+void spi_cs_enable(spi_bus_t *spi)
+{
+    /*
+     * No need set CS in Linux because the device name /dev/spi0.0
+     * will choose the first slave device, second slave is spi0.1
+     */
+    (void)0;
+}
+
+void spi_cs_disable(spi_bus_t *spi)
+{
+    (void)0;
+}
+
+void spi_xcmd(spi_bus_t *spi, uint8_t command)
+{
+    uint8_t                 rxbyte;
+    struct spi_ioc_transfer tr = {
+        .tx_buf = (unsigned long)&command,
+        .rx_buf = (unsigned long)&rxbyte,
+        .len = 1,
+        .delay_usecs = 0,
+        .speed_hz = SPI_SPEED,
+        .bits_per_word = SPI_BITS,
+    };
+
+    if( ioctl(spi->hspi, SPI_IOC_MESSAGE(1), &tr) < 0 )
+    {
+        printf("ERROR: can't send spi message:%s\n", strerror(errno));
+    }
+
+    return;
+}
+
+void spi_xfer(spi_bus_t *spi, uint8_t *send_buf, uint8_t *recv_buf, int bytes)
+{
+    struct spi_ioc_transfer tr = {
+        .tx_buf = (unsigned long)send_buf,
+        .rx_buf = (unsigned long)recv_buf,
+        .len = bytes,
+        .delay_usecs = 0,
+        .speed_hz = SPI_SPEED,
+        .bits_per_word = SPI_BITS,
+    };
+
+    if( ioctl(spi->hspi, SPI_IOC_MESSAGE(1), &tr) < 0 )
+    {
+        printf("ERROR: can't send spi message:%s\n", strerror(errno));
+    }
+
+    return;
+}
+
+#define SPI_INFO(_hspi, _cs) {\
+    .hspi       = _hspi,            \
+    .hspi       = _cs,            \
+    .select     = spi_cs_enable,    \
+    .deselect   = spi_cs_disable,   \
+    .xcmd       = spi_xcmd,         \
+    .xfer       = spi_xfer,         \
+}
+
+static spi_bus_t spinor_spi = SPI_INFO(-1, SPI_CS0);
+
+
+/*+-----------------------+
+ *|  W25Q SPI Norflash ID |
+ *+-----------------------+*/
+
+#define ARRAY_SIZE(x)           (sizeof(x)/sizeof(x[0]))
+
+/* JEDEC ID the 3rd bytes is the storage capacity */
+#pragma GCC diagnostic ignored "-Wshift-count-overflow"
+#define CAPCITY_ID(id)          (1UL<<(id&0xFF))
+#define NOR_INFO(_name, _jedec_id) \
+    .name       = _name,                                \
+.jedec_id   = _jedec_id,                            \
+.block_size = W25Q_BLKSIZE,                         \
+.sector_size= W25Q_SECSIZE,                         \
+.page_size  = W25Q_PAGSIZE,                         \
+.capacity   = CAPCITY_ID(_jedec_id),                \
+.n_blocks   = CAPCITY_ID(_jedec_id)/W25Q_BLKSIZE,   \
+.n_sectors  = CAPCITY_ID(_jedec_id)/W25Q_SECSIZE,   \
+.n_pages    = CAPCITY_ID(_jedec_id)/W25Q_PAGSIZE,   \
+
+static flash_t flash_ids[] = {
+    { NOR_INFO("W25Q512", 0xef4020) },
+    { NOR_INFO("W25Q256", 0xef4019) },
+    { NOR_INFO("W25Q128", 0xef4018) },
+    { NOR_INFO("W25Q64",  0xef4017) },
+    { NOR_INFO("W25Q32",  0xef4016) },
+    { NOR_INFO("W25Q16",  0xef4015) },
+    { NOR_INFO("W25Q80",  0xef4014) },
+    { NOR_INFO("W25Q40",  0xef4013) },
+    { NOR_INFO("W25Q20",  0xef4012) },
+    { NOR_INFO("W25Q10",  0xef4011) },
+};
+
+/*+-------------------------------+
+ *|   SPI Norflash HighLevel API  |
+ *+-------------------------------+*/
+
+/* SPI Norflash API test function */
+void spinor_test(void)
+{
+    spinor_t            spinor;
+    int                 i;
+    uint8_t             buf[W25Q_PAGSIZE*2];
+
+    if( spinor_init(&spinor) < 0 )
+        return ;
+
+    //spinor_erase_chip(&spinor);
+    //spinor_erase_block(&spinor, 1, W25Q_BLKSIZE);
+    spinor_erase_sector(&spinor, 1, W25Q_SECSIZE);
+
+    memset(buf, 0, sizeof(buf));
+    spinor_read(&spinor, 0, buf, sizeof(buf));
+    dump_buf("<<<Read data after erase:\n", (char *)buf, sizeof(buf));
+
+    /* Read/Write data test on address not page align */
+    for(i=0; i<sizeof(buf); i++)
+        buf[i] = i;
+    spinor_write(&spinor, 16, buf, W25Q_PAGSIZE);
+
+    memset(buf, 0, sizeof(buf));
+    spinor_read(&spinor, 0, buf, W25Q_PAGSIZE*2);
+    dump_buf("<<<Read data after write:\n", (char *)buf, sizeof(buf));
+
+    return ;
+}
+
+/* Initial SPI and detect the flash chip */
+int spinor_init(spinor_t *spinor)
+{
+    int                 i, found = 0;
+    uint32_t            jedec_id;
+
+    spinor->lock = 0;
+    spinor->spi = &spinor_spi;
+
+    /* Initial SPI bus */
+    if( spi_lowlevel_init(spinor->spi) < 0 )
+    {
+        printf("ERROR: SPI lowlevel init failed\r\n");
+        return -2;
+    }
+
+    /* Read JEDEC ID to find the flash */
+    jedec_id = spinor_read_jedecid(spinor->spi);
+    for(i=0; i<ARRAY_SIZE(flash_ids); i++)
+    {
+        if(flash_ids[i].jedec_id == jedec_id)
+        {
+            found = 1;
+            spinor->flash = &flash_ids[i];
+            break;
+        }
+    }
+
+    if( !found )
+    {
+        printf("ERROR: W25Q Norflash JEDEC ID detect failed!\r\n");
+        return -3;
+    }
+
+    printf("Norflash %s ID[0x%x] detected, capacity %llu KB, %u blocks, %u pages.\r\n",
+            spinor->flash->name, jedec_id, spinor->flash->capacity>>10,
+            spinor->flash->n_blocks, spinor->flash->n_pages);
+
+    return 0;
+}
+
+/* Description:  Erase whole flash chip.
+ * Reference  :  P60, 8.2.32 Chip Erase (C7h / 60h)
+ */
+int spinor_erase_chip(spinor_t *spinor)
+{
+    spi_bus_t          *spi = spinor->spi;
+    uint32_t            timeout;
+    uint8_t             regval;
+
+    while (spinor->lock == 1)
+        spinor_Delay(1);
+
+    spinor->lock = 1;
+    spi->select(spi);
+
+    spinor_print("Norflash EraseChip Begin...\r\n");
+
+    /* Wait for flash ready */
+    spinor_WaitForReady(spi);
+
+    /* Write Enable */
+    spinor_write_enable(spi);
+
+    /* Entire flash erase */
+    spi->xcmd(spi, SPINOR_OP_CHIP_ERASE);
+
+    /* Wait for erase finish */
+    timeout = 0;
+    while (1)
+    {
+        if (timeout == 300)
+        {
+            spinor_print("\r\nErase entire flash timeout\r\n");
+            break;
+        }
+
+        regval = spinor_read_status_reg(spi, SPINOR_OP_RDSR1);
+        if( !(regval&0x1) )
+        {
+            spinor_print("\r\nNorflash EraseChip done.\r\n");
+            break;
+        }
+
+#ifdef _W25QXX_DEBUG
+        printf(".");
+        spinor_Delay(1000);
+#endif
+    }
+
+    printf("\n");
+
+    spi->deselect(spi);
+    spinor->lock = 0;
+
+    return 0;
+}
+
+/* Description:  Erase blocks by 64KiB,
+ * Reference  :  P59, 8.2.31 64KB Block Erase with 4-Byte Address (DCh)
+ *  @address is the erase start physical address, which can be not block alignment such as 0x10001.
+ *  @size is the erase size, which can be larger than a block such as 4097, and it will erase 2 blocks;
+ */
+int spinor_erase_block(spinor_t *spinor, uint32_t address, uint32_t size)
+{
+    spi_bus_t          *spi = spinor->spi;
+    flash_t            *flash = spinor->flash;
+    uint32_t            block, first, last;
+    uint32_t            addr;
+    uint8_t             buf[5];
+    int                 bytes = 0;
+
+    while (spinor->lock == 1)
+        spinor_Delay(1);
+
+    spinor->lock = 1;
+    spi->select(spi);
+
+    spinor_print("Norflash Erase %d Bytes Block@0x%x Begin...\r\n", size, address);
+
+    /* find first and last erase block */
+    first = address / flash->block_size;
+    last  = (address+size-1) / flash->block_size;
+
+    /* start erase all the blocks */
+    for( block=first; block<=last; block++)
+    {
+        addr = block * flash->sector_size;
+        spinor_print("Norflash Erase Block@%x ...\r\n", addr);
+
+        spinor_WaitForReady(spi);
+        spinor_write_enable(spi);
+
+        if (spinor->flash->n_blocks >= 512 ) /* larger than W25Q256 */
+        {
+            buf[bytes++] = SPINOR_OP_BE_4K_4B;
+            buf[bytes++] = (addr & 0xFF000000) >> 24;
+        }
+        else
+        {
+            buf[bytes++] = SPINOR_OP_BE_4K;
+        }
+        buf[bytes++] = (addr & 0xFF0000) >> 16 ;
+        buf[bytes++] = (addr & 0xFF00) >> 8 ;
+        buf[bytes++] = (addr & 0xFF);
+
+        spi->xfer(spi, buf, NULL, bytes);
+
+        spinor_WaitForReady(spi);
+    }
+
+    spinor_print("Norflash EraseBlock@0x%x done.\r\n", address);
+
+    spi->deselect(spi);
+    spinor->lock = 0;
+
+    return 0;
+}
+
+/* Description:  Erase sectors by 4KiB
+ * Reference  :  P56, 8.2.28 Sector Erase with 4-Byte Address (21h)
+ *  @address is the erase start physical address, which can be not sector alignment such as 0x1001.
+ *  @size is the erase size, which can be larger than a sector such as 4097, and it will erase 2 sectors;
+ */
+int spinor_erase_sector(spinor_t *spinor, uint32_t address, uint32_t size)
+{
+    spi_bus_t          *spi = spinor->spi;
+    flash_t            *flash = spinor->flash;
+    uint32_t            sector, first, last;
+    uint32_t            addr;
+    uint8_t             buf[5];
+    int                 bytes = 0;
+
+    while (spinor->lock == 1)
+        spinor_Delay(1);
+
+    spinor->lock = 1;
+    spi->select(spi);
+
+    spinor_print("Norflash Erase %d Bytes Sector@0x%x Begin...\r\n", size, address);
+
+    /* find first and last erase sector */
+    first = address / flash->sector_size;
+    last  = (address+size-1) / flash->sector_size;
+
+    /* start erase all the sectors */
+    for( sector=first; sector<=last; sector++)
+    {
+        addr = sector * flash->sector_size;
+        spinor_print("Norflash Erase Sector@%x ...\r\n", addr);
+
+        spinor_WaitForReady(spi);
+        spinor_write_enable(spi);
+
+        if (spinor->flash->n_blocks >= 512 ) /* larger than W25Q256 */
+        {
+            buf[bytes++] = SPINOR_OP_SE_4B;
+            buf[bytes++] = (addr & 0xFF000000) >> 24;
+        }
+        else
+        {
+            buf[bytes++] = SPINOR_OP_SE;
+        }
+        buf[bytes++] = (addr & 0xFF0000) >> 16 ;
+        buf[bytes++] = (addr & 0xFF00) >> 8 ;
+        buf[bytes++] = (addr & 0xFF);
+
+        spi->xfer(spi, buf, NULL, bytes);
+
+        spinor_WaitForReady(spi);
+    }
+
+    spinor_print("Norflash EraseSector@0x%x done.\r\n", address);
+
+    spi->deselect(spi);
+    spinor->lock = 0;
+
+    return 0;
+}
+
+/* Description:  Page random write by 256B
+ *  @addr is the write start physical address, which can be not page alignment such as 0x101.
+ *  @size is the write size, which can be larger than a page such as 257, and it will write 2 pages;
+ */
+int spinor_write(spinor_t *spinor, uint32_t address, uint8_t *data, uint32_t size)
+{
+    spi_bus_t          *spi = spinor->spi;
+    flash_t            *flash = spinor->flash;
+    uint32_t            page, first, last;
+    uint32_t            addr, ofset, len;
+    uint8_t             buf[W25Q_PAGSIZE+OVERHEAD_SIZE];
+    int                 bytes = 0;
+
+    if( address+size > spinor->flash->capacity )
+        return -1;
+
+    while (spinor->lock == 1)
+        spinor_Delay(1);
+
+    spinor->lock = 1;
+    spi->select(spi);
+
+    spinor_print("Norflash Write %d Bytes to addr@0x%x Begin...\r\n", size, address);
+
+    /* find first and last write page */
+    first = address / flash->page_size;
+    last  = (address+size-1) / flash->page_size;
+
+    /* address in page and offset in buffer */
+    addr = address;
+    ofset = 0;
+
+    /* start write all the pages */
+    for( page=first; page<=last; page++)
+    {
+        len = flash->page_size - (addr%flash->page_size);
+        len = len > size ? size : len;
+
+        spinor_print("Norflash write addr@0x%x, %u bytes\r\n", addr, len);
+
+        spinor_WaitForReady(spi);
+        spinor_write_enable(spi);
+
+        bytes = 0;
+        if (spinor->flash->n_blocks >= 512 )
+        {
+            buf[bytes++] = SPINOR_OP_PP_4B;
+            buf[bytes++] = (addr & 0xFF000000) >> 24;
+        }
+        else
+        {
+            buf[bytes++] = SPINOR_OP_PP;
+        }
+        buf[bytes++] = (addr & 0xFF0000) >> 16 ;
+        buf[bytes++] = (addr & 0xFF00) >> 8 ;
+        buf[bytes++] = (addr & 0xFF);
+
+        /* send command and data */
+        memcpy(&buf[bytes], data+ofset, len);
+        bytes += len;
+
+        spi->xfer(spi, buf, NULL, bytes);
+
+        spinor_WaitForReady(spi);
+
+        addr  += len;
+        ofset += len;
+        size  -= len;
+    }
+
+    spinor_print("Norflash WriteByte@0x%x done.\r\n", address);
+
+    spi->deselect(spi);
+    spinor->lock = 0;
+
+    return 0;
+}
+
+/* Description:  The Fast Read instruction can read the entire memory chip.
+ * Reference  :  P41, 8.2.13 Fast Read with 4-Byte Address (0Ch)
+ *  @address is the read start physical address, which can be not page alignment such as 0x101.
+ *  @size is the read size, which can be larger than a page such as 257, and it will read 2 pages;
+ */
+int spinor_read(spinor_t *spinor, uint32_t address, uint8_t *data, uint32_t size)
+{
+    spi_bus_t          *spi = spinor->spi;
+    uint8_t             buf[W25Q_PAGSIZE+OVERHEAD_SIZE];
+    int                 bytes = 0;
+    int                 ofset;
+    uint32_t            addr = address;
+
+    if( address+size > spinor->flash->capacity )
+        return -1;
+
+    while (spinor->lock == 1)
+        spinor_Delay(1);
+
+    spinor->lock = 1;
+    spi->select(spi);
+
+    spinor_print("Norflash Read %d Bytes from addr@0x%x Begin...\r\n", size, address);
+
+    while( size > 0 )
+    {
+        bytes = size>W25Q_PAGSIZE ? W25Q_PAGSIZE : size;
+        memset(buf, SPI_DUMMY_BYTE, sizeof(buf));
+
+        spinor_print("Norflash read addr@0x%x, %d bytes\r\n", addr, bytes);
+
+        /* send instruction and address */
+        ofset = 0;
+        if (spinor->flash->n_blocks >= 512 )
+        {
+            buf[ofset++] = SPINOR_OP_READ_FAST_4B;
+            buf[ofset++] = (addr & 0xFF000000) >> 24;
+        }
+        else
+        {
+            buf[ofset++] = SPINOR_OP_READ_FAST;
+        }
+        buf[ofset++] = (addr & 0xFF0000) >> 16 ;
+        buf[ofset++] = (addr & 0xFF00) >> 8 ;
+        buf[ofset++] = (addr & 0xFF);
+
+        ofset += 1; /* Skip first dummy byte */
+
+        /* Send command and read data out */
+        spi->xfer(spi, buf, buf, ofset+bytes);
+
+        memcpy(data, &buf[ofset], bytes);
+        size -= bytes;
+        addr += bytes;
+        data += bytes;
+    }
+
+    spinor_print("Norflash ReadBytes@0x%x done.\r\n", address);
+
+    spi->deselect(spi);
+    spinor->lock = 0;
+
+    return 0;
+}
+
+/*+-------------------------------+
+ *|   SPI Norflash LowLevel API   |
+ *+-------------------------------+*/
+
+/* Description:  Read the chipset UNIQUE ID.
+ * Reference  :  P68, 8.2.40 Read Unique ID Number (4Bh)
+ */
+int spinor_read_uniqid(spi_bus_t *spi, uint8_t *uniq_id)
+{
+    uint8_t              i;
+    uint8_t              buf[13]; /* Instruction(1B) + Dummy(4B) + UID(8B)*/
+
+    if( !uniq_id )
+        return -1;
+
+    /* Enable SPI chip select */
+    spi->select(spi);
+
+    /* Wait for flash ready */
+    spinor_WaitForReady(spi);
+
+    buf[0] = SPINOR_OP_RDUID;
+    spi->xfer(spi, buf, buf, sizeof(buf));
+
+    /* Skip 4 bytes dummy bytes */
+    for (i=0; i<8; i++)
+    {
+        uniq_id[i] = buf[5+i];
+    }
+
+    /* Disable SPI chip select */
+    spi->deselect(spi);
+
+    return 0;
+}
+
+/* Description:  Read the chipset JEDEC ID.
+ * Reference  :  P69, 8.2.41 Read JEDEC ID (9Fh)
+ */
+uint32_t spinor_read_jedecid(spi_bus_t *spi)
+{
+    uint32_t            jedec_id = 0x0;
+    uint8_t             buf[4];
+
+    /* Enable SPI chip select */
+    spi->select(spi);
+
+    /* Wait for flash ready */
+    spinor_WaitForReady(spi);
+
+    /* Send Read JEDEC ID command 0x9F */
+    buf[0] = SPINOR_OP_RDID;
+    spi->xfer(spi, buf, buf, sizeof(buf));
+    jedec_id = (buf[1] << 16) | (buf[2] << 8) | buf[3];
+
+    /* Disable SPI chip select */
+    spi->deselect(spi);
+
+    return jedec_id;
+}
+
+/* Description:  Write Enable
+ * Reference  :  P31, 8.2.1 Write Enable (06h)
+ */
+void spinor_write_enable(spi_bus_t *spi)
+{
+    spi->xcmd(spi, SPINOR_OP_WREN);
+
+    spinor_WaitForReady(spi);
+}
+
+/* Description:  Write Disable
+ * Reference  :  P32, 8.2.3 Write Disable (04h)
+ */
+void spinor_write_disable(spi_bus_t *spi)
+{
+    spi->xcmd(spi, SPINOR_OP_WRDI);
+
+    spinor_WaitForReady(spi);
+}
+
+/* Description:  Read Status Register
+ * Reference  :  P32, 8.2.4 Read Status Register-1 (05h), Status Register-2 (35h) & Status Register-3 (15h)
+ */
+uint8_t spinor_read_status_reg(spi_bus_t *spi, uint8_t reg)
+{
+    uint8_t     buf[2];
+
+    buf[0] = reg;
+    spi->xfer(spi, buf, buf, sizeof(buf));
+
+    return buf[1];
+}
+
+/* Description:  Write Status Register
+ * Reference  :  P33, 8.2.5 Write Status Register-1 (01h), Status Register-2 (31h) & Status Register-3 (11h)
+ */
+void spinor_write_status_reg(spi_bus_t *spi, uint8_t reg, uint8_t value)
+{
+    uint8_t     buf[2];
+
+    buf[0] = reg;
+    buf[1] = value;
+    spi->xfer(spi, buf, buf, sizeof(buf));
+}
+
+/* Description:  Wait flash program/erase finished by read Status Register for BUSY bit
+ * Reference  :  P15, 7.1 Status Registers
+ */
+void spinor_WaitForReady(spi_bus_t *spi)
+{
+    uint8_t     regval;
+
+    do
+    {
+        regval = spinor_read_status_reg(spi, SPINOR_OP_RDSR1);
+    } while ((regval & 0x01) == 0x01);
+}
+
+/*+----------------------+
+ *|    Misc functions    |
+ *+----------------------+*/
+
+void dump_buf(const char *prompt, char *buf, size_t len)
+{
+    char        line[256];
+    size_t      i, j;
+    int         offset;
+
+    if( prompt )
+    {
+        printf("%s", prompt);
+    }
+
+    for(i = 0; i < len; i += 16)
+    {
+        offset = snprintf(line, sizeof(line), "%08zx: ", i);
+
+        /* Print hex representation */
+        for (j = 0; j < 16; j++)
+        {
+            if (i + j < len)
+                offset += snprintf(line + offset, sizeof(line) - offset, "%02x ", buf[i + j]);
+            else
+                offset += snprintf(line + offset, sizeof(line) - offset, "   ");
+        }
+
+        offset += snprintf(line + offset, sizeof(line) - offset, " ");
+
+        /* Print ASCII representation */
+        for (j = 0; j < 16; j++)
+        {
+            if (i + j < len)
+            {
+                unsigned char c = buf[i + j];
+                offset += snprintf(line + offset, sizeof(line) - offset, "%c", (c >= 32 && c <= 126) ? c : '.');
+            }
+            else
+            {
+                offset += snprintf(line + offset, sizeof(line) - offset, " ");
+            }
+        }
+
+        /* Print the line */
+        printf("%s\n", line);
+    }
+}
diff --git a/hal/modules/w25qflash.h b/hal/modules/w25qflash.h
new file mode 100644
index 0000000..6e946a0
--- /dev/null
+++ b/hal/modules/w25qflash.h
@@ -0,0 +1,181 @@
+/*********************************************************************************
+ *      Copyright:  (C) 2023 LingYun IoT System Studio
+ *                  All rights reserved.
+ *
+ *       Filename:  at24c.c
+ *    Description:  This file is AT24Cxx EEPROM code
+ *
+ *        Version:  1.0.0(10/08/23)
+ *         Author:  Guo Wenxue <guowenxue@gmail.com>
+ *      ChangeLog:  1, Release initial version on "10/08/23 17:52:00"
+ *
+ * Pin connection:
+ *                 W25QXX       Raspberry Pi 40Pin
+ *                   VCC   <--->   Pin#1 (3.3V)
+ *                   CS    <--->   Pin#24(CS)
+ *                   DO    <--->   Pin#21(MISO)
+ *                   GND   <--->   Pin#9 (GND)
+ *                   CLK   <--->   Pin#23(SCLK)
+ *                   DI    <--->   Pin#19(MOSI)
+ *
+ * /boot/config.txt:
+ *                  dtparam=spi=on
+ *
+ ********************************************************************************/
+
+#ifndef _W25QFLASH_H
+#define _W25QFLASH_H
+
+#include <stdbool.h>
+
+#define _W25QXX_DEBUG           1
+
+/* W25Q SPI Norflash page/sector/block size */
+#define W25Q_PAGSIZE            256     /* 1Page=256B */
+#define W25Q_SECSIZE            4096    /* 1Sector=16Pages=4KB */
+#define W25Q_BLKSIZE            65536   /* 1Block=16Sector=64KB */
+
+/* Flash opcodes. Refer to <<W25Q256JV.pdf>> P26 Table 8.1.2 Instruction Set Table */
+#define SPINOR_OP_RDID          0x9f    /* Read JEDEC ID */
+#define SPINOR_OP_RDUID         0x4b    /* Read unique ID */
+#define SPINOR_OP_WRSR1         0x01    /* Write status register-1 */
+#define SPINOR_OP_WRSR2         0x31    /* Write status register-2 */
+#define SPINOR_OP_WRSR3         0x11    /* Write status register-3 */
+#define SPINOR_OP_BP            0x02    /* Byte program */
+#define SPINOR_OP_READ          0x03    /* Read data bytes (low frequency) */
+#define SPINOR_OP_WRDI          0x04    /* Write disable */
+#define SPINOR_OP_RDSR1         0x05    /* Read status register-1 */
+#define SPINOR_OP_RDSR2         0x35    /* Read status register-2 */
+#define SPINOR_OP_RDSR3         0x15    /* Read status register-3 */
+#define SPINOR_OP_WREN          0x06    /* Write enable */
+#define SPINOR_OP_READ_FAST     0x0b    /* Read data bytes (high frequency) */
+#define SPINOR_OP_READ_FAST_4B  0x0c    /* Read data bytes (high frequency) */
+
+#define SPINOR_OP_CHIP_ERASE    0xc7    /* Erase whole flash chip */
+#define SPINOR_OP_BE_4K_4B      0xdc    /* Block erase (64KiB) with 4-Byte Address */
+#define SPINOR_OP_BE_4K         0xd8    /* Block erase (64KiB) */
+#define SPINOR_OP_SE_4B         0x21    /* Sector erase (4KiB) with 4-Byte Address */
+#define SPINOR_OP_SE            0x20    /* Sector erase (4KiB)  */
+#define SPINOR_OP_PP_4B         0x12    /* Page Program (up to 256 bytes) with 4-Byte Address */
+#define SPINOR_OP_PP            0x02    /* Page program (up to 256 bytes) */
+#define SPINOR_OP_SRSTEN        0x66    /* Software Reset Enable */
+#define SPINOR_OP_SRST          0x99    /* Software Reset */
+
+/* All the above command default work as 4 bytes mode */
+#define OVERHEAD_SIZE			6
+
+typedef struct spi_bus
+{
+    int  hspi; /* SPI bus handler */
+    int  cs;   /* SPI cs pin number */
+
+    void (*select)(struct spi_bus *spi);   /* CS enable function  */
+    void (*deselect)(struct spi_bus *spi); /* CS disable function */
+    void (*xcmd)(struct spi_bus *spi, uint8_t command); /* Send a byte command */
+    void (*xfer)(struct spi_bus *spi, uint8_t *send_buf, uint8_t *recv_buf, int bytes); /* Transmit and Receive N byte */
+} spi_bus_t;
+
+typedef struct flash_s 
+{
+    char            *name;          /* Chip name */
+    uint32_t         jedec_id;      /* JEDEC ID, 3 bytes */
+    uint64_t         capacity;      /* Chip   size in bytes */
+    uint32_t         block_size;    /* Block  size in bytes */
+    uint32_t         sector_size;   /* Sector size in bytes */
+    uint32_t         page_size;     /* Page   size in bytes */
+    uint32_t         n_blocks;      /* Number of blocks */
+    uint32_t         n_sectors;     /* Number of sectors */
+    uint32_t         n_pages;       /* Number of pages */
+} flash_t;
+
+
+typedef struct spinor_s
+{
+    spi_bus_t       *spi;
+    flash_t         *flash;
+    uint8_t          lock;
+} spinor_t;
+
+
+/*+-------------------------------+
+ *|   SPI Norflash HighLevel API  |
+ *+-------------------------------+*/
+
+/* SPI Norflash API test function */
+extern void spinor_test(void);
+
+/* Initial SPI and detect the flash chip */
+extern int spinor_init(spinor_t *spinor);
+
+/* Description:  Erase whole flash chip.
+ * Reference  :  P60, 8.2.32 Chip Erase (C7h / 60h)
+ */
+extern int spinor_erase_chip(spinor_t *spinor);
+
+/* Description:  Erase blocks by 64KiB,
+ * Reference  :  P59, 8.2.31 64KB Block Erase with 4-Byte Address (DCh)
+ *  @address is the erase start physical address, which can be not block alignment such as 0x10001.
+ *  @size is the erase size, which can be larger than a block such as 4097, and it will erase 2 blocks;
+ */
+extern int spinor_erase_block(spinor_t *spinor, uint32_t address, uint32_t size);
+
+/* Description:  Erase sectors by 4KiB
+ * Reference  :  P56, 8.2.28 Sector Erase with 4-Byte Address (21h)
+ *  @address is the erase start physical address, which can be not sector alignment such as 0x1001.
+ *  @size is the erase size, which can be larger than a sector such as 4097, and it will erase 2 sectors;
+ */
+extern int spinor_erase_sector(spinor_t *spinor, uint32_t address, uint32_t size);
+
+/* Description:  Page random write by 256B
+ *  @addr is the write start physical address, which can be not page alignment such as 0x101.
+ *  @size is the write size, which can be larger than a page such as 257, and it will write 2 pages;
+ */
+extern int spinor_write(spinor_t *spinor, uint32_t address, uint8_t *data, uint32_t bytes);
+
+/* Description:  The Fast Read instruction can read the entire memory chip.
+ * Reference  :  P41, 8.2.13 Fast Read with 4-Byte Address (0Ch)
+ *  @address is the read start physical address, which can be not page alignment such as 0x101.
+ *  @size is the read size, which can be larger than a page such as 257, and it will read 2 pages;
+ */
+extern int spinor_read(spinor_t *spinor, uint32_t address, uint8_t *buf, uint32_t bytes);
+
+/*+-------------------------------+
+ *|   SPI Norflash LowLevel API   |
+ *+-------------------------------+*/
+
+/* Description:  Read the chipset UNIQUE ID.
+ * Reference  :  P68, 8.2.40 Read Unique ID Number (4Bh)
+ */
+int spinor_read_uniqid(spi_bus_t *spi, uint8_t *uniq_id);
+
+/* Description:  Read the chipset JEDEC ID.
+ * Reference  :  P69, 8.2.41 Read JEDEC ID (9Fh)
+ */
+uint32_t spinor_read_jedecid(spi_bus_t *spi);
+
+/* Description:  Write Enable
+ * Reference  :  P31, 8.2.1 Write Enable (06h)
+ */
+void spinor_write_enable(spi_bus_t *spi);
+
+/* Description:  Write Disable
+ * Reference  :  P32, 8.2.3 Write Disable (04h)
+ */
+void spinor_write_disable(spi_bus_t *spi);
+
+/* Description:  Read Status Register
+ * Reference  :  P32, 8.2.4 Read Status Register-1 (05h), Status Register-2 (35h) & Status Register-3 (15h)
+ */
+uint8_t spinor_read_status_reg(spi_bus_t *spi, uint8_t reg);
+
+/* Description:  Write Status Register
+ * Reference  :  P33, 8.2.5 Write Status Register-1 (01h), Status Register-2 (31h) & Status Register-3 (11h)
+ */
+void spinor_write_status_reg(spi_bus_t *spi, uint8_t reg, uint8_t value);
+
+/* Description:  Wait flash program/erase finished by read Status Register for BUSY bit
+ * Reference  :  P15, 7.1 Status Registers
+ */
+void spinor_WaitForReady(spi_bus_t *spi);
+
+#endif

--
Gitblit v1.9.1