Linux I2C體系

I2C總線僅僅使用 SCL 、 SDA 兩根信號線就實現(xiàn)了設(shè)備之間的數(shù)據(jù)交互。

由于各種SOC都有自己的I2C總線,為了上層能統(tǒng)一接口,采用這種三層I2C架構(gòu).

I2C總線驅(qū)動主要實現(xiàn)了適用于特定I2C控制器的總線讀寫方法,并注冊到Linux內(nèi)核的I2C架構(gòu),I2C外設(shè)就可以通過I2C架構(gòu)完成設(shè)備和總線的適配。但是總線驅(qū)動本身并不會進(jìn)行任何的通訊,它只是提供通訊的實現(xiàn),等待設(shè)備驅(qū)動來調(diào)用其函數(shù)。
I2C Core的管理正好屏蔽了I2C總線驅(qū)動的差異,使得I2C設(shè)備驅(qū)動可以忽略各種總線控制器的不同,不用考慮其如何與硬件設(shè)備通訊的細(xì)節(jié)。

Linux的I2C構(gòu)架分為三個部分:

1)I2C core框架

提供了核心數(shù)據(jù)結(jié)構(gòu)的定義和相關(guān)接口函數(shù),用來實現(xiàn)I2C適配器
驅(qū)動和設(shè)備驅(qū)動的注冊、注銷管理,以及I2C通信方法上層的、與具體適配器無關(guān)的代碼,為系統(tǒng)中每個I2C總線增加相應(yīng)的讀寫方法。

kernel抽象出I2C bus(/sys/bus/i2c),用于掛載和I2C adapter通過I2C總線連接的各個I2C slave device。
I2C Bus 并不是通訊上的總線,而是linux系統(tǒng)為了管理設(shè)備和驅(qū)動而虛擬出來的,在I2C Bus用來掛載后面將會使用到的I2C 適配器(adapter)和I2C設(shè)備(client)

2) I2C總線驅(qū)動 (i2c_adapter)

定義描述具體I2C總線適配器的i2c_adapter數(shù)據(jù)結(jié)構(gòu)、實現(xiàn)在具體I2C適配器上的I2C總線通信方法,并由i2c_algorithm數(shù)據(jù)結(jié) 構(gòu)進(jìn)行描述。

封裝了 struct device ,因此它是作為一個設(shè)備注冊到內(nèi)核中去的(是注冊到i2c_bus_type里),此外非常重要的一個成員struct i2c_algorithm *algo ,這就是我們上邊提到的 i2c 控制器收發(fā)數(shù)據(jù)的方法。

經(jīng)過I2C總線驅(qū)動的的代碼,可以為我們控制I2C產(chǎn)生開始位、停止位、讀寫周期以及從設(shè)備的讀寫、產(chǎn)生ACK等。

I2C總線驅(qū)動具體實現(xiàn)在/drivers/i2c目錄下busses文件夾。

例如:
Linux I2C GPIO總線驅(qū)動為i2c_gpio.c.

全志 drivers/i2c/busses/i2c-sunxi.c

I2C總線算法在/drivers/i2c目錄下algos文件夾。

例如:Linux I2C GPIO總線驅(qū)動算法實現(xiàn)在i2c_algo_bit.c.

針對不同類型的I2C控制器,實現(xiàn)對I2C總線訪問的具體方法.(各種SOC不一樣)

3) I2C 設(shè)備驅(qū)動(I2C client driver)

是對具體I2C硬件驅(qū)動的實現(xiàn)。I2C 設(shè)備驅(qū)動通過I2C適配器與CPU通信。

其中主要包含i2c_driver和i2c_client數(shù)據(jù)結(jié)構(gòu)。
i2c_driver結(jié)構(gòu)對應(yīng)一套具體的驅(qū)動 方法,例如:probe、remove、suspend等,需要自己申明。

i2c_client數(shù)據(jù)結(jié)構(gòu)由內(nèi)核根據(jù)具體的設(shè)備注冊信息自動生成,設(shè)備驅(qū)動 根據(jù)硬件具體情況填充。

I2C 設(shè)備驅(qū)動具體實現(xiàn)放在在/drivers/i2c目錄下chips文件夾。

clipboard.png

重要的結(jié)構(gòu)體

i2c_driver
 struct i2c_driver {
 unsigned int class;
 int (*attach_adapter)(struct i2c_adapter *);//依附i2c_adapter函數(shù)指針
 int (*detach_adapter)(struct i2c_adapter *);//脫離i2c_adapter函數(shù)指針
 int (*probe)(struct i2c_client *, const struct i2c_device_id *);
 int (*remove)(struct i2c_client *);
 void (*shutdown)(struct i2c_client *);
 int (*suspend)(struct i2c_client *, pm_message_t mesg);
 int (*resume)(struct i2c_client *);
 void (*alert)(struct i2c_client *, unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void*arg);//命令列表
 struct device_driver driver;
13 const struct i2c_device_id *id_table;//該驅(qū)動所支持的設(shè)備ID表
 int (*detect)(struct i2c_client *, struct i2c_board_info *);
 const unsigned short *address_list;
 struct list_head clients;
 };
i2c_client
 struct i2c_client {
  unsigned short flags;//標(biāo)志  
  unsigned short addr; //低7位為芯片地址  
 char name[I2C_NAME_SIZE];//設(shè)備名稱
  struct i2c_adapter *adapter;//依附的i2c_adapter
  struct i2c_driver *driver;//依附的i2c_driver 
  struct device dev;//設(shè)備結(jié)構(gòu)體  
  int irq;//設(shè)備所使用的結(jié)構(gòu)體  
  struct list_head detected;//鏈表頭
 };
 struct i2c_adapter {
  struct module *owner;//所屬模塊
  unsigned int id;//algorithm的類型,定義于i2c-id.h,
  unsigned int class;    
  const struct i2c_algorithm *algo; //總線通信方法結(jié)構(gòu)體指針
  void *algo_data;//algorithm數(shù)據(jù)
  struct rt_mutex bus_lock;//控制并發(fā)訪問的自旋鎖
  int timeout;   
  int retries;//重試次數(shù)
  struct device dev; //適配器設(shè)備 
  int nr;
  char name[48];//適配器名稱
  struct completion dev_released;//用于同步
  struct list_head userspace_clients;//client鏈表頭
15 };
i2c_adapter與i2c_algorithm

i2c_adapter對應(yīng)與物理上的一個適配器,而i2c_algorithm對應(yīng)一套通信方法,一個i2c適配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下層與硬件相關(guān)的代碼提供)通信函數(shù)來控制適配器上產(chǎn)生特定的訪問周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指針。
  i2c_algorithm中的關(guān)鍵函數(shù)master_xfer()用于產(chǎn)生i2c訪問周期需要的start stop ack信號,以i2c_msg(即i2c消息)為單位發(fā)送和接收通信數(shù)據(jù)。
  i2c_msg也非常關(guān)鍵,調(diào)用驅(qū)動中的發(fā)送接收函數(shù)需要填充該結(jié)構(gòu)體

i2c_driver和i2c_client

i2c_driver對應(yīng)一套驅(qū)動方法,其主要函數(shù)是attach_adapter()和detach_client()
  i2c_client對應(yīng)真實的i2c物理設(shè)備device,每個i2c設(shè)備都需要一個i2c_client來描述
  i2c_driver與i2c_client的關(guān)系是一對多。一個i2c_driver上可以支持多個同等類型的i2c_client.

i2c_adapter和i2c_client

i2c_adapter和i2c_client的關(guān)系與i2c硬件體系中適配器和設(shè)備的關(guān)系一致,即i2c_client依附于i2c_adapter,由于一個適配器上可以連接多個i2c設(shè)備,所以i2c_adapter中包含依附于它的i2c_client的鏈表。

    struct list_head userspace_clients;//client鏈表頭

重要的接口函數(shù)

注冊一個驅(qū)動

【函數(shù)原型】:i2c_add_driver

#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)

【功能描述】:注冊一個I2C設(shè)備驅(qū)動。從代碼可以看帶i2c_add_driver()是一個宏,由函數(shù)i2c_register_driver()實現(xiàn)。
【參數(shù)說明】:driver,i2c_driver類型的指針,其中包含了I2C設(shè)備的名稱、probe、detect等接口信息。

注冊一個設(shè)備

【函數(shù)原型】:i2c_register_board_info

int i2c_register_board_info(int busnum, struct i2c_board_info const *info,
unsigned n)

【功能描述】:向某個I2C總線注冊I2C設(shè)備信息,I2C子系統(tǒng)通過此接口保存I2C總線和I2C設(shè)備的適配關(guān)系。
busnum 通過總線號指定這個(些)設(shè)備屬于哪個總線
info i2c設(shè)備的數(shù)組集合i2c_board_info格式

i2c_register_board_info具體實現(xiàn): 相關(guān)信息放到鏈表中就算完事

int __init
i2c_register_board_info(int busnum,
    struct i2c_board_info const *info, unsigned len)
{
    int status;
 
    down_write(&__i2c_board_lock);  //i2c設(shè)備信息讀寫鎖,鎖寫操作,其他只讀
 
    /* dynamic bus numbers will be assigned after the last static one */
    if (busnum >= __i2c_first_dynamic_bus_num)  //與動態(tài)分配的總線號相關(guān),動態(tài)分配的總線號應(yīng)該是從已經(jīng)現(xiàn)有最大總線號基礎(chǔ)上+1的,這樣能夠保證動態(tài)分配出的總線號與板級總線號不會產(chǎn)生沖突
        __i2c_first_dynamic_bus_num = busnum + 1;
 
    for (status = 0; len; len--, info++) {  //處理info數(shù)組中每個成員
        struct i2c_devinfo    *devinfo;
 
        devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
        if (!devinfo) {
            pr_debug("i2c-core: can't register boardinfo!\n");
            status = -ENOMEM;
            break;
        }
 
        devinfo->busnum = busnum;  //組裝總線號
        devinfo->board_info = *info;  //組裝設(shè)備信息
        list_add_tail(&devinfo->list, &__i2c_board_list);  //加入到__i2c_board_list鏈表中(尾部)
    }
 
    up_write(&__i2c_board_lock);  //釋放讀鎖,其他可讀可寫
 
    return status;
}

調(diào)用i2c_register_board_info的I2C設(shè)備注冊過程應(yīng)該在板級代碼初始化期間,也就是arch_initcall前后的時間,
在I2C適配器驅(qū)動注冊前完成。

如果在I2C適配器注冊完后還想要添加I2C設(shè)備的話,就要通過新方式?。磇2c_new_device)

【函數(shù)原型】: i2c_new_device

i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)

adap 此設(shè)備所依附的I2C適配器指針
info 此設(shè)備描述,i2c_board_info格式,bus_num成員是被忽略的

struct i2c_board_info info={
    .type = SENSOR=NAME,
    .addr = SENSOR_I2C_ADDR,
}
adapter = i2c_get_adapter(0);    //參數(shù)代表i2c num
client = i2c_new_device(adapter, &info);
數(shù)據(jù)傳輸

I2C設(shè)備驅(qū)動使用"struct i2c_msg"向I2C總線請求讀寫I/O。
一個i2c_msg中包含了一個I2C操作,通過調(diào)用i2c_transfer()接口觸發(fā)I2C總線的數(shù)據(jù)收發(fā)。

【函數(shù)原型】:i2c_transfer

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

【功能描述】:完成I2C總線和I2C設(shè)備之間的一定數(shù)目的I2C message交互。

【函數(shù)原型】:i2c_master_recv

int i2c_master_recv(const struct i2c_client *client, char *buf, int count)

【功能描述】:通過封裝i2c_transfer()完成一次I2c接收操作。

【函數(shù)原型】:i2c_master_send

int i2c_master_send(const struct i2c_client *client, const char *buf, int count)

【功能描述】:通過封裝i2c_transfer()完成一次I2c發(fā)送操作。

【函數(shù)原型】:i2c_smbus_read_byte

s32 i2c_smbus_read_byte(const struct i2c_client *client)

【功能描述】:從I2C總線讀取一個字節(jié)。(內(nèi)部是通過i2c_transfer()實現(xiàn),以下幾個接口同。)

【函數(shù)原型】:i2c_smbus_write_byte

s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value)

【功能描述】:從I2C總線寫入一個字節(jié)。

驅(qū)動代碼例子

設(shè)備

1)BSP文件中靜態(tài)聲明一個I2C設(shè)備

static struct i2c_board_info i2c_devices[] __initdata = {  
  
    {I2C_BOARD_INFO("24c02", 0x50), },  
  
     {}  
  
}; 

2)向總線注冊I2C設(shè)備信息:

i2c_register_board_info(0,i2c_devices,ARRAY_SIZE(i2c_devices));  
驅(qū)動

1)模塊初始化時添加/撤銷時刪除i2c_driver
module_init(mma7660_init); //模塊入口
module_exit(mma7660_exit); //模塊出口

2)
init中和驅(qū)動框架相關(guān)的就一句話ret = i2c_add_driver(&mma7660_driver)而這一句話表示向I2C總線注冊一個驅(qū)動,根據(jù)宏定義i2c_driver結(jié)構(gòu)并完成其相應(yīng)函數(shù):

static struct i2c_driver my_i2c_driver = {  
  .driver = {  
      .name = "i2c_demo",  
      .owner = THIS_MODULE,  
    },  
    .probe = my_i2c_probe,  
    .remove = my_i2c_remove,  
    .id_table = my_ids,  
 };  

3)使用/dev entry 訪問方法

register_chrdev(I2C_MAJOR,DEVICE_NAME,&i2c_fops);  
  
創(chuàng)建類class_create(THIS_MODULE, DEVICE_NAME);  
  
在/dev下創(chuàng)建設(shè)備節(jié)點  
  
device_create(my_dev_class, &client->dev,MKDEV(I2C_MAJOR, 0), NULL, DEVICE_NAME);  
I2c detect的方法

必須要定義address_list

static unsigned short s_Normal_I2c[] = {0x35, I2C_CLIENT_END};    // 芯片地址

static const struct i2c_device_id i2c_detect_id[] = {
    {LMX_I2C_DETECT_DEVICE_NAME, 0},
    {}
};
static struct i2c_driver i2c_detect_driver = {
    .class = I2C_CLASS_HWMON,
    .driver = {
            .owner = THIS_MODULE,
            .name = LMX_I2C_DETECT_DEVICE_NAME,
            },
    .detect = i2c_detect_Detect,   // 會往所有的 I2C 控制器上尋指定的 I2C 設(shè)備地址的 I2C 設(shè)備,若有 ACK 回應(yīng)則會調(diào)用該函數(shù)
    .id_table = i2c_detect_id,
    .address_list = s_Normal_I2c,  // 地址列表
};

i2c_core.c 里面有一個 i2c_detect函數(shù)

if(driver->detect || !address_list)
return 0;
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 簡介 I2C驅(qū)動由I2C核心,I2C總線驅(qū)動和I2C設(shè)備驅(qū)動組成.I2C核心是I2C總線驅(qū)動和I2C設(shè)備驅(qū)動的中間...
    傀儡世界閱讀 1,216評論 0 1
  • Linux i2c system I2C總線是由PHILIPS公司開發(fā)的兩線式串行總線,每個連接到總線的器件都可以...
    Creator_Ly閱讀 2,099評論 0 8
  • 本文開啟 linux 內(nèi)核 V4L2 框架部分的學(xué)習(xí)之旅,本文僅先對 V4L2 的框架做一個綜述性的概括介紹,然后...
    yellowmax閱讀 7,855評論 0 13
  • 宋寶華 Barry Song 21cnbao@gmail.comhttp://blog.csdn.net/21cn...
    JosephDHF閱讀 1,442評論 0 1
  • 1-50的平方3條地鐵線路1副撲克牌順序phpstorm快捷鍵vim快捷鍵thinkphp5文檔 2017-08-...
    chaosii閱讀 495評論 0 0

友情鏈接更多精彩內(nèi)容