
I2C核心(i2c_core)
I2C核心維護了i2c_bus結構體,提供了I2C總線驅動和設備驅動的注冊、注銷方法,維護了I2C總線的驅動、設備鏈表,實現了設備、驅動的匹配探測。此部分代碼由Linux內核提供。
I2C總線驅動
I2C總線驅動維護了I2C適配器數據結構(i2c_adapter)和適配器的通信方法數據結構(i2c_algorithm)。所以I2C總線驅動可控制I2C適配器產生start、stop、ACK等。此部分代碼由具體的芯片廠商提供,比如Samsung、高通。
I2C設備驅動
I2C設備驅動主要維護兩個結構體:i2c_driver和i2c_client,實現和用戶交互的文件操作集合fops、cdev等。此部分代碼就是驅動開發者需要完成的。
第二:Linux內核中描述I2C的四個核心結構體
1)i2c_client—掛在I2C總線上的I2C從設備
每一個i2c從設備都需要用一個i2c_client結構體來描述,i2c_client對應真實的i2c物理設備device。
struct i2c_client {
unsigned short flags; //標志位 (讀寫)
unsigned short addr; //7位的設備地址(低7位)
char name[I2C_NAME_SIZE]; //設備的名字,用來和i2c_driver匹配
struct i2c_adapter *adapter; //依附的適配器(adapter),適配器指明所屬的總線(i2c0/1/2_bus)
struct device dev; //繼承的設備結構體
int irq; //設備申請的中斷號
struct list_head detected; //已經被發現的設備鏈表
};
但是i2c_client不是我們自己寫程序去創建的,而是通過以下常用的方式自動創建的:
方法一: 分配、設置、注冊i2c_board_info
方法二: 獲取adapter調用i2c_new_device
方法三: 通過設備樹(devicetree)創建
方法1和方法2通過platform創建,這兩種方法在內核3.0版本以前使用所以在這不詳細介紹;**方法3是最新的方法,**3.0版本之后的內核都是通過這種方式創建的,文章后面的案例就按方法3。
2)i2c_adapter
I2C總線適配器,即soc中的I2C總線控制器,硬件上每一對I2C總線都對應一個適配器來控制它。在Linux內核代碼中,每一個adapter提供了一個描述它的結構(struct i2c_adapter),再通過i2c core層將i2c設備與i2c adapter關聯起來。主要用來完成i2c總線控制器相關的數據通信,此結構體在芯片廠商提供的代碼中維護。
struct i2c_adapter {
struct module *owner;
unsigned int class; //允許匹配的設備的類型
const struct i2c_algorithm *algo; //指向適配器的驅動程序,實現發送數據的算法
struct device dev; //指向適配器的設備結構體
char name[48]; //適配器的名字
};
3)i2c_algorithm
I2C總線數據通信算法,通過管理I2C總線控制器,實現對I2C總線上數據的發送和接收等操作。亦可以理解為I2C總線控制器(適配器adapter)對應的驅動程序,每一個適配器對應一個驅動程序,用來描述適配器和設備之間的通信方法,由芯片廠商去實現的。
struct i2c_algorithm {
//傳輸函數指針,指向實現IIC總線通信協議的函數
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
};
4)i2c_driver
用于管理I2C的驅動程序和i2c設備(client)的匹配探測,實現與應用層交互的文件操作集合fops、cdev等。
struct i2c_driver {
int (*probe)(struct i2c_client *, const struct i2c_device_id *); //設備匹配成功調用的函數
int (*remove)(struct i2c_client *); //設備移除之后調用的函數
struct device_driver driver; //設備驅動結構體
const struct i2c_device_id *id_table; //設備的ID表,匹配用platform創建的client
};
第三:應用實例,實現mpu6050驅動,讀取溫度
在設備樹中描述I2C設備信息
@i2c-0 {//表示這個i2c_client所依附的adapter是i2c-0
//對應i2c_client的name = "invensense,mpu6050"
compatible = "invensense,mpu6050";
//對應i2c_client的addr = 0x69 -- 從機設備的地址
reg = <0x69>;
//對應i2c_client的irq
interrupts = <70>;
};
最終內核會將這個設備樹的節點解析為一個i2c_client結構體與i2c_driver結構體進行匹配。
第四:編寫驅動代碼
分配、設置、注冊i2c_driver結構體
struct i2c_driver mpu6050_driver = { .
driver = {
.name = "mpu6050",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(mpu6050_of_match),
},
.probe = mpu6050_probe, .remove = mpu6050_remove,
};
static int mpu6050_init(void)
{
printk("%s called
", __func__);
i2c_add_driver(&mpu6050_driver);
return 0;
}
i2c總線驅動模型屬于設備模型中的一類,同樣struct i2c_driver結構體繼承于struct driver,匹配方法和設備模型中講的一樣,這里要去匹配設備樹,所以必須實現i2c_driver結構體中的driver成員中的of_match_table成員:
/* 用來匹配mpu6050的設備樹 */
static struct of_device_id mpu6050_of_match[] = {
{.compatible = "invensense,mpu6050"},
{},
};
如果和設備樹匹配成功,那么就好調用probe函數
/* 匹配函數,設備樹中的mpu6050結點對應轉換為一個client結構體 */
static int mpu6050_probe(struct i2c_client * client, const struct i2c_device_id * id)
{
int ret;
printk("mpu6050 match ok!
");
mpu6050_dev.client = client; /* 注冊設備號 */
mpu6050_dev.devno = MKDEV(MAJOR, MINOR);
ret = register_chrdev_region(mpu6050_dev.devno, 1, "mpu6050");
if (ret < 0) goto err1;
cdev_init(&mpu6050_dev.cdev, &mpu6050_fops);
mpu6050_dev.cdev.owner = THIS_MODULE;
ret = cdev_add(&mpu6050_dev.cdev, mpu6050_dev.devno, 1);
if (ret < 0) goto err2;
return 0;
err2:
unregister_chrdev_region(mpu6050_dev.devno, 1);
err1:
return -1;
}
實現文件操作集合
struct file_operations mpu6050_fops = {
.owner = THIS_MODULE,
.open = mpu6050_open,
.release = mpu6050_release,
.unlocked_ioctl = mpu6050_ioctl,
};
static int mpu6050_open(struct inode * inodep, struct file * filep)
{
printk("%s called
", __func__);
mpu6050_write_byte(mpu6050_dev.client, PWR_MGMT_1, 0x00);
mpu6050_write_byte(mpu6050_dev.client, SMPLRT_DIV, 0x07);
mpu6050_write_byte(mpu6050_dev.client, CONFIG, 0x06);
mpu6050_write_byte(mpu6050_dev.client, GYRO_CONFIG, 0xF8);
mpu6050_write_byte(mpu6050_dev.client, ACCEL_CONFIG, 0x19);
return 0;
}
static int mpu6050_release(struct inode * inodep, struct file * filep)
{
printk("%s called
", __func__);
return 0;
}
void get_temp(union mpu6050_data * data)
{
data->temp = mpu6050_read_byte(mpu6050_dev.client, TEMP_OUT_L);
data->temp |= mpu6050_read_byte(mpu6050_dev.client, TEMP_OUT_H) << 8;
}
static long mpu6050_ioctl(struct file * filep, unsigned int cmd, unsigned long arg)
{
union mpu6050_data data;
switch (cmd)
{
case GET_TEMP:
get_temp(&data);
break;
default:
break;
}
if (copy_to_user((unsigned int *)arg, &data, sizeof(data)))
return -1;
return 0;
}
如何實現對i2c從設備的讀寫操作?
/* 讀取mpu6050中一個字節的數據,將讀取的數據的地址返回 */
static int mpu6050_read_byte(struct i2c_client * client, unsigned char reg_add)
{
int ret; /* 要讀取的那個寄存器的地址 */
char txbuf = reg_add; /* 用來接收讀到的數據 */
char rxbuf[1]; /* i2c_msg指明要操作的從機地址,方向,緩沖區 */
struct i2c_msg msg[] = {
{client->addr, 0, 1, &txbuf}, //0表示寫,向往從機寫要操作的寄存器的地址
{client->addr, I2C_M_RD, 1, rxbuf}, //讀數據
};
/* 通過i2c_transfer函數操作msg */
ret = i2c_transfer(client->adapter, msg, 2); //執行2條msg
if (ret < 0)
{
printk("i2c_transfer read err
");
return -1;
}
return rxbuf[0];
}
static int mpu6050_write_byte(struct i2c_client * client, unsigned char reg_addr, unsigned char data)
{
int ret; /* 要寫的那個寄存器的地址和要寫的數據 */
char txbuf[] = {reg_addr, data}; /* 1個msg,寫兩次 */
struct i2c_msg msg[] = {
{client->addr, 0, 2, txbuf}
};
ret = i2c_transfer(client->adapter, msg, 1); if (ret < 0)
{
printk("i2c_transfer write err
");
return -1;
}
return 0;
}
在實現讀寫操作的時候,使用了一個重要的函數i2c_transfer(),這個函數是i2c核心提供給設備驅動的,通過它發送的數據需要被打包成i2c_msg結構,這個函數最終會回調相應i2c_adapter->i2c_algorithm->master_xfer()接口將i2c_msg對象發送到i2c物理控制器。
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags; /* 1 - 讀 0 - 寫 */
__u16 len; /* msg length */
__u8 *buf; /* 要發送的數據 */
};
以上是我對Linux中I2C驅動框架的分析及實際案例分析,如有不足歡迎指出。
審核編輯:劉清
-
控制器
+關注
關注
114文章
17668瀏覽量
190643 -
內核
+關注
關注
4文章
1437瀏覽量
42541 -
適配器
+關注
關注
9文章
2123瀏覽量
71054 -
Linux
+關注
關注
88文章
11641瀏覽量
218188 -
I2C
+關注
關注
28文章
1547瀏覽量
130575 -
I2C總線
+關注
關注
8文章
414瀏覽量
63044 -
LINUX內核
+關注
關注
1文章
319瀏覽量
23076 -
MPU6050
+關注
關注
39文章
310瀏覽量
74562
原文標題:Linux系統中I2C子系統基本分析
文章出處:【微信號:嵌入式開發愛好者,微信公眾號:嵌入式開發愛好者】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
Linux內核中I2C系統的設計思路
請問imx6dl的第四個I2C接口該如何配置使能呢
《Linux設備驅動開發詳解》第15章、Linux的I2C核心、總線與設備驅動
基于嵌入式Linux下的I2C設備驅動的總體思路與框架設計
linux自帶i2c工具使用
Linux驅動:I2C設備驅動(基于Freescale i.MX6ULL平臺了解I2C的驅動框架,順便寫個簡陋的MPU6050驅動)

Linux內核中描述I2C的四個核心結構體
評論