28.Linux-IIC驅動(詳解)

来源:http://www.cnblogs.com/lifexy/archive/2017/11/10/7816324.html
-Advertisement-
Play Games

上一節 我們學習了: IIC介面下的24C02 驅動分析: http://www.cnblogs.com/lifexy/p/7793686.html 接下來本節, 學習Linux下如何利用linux下I2C驅動體繫結構來操作24C02 1. I2C體繫結構分析 1.1首先進入linux內核的driv ...


上一節 我們學習了:  

IIC介面下的24C02 驅動分析: http://www.cnblogs.com/lifexy/p/7793686.html

接下來本節, 學習Linux下如何利用linux下I2C驅動體繫結構來操作24C02


 

1. I2C體繫結構分析

1.1首先進入linux內核的driver/i2c目錄下,如下圖所示:

 

其中重要的文件介紹如下:

1)algos文件夾(algorithms)

裡面保存I2C的通信方面的演算法

2)busses文件夾

裡面保存I2C匯流排驅動相關的文件,比如i2c-omap.c、 i2c-versatile.c、 i2c-s3c2410.c等。

3) chips文件夾

裡面保存I2C設備驅動相關的文件,如下圖所示,比如m41t00,就是RTC實時鐘

 

4) i2c-core.c
這個文件實現了I2C核心的功能(I2C匯流排的初始化、註冊和適配器添加和註銷等相關工作)以及/proc/bus/i2c*介面。
5) i2c-dev.c
提供了通用的read( ) 、 write( ) 和ioctl( ) 等介面,實現了I2C適配器設備文件的功能,其中I2C設備的主設備號都為89, 次設備號為0~255。
應用層可以借用這些介面訪問掛接在適配器上的I2C設備的存儲空間或寄存器, 並控制I2C設備的工作方

顯然,它和前幾次驅動類似, I2C也分為匯流排驅動和設備驅動,匯流排就是協議相關的,它知道如何收發數據,但不知道數據含義,設備驅動卻知道數據含義

1.2 I2C驅動架構,如下圖所示:

 

如上圖所示,每一條I2C對應一個adapter適配器,在kernel中, adapter適配器是通過struct adapter結構體定義,主要是通過i2c core層將i2c設備與i2c adapter關聯起來.

在kernel中提供了兩個adapter註冊介面,分別為i2c_add_adapter()和i2c_add_numbered_adapter().由於在系統中可能存在多個adapter,因為將每一條I2C匯流排對應一個編號,下文中稱為I2C匯流排號.這個匯流排號的PCI中的匯流排號不同.它和硬體無關,只是軟體上便於區分而已.

對於i2c_add_adapter()而言,它使用的是動態匯流排號,即由系統給其分析一個匯流排號,而i2c_add_numbered_adapter()則是自己指定匯流排號,如果這個匯流排號非法或者是被占用,就會註冊失敗.

 

2.接下來便來分析I2C匯流排驅動

參考 drivers/i2c/busses/i2c-s3c2410.c

先進入init入口函數,如下圖所示:

 

在init函數中,註冊了一個 “s3c2440-i2c”的platform_driver平臺驅動,我們來看看probe函數做了些什麼

 

3.進入s3c24xx_i2c_probe函數

struct i2c_adapter  adap;

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
    struct s3c24xx_i2c *i2c = &s3c24xx_i2c;
       ... ...

       /*獲取,使能I2C時鐘*/
       i2c->clk = clk_get(&pdev->dev, "i2c");               //獲取i2c時鐘
       clk_enable(i2c->clk);                                         //使能i2c時鐘

       ... ....
       /*獲取資源*/
       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
       i2c->regs = ioremap(res->start, (res->end-res->start)+1);

       ... ....

       /*設置i2c_adapter適配器結構體, 將i2c結構體設為adap的私有數據成員*/
    i2c->adap.algo_data = i2c;          //i2c_adapter適配器指向s3c24xx_i2c;
       i2c->adap.dev.parent = &pdev->dev;

 
    /* initialise the i2c controller */
       /*初始化2440的I2C相關的寄存器*/
       ret = s3c24xx_i2c_init(i2c);
       if (ret != 0)
              goto err_iomap;

       ... ...
       /*註冊中斷服務函數*/
       ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,pdev->name, i2c);
       ... ...

       /*註冊i2c_adapter適配器結構體*/
       ret = i2c_add_adapter(&i2c->adap);
       ... ...
}

其中i2c_adapter結構體是放在s3c24xx_i2c->adap下,如下圖所示:

 

 

4.接下來我們進入i2c_add_adapter()函數看看,到底如何註冊的

int i2c_add_adapter(struct i2c_adapter *adapter)
{
       int   id, res = 0;

retry:
       if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0) //調用idr_pre_get()為i2c_adapter預留記憶體空間
              return -ENOMEM;

       mutex_lock(&core_lists);

       /* "above" here means "above or equal to", sigh */
       res = idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id);
       //調用idr_get_new_above()將結構插入i2c_adapter_idr中,並將插入的位置賦給id,以後可以通過id在i2c_adapter_idr中找到相應的i2c_adapter結構體

       mutex_unlock(&core_lists);

       if (res < 0) {
              if (res == -EAGAIN)
                    goto retry;
              return res;
       }
       adapter->nr = id;
       return i2c_register_adapter(adapter);  //調用i2c_register_adapter()函數進一步來註冊.
}

其中i2c_register_adapter()函數代碼如下所示:

static int i2c_register_adapter(struct i2c_adapter *adap)
{
       struct list_head  *item;               //鏈表頭,用來存放i2c_driver結構體的表頭
       struct i2c_driver *driver;                     //i2c_driver,用來描述一個IIC設備驅動
        list_add_tail(&adap->list, &adapters);       //添加到內核的adapter鏈表中
        ... ...
       list_for_each(item,&drivers) {        //for迴圈,從drivers鏈表裡找到i2c_driver結構體的表頭
              driver = list_entry(item, struct i2c_driver, list); //通過list_head表頭,找到i2c_driver結構體
              if (driver->attach_adapter)  
                     /* We ignore the return code; if it fails, too bad */
                     driver->attach_adapter(adap);    
                
//調用i2c_driver的attach_adapter函數來看看,這個新註冊的設配器是否支持i2c_driver

}
}

在i2c_register_adapter()函數里主要執行以下幾步:

將adapter放入i2c_bus_type的adapter鏈表
將所有的i2c設備調出來,執行i2c_driver設備的attach_adapter函數來匹配

其中, i2c_driver結構體會在後面講述到

而i2c_adapter適配器結構體的成員結構,如下所示:

struct i2c_adapter {  

 struct module *owner;              //所屬模塊  
 unsigned int id;                //algorithm的類型,定義於i2c-id.h,  
 unsigned int class;      
 const struct i2c_algorithm *algo;     //匯流排通信方法結構體指針  
 void *algo_data;               //algorithm數據  
 struct rt_mutex bus_lock;        //控制併發訪問的自旋鎖  
 int timeout;     
 int retries;                //重試次數  
 struct device dev;             //適配器設備   
 int nr;                          //存放在i2c_adapter_idr里的位置號
 char name[48];              //適配器名稱  
 struct completion dev_released;    //用於同步  
 struct list_head userspace_clients;   //client鏈表頭  

};  

i2c_adapter表示物理上的一個i2C設備(適配器), 在i2c-s3c2410.c中,是存放在s3c24xx_i2c結構體下的(struct  i2c_adapter  adap)成員中

5.其中s3c24xx_i2c的結構體成員如下所示

static const struct i2c_algorithm s3c24xx_i2c_algorithm = {            
       .master_xfer          = s3c24xx_i2c_xfer,  //主機傳輸
       .functionality          = s3c24xx_i2c_func,                    
};

static struct s3c24xx_i2c s3c24xx_i2c = {
       .lock              = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
       .wait              = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
       .tx_setup = 50,                        //用來延時,等待SCL被釋放
       .adap             = {                                             // i2c_adapter適配器結構體
              .name                   = "s3c2410-i2c",
              .owner                  = THIS_MODULE,
              .algo                     = &s3c24xx_i2c_algorithm,           //存放i2c_algorithm演算法結構體
              .retries           = 2,                                       //重試次數
              .class                    = I2C_CLASS_HWMON,
       },
};

顯然這裡是直接設置了i2c_adapter結構體,所以在s3c24xx_i2c_probe ()函數中沒有分配i2c_adapter適配器結構體,

其中, i2c_adapter結構體的名稱等於"s3c2410-i2c",它的通信方式等於s3c24xx_i2c_algorithm,重試次數等於2

PS:如果缺少i2c_algorithm的i2c_adapter什麼也做不了,就只是個I2C設備,而沒有通信方式

s3c24xx_i2c_algorithm中的關鍵函數master_xfer()就是用於產生i2c訪問周期需要的start stop ack等信號

比如,在s3c24xx_i2c_algorithm中的關鍵函數master_xfer()里,調用了:

s3c24xx_i2c_xfer -> s3c24xx_i2c_doxfer()->s3c24xx_i2c_message_start()

來啟動傳輸message信息, 其中s3c24xx_i2c_message_start()函數代碼如下:

static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c, struct i2c_msg *msg)
{

 unsigned int addr = (msg->addr & 0x7f) << 1;              //IIC從設備地址的最低位為讀寫標誌位
       ... ...

       stat = 0;
       stat |=  S3C2410_IICSTAT_TXRXEN;     //設置標誌位啟動IIC收發使能

       if (msg->flags & I2C_M_RD) {                     //判斷是讀,還是寫
              stat |= S3C2410_IICSTAT_MASTER_RX;       
              addr |= 1;                                          //設置從IIC設備地址為讀標誌
       } else
              stat |= S3C2410_IICSTAT_MASTER_TX;

       s3c24xx_i2c_enable_ack(i2c);                //使能ACK信號

    iiccon = readl(i2c->regs + S3C2410_IICCON);    //讀出IICCON寄存器

       writel(stat, i2c->regs + S3C2410_IICSTAT);   //寫入IICSTAT寄存器,使能IIC的讀或寫標誌

       dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);

       writeb(addr, i2c->regs + S3C2410_IICDS);  //將IIC從設備地址寫入IICDS寄存器

       /* delay here to ensure the data byte has gotten onto the bus
        * before the transaction is started */

       ndelay(i2c->tx_setup);         //延時,等待SCL被釋放,下麵便可以發送起始信號+IIC設備地址值


       dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);
       writel(iiccon, i2c->regs + S3C2410_IICCON);            

       stat |=  S3C2410_IICSTAT_START;              
       writel(stat, i2c->regs + S3C2410_IICSTAT); 
            //設置IICSTAT寄存器的bit5=1,開始發送起始信號+IIC從設備地址值,並回應ACK
}

通過上面的代碼和註釋,發現主要是寫入IIC從設備地址,然後發送起始信號+IIC從設備地址值,並回應ACK

顯然IIC匯流排驅動i2c-s3c2410.c,主要設置適配器adapter,裡面幫我們做好了IIC通信的架構,就是不知道發什麼內容

我們進入driver/i2c/chips中,看看eeprom設備驅動是如何寫的

參考: driver/i2c/chips/eeprom.c

6.還是首先來看它的init入口函數:

 

其中struct  i2c_driver  eeprom_driver的成員如下:

static struct i2c_driver eeprom_driver = {
       .driver = {
              .name     = "eeprom",                        //名稱
        },
       .id           = I2C_DRIVERID_EEPROM,           //IIC設備標識ID
       .attach_adapter     = eeprom_attach_adapter,  //用來與匯流排驅動的適配器匹配,匹配成功添加到適配器adapter中
       .detach_client = eeprom_detach_client,      //與匯流排驅動的適配器解綁,分離這個IIC從設備
};

如下圖所示, eeprom_driver結構體的ID成員在i2c-id.h中,裡面還定義了大部分常用I2C設備驅動的設備ID

 

顯然,在init函數中通過i2c_add_driver()註冊i2c_driver結構體,然後通過i2c_driver ->attach_adapter來匹配內核中的各個匯流排驅動的適配器, 發送這個設備地址,若有ACK響應,表示匹配成功

7.接下來,我們進入i2c_add_driver()來看看是不是這樣的

int i2c_add_driver(struct module *owner, struct i2c_driver *driver)
{
       driver->driver.owner = owner;
       driver->driver.bus = &i2c_bus_type;    //將i2c_driver放在i2c_bus_type鏈表中   

       res = driver_register(&driver->driver); //註冊一個i2c_driver
       ... ...

       if (driver->attach_adapter) {
              struct i2c_adapter *adapter;                     //定義一個i2c_adapter適配器
          list_for_each_entry(adapter, &adapters, list)  //for迴圈提取出adapters鏈表中所有的i2c_adapter適配器,放入到adapter結構體中
      {
          driver->attach_adapter(adapter); //來匹配取出來的i2c_adapter適配器
          }
  }
      ... ...
return 0;
}

在i2c_add_driver ()函數里主要執行以下幾步:

放入到i2c_bus_type鏈表

取出adapters鏈表中所有的i2c_adapter,然後執行i2c_driver->attach_adapter()


所以i2c_adapter適配器和i2c_driver設備驅動註冊框架如下所示:

 

 這裡調用了i2c_driver ->attach_adapter(adapter),我們看看裡面是不是通過發送IIC設備地址,等待ACK響應來匹配的

8.以struct i2c_driver eeprom_driver 為例,進入i2c_driver ->eeprom_attach_adapter()函數

 

如下圖所示,裡面調用了i2c_probe(adapter, &addr_data, eeprom_detect)函數

 

上圖的第1個參數就是i2c_adapter適配器,第2個參數addr_data變數,裡面存放了IIC設備地址的信息,第3個參數eeprom_detect就是具體的設備探測回調函數i2c_probe()函數,會通過adapter適配器發送IIC設備地址addr_data,如果收到ACK信號,就調用eeprom_detect()回調函數來註冊i2c_client結構體,該結構體對應真實的物理從設備,而i2c_driver對應的是設備驅動,也就是說,只有當適配器支持這個設備驅動,才會註冊i2c_client從設備,後面會講這個回調函數如何註冊i2c_client

而在i2c_driver ->detach_client()中,則註銷i2c_client結構體

其中addr_data變數是struct i2c_client_address_data結構體,它的成員如下所示:

struct i2c_client_address_data {
       unsigned short *normal_i2c;     //存放正常的設備高7位地址數據
       unsigned short *probe;          //存放不受*ignore影響的高7位設備地址數據
       unsigned short *ignore;         //存放*ignore的高7位設備地址數據
       unsigned short **forces;        //forces表示適配器匹配不了該設備,也要將其放入適配器中

};

當上面結構體的數組成員以I2C_CLIENT_END結尾,則表示地址已結束,比如at24c02設備為例,看這個結構體如何定義的:

#define  AT24C02_ADDR           (0xA0>>1)           //AT24C02地址

static unsigned short  ignore[] = { I2C_CLIENT_END };
static unsigned short  normal_addr[] = { AT24C02_ADDR, I2C_CLIENT_END };
static unsigned short   force_addr[] = {ANY_I2C_BUS, AT24C02_ADDR ,2C_CLIENT_END};
static unsigned short   * forces[] = {force_addr, NULL};
            //ANY_I2C_BUS:表示支持所有適配器匯流排,若填指定的適配器匯流排ID,則表示該設備只支持指定的那個適配器 static struct i2c_client_address_data addr_data = { .normal_i2c = normal_addr, //存放at24c02地址 .probe = ignore, //表示無地址 .ignore = ignore, //表示無地址 . forces = forces, //存放強制的at24c02地址,表示強制支持 };

一般而言,都不會設置.forces成員,這裡只是打個比方

8.1接下來繼續進入i2c_probe()函數繼續分析,如下所示:

int i2c_probe(struct i2c_adapter *adapter,struct i2c_client_address_data *address_data,int (*found_proc) (struct i2c_adapter *, int, int))
{
       ... ...
       err = i2c_probe_address(adapter,forces[kind][i + 1],kind, found_proc);
}

裡面調用了i2c_probe_address()函數,從名稱上來看,顯然它就是用來發送起始信號+設備地址,來探測IIC設備地址用的

8.2進入i2c_probe_address()函數:

static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,int (*found_proc) (struct i2c_adapter *, int, int))
{

       /*判斷設備地址是否有效,addr里存放的是設備地址前7位,比如AT24C02=0xA0,那麼addr=0x50*/
       if (addr < 0x03 || addr > 0x77) {
              dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",addr);    //列印地址無效,並退出
              return -EINVAL;
       }

       /*查找鏈表中其它IIC設備的設備地址,若這個設備地址已經被使用,則return*/
       if (i2c_check_addr(adapter, addr))
              return 0; 

       if (kind < 0) {
              if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL) < 0)      //進入I2C傳輸函數
         return 0;
       ... ...
}

 

8.3 其中i2c_smbus_xfer()傳輸函數如下:

s32 i2c_smbus_xfer(struct i2c_adapter * adapter, u16 addr, unsigned short flags,char read_write, u8 command, int size,union i2c_smbus_data * data)
{
       s32 res;

       flags &= I2C_M_TEN | I2C_CLIENT_PEC;

       if (adapter->algo->smbus_xfer) {   //如果adapter適配器有smbus_xfer這個函數
              mutex_lock(&adapter->bus_lock);                            //加互斥鎖
              res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write,command,size,data);  
                                            //調用adapter適配器里的傳輸函數 mutex_unlock(&adapter->bus_lock); //解互斥鎖 } else //否則使用預設函數傳輸設備地址 res = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data); return res; }

看了上面代碼後,顯然我們的s3c2410-i2c適配器沒有algo->smbus_xfer函數,而是使用i2c_smbus_xfer_emulated()函數,如下圖所示:

 

PS:通常適配器都是不支持的,使用預設的i2c_smbus_xfer_emulated()函數

8.4 接下來看i2c_smbus_xfer_emulated()函數如何傳輸的:

static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr,unsigned short flags,char read_write, u8 command, int size, union i2c_smbus_data * data)
{
       unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];              //屬於 msg[0]的buf成員
       unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];              //屬於 msg[1]的buf成員
       int num = read_write == I2C_SMBUS_READ?2:1;              //如果為讀命令,就等於2,表示要執行兩次數據傳輸
       struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },
                    { addr, flags | I2C_M_RD, 0, msgbuf1 }};           //定義兩個i2c_msg結構體,


       msgbuf0[0] = command;             //IIC設備地址最低位為讀寫命令
       ... ...
if (i2c_transfer(adapter, msg, num) < 0) return -1; /*設置i2c_msg結構體成員*/ if (read_write == I2C_SMBUS_READ) switch(size) { ... ... case I2C_SMBUS_BYTE_DATA: //如果是讀位元組 if (read_write == I2C_SMBUS_READ) msg[1].len = 1; else { msg[0].len = 2; msgbuf0[1] = data->byte; } break; ... ... } ... ... if (i2c_transfer(adapter, msg, num) < 0) //將 i2c_msg結構體的內容發送給I2C設備 return -1; ... ... }

其中i2c_msg結構體的結構,如下所示:

struct i2c_msg {
       __u16 addr;          //I2C從機的設備地址
       __u16 flags;           //當flags=0表示寫, flags= I2C_M_RD表示讀
       __u16 len;              //傳輸的數據長度,等於buf數組裡的位元組數
       __u8 *buf;              //存放數據的數組
};

上面代碼中之所以讀操作需要兩個i2c_msg,寫操作需要一個i2c_msg,是因為讀IIC設備是兩個流程

在上一節IIC介面下的24C02 驅動分析: http://www.cnblogs.com/lifexy/p/7793686.html里就已經分析到了,

只要發送一個S起始信號則就是一個i2c_msg,如下兩個讀寫操作圖所示:

 

而在i2c_transfer()函數中,最終又是調用了之前分析的i2c_adapter->algo->master_xfer()發送函數,如下圖所示:

 

其中i2c_transfer()的參數*adap表示通過哪個適配器傳輸出去,msgs表示I2C消息,num表示msgs的數目

內核每發送一個Msg都會先發出S開始信號和設備地址.直到所有Msg傳輸完畢,最後發出P停止信號。

i2c_transfer()返回值為正數,表示已經傳輸正數個數據,當返回負數,說明I2C傳輸出錯

 

8.5 所以在i2c_driver ->attach_adapter(adapter)函數里主要執行以下幾步:

1) 調用 i2c_probe(adap, i2c_client_address_data設備地址結構體, 回調函數);

2) 將要發的設備地址結構體打包成i2c_msg,

3) 然後執行i2c_transfer()來調用i2c_adapter->algo->master_xfer()將i2c_msg發出去

4)若收到ACK回應,便進入回調函數,註冊i2c_client從設備,使該設備與適配器聯繫在一起

所以適配器和iic設備驅動最終註冊框架圖如下所示:

 

 

9.接下來便來分析回調函數如何註冊i2c_client從設備的

先來看看i2c_client結構體:

struct i2c_client {  

 unsigned short flags;//標誌    

 unsigned short addr; //該i2c從設備的設備地址,存放地址高7位  

 char name[I2C_NAME_SIZE];   //設備名字

 struct i2c_adapter *adapter;//依附的i2c_adapter,表示該IIC設備支持哪個適配器  

 struct i2c_driver *driver;//依附的i2c_driver ,表示該IIC從設備的驅動是哪個

 struct device dev;//設備結構體    

 int irq;//設備所使用的結構體    

 struct list_head detected;//鏈表頭  

 };  

還是以driver/i2c/chips/eeprom.c為例,如下圖所示:

 

9.1這裡的回調函數是eeprom_detect()函數,代碼如下所示:

static int eeprom_detect(struct i2c_adapter *adapter, int address, int kind)
{
struct i2c_client *new_client;        //定義一個i2c_client結構體局部變數

new_client =kzalloc(sizeof(struct i2c_client), GFP_KERNEL);      //分配i2c_client結構體為全局變數


/*設置i2c_client結構體*/
new_client->addr = address;               //設置設備地址
new_client->adapter = adapter;          //設置依附的i2c_adapter
new_client->driver = &eeprom_driver;  //設置依附的i2c_driver
new_client->flags = 0;                         //設置標誌位為初始值
strlcpy(new_client->name, "eeprom", I2C_NAME_SIZE);     //設置名字


 /*註冊i2c_client*/
 if ((err = i2c_attach_client(new_client)))
        goto exit_kfree;    //註冊失敗,便釋放i2c_client這個全局變數
 ... ...
exit_kfree: kfree(new_client); exit:
return err; }

當註冊了i2c_client從設備後,便可以使用i2c_transfer()來實現與設備傳輸數據了

 

10.接下來,我們便參考driver/i2c/chips/eeprom.c驅動,來寫出24C02驅動以及測試程式

驅動代碼步驟如下:

1.定義file_operations結構體 ,設置字元設備的讀寫函數(實現對24C02的讀寫操作)
//構造i2c_msg結構體, 使用i2c_transfer()來實現與設備傳輸數據

2.定義i2c_client_address_data結構體,裡面保存24C02的設備地址
3. 定義一個i2c_driver驅動結構體
       3.1 設置i2c_driver-> attach_adapter
     // 裡面直接調用 i2c_probe(adap, i2c_client_address_data結構體, 回調函數);

    3.2 設置i2c_driver-> detach_client
            //裡面卸載i2c_client, 字元設備

4.回調函數,裡面註冊i2c_client,字元設備( 字元設備用來實現讀寫24C02里的數據)
      4.1 分配並設置i2c_client

     4.2 使用i2c_attach_client()將i2c_client與適配器進行連接

    4.3 註冊字元設備

5. 寫init入口函數,exit出口函數
init: 使用i2c_add_driver()註冊i2c_driver
exit: 使用i2c_del_driver ()卸載i2c_driver

 

具體驅動代碼如下所示:

/*
 *  I2C-24C02
 */
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

static struct i2c_client *at24c02_client;         //從設備結構體
static struct class *at24c02_class;                //類結構體
static unsigned int at24c02_major;                 

 /*1.定義file_operations結構體 ,
  *  設置字元設備的讀寫函數(實現對24C02的讀寫操作)
  */
static ssize_t at24c02_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
       struct i2c_msg msg[2];
       u8 addr;
       u8 data;
       int ret;
       
        if(size!=1)
            return -EINVAL;

       copy_from_user(&addr,buf,1);                       //獲取讀地址

        msg[0].addr=at24c02_client->addr;
        msg[0].flags=0;                                            //寫標誌
        msg[0].len  =1;
        msg[0].buf  =&addr;                                     //寫入要讀的地址

        msg[1].addr=at24c02_client->addr;
        msg[1].flags=I2C_M_RD;                               //讀標誌
        msg[1].len  =1;
        msg[1].buf  =&data;                                     //讀出數據 

        ret=i2c_transfer(at24c

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • SQL Server 2008添加欄位成功顯示列名無效,SQL Server的intellisense完美解決問題! ...
  • 先簡單放一下用到的: MYSQL : 5.6版本 python3 powershell。 將csv導入mysql,主要就是用mysql的load data功能實現,其他前輩也都講得很清楚了,這裡簡單列一下語句(註意enclosed只有一個雙引號,此外‘忽略’那裡,rows表示的是列,lines表示的 ...
  • oracle資料庫管理系統常見的錯誤之一如下: Listener refused the connection with the following error:ORA-12519, TNS:no appropriate service handler foundThe Connection des ...
  • 一、Oracle資料庫操作 1、創建資料庫 create database databasename 2、刪除資料庫 drop database dbname 3、備份資料庫 完全備份 exp demo/demo@orcl buffer=1024 file=d:\back.dmp full=y de ...
  • SQL函數 coalesce 功能: 返回參數中第一個非null的值。 語法: coalesce(參數1,參數2,參數3,...);返回第一個非null的值。 一般情況下會與Nullif()函數一起使用。 ...
  • 1、獲取信息 2、篩選信息 3、整理數據 例如用Excel整理記憶體使用情況,這裡把獲取的時間和記憶體信息放在Excel內部,並把記憶體列用Excel分列,用時間和使用的記憶體大小列可以製作出一張記憶體使用趨勢圖;同理也可以製作CPU、cached及各個微服務的CPU和記憶體趨勢圖。 ...
  • Shell按行讀取文件的方法有很多,下麵寫出三種方法: 寫法一: 寫法二: 寫法三: 註:for迴圈讀行操作,與while讀行的區別 while是完全按行讀取,不管行內有多少段文字; for是按行讀取,如果行內文字有空格,則分開讀取,即一次讀取一個字元串。 ...
  • 1》什麼是Quota 在Linux中,由於是多用戶,多任務的環境,所以會有多用戶共同使用一個硬碟空間的情況發生,如果其中有少數幾個用戶大量占用掉了硬碟空間的話,那肯定影響其他用戶的使 用許可權,因此管理員應該適當限制硬碟的空間給用戶,以妥善分配系統資源,使磁碟的容量使用較為公平;舉例來說,我們用戶的默 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...