SylixOS中EEPROM設備驅動實現

1.開發環境
操作系統:SylixOS 
編程環境:RealEvo-IDE3.1.5
硬件平臺:SAMA5D2 Xplained開發板

2.EEPROM簡介
       EEPROM,或寫作E2PROM,全稱電子抹除式可複寫只讀存儲器 (英語:Electrically-Erasable Programmable Read-Only Memory),是一種可以通過電子方式多次複寫的半導體存儲設備。相比EPROM,EEPROM不需要用紫外線照射,也不需取下,就可以用特定的電壓,來抹除芯片上的信息,以便寫入新的數據。
2.1 存儲結構及設備地址
        本篇使用的EEPROM芯片型號是AT24MAC402,該芯片提供2Kbit串行電可擦除可編程的存儲單元,即256 bytes,並可通過I2C兼容的串行接口(TWI)進行讀寫操作。此外,AT24MAC402可用來存放全球唯一的MAC或EUI地址(EUI-48)。其內部存儲組織結構如圖 2-1所示。

             

圖 2-1  AT24MAC402內部存儲結構

        由圖 2-1可知,AT24MAC402提供了128-bit Serial Number和48-bit(9Ah-9Fh)的擴展存儲部分用來存儲序列號和全球唯一的MAC或EUI地址。作爲I2C從設備,可通過兩個不同的設備地址訪問EEPROM的這兩部分(標準和擴展)的內部存儲地址。AT24MAC402的芯片手冊對這兩部分的編址如圖 2-2所示。

             

圖 2-2  設備地址

        其中Bit[3:1]由硬件引腳電平決定,在沒有設置寫保護的情況下,對於標準EEPROM可進行讀寫操作,而擴展部分僅支持讀操作。SAMA5D2開發板EEPROM的電路圖如圖 2-3所示。

             

圖 2-3  EEPROM電路圖連線

      結合圖 2-2可知EEPROM標準部分的設備地址是‘1010100’,即0x54;擴展部分的設備地址是‘1011100’,即0x5C。
2.2 操作模式
2.2.1 讀操作
        標準EEPROM部分和擴展部分均支持讀操作,EEPROM支持以下三種類型的讀操作:
        當前地址讀:在當前地址讀操作方式時無需發送讀字節地址,每次只將當前地址所存數據讀出,片內地址始終保持自加,直到讀完整個EEPROM後又回到0地址。
        隨機地址讀:主設備發送有效從設備內部地址,並且從設備發送響應信號後將會將該內部地址處的數據通過I2C發送給主設備。
        順序讀:多字節連續讀操作既可以是當前地址讀,也可以是隨機地址讀,每次處理器接收到一字節數據都返回一個ACK,EEPROM接收到此ACK後會自動地址加1,接着輸出下一個字節數據,直到處理器返回NO ACK時,讀過程結束。
2.2.2 寫操作
        標準EEPROM部分,在寫保護被禁止的情況下提供寫操作,並且支持以下兩種寫操作:
        字節寫:按字節寫時通常在向EEPROM發送設備地址並收到應答信號後,發送寫字節地址再次收到ACK後開始寫數據,最後發送停止位結束寫操作。
        頁寫:寫頁時EEPROM可一次連續寫入整頁數據(一頁爲16字節)。其發地址過程與寫字節時完全相同。不同的是,當寫完一個數據字節後,處理器發不發停止狀態,而是在應答信號後繼續寫入數據,每一個字節接收完畢後,EEPROM都返回一個ACK,一直到寫完整頁。如果頁寫時寫入數超出該物理頁邊界,則超出數據將重新寫入頁首地址覆蓋之前所寫數據。
3.技術實現
        本篇通過內核模塊的方式實現EEPROM的設備驅動。
        EEPROM驅動的編寫同樣是實現設備文件操作控制塊結構體file_operations的成員函數,在EEPROM設備驅動中主要實現了__e2promOpen、__e2promClose、__e2promRead、__e2promWrite、__e2promIoctl函數功能,__e2promIoctl函數用來設置待訪問的EEPROM的內部地址。
        應用程序可以通過訪問標準文件I/O函數來讀寫EEPROM設備,在讀寫EEPROM設備前,可調用lseek函數設置要讀/寫的eeprom內部寄存器地址,然後調用標準文件I/O對該內部地址進行讀/寫操作。
EEPROM的讀寫功能,實質上是調用I2C設備發送接口的方式實現的。這裏使用字符驅動的框架來實現EEPROM的讀寫操作。由於標準EEPROM和擴展部分的設備地址不同,但是對這兩部分的操作是一樣的,因此本篇僅給出標準EEPROM設備的驅動實現。
        標準EEPROM設備文件操作結構體如程序清單 3-1所示。

程序清單 3-1  e2prom設備文件操作集

/*********************************************************************************************************  
**  e2prom設備文件操作集  *********************************************************************************************************/  
struct file_operations GfileOperate = {  
    .fo_open  = __e2promOpen,  
    .fo_close = __e2promClose,  
    .fo_read  = __e2promRead,  
    .fo_write = __e2promWrite,  
    .fo_ioctl = __e2promIoctl  
};

        通過調用標準I/O函數,可最終調用到file_operations結構體中的對應的成員函數。
3.1 讀操作
        __e2promRead讀取EEPROM內部數據,其實現如程序清單 3-2所示。

程序清單 3-2  __e2promRead實現

/*********************************************************************************************************  ** 函數名稱: __e2promRead  
** 功能描述: 讀取eeprom設備  
** 輸 入  : pvArg      版本類型選擇參數  
**           pcBuffer   緩衝區  
**           stMaxByte  緩衝區大小  
** 輸 出  : ERROR  *********************************************************************************************************/  
static ssize_t __e2promRead(PVOID pvArg, PCHAR pcBuffer, size_t stMaxByte)  
{  
    UINT32 uiRet;  
    if(!pcBuffer) {  
        return PX_ERROR;  
    }  
    uiRet   = __at24xxRead(Gi2cDev, Goffset, (UINT8 *)pcBuffer, stMaxByte);  
    Goffset = (Goffset + stMaxByte) % EEPROM_MEM_SIZE;                  /*  內部地址計數器保存值        */  
return (uiRet == ERROR_NONE) ? stMaxByte:PX_ERROR;  
}

__e2promRead將會調用at24xxRead函數實現讀操作,at24xxRead實現如程序清單 3-3所示。

程序清單 3-3  at24xxRead實現

/*********************************************************************************************************  ** 函數名稱: __at24xxRead  
** 功能描述: AT24xx         寄存器讀函數  
** 輸 入  : pI2cDev         i2c設備  
**           RegAddress      寄存器地址  
**           buf             數據接收緩衝區  
**           len             需要讀取的數據長度  
** 輸 出  : 返回 0 表示函數執行成功  *********************************************************************************************************/  
static int __at24xxRead (PLW_I2C_DEVICE  pI2cDev, UINT8 ucRegAddress, UINT8 *ucBuf, UINT uiLen)  
{  
    INT      iError;  
  
    LW_I2C_MESSAGE  i2cMsgs[2] = {  
        {  
            .I2CMSG_usAddr    = pI2cDev->I2CDEV_usAddr,  
            .I2CMSG_usFlag    = 0,                                      /*  0表示寫操作                 */  
            .I2CMSG_usLen     = sizeof(ucRegAddress),  
            .I2CMSG_pucBuffer = &ucRegAddress,                          /*  先寫要讀的寄存器地址        */  
        },  
        {  
            .I2CMSG_usAddr    = pI2cDev->I2CDEV_usAddr,  
            .I2CMSG_usFlag    = LW_I2C_M_RD,                         /*  表示讀操作                  */  
            .I2CMSG_usLen     = uiLen,  
            .I2CMSG_pucBuffer = ucBuf,                                  /*  接着讀操作                  */  
        }  
    };  
  
    iError = API_I2cDeviceTransfer(pI2cDev, i2cMsgs, 2);  
    if (iError < 0) {  
        return  (PX_ERROR);  
    }  
  
    return  (ERROR_NONE);  
}

        實質上,應用層調用read函數,最終是調用的API_I2cDeviceTransfer函數實現接收與發送操作。
3.2 寫操作
        __e2promWrite向EEPROM寫入數據,其實現如程序清單 3-4所示。

程序清單 3-4  e2promWrite實現

/*********************************************************************************************************  ** 函數名稱: __e2promWrite  
** 功能描述: 寫eeprom設備  
** 輸 入  : pvArg      版本類型選擇參數  
**           pcBuffer   緩衝區  
**           stMaxByte  緩衝區大小  
** 輸 出  : ERROR  *********************************************************************************************************/  
static ssize_t __e2promWrite(PVOID pvArg, PCHAR pcBuffer, size_t stMaxByte)  
{  
    UINT32 uiRet;  
    if(!pcBuffer) {  
        return PX_ERROR;  
    }  
    uiRet = __at24xxWrite (Gi2cDev, Goffset, (UINT8 *)pcBuffer, stMaxByte);  
    Goffset = (Goffset + stMaxByte) % EEPROM_MEM_SIZE;                  /*  內部地址計數器保存值        */  
return (uiRet == ERROR_NONE) ? stMaxByte:PX_ERROR;  
}

        __e2promWrite將會調用at24xxWrite函數實現EEPROM的寫操作,at24xxWrite實現如程序清單 3-5所示。

程序清單 3-5  at24xxWrite實現

/*********************************************************************************************************  ** 函數名稱: __at24xxWrite  
** 功能描述: AT24xx         寄存器寫函數  
** 輸 入  : pI2cDev         i2c設備  
**           RegAddress      寄存器地址  
**           buf             需要寫入寄存器的數據  
**           len             寫入數據長度  
** 輸 出  : 返回 0 表示函數執行成功  *********************************************************************************************************/  
static int __at24xxWrite (PLW_I2C_DEVICE  pI2cDev, UINT8 ucRegAddress, UINT8 *ucBuf, UINT uiLen)  
{  
    INT             iError;  
    if(!pI2cDev) {  
        return PX_ERROR;  
    }  
    /*  
     * 發送緩存大小:至少爲(數據+地址)字節數  
     */  
    UINT8  *pui2cBuf = (UINT8 *)malloc(uiLen+1);  
  
    LW_I2C_MESSAGE  i2cMsgs[1] = {  
        {  
            .I2CMSG_usAddr    = pI2cDev->I2CDEV_usAddr,  
            .I2CMSG_usFlag    = 0,                                      /*  0表示寫操作                 */  
            .I2CMSG_usLen     = uiLen + sizeof(ucRegAddress),           /*  (數據+地址)字節數           */  
            .I2CMSG_pucBuffer = pui2cBuf,  
        },  
    };  
  
    /*  
     * 發送緩存開頭存放的是地址信息,然後纔是數據  
     */  
    pui2cBuf[0] = ucRegAddress;  
    memcpy(&pui2cBuf[1], &ucBuf[0], uiLen);  
  
    iError = API_I2cDeviceTransfer(pI2cDev, i2cMsgs, 1);  
    if (iError < 0) {  
        free(pui2cBuf);  
        printk(KERN_ERR "__at24xxWrite(): failed to i2c transfer!\n");  
        return  (PX_ERROR);  
    }  
    free(pui2cBuf);  
    return  (ERROR_NONE);  
}

        實質上,應用層調用write函數,最終是調用的API_I2cDeviceTransfer函數實現接收與發送操作。
3.3 設置讀寫地址
        通過實現__e2promIoctl函數,完成設置待讀/寫的EEPROM的內部寄存器地址,其實現如程序清單 3-6所示。

程序清單 3-6  __e2promIoctl實現

/*********************************************************************************************************  ** 函數名稱: __e2promIoctl  
** 功能描述: 控制eeprom設備  
** 輸 入  : pdevhdrHdr 設備頭  
**           iCmd       命令  
**           lArg       命令參數  
** 輸 出  : ERROR  *********************************************************************************************************/  
static INT __e2promIoctl(PLW_DEV_HDR    pdevhdrHdr, INT  iCmd, LONG  lArg)  
{  
    INT      iError;  
    struct stat *pstat;  
  
    switch(iCmd) {  
    case FIOSEEK:                                                       /*  獲取e2prom內部地址偏移      */  
        Goffset = *(off_t *)lArg;  
        break;  
    case FIOFSTATGET:                                                   /*  獲得文件屬性                */  
        pstat = (struct stat *)lArg;  
        pstat->st_dev     = (dev_t)pdevhdrHdr;  
        pstat->st_ino     = (ino_t)0;                                   /*  相當於唯一節點              */  
        pstat->st_mode    = 0644 | S_IFCHR;                             /*  默認屬性                    */  
        pstat->st_nlink   = 1;  
        pstat->st_uid     = 0;  
        pstat->st_gid     = 0;  
        pstat->st_rdev    = 1;  
        pstat->st_size    = 0;  
        pstat->st_blksize = 0;  
        pstat->st_blocks  = 0;  
        pstat->st_atime   = API_RootFsTime(LW_NULL);                    /*  默認使用 root fs 基準時間   */  
        pstat->st_mtime   = API_RootFsTime(LW_NULL);  
        pstat->st_ctime   = API_RootFsTime(LW_NULL);  
        break;  
    default:  
        errno  = ENOSYS;  
        iError = PX_ERROR;  
        break;  
    }  
    return ERROR_NONE;  
}

        通過在應用層調用lseek,可以調用到底層的__e2promIoctl函數,在__e2promIoctl函數中通過給全局變量Goffset賦值,在調用read/write函數時,底層相應的__e2promRead/ __e2promWrite便可獲得Goffset的偏移值,進而讀取/寫入到EEPROM內部寄存器中。
3.4 驅動模塊初始化及卸載
        驅動模塊初始化實現如程序清單 3-7所示。

程序清單 3-7  模塊初始化

/*********************************************************************************************************  
** 函數名稱: module_init  
** 功能描述: 驅動加載模塊  
** 輸 入  : NONE  
** 輸 出  : ERROR_CODE  *********************************************************************************************************/  
int module_init (void)  
{  
    printk("hello_module init!\n");  
    INT iDrvNum    = API_IosDrvInstallEx(&GfileOperate);                 /*  安裝驅動程序                */  
    API_IosDevAdd (&GdevhdrHdr, "/dev/eeprom", iDrvNum);                /*  安裝設備                    */  
    Gi2cDev = API_I2cDeviceCreate("/bus/i2c/1",  
                                  "/dev/eeprom",  
                                  DEVICE_ADDR,  
                                  0);  
    return ERROR_NONE;  
}

        模塊卸載實現如程序清單 3-8所示。

程序清單 3-8  模塊卸載

/*********************************************************************************************************  

** 函數名稱: module_exit  
** 功能描述: 驅動卸載模塊  
** 輸 入  : NONE  
** 輸 出  : NONE  *********************************************************************************************************/  
void module_exit (void)  
{  
    printk("hello_module exit!\n");  
  
    API_IosDevDelete(&GdevhdrHdr);                                      /*  刪除設備                    */  
    API_I2cDeviceDelete(Gi2cDev);                                        /*  刪除指定的 i2c 設備          */  
    return ;  
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章