参考资料
- 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");