I2C子系統驅動框架及應用 (轉)
I2C子系統驅動框架: 應用程序層(app層) ——————————————————————————————————– i2c driver層: 從設備驅動層(TS Sensor等) 1. 需要和應用層交互(fops cdev) 2. 封裝數據,但是不知道數據如何寫入到硬件,需要調用adapter層的相關函數去寫 ——————————————————————————————————– i2c core:維護i2c bus, 包括i2c driver和i2c client鏈表 1. 實現i2c client和i2c driver的匹配 ——————————————————————————————————– i2c adapter層: i2c控制器層,初始化i2c控制器,實現i2c時序 1. 將數據寫入或讀取從設備 2. 不知道具體數據(i2c driver提供的數據)是什麽,但知道具體如何操作(讀/寫)從設備 這一層是具體的廠商實現的,比如三星:driver/i2c/busser/i2c-s3c2410.c
框架中,i2c core是由Linux內核實現的(i2c-core.c),i2c adapter是由具體的芯片廠商實現的,比如三星的芯片adapter實現都在driver/i2c/busser/i2c-s3c2410.c。所以這連個部分需要編譯到uImage中(make menuconfig -> device driver -> <*> i2c support -> i2c hardware Bus support -> S3C2410 I2C driver)。 如果在/sys/bus/i2c/devices/i2c-0/1/2 表示有i2c-adapter 存在
在總結的時候看到有其他博友整理的框圖非常好,我就借過來給大家分享!
從i2c驅動架構圖中可以看出,linux內核對i2c架構抽象了一個叫核心層core的中間件,它分離了設備驅動device driver和硬件控制的實現細節(如操作i2c的寄存器),core層不但為上面的設備驅動提供封裝後的內核註冊函數,而且還為下面的硬件事件提供註冊接口(也就是i2c總線註冊接口i2c_add_register),可以說core層起到了承上啟下的作用。
相關的重要結構體和函數: 1. i2c_client 每一個i2c從設備都需要用一個i2c_client結構體來描述,i2c_client對應真實的i2c物理設備device,但是i2c_client不是我們自己寫程序去創建的,而是通過以下常用的方式自動創建的(這個地方不做詳細說明,以介紹總體框架為主): platform創建: 1. 註冊i2c_board_info 2. 獲取對應的adapter,然後i2c_new_device devicetree創建: 3. 通過設備樹的一個節點去描述一個從設備,設備樹在解析的時候會自動創建client
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; //已經被發現的設備鏈表
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2. i2c_driver driver是指向從設備的驅動程序,由我們自己去實現並通過i2c_add_register註冊到i2c的bus中,和i2c clinet進行匹配,匹配成功則調用probe函數。
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
};
- 1
- 2
- 3
- 4
- 5
- 6
3. i2c_adapter i2c總線適配器其實就是一個i2c總線控制器,本質上是一個物理設備,主要用來完成i2c總線控制器相關的數據通信 由芯片廠商去實現的。
struct i2c_adapter {
struct module *owner;
unsigned int class; //允許匹配的設備的類型
const struct i2c_algorithm *algo; //指向適配器的驅動程序,實現發送數據的算法
struct device dev; //指向適配器的設備結構體
char name[48]; //適配器的名字
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
4. i2c_algorithm i2c算法,適配器對應的驅動程序,每一個適配器對應一個驅動程序,用來描述適配器和設備之間的通信方法 由芯片廠商去實現的。
struct i2c_algorithm {
//傳輸函數指針,指向實現IIC總線通信協議的函數
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
};
- 1
- 2
- 3
- 4
5. i2c_msg 把要發送的數據封裝成msg結構體(比如16個字節進行拆分)
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags; /* 1 - 讀 0 - 寫 */
__u16 len; /* msg length */
__u8 *buf; /* 要發送的數據 */
};
- 1
- 2
- 3
- 4
- 5
- 6
6. i2c_add_register 註冊一個i2c_driver結構體,通過name或者id_tables或者of_match_table去匹配一個i2c_client,如果匹配成功,則會調用i2c_driver結構體裏面的probe函數,並將對應的i2c_client結構體傳過來。 #define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver) int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
7. i2c_transfer 負責通過對應的i2c總線對依附於這個adapter的從機設備(i2c_client)進行讀寫數據(雙向的)。其中要讀寫的數據要封裝成為一個i2c_msg結構體,根據msg的flags標誌位是0還是1來決定是讀還是寫。其實i2c_transfer是對master_xfer的封裝。
/**
* i2c_transfer - execute a single or combined I2C message
* @adap: Handle to I2C bus
* @msgs: One or more messages to execute before STOP is issued to
* terminate the operation; each message begins with a START.
* @num: Number of messages to be executed.
*
* Returns negative errno, else the number of messages executed.
*
* Note that there is no requirement that each message be sent to
* the same slave address, although that is the most common model.
*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
eg.
/* i2c_msg指明要操作的從機地址,方向,緩沖區 */
struct i2c_msg msg[] = {
{client->addr, 0, 1, &txbuf}, //0表示寫,向往從機寫要操作的寄存器的地址
{client->addr, 1, 1, &rxbuf}, //讀數據
};
/* 通過i2c_transfer函數操作msg */
ret = i2c_transfer(client->adapter, msg, 2); //執行2條msg
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
這幾個重要結構體之間的關系: a – i2c_adapter與i2c_algorithm i2c_adapter對應與物理上的一個適配器,而i2c_algorithm對應一套通信方法,一個i2c適配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下層與硬件相關的代碼提供)通信函數來控制適配器上產生特定的訪問周期。缺少i2c_algorithm的i2c_adapter什麽也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指針。 i2c_algorithm中的關鍵函數master_xfer()用於產生i2c訪問周期需要的start、stop、ack信號,以i2c_msg為單位發送和接收通信數據。 i2c_msg也非常關鍵,調用驅動中的發送接收函數需要填充該結構體
b –i2c_driver和i2c_client i2c_driver對應一套驅動方法 i2c_client對應真實的i2c物理設備device,每個i2c設備都需要一個i2c_client來描述 i2c_driver與i2c_client的關系是一對多。一個i2c_driver上可以支持多個同等類型的i2c_client.
c – i2c_adapter和i2c_client i2c_adapter和i2c_client的關系與i2c硬件體系中適配器和設備的關系一致,即i2c_client依附於i2c_adapter,由於一個適配器上可以連接多個i2c設備,所以i2c_adapter中包含依附於它的i2c_client的鏈表
下面給出一個i2c子系統實例代碼(用設備樹實現): 主機 - 三星的某款cpu 從機 - mpu6050三軸加速度傳感器
設備樹描述: 當設備樹被內核解析後會生成一個依附於i2c-0這個adapter的i2c_client
@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>;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
driver代碼:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include "mpu6050.h"
MODULE_LICENSE("GPL");
#define SMPLRT_DIV 0x19
#define CONFIG 0x1A
#define GYRO_CONFIG 0x1B
#define ACCEL_CONFIG 0x1C
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define PWR_MGMT_1 0x6B
int MAJOR = 255;
int MINOR = 0;
struct mpu6050_device {
struct cdev cdev;
dev_t devno;
struct i2c_client * client;
}mpu6050_dev;
/* 讀取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\n");
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\n");
return -1;
}
return 0;
}
static int mpu6050_open(struct inode * inodep, struct file * filep)
{
printk("%s called\n", __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\n", __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;
}
struct file_operations mpu6050_fops = {
.owner = THIS_MODULE,
.open = mpu6050_open,
.release = mpu6050_release,
.unlocked_ioctl = mpu6050_ioctl,
};
/* 匹配函數,設備樹中的mpu6050結點對應轉換為一個client結構體 */
static int mpu6050_probe(struct i2c_client * client, const struct i2c_device_id * id)
{
int ret;
printk("mpu6050 match ok!\n");
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;
}
static int mpu6050_remove(struct i2c_client * client)
{
printk("mpu6050 removed!\n");
cdev_del(&mpu6050_dev.cdev);
unregister_chrdev_region(mpu6050_dev.devno, 1);
return 0;
}
/* 用來匹配mpu6050的設備樹 */
static struct of_device_id mpu6050_of_match[] = {
{.compatible = "invensense,mpu6050"},
{},
};
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\n", __func__);
i2c_add_driver(&mpu6050_driver);
return 0;
}
static void mpu6050_exit(void)
{
printk("%s called\n", __func__);
i2c_del_driver(&mpu6050_driver);
return ;
}
module_init(mpu6050_init);
module_exit(mpu6050_exit);
http://blog.csdn.net/hanp_linux/article/details/72832158
I2C子系統驅動框架及應用 (轉)