Linux I2C驅動架構

博客說明

撰寫日期 2019.11.20
完稿日期 2019.11.21
最近維護 暫無
本文作者 multimicro
聯繫方式 [email protected]
資料鏈接 本文無附件資料
GitHub https://github.com/wifialan/drivers
原文鏈接 https://blog.csdn.net/multimicro/article/details/103164746

開發環境

環境說明 詳細信息 備註信息
操作系統 Ubunut 18.04
開發板 JZ2440-V3
u-boot uboot-2012.04.01
busybox busybox-1.22.1
u-boot和busybox編譯器 arm-linux-gcc (4.4.3)
Linux內核 linux-4.19-rc3
Linux內核編譯器 arm-linux-gnueabi-gcc (4.9.4)

1. Linux I2C 體系結構

i2c的編程應用流程請參考我的上一篇文章Linux 設備樹學習——基於i2c總線分析,這篇文章我是基於i2c總線而寫的,裏面包含註冊和匹配方式的介紹,和相應的模板。

參考宋寶華 《Linux設備驅動開發詳解》 第15章內容。


Linux的I2C體系結構分爲3個組成部分:

  1. I2C核心
  2. I2C總線驅動(適配器驅動)
  3. I2C設備驅動

1.1 Linux I2C核心

借鑑 宋寶華 《Linux設備驅動開發詳解》 第15章第2節內容。

I2C核心(drivers/i2c/i2c-core-base.c)中提供了一組不依賴於硬件平臺的接口函數,這個文件一般不需要被工程師修改,但是理解其中的主要函數非常關鍵,因爲I2C總線驅動和設備驅動之間以I2C核心作爲紐帶。I2C核心中的主要函數如下。

1.1.1 增加 / 刪除 i2c_adapter

int i2c_add_adapter(struct i2c_adapter *adap);
void i2c_del_adapter(struct i2c_adapter *adap);

在有設備樹版本中,內部都封裝了從設備樹中獲取i2c設備並註冊爲i2c client的方法,如下:

/i2c節點一般表示i2c控制器, 它會被轉換爲platform_device, 在內核中有對應的platform_driver;
   platform_driver的probe函數中會調用i2c_add_numbered_adapter:
   
      
   i2c_add_numbered_adapter// drivers/i2c/i2c-core-base.c
			 __i2c_add_numbered_adapter
           			 i2c_register_adapter
                			of_i2c_register_devices(adap);   // drivers/i2c/i2c-core-of.c
                   					 for_each_available_child_of_node(bus, node) {
                       						 client = of_i2c_register_device(adap, node);
                                        			client = i2c_new_device(adap, &info);   // 設備樹中的i2c子節點被轉換爲i2c_client
                    }

上述第一個函數i2c_add_numbered_adapter內容如下,通過Source Insight軟件,可以一步一步定位分析,可以很好的幫助理解設備樹彙總的i2c子設備節點是如何轉換爲i2c client的。

int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
	if (adap->nr == -1) /* -1 means dynamically assign bus id */
		return i2c_add_adapter(adap);

	return __i2c_add_numbered_adapter(adap);
}

1.1.2 增加 / 刪除 i2c_driver

int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *driver);
#define i2c_add_driver(driver)		\
				i2c_register_driver(THIS_MODULE, driver)

這些函數一般用在編寫i2c設備driver中,如
drivers/char/at24c256.c

static int __init at24_init(void)
{
    return i2c_add_driver(&at24c256_driver);
}

static void __exit at24_exit(void)
{
    i2c_del_driver(&at24c256_driver);
    printk(DRV_NAME "\tRemove i2c driver success\n");
}

module_init(at24_init);
module_exit(at24_exit);

1.1.3 I2C傳輸、發送和接收

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
int i2c_master_send(const struct i2c_client *client,  const char *buf, int count);
int i2c_master_recv(const struct i2c_client *client, char *buf, int count);

這三個函數的區別在於:
i2c_transfer一次可以傳輸多個i2c_msg
i2c_master_send 一次只能發送一個i2c_msg
i2c_master_recv 一次只能接收一個i2c_msg

在開發比較複雜的i2c時序時,採用最多的是i2c_transfer。參考 Code 1

Code 1

	int ret;
    struct i2c_msg msg[2];
    char buffer_data[100];

    memset(buffer_data,0,sizeof(buffer_data));
    buffer_data[0] = (char)0x00;
    buffer_data[1] = (char)0x00;
    buffer_data[2] = (char)0xAA;
    buffer_data[3] = (char)0xAB;
    buffer_data[4] = (char)0xAC;
    
    /* 1st write data */
    msg[0].addr = at24_dev->client->addr | 0x01;			//這個是i2c從設備的地址
    msg[0].flags = at24_dev->client->flags & 0;					//這個是對i2c從設備的讀寫標誌位,和上面的addr組成i2c的第一個8bit數據
    msg[0].buf = &buffer_data[0];											//這個是i2c從設備地址後的數據,數組中,每一個數據都是一個8bit數據
    msg[0].len = 5;								//指定發送除地址位外的多少個數據,上述buffer_data中,前兩個是at24c256的從設備地址,後3個是寫入的數據
    /* 2nd write data */
    msg[1].addr = at24_dev->client->addr | 0x01;
    msg[1].flags = I2C_M_RD;
    msg[1].buf = &buffer_data[5];	//指定接受的數據存放位置,這裏從buffer_data中的第6個位置中開始存儲讀到的數據,
    msg[1].len = 3;									//指定讀的數據個數
    
    //開始i2c傳輸,發送成功後,會返回發送的次數,由最後一個參數爲2可以,需要開啓兩次傳輸,那麼發送成功後就會返回2
    ret = i2c_transfer(at24_dev->client->adapter, &msg[0], 2);	
    tmp = (ret == 2) ? msg[1].len : ret;

i2c_transfer發送失敗會返回特定的負數,詳情參考linux i2c 的通信函數i2c_transfer在什麼情況下出現錯誤

1.2 Linux I2C適配器驅動

1.2.1 I2C適配器驅動的註冊與註銷

借鑑 宋寶華 《Linux設備驅動開發詳解》 第15章第3節內容。

由於I2C總線控制器通常是在內存上的,所以它本身也連接在platform總線上,要通過platform_driver和platform_device的匹配來執行。因此儘管I2C適配器給別人提供了總線,它自己也被認爲是接在platform總線上的一個客戶。Linux的總線、設備和驅動模型實際上是一個樹形結構,每個節點雖然可能成爲別的總線控制器,但是自己也被認爲是從上一級總線枚舉出來的。

也就是說,I2C適配器的初始化,需要在platform_driverprobe()函數中完成。這部分大多由芯片廠商提供好了,自己不需要編寫。

1.2.2 I2C總線的通信方法

只有I2C適配器驅動是沒有靈魂的,需要給它注入一個通信算法i2c_algorithm,才能然I2C運作起來。

可以說I2C適配器驅動是來配置I2C硬件,而i2c_algorithm是來操作I2C硬件產生對應的I2C時序波形,完成基於I2C總線的數據傳輸。

我在開發時,沒有自己編寫通信方法,但是也可以實現通信,猜測這個也是芯片廠商給寫好的,直接調用接口

1.3 Linux I2C設備驅動

這方面主要是完成I2C設備驅動模塊的加載與卸載,並實現數據傳輸,模塊的加載與卸載就是經常編寫的驅動開發流程,詳情參考附錄中github代碼。

在編寫i2c設備driver中的i2c設備讀寫函數中,如下,我將AT24C256這個i2c設備註冊成了字符設備,調用了file_operations結構體裏面的讀寫方法。參考 Code 2Code 3

drivers/char/at24c256.c
Code 2

static ssize_t at24_write( struct file *filp, const char __user *buffer, size_t size, loff_t *f_pos )
{
    int ret,tmp;
    char i;
    struct i2c_msg msg;
    char buffer_data[100];

    copy_from_user(buffer_data, buffer, 10);

    memset(buffer_data,0,sizeof(buffer_data));
    buffer_data[0] = (char)0x00;
    buffer_data[1] = (char)0x00;

    for (i = 0; i < 64; ++i)
    {
        buffer_data[i+2] = i;
    }

    msg.addr = at24_dev->client->addr | 0x01;			//這個是i2c從設備的地址
    msg.flags = at24_dev->client->flags & 0;
    msg.buf = &buffer_data[0];										   //這個是i2c從設備地址後的數據
    msg.len = 66;

    ret = i2c_transfer(at24_dev->client->adapter,&msg,1);
    tmp = (ret == 1) ? msg.len : ret;

    printk("i2c code: %d  return code: %d addr: 0x%02x%02x ",\
        ret,tmp,buffer_data[0],buffer_data[1]);

    for (i = 0; i < 64; ++i)
    {
        printk("Write Data: 0x%02x",buffer_data[i+2]);
    }
    
    return 0;
}

Code 3

static ssize_t at24_read( struct file *filp, const char __user *buffer, size_t size, loff_t *f_pos )
{
    int ret,tmp;
    unsigned long i;
    struct i2c_msg msg[3];
    char buffer_data[100];

    memset(buffer_data,0,sizeof(buffer_data));
    
    msg[0].addr = at24_dev->client->addr | 0x01;
    msg[0].flags = I2C_M_RD;
    msg[0].buf = &buffer_data[2];
    msg[0].len = 64;

    ret = i2c_transfer(at24_dev->client->adapter, &msg[0], 1);
    tmp = (ret == 1) ? msg[0].len : ret;

    printk("i2c code: %d  return code: %d addr: 0x%02x%02x ",\
        ret,tmp,buffer_data[0],buffer_data[1]);

    for (i = 0; i < 64; ++i)
    {
        printk("Read Data: 0x%02x",buffer_data[i+2]);
    }

    return 0;
}

附錄

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