I2C驅動框架+I2C設備驅動編寫方法

I2C驅動框架

1、主要對象

1. I2C總線

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match, //匹配規則
	.probe		= i2c_device_probe, //匹配成功後的行爲
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.pm		= &i2c_device_pm_ops,
};
複製代碼

I2C總線對應着/bus下的一條總線,這個i2c總線結構體管理着i2c設備與I2C驅動的匹配,刪除等操做,I2C總線會調用i2c_device_match函數看I2C設備和I2C驅動是否匹配,若是匹配就調用i2c_device_prob函數,進而調用I2C驅動的probe函數
特別提示:i2c_device_match會管理I2C設備和I2C總線匹配規則,這將和如何編寫I2C驅動程序息息相關。node

2. I2C驅動

struct i2c_driver {
	int (*probe)(struct i2c_client *, const struct i2c_device_id *); //probe函數
	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; //設備鏈表
};
複製代碼

3. I2C設備

struct i2c_client {
	unsigned short addr; //設備地址
	char name[I2C_NAME_SIZE]; //設備名稱
	struct i2c_adapter *adapter; //適配器,I2C控制器。
	struct i2c_driver *driver; //設備對應的驅動
	struct device dev; //代表這是一個設備
	int irq; //中斷號
	struct list_head detected; //節點
};
複製代碼

4. I2C適配器

I2C適配器是什麼?算法

通過上面的介紹,知道有I2C驅動和I2C設備,咱們須要經過I2C驅動去和I2C設備通信,這其中就須要一個I2C適配器,I2C適配器對應的就是SOC上的I2C控制器。數組

struct i2c_adapter {    //適配器
	unsigned int id; //適配器的編號
	const struct i2c_algorithm *algo; //算法,發送時序
	struct device dev; //代表這是一個設備
};
複製代碼

5. I2C算法

I2C算法對應的就是如何發送I2C時序數據結構

struct i2c_algorithm {
    /* 做爲主設備時的發送函數 */
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);

    /* 做爲從設備時的發送函數 */
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
			   unsigned short flags, char read_write,
			   u8 command, int size, union i2c_smbus_data *data);
};
複製代碼

小結

I2C驅動有4個重要的東西,I2C總線、I2C驅動、I2C設備、I2C適配器框架

  • I2C總線:維護着兩個鏈表(I2C驅動、I2C設備),管理I2C設備和I2C驅動的匹配和刪除等
  • I2C驅動:對應的就是I2C設備的驅動程序
  • I2C設備:是具體硬件設備的一個抽象
  • I2C適配器:用於I2C驅動和I2C設備間的通信,是SOC上I2C控制器的一個抽象

I2C總線的運行機制 函數

I2C驅動框架能夠分爲四部分,I2C核心、I2C設備、I2C驅動、I2C適配器,其中I2C總線位於I2C核心中。

  • I2C核心維護着一條I2C總線,提供了註冊I2C設備、I2C驅動、I2C適配器的接口
  • I2C總線維護着一條設備鏈表和驅動鏈表,當向I2C核心層註冊設備時,會將其添加到總線的設備鏈表中,而後遍歷總線上的驅動鏈表,查看兩者是否匹配,若是匹配就調用驅動的probe函數。
  • 當註冊I2C驅動時,也會將其添加到I2C總線的驅動鏈表中,而後遍歷總線的設備鏈表,查看兩者是否匹配,若是匹配就調用驅動的probe函數。
  • 在I2C驅動程序中,經過I2C適配器中的算法向I2C硬件設備傳輸數據。

2、內核源碼分析

1. 註冊I2C設備

註冊I2C設備能夠經過i2c_new_device,此函數會生成一個i2c_client,指定對應的總線爲I2C總線,而後向總線註冊設備。源碼分析

struct i2c_client * i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) {
    struct i2c_client *client;
    client = kzalloc(sizeof *client, GFP_KERNEL);
        
    client->dev.bus = &i2c_bus_type; //指定I2C總線
    
    device_register(&client->dev); //向總線註冊設備
    return client;
}
複製代碼

device_register首先會將設備添加到總線的設備鏈表中,而後遍歷總線的驅動鏈表,判斷設備和驅動是否匹配,若是匹配就調用驅動的probe函數:ui

//第一層
int device_register(struct device *dev) {
    device_add(dev);
}
//第二層
int device_add(struct device *dev) {
    /* 添加設備到總線的設備鏈表中 */
    bus_add_device(dev); 
    /* 遍歷總線的驅動進行操做 */
    bus_probe_device(dev); 
}
//第三層
int bus_add_device(struct device *dev) {
    klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
void bus_probe_device(struct device *dev) {
    device_attach(dev); 
}
//第四層
int device_attach(struct device *dev) {
    /* 遍歷總線的驅動鏈表每一項,而後調用__device_attach */
    bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
}
//第五層
static int __device_attach(struct device_driver *drv, void *data)
{
    /* 判斷設備和驅動是否匹配 */
	if (!driver_match_device(drv, dev)) 
		return 0;
    /* 匹配成功 */
	return driver_probe_device(drv, dev);
}
//第六層
static inline int driver_match_device(struct device_driver *drv, struct device *dev) {
    /* 調用了總線的match函數,這裏的總線在註冊i2c設備時以及設置爲I2C總線了 */
	return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

int driver_probe_device(struct device_driver *drv, struct device *dev) {
    really_probe(dev, drv);
}

//第七層
struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match, //匹配規則
	.probe		= i2c_device_probe, //匹配後的行爲
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.pm		= &i2c_device_pm_ops,
};
/* 這裏調用了i2c_device_match函數 * i2c_device_match會經過I2C驅動的id_table中每一的name和I2C設備的name進行匹配 */
 static int i2c_device_match(struct device *dev, struct device_driver *drv) {
    i2c_match_id(driver->id_table, client);
}

static int really_probe(struct device *dev, struct device_driver *drv) {
    i2c_device_probe(dev)
}
//第八層
 static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id, const struct i2c_client *client) {
	while (id->name[0]) {
		if (strcmp(client->name, id->name) == 0) //字符串匹配
			return id;
		id++;
	}
	return NULL;
}

static int i2c_device_probe(struct device *dev) {
    /* 調用驅動的probe函數 */
    driver->probe(client, i2c_match_id(driver->id_table, client));
}
複製代碼

2. 註冊I2C驅動

與註冊設備驅動過程基本一致spa

//第一層
int i2c_register_driver(struct module *owner, struct i2c_driver *driver) {
    driver->driver.bus = &i2c_bus_type; //指定I2C總線
    
    driver_register(&driver->driver); //向總線註冊驅動
}
//第二層
int driver_register(struct device_driver *drv) {
    bus_add_driver(drv);
}
//第三層
int bus_add_driver(struct device_driver *drv) {
    driver_attach(drv); //此函數會遍歷總線設備鏈表進行操做
    
    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); // 添加進bus的driver鏈表中
}
//第四層
int driver_attach(struct device_driver *drv) {
    /* 遍歷總線的設備鏈表,調用__driver_attach */
    bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
//第五層
static int __driver_attach(struct device *dev, void *data)
{
	if (!driver_match_device(drv, dev))
		return 0;
    
    driver_probe_device(drv, dev);
}
複製代碼

3. I2C適配器構建及其驅動介紹

I2C適配器驅動就是SOC的I2C控制器驅動,主要是由SOC廠商去編寫,咱們不用過度注意細節。內部兩個重要的數據結構i2c_adapter和 i2c_algorithmcode

//第一層
struct i2c_adapter{
    const struct i2c_algorithm *algo; /* 總線訪問算法 */
}
/* i2c_algorithm 就是I2C適配器與IIC設備進行通訊的方法。*/
//第二層
struct i2c_algorithm {
    ......
    /* I2C適配器的傳輸函數,此函數完成與IIC設備的通訊 */
    int (*master_xfer)(struct i2c_adapter *adap,
    struct i2c_msg *msgs,
    int num); 
    /* SMBUS總線的傳輸函數 */
    int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
    unsigned short flags, char read_write,
    u8 command, int size, union i2c_smbus_data *data);
    
    /* To determine what the adapter supports */
    u32 (*functionality) (struct i2c_adapter *);
    ......
};
/* 實例-構建適配器 */
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
	.master_xfer		= s3c24xx_i2c_xfer,
	.functionality		= s3c24xx_i2c_func,
};

static int s3c24xx_i2c_probe(struct platform_device *pdev) {
    i2c->adap.algo    = &s3c24xx_i2c_algorithm; //構建了算法
    i2c_add_numbered_adapter(&i2c->adap); //註冊了適配器
}
複製代碼

4. I2C數據傳輸

上面介紹I2C數據傳輸是經過I2C適配器完成的,下面來分析一下源碼在I2C驅動中,使用i2c_transfer來傳輸I2C數據,此函數確定是經過I2C適配器的算法進行操做的,以下

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) {
    adap->algo->master_xfer(adap, msgs, num); //調用適配器的算法
}

複製代碼

設備驅動編寫方法

i2c設備驅動重點關注兩個數據結構i2c_client 和 i2c_driver,前者是描述設備信息的,後者是描述驅動的。

1、註冊設備

1. 設置I2C設備驅動信息

  • 匹配ID方式
static const struct i2c_device_id my_i2c_dev_id[] = {
	{ "my_i2c_dev", 0},  /* 設備名字 */
	{ }
};
 
static struct i2c_driver my_i2c_drv = {
	.driver = {
		.name	= "no", /* 這個名字不重要 */
        .owner = THIS_MODULE,
	},
	.probe		= my_i2c_drv_probe, /* 當匹配到i2c設備時調用 */
	.remove		= my_i2c_drv_remove, /* 當卸載i2c設備或驅動時調用 */
	.id_table	= my_i2c_dev_id, /* 這個結構體中的名字很重要 */
};
複製代碼

其中my_i2c_dev很是的重要,由於這個名字就是用來和設備進行匹配的名字。

  • 設備樹匹配方式
/* 設備樹匹配列表 */
static const struct of_device_id my_i2c_dev_of_match[] = {
	{ .compatible = "my_i2c_dev, 0" },
	{ /* Sentinel */ }
};

/* i2c驅動結構體 */	
static struct i2c_driver my_i2c_drv = {
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
	.driver = {
			.owner = THIS_MODULE,
		   	.name = "no",
		   	.of_match_table = my_i2c_dev_of_match,
		   },
};

複製代碼

2. 註冊i2c設備驅動

static int __init my_i2c_drv_init(void) {
 
	i2c_add_driver(&my_i2c_drv);
 
	return 0;
}
複製代碼

3.註冊i2c設備

  • 匹配id方式
    /* 靜態註冊-只能在內核啓動時就進行i2c設備註冊 */
  1. 定義一個i2c_board_info對象
static struct i2c_board_info my_i2c_dev_info = {
	I2C_BOARD_INFO("my_i2c_dev", 0x20), //名字,設備地址
};
複製代碼
  1. 經過i2c_register_board_info註冊
/* * busnum:哪一條總線,也就是選擇哪個i2c控制器 * info:i2c設備信息數組 * n:數組有幾項 */
i2c_register_board_info(int busnum, struct i2c_board_info const * info, unsigned n);
i2c_register_board_info(0, my_i2c_dev_info, ARRAY_SIZE(my_i2c_dev_info));
複製代碼

/* 動態註冊能夠在內核運行期間註冊,也就是能夠應用 在加載驅動模塊中 */

  1. 定義一個i2c_board_info對象
static struct i2c_board_info my_i2c_dev_info = {
	I2C_BOARD_INFO("my_i2c_dev", 0x20), //名字,設備地址
};
複製代碼
  1. 經過i2c_new_device來動態註冊
/* * adap:指定i2c設備器,之後訪問設備的時候,使用過哪個設備器(i2c主機控制器)去訪問 * info:指定i2c設備信息 */
struct i2c_client * i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);

複製代碼
  • 使用設備樹時
/* 在i2c節點下添加設備信息 */
&i2c1 {
    my_i2c_dev@20 {
        compatible = "my_i2c_dev,0"
    } 
}
複製代碼

2、數據傳輸函數介紹

1. 傳輸函數

/* * adap:i2c適配器 * msgs:消息數據 * num:數組的個數 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) 複製代碼

2. 傳輸報文msg的組成

struct i2c_msg {
	__u16 addr;	//從設備地址
	__u16 flags; //讀或寫
	__u16 len;	//消息的長度
	__u8 *buf;	//消息
};
複製代碼
  • 例子

/* 定義 i2c_msg 結構體 */
struct i2c_msg msg[2]; 
 
char val[10]
 
/* 填充msg */
msg[0].addr = my_i2c_client->addr; /* 這個client在probe函數中獲得的 */
msg[0].flags = 0; /* 0表示寫,1表示讀 */
msg[0].buf = 0x80; /* 寫:要發送的數據地址,讀:讀取到的數據存放的地址 */
msg[0].len = 1; /* 想要傳輸的字節數 */
 
/* 填充msg */
msg[1].addr = my_i2c_client->addr; /* 這個client在probe函數中獲得的 */
msg[1].flags = 1; /* 1表示讀 */
msg[1].buf = val; /* 讀到的數據存在這裏 */
msg[1].len = 4; /* 想要讀取的字節數 */
 
 
/* 傳輸數據 */
i2c_transfer(my_i2c_client->adapter, msg, 2); /* 有兩個msg */

複製代碼
相關文章
相關標籤/搜索