參考資料
- Linux核心文檔:
-
Documentation\devicetree\bindings\i2c\i2c-gpio.txt
-
- Linux核心驅動程式:使用GPIO模拟I2C
-
drivers\i2c\busses\i2c-gpio.c
-
- Linux核心真正的I2C控制器驅動程式
-
drivers\i2c\busses\i2c-imx.c
-
一、回顧
1.1 I2C驅動程式的層次
- App:應用程式,想做某些事情。
- I2C device driver:核心中的i2c驅動和i2c裝置,驅動知道怎麼做這些事情。
- I2C Core:i2c一些通用的函數,對于這些事情的輔助作用。
- I2C Controller Driver:一些相關的函數,具體做這些事情的控制器。
1.2 I2C總線驅動模型
左右兩邊一一比對比較,不寫了。
二、I2C_Adapter驅動架構
2.1 i2c_adapter
i2c_adpater裡面有兩個比較重要的結構體:
const struct i2c_algorithm *algo;
和
int nr;
一個是算法,一個是第幾條總線。
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus 算法結構體 */
void *algo_data;
/* data fields that are valid for all devices */
const struct i2c_lock_operations *lock_ops;
struct rt_mutex bus_lock;
struct rt_mutex mux_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr; /* 表示第幾條總線 */
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
};
2.2 i2c_algorithm
然後在i2c_algorithm中主要是
master_xfer
和
functionality
函數。
struct i2c_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
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);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
int (*reg_slave)(struct i2c_client *client);
int (*unreg_slave)(struct i2c_client *client);
#endif
};
- master_xfer:這是最重要的函數,它實作了一般的I2C傳輸,用來傳輸一個或多個i2c_msg
- master_xfer_atomic:
- 可選的函數,功能跟master_xfer一樣,在atomic context環境下使用,原子版本
- 比如在關機之前、所有中斷都關閉的情況下,用來通路電源管理晶片
- smbus_xfer:實作SMBus傳輸,如果不提供這個函數,SMBus傳輸會使用master_xfer來模拟
- smbus_xfer_atomic:
- 可選的函數,功能跟smbus_xfer一樣,在atomic context環境下使用
- 比如在關機之前、所有中斷都關閉的情況下,用來通路電源管理晶片
- functionality:傳回所支援的flags:各類I2C_FUNC_*
- reg_slave/unreg_slave:
- 有些I2C Adapter也可工作與Slave模式,用來實作或模拟一個I2C裝置
- reg_salve就是讓把一個i2c_client注冊到I2C Adapter,換句話說就是讓這個I2C Adapter模拟該i2c_client
- unreg_slave:反注冊
三、編寫驅動程式架構
配置設定、設定、注冊一個i2c_adapter結構體,這個結構體是在i2c_driver的probe函數裡注冊的。
- i2c_adapter的核心是i2c_algorithm
- i2c_algorithm的核心是masterxfer函數
3.1 所涉及的函數
- 配置設定
struct i2c_adapter *adap = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL);
- 設定
adap->owner = THIS_MODULE;
adap->algo = &example_algo;
- 注冊:i2c_add_adapter / i2c_add_umbered_adapter
ret = i2c_add_adapter(adap); //不管adap->nr是什麼,都動态設定adap->nr
ret = i2c_add_numbered_adapter(adap); //如果adap->nr == -1,則動态配置設定nr;佛則使用該nr
- 反注冊
i2c_del_adapter(adap);
3.2 編寫裝置樹檔案
/ {
i2c-bus-virtual {
compatible = "100ask,i2c-bus-virtual"; //使用字元串比對i2c_adpter_drv
};
};
3.3 編寫adapter_drv的思路
- adapter驅動是一個platform_driver的驅動,是以我們先增加platform的init函數和exit函數。
- 在注冊時需要一個platform_driver的結構體,是以我們需要建立一個i2c_bus_virtual_driver結構體。
- 在i2c_bus_virtual_driver中包括,probe函數,remove函數,driver結構體比對裝置樹
- 在probe函數中需要建立一個全局的i2c_adapter結構體,裡面包括傳輸算法,屬于第幾個總線。
- 在remove函數中去删除i2c_adapter結構體。
- 因為隻是模拟eeprom的裝置,是以就在傳輸函數上增加了對資料存儲和讀取的模拟。
四、相關源碼
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/slab.h>
static struct i2c_adapter *g_adapter;
static unsigned char eeprom_buffer[512];
static int eeprom_cur_addr = 0;
static void eeprom_emulate_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msg)
{
int i;
if(msg->flags | I2C_M_RD) {
for(i=0;i<msg->len;i++) {
msg->buf[i] = eeprom_buffer[eeprom_cur_addr++];
if(eeprom_cur_addr == 512)
eeprom_cur_addr = 0;
}
} else {
if(msg->len >= 1) {
eeprom_cur_addr = msg->buf[0];
for(i=0;i<msg->len-1;i++) {
eeprom_buffer[eeprom_cur_addr++] = msg->buf[i];
if(eeprom_cur_addr == 512)
eeprom_cur_addr = 0;
}
}
}
}
static int i2c_bus_virtual_xfer(struct i2c_adapter *i2c_adap,
struct i2c_msg msgs[], int num)
{
int i;
//emulate eeprom addr=0x50
for(i=0;i<num;i++) {
// do transfer msgs[i]
if(msgs[i].addr == 0x50) {
eeprom_emulate_xfer(i2c_adap, &msgs[i]);
} else {
i = -EIO;
break;
}
}
return i;
}
static u32 i2c_bus_virtual_func(struct i2c_adapter *adap)
{
return (I2C_FUNC_I2C | I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA);
}
const struct i2c_algorithm i2c_bus_virtual_algo = {
.master_xfer = i2c_bus_virtual_xfer,
.functionality = i2c_bus_virtual_func,
};
static int i2c_bus_virtual_probe(struct platform_device *pdev)
{
/* get info from device tree, to set i2c_adapter or hardware */
/* alloc, set, register i2c_adapter */
g_adapter = kzalloc(sizeof(*g_adapter), GFP_KERNEL);
g_adapter->owner = THIS_MODULE;
g_adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
g_adapter->nr = -1;
g_adapter->algo = &i2c_bus_virtual_algo;
snprintf(g_adapter->name, sizeof(g_adapter->name), "i2c-bus-virtual");
i2c_add_adapter(g_adapter); //i2c_add_numerd_adapter(g_adapter);
return 0;
}
static int i2c_bus_virtual_remove(struct platform_device *dev)
{
i2c_del_adapter(g_adapter);
return 0;
}
static const struct of_device_id i2c_bus_virtual_ids[] = {
{ .compatible = "100ask,i2c-bus-virtual", },
{ /* end of list */ }
};
struct platform_driver i2c_bus_virtual_driver = {
.driver = {
.name = "i2c-bus-virtual",
.of_match_table = i2c_bus_virtual_ids,
},
.probe = i2c_bus_virtual_probe,
.remove = i2c_bus_virtual_remove,
};
static int i2c_bus_virtual_init(void)
{
int ret;
ret = platform_driver_register(&i2c_bus_virtual_driver);
if(ret)
printk(KERN_ERR"i2c_driver register failed\n");
return 0;
}
static void i2c_bus_virtual_exit(void)
{
platform_driver_unregister(&i2c_bus_virtual_driver);
}
module_init(i2c_bus_virtual_init);
module_exit(i2c_bus_virtual_exit);
MODULE_AUTHOR("tuo");
MODULE_LICENSE("GPL");