linux I2C驱动详解(linux i2c从设备驱动)
一、i2c驱动总览
i2c通讯广泛运用在主控对周围芯片的配置上,i2c协议比较简单,这里不加说明,但linux的i2c驱动实现相当复杂,下面是i2c驱动在整个系统中的位置以及读写流程。
二、i2c驱动相关结构体
i2c_adapter:soc的i2c控制器
i2c_algorithm:i2c控制器具体发送和接收i2c数据方法
i2c_client:i2c从设备,比如带i2c接口的温湿度传感器
i2c_driver:i2c从设备驱动,比如读取温湿度传感器的驱动
struct i2c_adapter {
struct module *owner; // 所有者
unsigned int id;
unsigned int class; // 该适配器支持的从设备的类型
const struct i2c_algorithm *algo; // 该适配器与从设备的通信算法
void *algo_data;
/* data fields that are valid for all devices */
struct rt_mutex bus_lock;
int timeout; // 超时时间
int retries;
struct device dev; // 该适配器设备对应的device,i2c控制器是实际存在的,所有有个device成员
int nr; // 适配器的编号
char name[48]; // 适配器的名字
struct completion dev_released;
struct list_head userspace_clients; // 用来挂接与适配器匹配成功的从设备i2c_client的一个链表头
};
struct i2c_client { // 用来描述一个从次设备
unsigned short flags; // 描述i2c从设备特性的标志位
unsigned short addr; // i2c 从设备的地址
char name[I2C_NAME_SIZE]; // i2c从设备的名字
struct i2c_adapter *adapter; // 指向与从设备匹配成功的适配器
struct i2c_driver *driver; // 指向与从设备匹配成功的设备驱动
struct device dev; // 该从设备对应的device,i2c从设备是个实际存在的,所以有device
int irq; // 从设备的中断引脚
struct list_head detected; // 作为一个链表节点挂接到与他匹配成功的i2c_driver 相应的链表头上
};
struct i2c_driver { // 代表一个i2c设备驱动
unsigned int class; // i2c设备驱动所支持的i2c设备的类型
int (*attach_adapter)(struct i2c_adapter *); // 用来匹配适配器的函数 adapter
int (*detach_adapter)(struct i2c_adapter *);
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *); // 设备驱动层的probe函数
int (*remove)(struct i2c_client *); // 设备驱动层卸载函数
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
void (*alert)(struct i2c_client *, unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver; // 该i2c设备驱动所对应的device_driver
const struct i2c_device_id *id_table; // 设备驱动层用来匹配设备的id_table
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list; // 该设备驱动支持的所有次设备的地址数组
struct list_head clients; // 用来挂接与该i2c_driver匹配成功的i2c_client (次设备)的一个链表头
};
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num); //i2c读写方法,有芯片厂家实现
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
};
这四个结构体的关系如下所示:
三、i2c驱动的加载过程
下面以hi3559为例说明,linux内核代码版本是4.9.
先看一hi3559设备树中对i2c控制器的定义,设备树文件为\Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\arch\arm64\boot\dts\hisilicon\hi3559av100.dtsi,i2c节点定义如下所示:
从设备节点的compatible信息可以找到hi3559 i2c驱动文件为\Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\drivers\i2c\busses\i2c-hibvt.c
可以看到,i2c-hibvt.c中的hibvt_i2c_match[]的第一项compatible与设备树是对应的,这里的驱动入口函数就是hibvt_i2c_probe。先看一下hi3559私有的i2c驱动结构体定义:
struct hibvt_i2c_dev {
struct device *dev;
struct i2c_adapter adap; //i2c控制器
resource_size_t phybase; //hi3559 i2c控制器寄存器起始地址
void __iomem *base; //经过地址转换的起始地址
struct clk *clk;
int irq;
unsigned int freq;
struct i2c_msg *msg;
unsigned int msg_num;
unsigned int msg_idx;
unsigned int msg_buf_ptr;
struct completion msg_complete;
spinlock_t lock;
int status;
};
hibvt_i2c_probe函数初始化都以这个结构体展开,里面最重要的就是struct i2c_adapter adap,这个是i2c控制器的通用抽象,相当于c++中的基类。其它的是该i2c控制器的物理信息,大部分都可以从设备树中读取。
static int hibvt_i2c_probe(struct platform_device *pdev)
{
int status;
struct hibvt_i2c_dev *i2c;
struct i2c_adapter *adap;
struct resource *res;
i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);
if (!i2c) {
return -ENOMEM;
}
platform_set_drvdata(pdev, i2c);
i2c->dev = &pdev->dev;
spin_lock_init(&i2c->lock);
init_completion(&i2c->msg_complete);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(i2c->dev, "Invalid mem resource./n");
return -ENODEV;
}
i2c->phybase = res->start; //从设备树中读取i2c的起始物理地址
i2c->base = devm_ioremap_resource(&pdev->dev, res);//转换成虚拟地址
if (IS_ERR(i2c->base)) {
dev_err(i2c->dev, "cannot ioremap resource\n");
return -ENOMEM;
}
i2c->clk = devm_clk_get(&pdev->dev, NULL); //读取时钟
if (IS_ERR(i2c->clk)) {
dev_err(i2c->dev, "cannot get clock\n");
return -ENOENT;
}
clk_prepare_enable(i2c->clk);
if (of_property_read_u32(pdev->dev.of_node, "clock-frequency",
&i2c->freq)) {
dev_warn(i2c->dev, "setting default clock-frequency@%dHz\n",
I2C_DEFAULT_FREQUENCY);
i2c->freq = I2C_DEFAULT_FREQUENCY;
}
/* i2c controller initialization, disable interrupt */
hibvt_i2c_hw_init(i2c);//初始化其它硬件相关参数
i2c->irq = platform_get_irq(pdev, 0); //获取中断信息
status = devm_request_irq(&pdev->dev, i2c->irq, hibvt_i2c_isr,
IRQF_SHARED, dev_name(&pdev->dev), i2c);
if (status) {
dev_dbg(i2c->dev, "falling back to polling mode");
i2c->irq = -1;
}
adap = &i2c->adap;
i2c_set_adapdata(adap, i2c);
adap->owner = THIS_MODULE;
strlcpy(adap->name, "hibvt-i2c", sizeof(adap->name));
adap->dev.parent = &pdev->dev;
adap->dev.of_node = pdev->dev.of_node;
adap->algo = &hibvt_i2c_algo; //i2c读写方法
/* Add the i2c adapter */
status = i2c_add_adapter(adap); //注册adapter
if (status) {
dev_err(i2c->dev, "failed to add bus to i2c core\n");
goto err_add_adapter;
}
dev_info(i2c->dev, "%s%d@%dhz registered\n",
adap->name, adap->nr, i2c->freq);
return 0;
err_add_adapter:
clk_disable_unprepare(i2c->clk);
return status;
}
从该函数流程可以看出,主要做的工作是:
1、从设备树读取i2c控制器相关参数填充到hibvt_i2c_dev上
2、需要提前实现一个hibvt_i2c_algo,这个是对i2c控制器的读写方法
3、调用i2c_add_adapter注册adapter
int i2c_add_adapter(struct i2c_adapter *adapter)
{
struct device *dev = &adapter->dev;
int id;
if (dev->of_node) {
id = of_alias_get_id(dev->of_node, "i2c");
printk("yy i2c name =%s, id = %d\n", dev->init_name, id);
if (id >= 0) {
adapter->nr = id;
return __i2c_add_numbered_adapter(adapter);
}
}
mutex_lock(&core_lock);
id = idr_alloc(&i2c_adapter_idr, adapter,
__i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
mutex_unlock(&core_lock);
if (WARN(id < 0, "couldn't get idr"))
return id;
adapter->nr = id;
return i2c_register_adapter(adapter);
}
of_alias_get_id函数会去设备树中查找i2c控制器的id号,以为一个soc里面集成多个i2c控制器,需要给每个控制器分配一个编号。这个函数是从hi3559av100-demb.dts中去查找aliases节点的i2c编号,这里adapter->nr的值会是0~19。如下图所示:
__i2c_add_numbered_adapter这个函数调用 idr_alloc 使 ID 号和 adapter 结构体按照 IDR 机制关联起来。IDR 用类基数树结构来构造一个稀疏数组,以 ID 为索引找到对应数组元素,进而找到对应的数据结构指针,这里面具体没研究过。最后调用了i2c_register_adapter继续完成注册。i2c_register_adapter这个函数主要完成以下工作:
1、初始化struct i2c_adapter的一些成员变量
2、调用of_i2c_register_devices查找i2c节点下是否有i2c从设备,如果有则生成i2c_client
四、i2c的节点生成
i2c驱动最终会在/dev下生成i2c字符设备节点,/dev/i2c-0、/dev/i2c-1 等。生成这些节点的驱动在drivers/i2c/i2c-dev.c中,我们看一下是怎么生成的。
这个文件里有个代码i2c字符设备的结构体
struct i2c_dev {
struct list_head list; //所有i2c字符设备都加入到链表i2c_dev_list中
struct i2c_adapter *adap; //i2c 控制器
struct device *dev; //dev目录下的设备节点
struct cdev cdev; //字符设备
};
在i2c-dev.c文件中,会调用module_init加载i2c_dev_init,
static int __init i2c_dev_init(void)
{
int res;
printk(KERN_INFO "i2c /dev entries driver\n");
//申请主设备号为I2C_MAJOR的I2C_MINORS个i2c设备号
res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
if (res)
goto out;
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
if (IS_ERR(i2c_dev_class)) {
res = PTR_ERR(i2c_dev_class);
goto out_unreg_chrdev;
}
i2c_dev_class->dev_groups = i2c_groups;
/* 初始化i2c总线的通知链,i2c_register_adapter函数中在调用device_register注册adap->dev时会启动总线通知*/
res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
if (res)
goto out_unreg_class;
/* 那些已经注册完的adap,这里会遍历i2c_bus_type上所有adap,然后注册字符设备 */
i2c_for_each_dev(NULL, i2cdev_attach_adapter);
return 0;
out_unreg_class:
class_destroy(i2c_dev_class);
out_unreg_chrdev:
unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:
printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
return res;
}
i2cdev_notifier里面的回调函数是i2cdev_notifier_call,该函数里面有两个函数,一个是注册一个i2c字符设备,另一个是删除。下面看一下注册这个函数
static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
struct i2c_adapter *adap;
struct i2c_dev *i2c_dev;
int res;
if (dev->type != &i2c_adapter_type)
return 0;
adap = to_i2c_adapter(dev);
i2c_dev = get_free_i2c_dev(adap); //申请一个i2c_dev,并将该i2c_dev加入到i2c_dev_list中
if (IS_ERR(i2c_dev))
return PTR_ERR(i2c_dev);
cdev_init(&i2c_dev->cdev, &i2cdev_fops); //初始化字符设备
i2c_dev->cdev.owner = THIS_MODULE;
res = cdev_add(&i2c_dev->cdev, MKDEV(I2C_MAJOR, adap->nr), 1); //添加一个字符设备
if (res)
goto error_cdev;
/* register this i2c device with the driver core */
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev, //在/dev下生成i2c字符设备节点
MKDEV(I2C_MAJOR, adap->nr), NULL,
"i2c-%d", adap->nr);
if (IS_ERR(i2c_dev->dev)) {
res = PTR_ERR(i2c_dev->dev);
goto error;
}
pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
adap->name, adap->nr);
return 0;
error:
cdev_del(&i2c_dev->cdev);
error_cdev:
put_i2c_dev(i2c_dev);
return res;
}
最后就是i2cdev_fops的这几个open、read、write、ioctl回调函数了。
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};
下面看一下i2cdev_open这个函数
static int i2cdev_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);//通过inode获取次设备号
struct i2c_client *client;
struct i2c_adapter *adap;
adap = i2c_get_adapter(minor);//通过次设备号获取adpa,以为adpa->nr就是次设备号
if (!adap)
return -ENODEV;
/* This creates an anonymous i2c_client, which may later be
* pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
*
* This client is ** NEVER REGISTERED ** with the driver model
* or I2C core code!! It just holds private copies of addressing
* information and maybe a PEC flag.
*/
client = kzalloc(sizeof(*client), GFP_KERNEL); //这里会申请一个i2c client
if (!client) {
i2c_put_adapter(adap);
return -ENOMEM;
}
snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
client->adapter = adap;
file->private_data = client;
return 0;
}
下面看一下i2cdev_write
static ssize_t i2cdev_write(struct file *file, const char __user *buf,
size_t count, loff_t *offset)
{
int ret;
char *tmp;
struct i2c_client *client = file->private_data; //通过file中的私有数据得到i2c client ,在open时候已经将该私有数据设置成cilent
if (count > 8192)
count = 8192;
if (count == 0)
return -EINVAL;
tmp = memdup_user(buf, count);
if (IS_ERR(tmp))
return PTR_ERR(tmp);
pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n",
iminor(file_inode(file)), count);
ret = i2c_master_send(client, tmp, count); //调用该函数完成数据传输即可
kfree(tmp);
return ret;
}