天天看點

I2C總線架構 之 裝置驅動引言流程解析總結

引言

I2C裝置驅動是I2C架構中最接近應用層的,其上接應用層,下接I2C核心。也是驅動開發人員需要實作的代碼,在此驅動中我們隻需負責以下步驟(以ap3216c為例):

a. 新增硬體資訊(裝置樹)

b. 搭建驅動架構

c. 建構i2c_driver,并注冊到linux i2c中

d. 注冊字元裝置

e. 向應用層提供i2c裝置操作接口

f. 登出i2c裝置

本篇文章會按照以上六個階段展開解析。

流程解析

a. 新增硬體資訊裝置樹(裝置樹)

首先觀察硬體i2c裝置挂載到哪個i2c總線上,然後在裝置樹檔案找到該總線的裝置節點,在節點下建立子節點描述i2c裝置硬體資訊即可。

&i2c1 {
  clock-frequency = <100000>;
  pinctrl-names = "default";
  pinctrl-0 = <&pinctrl_i2c1>;
  status = "okay";

  ap3216c@1e {
    compatible = "100ask,ap3216c";
    reg = <0x1e>;
  };
};           

複制

b. 搭建驅動架構

所謂搭建驅動架構,無非就是字元驅動将驅動入口、出口、以及對應用層的接口實作。與其他字元驅動的搭建是一樣的。

c. 建構i2c_driver,并注冊到linux

首先先看需要建構的i2c_driver結構體原型:

struct i2c_driver {
 unsigned int class;

 int (*attach_adapter)(struct i2c_adapter *) __deprecated;
 int (*probe)(struct i2c_client *, const struct i2c_device_id *);
 int (*remove)(struct i2c_client *);
 void (*shutdown)(struct i2c_client *);
 void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
        unsigned int data);

 int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

 struct device_driver driver;
 const struct i2c_device_id *id_table;

 int (*detect)(struct i2c_client *, struct i2c_board_info *);
 const unsigned short *address_list;
 struct list_head clients;
};           

複制

注: i2c_driver類似于platform_driver,在i2c_driver注冊到核心且名稱與裝置樹比對一緻就會進入probe中,在要解除安裝該驅動時會進入remove中。是以要填充i2c_driver的入口函數probe、出口函數(remove)和用于比對的硬體資訊driver。

static struct i2c_driver ap3216c_device_driver = {
 .probe  = ap3216c_probe,
 .remove = ap3216c_remove,
 .driver = {
  .name = PLATFORM_NAME,
  .owner = THIS_MODULE,
  .of_match_table = ap3216c_table,
   },
 .id_table = ap3216c_id,
};           

複制

d. 注冊i2c裝置

static int __init ap3216c_init(void)
{
    int ret = 0;
    printk("%s:%d: Entry %s \r\n", __FILE__, __LINE__, __func__);

 ret = i2c_add_driver(&ap3216c_device_driver);
 
 return ret;
}           

複制

注冊i2c裝置很簡單,隻需要在初始化中調用Linux提供的宏i2c_add_driver 即可。但是i2c_add_driver具體如何實作,有必要了解一下:

首先,這個宏調用了i2c_register_driver:

#define i2c_add_driver(driver) \
 i2c_register_driver(THIS_MODULE, driver)           

複制

再大緻了解i2c_register_driver函數流程:

--- drivers ---  i2c_core.c --- i2c_register_driver(         --- driver->driver.bus = &i2c_bus_type
                                    |    struct module *owner,       |- INIT_LIST_HEAD(&driver->clients)
                                    |    struct i2c_driver *driver)  |- driver_register(&driver->driver)
                                    |                                |- i2c_for_each_dev(driver, __process_new_driver)
                                    |- i2c_for_each_dev(                    --- bus_for_each_dev(&i2c_bus_type, NULL, data, fn)
                                    |    void *data,
                                    |    int (*fn)(struct device *, void *))
                                    |- __process_new_driver( --- if (dev->type != &i2c_adapter_type)
                                         struct device *dev,  |      return 0;
                                         void *data)          |- i2c_do_add_adapter(data, to_i2c_adapter(dev));           

複制

小結:

從上圖流程來看,在将i2c結構體注冊進核心時,調用了屬于i2c核心的i2c_register_driver。進入i2c核心中,會将i2c結構體添加到i2c連結清單中,并實作i2c_client與i2c_driver的比對,比對成功會進入i2c_driver 結構體的probe函數中。(具體實作放在I2C核心文章分析)

e. 向應用層提供i2c裝置操作接口

成功進入probe函數後,就說明i2c驅動配置基本成功。接下來在probe中需要實作字元驅動的注冊,以及實作對外的讀寫接口。字元驅動的注冊代碼,與其他字元驅動是一緻的,浏覽代碼實作即可。主要分析對外接口的讀寫i2c裝置操作:

在單片機的程式中,實作對i2c裝置的讀寫,需要手動實作讀寫i2c寄存器,或者通過GPIO模拟i2c時序與i2c裝置通信。而在Linux中,如何與i2c裝置的具體通信已經被封裝成固定的API,在程式中填充這些API的資料參數調用即可,列舉讀寫單個位元組的實作:

static int ap3216c_read_regs(struct sap3216c_dev *dev, unsigned char reg, 
                                              void *val, int len)
{
    int ret;
    struct i2c_msg msg[2];
    struct i2c_client *client = (struct i2c_client *)dev->private_data;
    
    msg[0].addr = client->addr;
    msg[0].flags = 0;
    msg[0].buf = &reg;
    msg[0].len = 1;

    msg[1].addr = client->addr;
    msg[1].flags = I2C_M_RD;
    msg[1].buf = val;
    msg[1].len = len;

    ret = i2c_transfer(client->adapter, msg, 2);
    if (ret ==2 ) {
        ret = 0;    
    } else {
        printk("%s %d: i2c transfer error!\n ", __func__, __LINE__);
        ret = -EREMOTEIO;    
    }    

    return ret;
}

static int ap3216c_write_regs(struct sap3216c_dev *dev, unsigned char reg, 
                                   unsigned char *buf, unsigned char len) 
{
    unsigned char temp_buf[256];
    struct i2c_msg msg;
    struct i2c_client *client = (struct i2c_client *)dev->private_data;

    temp_buf[0] = reg;
    memcpy(&temp_buf[1], buf, len);
    
    msg.addr = client->addr;
    msg.flags = 0;
    msg.buf = temp_buf;
    msg.len = len+1;

    return i2c_transfer(client->adapter, &msg, 1);
}           

複制

小結:

由以上代碼發現,在與i2c裝置的讀寫通信中,都是通過調用i2c_transfer實作。

i2c_transfer三個參數意義 :

(1) client->adapter: 該i2c裝置連接配接的i2c總線擴充卡;

(2) msg:需要發送的資料;

(3) 1:需要發送的msg個數。

通過以上讀寫的實作,與上一篇文章 《I2C總線架構 之 I2C協定》 讀寫時序是對應的:

(1) 寫操作隻需要一個msg結構體:

  • 起始位 + 寫操作(msg[0]) + 停止位。

(2) 讀操作需要兩個msg結構體 :

  • 起始位+ 寫操作(寫入位址 msg[0])+ 起始位 + 讀操作(存入msg[1]) + 停止位。

f. 登出i2c裝置

登出操作:在字元驅動出口函數中,解除安裝掉注冊的i2c裝置。這裡調用i2c_del_driver即可實作,與i2c_add_driver是對應的。

static void __exit ap3216c_exit(void)
{
 printk("%s:%d: Entry %s \r\n", __FILE__, __LINE__, __func__);

 i2c_del_driver(&ap3216c_device_driver);
}           

複制

總結

到這裡本篇文章對i2c裝置驅動的具體分析基本完成。本篇以ap3216c光敏傳感器代碼為例,從入口到出口代碼走向展開分析。通讀文章大緻了解,會發現本篇i2c裝置驅動與虛拟總線platform架構類似。不同的是platform是軟體實作的虛拟總線,在soc上并不存在;而i2c總線,在soc上是實際存在的。相同的是兩者實作将驅動分層為硬體參數和驅動抽象,在注冊時周遊比對,然後進入正文probe中!

由于Linux内部的實作較為複雜,本篇主要以裝置驅動的角度來分析整個驅動的代碼走向,涉及到内部API的實作,本篇隻大概介紹其功能,剩餘部分會放在i2c核心繼續分析。

參考:

《Linux裝置驅動開發詳解》

《【正點原子】I.MX6U嵌入式Linux驅動開發指南V1.4》

https://blog.csdn.net/Egean/article/details/81085077