linux i2c驱动
i2c接口使用
下面以hi3559a为例说明如何i2c接口,例如要在i2c_bus0下挂一个ap3216c传感器,向i2c_bus0节点添加ap3216c节点即可。如下所示:
向\Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\arch\arm64\boot\dts\hisilicon\hi3559av100-demb.dts文件的i2c_bus0节点追加ap3216c传感器节点
重新编译并内核,烧写启动后会在/sys/bus/i2c/devices目录下生成0-002e的ap3216c目录,进入0-002e,cat name,可看到0-002e名字就是ap3216c,如下所示:
i2c从设备添加好后,还需要写一个ap3216c的驱动,这个驱动就是i2c_driver。
ap3216c驱动参考正点原子教程
#include <linux/init.h> #include <linux/platform_device.h> #include <linux/module.h> #include <linux/ioport.h> #include <asm/io.h> #include <linux/cdev.h> #include <linux/i2c.h> #include <asm/uaccess.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("yyfage"); struct cdev_private { struct cdev cdev; struct class *class; struct device *dev; /* 设备 */ struct device_node *nd; /* 设备节点 */ int major; /* 主设备号 */ dev_t devid; /* 设备号 */ struct file_operations *fops; /* 字符设备操作函数,需要事先指定 */ }; static struct i2c_client *yy_i2c_client; /* * @description : 从i2c设备读取多个寄存器数据 * @param - dev: i2c设备 * @param - reg: 要读取的寄存器首地址 * @param - val: 读取到的数据 * @param - len: 要读取的数据长度 * @return : 操作结果 */ static int ap3216c_read_regs(struct i2c_client *client, u8 reg, void *val, int len) { int ret; struct i2c_msg msg[2]; /* msg[0]为发送要读取的首地址 */ msg[0].addr = client->addr; /* ap3216c地址 */ msg[0].flags = 0; /* 标记为发送数据 */ msg[0].buf = ® /* 读取的首地址 */ msg[0].len = 1; /* reg长度*/ /* msg[1]读取数据 */ msg[1].addr = client->addr; /* ap3216c地址 */ msg[1].flags = I2C_M_RD; /* 标记为读取数据*/ msg[1].buf = val; /* 读取数据缓冲区 */ msg[1].len = len; /* 要读取的数据长度*/ ret = i2c_transfer(client->adapter, msg, 2); if(ret == 2) { ret = 0; } else { printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len); ret = -EREMOTEIO; } return ret; } /* * @description : 向i2c设备多个寄存器写入数据 * @param - dev: i2c设备 * @param - reg: 要写入的寄存器首地址 * @param - val: 要写入的数据缓冲区 * @param - len: 要写入的数据长度 * @return : 操作结果 */ static s32 yy_i2c_write_regs(struct i2c_client *client, u8 reg, u8 *buf, u8 len) { u8 b[256]; struct i2c_msg msg; b[0] = reg; /* 寄存器首地址 */ memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */ msg.addr = client->addr; /* ap3216c地址 */ msg.flags = 0; /* 标记为写数据 */ msg.buf = b; /* 要写入的数据缓冲区 */ msg.len = len + 1; /* 要写入的数据长度 */ return i2c_transfer(client->adapter, &msg, 1); } /*********************************************以下字符设备****************************************************/ static int yy_i2c_cdev_fops_open(struct inode *inode , struct file *filp) { u8 buf[20] = {0}; printk("yy_uu_cdev_fops_open is run\n"); buf[0] = 0x20; //yy_i2c_write_regs(yy_i2c_client, 0x40, buf, 1); /* 复位i2c设备*/ return 0; } static int yy_i2c_cdev_fops_release(struct inode *inode , struct file *filp) { printk("yy_uu_cdev_fops_release is run\n"); return 0; } static ssize_t yy_i2c_cdev_fops_read(struct file *filp , char __user *buf, size_t count, loff_t *f_pos) { u8 date[256] = {0}; int ret = 0; printk("yy_i2c_cdev_fops_read is run\n"); //ret = ap3216c_read_regs(yy_i2c_client, 0x50, date, count); //读取i2c设备首地址为0x50,连续读count个 if (ret) { printk("read yy_i2c device error\n"); return ret; } copy_to_user(buf, date, count); return ret; } static ssize_t yy_i2c_cdev_fops_write(struct file *filp , const char __user *buf,size_t count ,loff_t *f_pos) { printk("yy_i2c_cdev_fops_write is run\n"); return 0; } static struct file_operations yy_i2c_fops = { .owner = THIS_MODULE , .read = yy_i2c_cdev_fops_read , .write = yy_i2c_cdev_fops_write , .open = yy_i2c_cdev_fops_open , .release = yy_i2c_cdev_fops_release , }; static struct cdev_private yy_i2c_cdev = { .fops = &yy_i2c_fops, }; static int creat_yy_i2c_cdev(struct cdev_private *cdev_pri, const char *cdev_name) { int ret = 0; ret = alloc_chrdev_region(&cdev_pri->devid, 0 , 1, cdev_name); if (ret < 0) { printk("alloc_chrdev_region error"); return ret; } cdev_pri->major = MAJOR(cdev_pri->devid); cdev_init(&cdev_pri->cdev , cdev_pri->fops);//用上面声明的scull_fops初始化cdev。 cdev_pri->cdev.owner = THIS_MODULE; cdev_pri->cdev.ops = cdev_pri->fops; ret = cdev_add (&cdev_pri->cdev , cdev_pri->devid, 1);//这个是在字符设备中添加一个设备。 if (ret) { printk("cdev_add error"); unregister_chrdev_region(cdev_pri->devid, 1); return ret; } /* 3、创建类 */ cdev_pri->class = class_create(THIS_MODULE, cdev_name); if (IS_ERR(cdev_pri->class)) { return PTR_ERR(cdev_pri->class); } /* 4、创建设备 */ cdev_pri->dev = device_create(cdev_pri->class, NULL, cdev_pri->devid, NULL, cdev_name); if (IS_ERR(cdev_pri->dev)) { return PTR_ERR(cdev_pri->dev); } return ret; } /*********************************************以上字符设备****************************************************/ static int yy_i2c_probe(struct i2c_client *client, const struct i2c_device_id *device_id) { int ret = 0; printk("yy_i2c_probe is run\n"); yy_i2c_client = client; creat_yy_i2c_cdev(&yy_i2c_cdev, "yy_i2c"); return ret; } static int yy_i2c_remove(struct i2c_client *pclient) { int ret = 0; printk("yy_i2c_remove is run\n"); /* 删除设备 */ cdev_del(&yy_i2c_cdev.cdev); unregister_chrdev_region(yy_i2c_cdev.devid, 1); /* 注销掉类和设备 */ device_destroy(yy_i2c_cdev.class, yy_i2c_cdev.devid); class_destroy(yy_i2c_cdev.class); return ret; } /* 传统匹配方式ID列表 */ static const struct i2c_device_id ap3216c_id[] = { {"alientek,ap3216c", 0}, {} }; /* 设备树匹配列表 */ static const struct of_device_id ap3216c_of_match[] = { { .compatible = "alientek,ap3216c" }, { /* Sentinel */ } }; /* i2c驱动结构体 */ static struct i2c_driver yy_i2c_drv = { .probe = yy_i2c_probe, .remove = yy_i2c_remove, .driver = { .owner = THIS_MODULE, .name = "ap3216c", .of_match_table = ap3216c_of_match, }, .id_table = ap3216c_id, }; static int __init init_yy_i2c(void) { int ret = 0; printk("init_yy_i2c_dev is run\n"); ret = i2c_add_driver(&yy_i2c_drv); return ret; } static void __exit exit_yy_i2c(void) { printk("exit_yy_i2c is run\n"); i2c_del_driver(&yy_i2c_drv); } module_init(init_yy_i2c); module_exit(exit_yy_i2c);
编译并insmod ko文件后会在/dev目录下生成yy_i2c节点,通过读写该节点就能读写ap3216c的寄存器。在/sys/bus/i2c/drivers目录目录下也会生成ap3216c的驱动目录,如下所示:
linux 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设备的类型 /* Notifies the driver that a new bus has appeared or is about to be * removed. You should avoid using this if you can, it will probably * be removed in a near future. */ 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 *); /* Alert callback, for example for the SMBus alert protocol. * The format and meaning of the data value depends on the protocol. * For the SMBus alert protocol, there is a single bit of data passed * as the alert response's low bit ("event flag"). */ void (*alert)(struct i2c_client *, unsigned int data); /* a ioctl like command that can be used to perform specific functions * with the device. */ 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 (次设备)的一个链表头 };
i2c_adapter、i2c_client、i2c_driver代表i2c控制器设备、i2c从设备、i2c从设备驱动,linux 是按设备、驱动、总线的方式来管理设备和驱动,现在还需要一个i2c总线。
系统已经定义了一条i2c总线,定义在drivers\i2c\i2c-core.c中,如下所示:
这个图i2c总线实际上就是bus_type,注册后会在/sys/bus/目录下生成i2c目录。如下所示:
向系统注册的所有i2c_adapter、i2c_client、i2c_driver都会挂载到这条i2c总线下面。/sys/bus/i2c目录下有i2c-0到i2c-10总共11个soc自带的i2c控制器,0-002e是挂载在i2c-0下面的i2c从设备。如下所示:
i2c驱动程序分析
下面以hi3559为例说明i2c驱动:
hi3559的i2c设备树文件为:\Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\arch\arm64\boot\dts\hisilicon\hi3559av100.dtsi。i2c0节点如下所示:
内核会以platform_device的形式将这个i2c节点加载到内核,对应的sysfs位置为:/sys/devices/platform/soc/soc:amba/12110000.i2c。
通过compatible可以找到该i2c的驱动文件是:\Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\drivers\i2c\busses\i2c-hibvt.c。
linux设备树中所有设备节点都是以platform_device的形式加载到内核中的,内核中有个platform_driver对应这个设备的驱动。hi3559的i2c的platform_driver如下所示:
只要compatible匹配,就会执行platform_driver的probe函数。这个platform_driver并不是I2c控制器的驱动,它像一个药引子一样,只是引导驱动实现,实际驱动是在probe函数中实现的。
i2c-hibvt.c定义了一个hibvt_i2c_dev结构体,这个结构体就代表hi3559自己i2c控制器,里面有个struct i2c_adapter adap,这个adap可以理解为内核提供通用的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函数主要做两件事情,一是向系统注册hibvt_i2c_dev中的adap,另一件事查找i2c设备树中有没有挂载在该i2c控制器下面的i2c设备,如果有,就会生成一个i2c_client。
hi3559 i2c驱动代码分析
i2c-hibvt.c
-->hibvt_i2c_probe
-->i2c_add_adapter
-->i2c_register_adapter
hibvt_i2c_probe函数最核心的功能是调用i2c_add_adapter向系统注册hibvt_i2c_dev中的adap,adap注册成后会在sysfs下生成一个/sys/devices/platform/soc/soc:amba/12110000.i2c/i2c-0,这个i2c0就表示i2c控制器0。下面分析一下i2c_add_adapter中的i2c_register_adapter函数:
static int i2c_register_adapter(struct i2c_adapter *adap) { int res = -EINVAL; /* Can't register until after driver model init */ if (WARN_ON(!is_registered)) { res = -EAGAIN; goto out_list; } /* Sanity checks */ if (WARN(!adap->name[0], "i2c adapter has no name")) goto out_list; if (!adap->algo) { pr_err("adapter '%s': no algo supplied!\n", adap->name); goto out_list; } if (!adap->lock_ops) adap->lock_ops = &i2c_adapter_lock_ops; rt_mutex_init(&adap->bus_lock); rt_mutex_init(&adap->mux_lock); mutex_init(&adap->userspace_clients_lock); INIT_LIST_HEAD(&adap->userspace_clients); /* Set default timeout to 1 second if not already set */ if (adap->timeout == 0) adap->timeout = HZ; dev_set_name(&adap->dev, "i2c-%d", adap->nr); adap->dev.bus = &i2c_bus_type; //i2c_bus_type就是上面提到的/sys/bus/i2c adap->dev.type = &i2c_adapter_type; res = device_register(&adap->dev); //注册adap中的dev, if (res) { pr_err("adapter '%s': can't register device (%d)\n", adap->name, res); goto out_list; } dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name); pm_runtime_no_callbacks(&adap->dev); pm_suspend_ignore_children(&adap->dev, true); pm_runtime_enable(&adap->dev); #ifdef CONFIG_I2C_COMPAT res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev, adap->dev.parent); if (res) dev_warn(&adap->dev, "Failed to create compatibility class link\n"); #endif i2c_init_recovery(adap); /* create pre-declared device nodes */ of_i2c_register_devices(adap); //从设备树中查找i2c节点下是否有i2c子设备,如果有,则生成i2c_client i2c_acpi_register_devices(adap); i2c_acpi_install_space_handler(adap); if (adap->nr < __i2c_first_dynamic_bus_num) i2c_scan_static_board_info(adap); /* Notify drivers */ mutex_lock(&core_lock); bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); mutex_unlock(&core_lock); return 0; out_list: mutex_lock(&core_lock); idr_remove(&i2c_adapter_idr, adap->nr); mutex_unlock(&core_lock); return res; }
of_i2c_register_devices函数是从设备树i2c节点下有无i2c从设备节点,如果有,就创建一个i2c_client代表一个i2c从设备。
static void of_i2c_register_devices(struct i2c_adapter *adap) { struct device_node *bus, *node; struct i2c_client *client; /* Only register child devices if the adapter has a node pointer set */ if (!adap->dev.of_node) return; dev_dbg(&adap->dev, "of_i2c: walking child nodes\n"); bus = of_get_child_by_name(adap->dev.of_node, "i2c-bus"); if (!bus) bus = of_node_get(adap->dev.of_node); for_each_available_child_of_node(bus, node) { /*从i2c控制器节点中查找i2c从设备节点*/ if (of_node_test_and_set_flag(node, OF_POPULATED)) continue; client = of_i2c_register_device(adap, node); /*如果有i2c从设备,这注册这个从设备,并生成一个i2c_client*/ if (IS_ERR(client)) { dev_warn(&adap->dev, "Failed to create I2C device for %s\n", node->full_name); of_node_clear_flag(node, OF_POPULATED); } } of_node_put(bus); }
i2c_adapter注册完成后,还要在/dev下面生成i2c字符设备节点,这些节点的生成代码是\Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\drivers\i2c\i2c-dev.c
i2c从设备驱动i2c_driver注册过程
可以调用i2c_add_driver函数去注册一个i2c的从设备驱动,i2c_add_driver实际上是调用了i2c_register_driver,下面分析一下这个函数
int i2c_register_driver(struct module *owner, struct i2c_driver *driver) { int res; /* Can't register until after driver model init */ if (WARN_ON(!is_registered)) return -EAGAIN; /* add the driver to the list of i2c drivers in the driver core */ driver->driver.owner = owner; driver->driver.bus = &i2c_bus_type; //i2c_bus_type就是上面提到的i2c总线 INIT_LIST_HEAD(&driver->clients); /* When registration returns, the driver core * will have called probe() for all matching-but-unbound devices. */ res = driver_register(&driver->driver); //注册i2c_driver下面的device_driver,如果设备和驱动匹配成功,会调用i2c_driver的prob函数,i2c_bus_type指定了bus的prob函数,这个函数会调用i2c_driver的prob函数 if (res) return res; pr_debug("driver [%s] registered\n", driver->driver.name); /* Walk the adapters that are already present */ i2c_for_each_dev(driver, __process_new_driver); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理