
Linux的总线基本都遵循分层分离原则。


可以看到I2C子系统也是分层分离。
在STM32的I2C驱动中,一般要编写两部分代码,一部分是STM32的I2C控制器代码,一部分跟具体芯片相关的代码。 跟I2C控制器驱动的代码一旦写好,后续使用就不用修改
Linux中的I2C驱动也是这样做的,抽象出了两部分 : I2C适配器驱动 和 I2C设备驱动
I2C适配器驱动是跟芯片控制器相关的驱动,一般是由芯片厂商实现添加进linux内核源码中。
而一般工程师要实现的就是I2C设备驱动
include\linux\i2c.h
structi2c_adapter {structmodule *owner;unsignedintclass;/* classes to allow probing for */conststructi2c_algorithm *algo;/* the algorithm to access the bus */void *algo_data;/* data fields that are valid for all devices */conststructi2c_lock_operations *lock_ops;structrt_mutexbus_lock;structrt_mutexmux_lock;int timeout; /* in jiffies */int retries;structdevicedev;/* the adapter device */int nr;char name[48];structcompletiondev_released;structmutexuserspace_clients_lock;structlist_headuserspace_clients;structi2c_bus_recovery_info *bus_recovery_info;conststructi2c_adapter_quirks *quirks;structirq_domain *host_notify_domain;};#define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev)struct i2c_adapter描述I2C适配器驱动struct i2c_algorithm *algo这是imx6ull的I2C适配器驱动实现:drivers\i2c\busses\i2c-imx.c
staticstructplatform_driveri2c_imx_driver = { .probe = i2c_imx_probe, .remove = i2c_imx_remove, .driver = { .name = DRIVER_NAME, .pm = I2C_IMX_PM_OPS, .of_match_table = i2c_imx_dt_ids, }, .id_table = imx_i2c_devtype,};staticint __init i2c_adap_imx_init(void){return platform_driver_register(&i2c_imx_driver);}subsys_initcall(i2c_adap_imx_init);staticvoid __exit i2c_adap_imx_exit(void){ platform_driver_unregister(&i2c_imx_driver);}module_exit(i2c_adap_imx_exit);可以看到i2c适配器驱动也是使用platform总线实现的。
适配器算法:
structi2c_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,unsignedshort 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完成smbus_xfer进行传输I2C设备驱动则主要关注以下数据结构:
structi2c_client {unsignedshort flags; /* div., see below */unsignedshort addr; /* chip address - NOTE: 7bit *//* addresses are stored in the *//* _LOWER_ 7 bits */char name[I2C_NAME_SIZE];structi2c_adapter *adapter;/* the adapter we sit on */structdevicedev;/* the device structure */int irq; /* irq issued by device */structlist_headdetected;#if IS_ENABLED(CONFIG_I2C_SLAVE)i2c_slave_cb_t slave_cb; /* callback for slave mode */#endif};struct i2c_client描述设备信息(硬件信息),类型platform平台总线的platform_devicestructi2c_driver {unsignedintclass;/* Standard driver model interfaces */int (*probe)(struct i2c_client *, conststruct i2c_device_id *);int (*remove)(struct i2c_client *);/* New driver model interface to aid the seamless removal of the * current probe()'s, more commonly unused than used second parameter. */int (*probe_new)(struct i2c_client *);/* driver model interfaces that don't relate to enumeration */void (*shutdown)(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"). * For the SMBus Host Notify protocol, the data corresponds to the * 16-bit payload data reported by the slave device acting as master. */void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,unsignedint data);/* a ioctl like command that can be used to perform specific functions * with the device. */int (*command)(struct i2c_client *client, unsignedint cmd, void *arg);structdevice_driverdriver;conststructi2c_device_id *id_table;/* Device detection callback for automatic device creation */int (*detect)(struct i2c_client *, struct i2c_board_info *);constunsignedshort *address_list;structlist_headclients;bool disable_i2c_core_irq_mapping;};注册/注销I2C驱动:
inti2c_add_driver(struct i2c_driver *driver)voidi2c_del_driver(struct i2c_driver *driver)内核提供的I2C传输接口:
inti2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num){int ret;if (adap->algo->master_xfer) {#ifdef DEBUGfor (ret = 0; ret < num; ret++) { dev_dbg(&adap->dev,"master_xfer[%d] %c, addr=0x%02x, len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD) ? 'R' : 'W', msgs[ret].addr, msgs[ret].len, (msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : ""); }#endifif (in_atomic() || irqs_disabled()) { ret = i2c_trylock_bus(adap, I2C_LOCK_SEGMENT);if (!ret)/* I2C activity is ongoing. */return -EAGAIN; } else { i2c_lock_bus(adap, I2C_LOCK_SEGMENT); } ret = __i2c_transfer(adap, msgs, num); i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);return ret; } else { dev_dbg(&adap->dev, "I2C level transfers not supported\n");return -EOPNOTSUPP; }}if (adap->algo->master_xfer)master_xfer函数i2c读写关键数据结构:structi2c_msg { __u16 addr; /* slave address */ __u16 flags;#define I2C_M_RD 0x0001 /* read data, from slave to master *//* I2C_M_RD is guaranteed to be 0x0001! */#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */#define I2C_M_DMA_SAFE 0x0200 /* the buffer of this message is DMA safe *//* makes only sense in kernelspace *//* userspace buffers are copied anyway */#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */ __u16 len; /* msg length */ __u8 *buf; /* pointer to msg data */};注释很清楚解析了成员是干什么用的。
I2C读写模板:
staticintap3216c_i2c_write_reg(struct i2c_client *client, uint8_t reg_addr, uint8_t data){int ret = 0;#if 0 structi2c_msgmsgs[2]; msgs[0].addr = client->addr; msgs[0].buf = ®_addr; /* 发送寄存器地址 */ msgs[0].flags = 0; /* I2C方向 :写数据 */ msgs[0].len = sizeof(reg_addr); msgs[1].addr = client->addr; msgs[1].buf = &data; /* 写数据 */ msgs[1].flags = 0; /* I2C方向 :写数据 */ msgs[1].len = sizeof(data);#elseuint8_t buf[2];structi2c_msgmsg; buf[0] = reg_addr; buf[1] = data; msg.addr = client->addr; msg.buf = buf; /* 发送寄存器地址 */ msg.flags = 0; /* I2C方向 :写数据 */ msg.len = 2;#endif ret = i2c_transfer(client->adapter, &msg, 1);if (ret < 0)return ret;elseif (ret != 1)return -EIO;return0;}staticintap3216c_i2c_read_reg(struct i2c_client *client, uint8_t reg_addr, uint8_t *data){int ret = 0;structi2c_msgmsgs[2]; msgs[0].addr = client->addr; msgs[0].buf = ®_addr; /* 发送寄存器地址 */ msgs[0].flags = 0; /* I2C方向 :写数据 */ msgs[0].len = sizeof(reg_addr); msgs[1].addr = client->addr; msgs[1].buf = data; /* 读取寄存器数据 */ msgs[1].flags = I2C_M_RD; /* I2C方向 :读数据 */ msgs[1].len = 1; ret = i2c_transfer(client->adapter, msgs, 2);if (ret < 0)return ret;elseif (ret != 2)return -EIO;return0;}arch\arm\boot\dts\imx6ull.dtsi
适配器驱动描述:
i2c1: i2c@21a0000 {#address-cells = <1>;#size-cells = <0>; compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c"; reg = <0x21a00000x4000>; interrupts = 36 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6UL_CLK_I2C1>; status = "disabled";};描述设备驱动的设备树节点:需要用户编写
&i2c1 { clock-frequency = <100000>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c1>; status = "okay"; #address-cells = <1>;#size-cells = <0>; /* 设备树修改查看是否修改成功,查看内核目录/sys/bus/i2c/devices/看是否存在0x1E地址的设备 */ ap3216c@1E { compatible = "xgj-ap3216c"; reg = <0x1E>; interrupt-parent = <&gpio5>; interrupts = <2 IRQ_TYPE_EDGE_BOTH>; //IRQ_TYPE_EDGE_RISING status = "okay"; }; mpu6050@68 { compatible = "xgj,mpu6050"; status = "okay"; reg = <0x68>; interrupt-parent = <&gpio5>; interrupts = <0 IRQ_TYPE_LEVEL_LOW>; }; };#include<linux/init.h>#include<linux/module.h>#include<linux/fs.h>#include<linux/cdev.h>#include<linux/uaccess.h>#include<linux/types.h>#include<linux/kernel.h>#include<linux/delay.h>#include<linux/ide.h>#include<linux/errno.h>#include<linux/gpio.h>#include<asm/mach/map.h>#include<linux/of.h>#include<linux/of_address.h>#include<linux/of_gpio.h>#include<asm/io.h>#include<linux/device.h>#include<linux/timer.h>#include<linux/jiffies.h>#include<linux/platform_device.h>#include<linux/of_irq.h>#include<linux/wait.h>#include<linux/sched/signal.h>#include<linux/poll.h>#include<linux/atomic.h>#include<linux/i2c.h>#include<linux/input.h>structxxx_device {int irq; /* 中断号 */int gpio;dev_t dev_no; /* 设备号 */structcdevchrdev;structclass *class;structmutexm_lock;structi2c_client *client;};staticstructxxx_device *xxx_dev;staticintxxx_i2c_write_reg(struct i2c_client *client, uint8_t reg, uint8_t data){int ret = 0;uint8_t w_buf[2];structi2c_msgmsg; w_buf[0] = reg; w_buf[1] = data; msg.addr = client->addr; msg.buf = w_buf; msg.flags = 0; /* I2C direction : write */ msg.len = sizeof(w_buf); ret = i2c_transfer(client->adapter, &msg, 1);if (ret < 0)return ret;elseif (ret != 1)return -EIO;return0;}staticintxxx_i2c_read_reg(struct i2c_client *client, uint8_t reg, uint8_t *data){int ret = 0;structi2c_msgmsgs[2]; msgs[0].addr = client->addr; msgs[0].buf = ® /* send regsiter */ msgs[0].flags = 0; /* I2C direction : write */ msgs[0].len = 1; msgs[1].addr = client->addr; msgs[1].buf = data; /* 读取寄存器数据 */ msgs[1].flags = I2C_M_RD; /* I2C direction : read */ msgs[1].len = 1; ret = i2c_transfer(client->adapter, msgs, 2); /* */if (ret < 0)return ret;elseif (ret != 2)return -EIO;return0;}staticint _drv_open(struct inode *node, struct file *file){int ret = 0; ... ...return0;}staticssize_t _drv_read(struct file *filp, char __user *buf, size_t size, loff_t *offset){int ret = 0; ...return ret;}/* 使驱动支持多路复用IO */static__poll_t _drv_poll(struct file *filp, struct poll_table_struct *wait){__poll_t mask = 0; ...return mask;}staticlong _drv_ioctl(struct file *filp, unsignedint cmd, unsignedlong arg){int ret = 0;switch (cmd) {case CMD1:break;case CMD2:break; ... ... }return ret;}staticint _drv_release(struct inode *node, struct file *filp){int ret = 0; ...return ret;}staticstructfile_operationsxxx_drv_ops = { .owner = THIS_MODULE, .open = _drv_open, .read = _drv_read, .poll = _drv_poll, .release = _drv_release,};staticint _driver_probe(struct i2c_client *client, conststruct i2c_device_id *id){int err = 0;structdevice *_dev;structdevice_node *_dts_node;/* 内核自动分配设备号 */ err = alloc_chrdev_region(&xxx_dev->dev_no, 0, 1, DEV_NAME);if (err < 0) { pr_err("Error: failed to register mbochs_dev, err: %d\n", err);goto out_free_irq; } cdev_init(&xxx_dev->chrdev, &xxx_drv_ops); err = cdev_add(&xxx_dev->chrdev, xxx_dev->dev_no, 1);if (err) {goto out_unregister; } xxx_dev->class = class_create(THIS_MODULE, DEV_NAME);if (IS_ERR(xxx_dev->class)) { err = PTR_ERR(xxx_dev->class);goto out_cdev_del; }/* 创建设备节点 */ _dev = device_create(xxx_dev->class, NULL, xxx_dev->dev_no, NULL, DEV_NAME);if (IS_ERR(_dev)) { /* 判断指针是否合法 */ err = PTR_ERR(_dev);goto out_class_del; }// mutex_init(&xxx_dev->m_lock); /* 初始化互斥锁 */ printk("%s probe success\r\n", DEV_NAME);goto out;out_class_del: class_destroy(xxx_dev->class);out_cdev_del: cdev_del(&xxx_dev->chrdev);out_unregister: unregister_chrdev_region(xxx_dev->dev_no, 1); /* 注销设备 */out_free_dev: kfree(xxx_dev); xxx_dev = NULL;out:#endifreturn err;}staticint _driver_remove(struct i2c_client *client){ device_destroy(xxx_dev->class, xxx_dev->dev_no); class_destroy(xxx_dev->class); cdev_del(&xxx_dev->chrdev); unregister_chrdev_region(xxx_dev->dev_no, 1); /* 注销设备 */ kfree(xxx_dev);return0;}/* 设备树的匹配列表 */staticstructof_device_idxxx_of_match_table[] = { {.compatible = "xxx"}, /* 通过设备树来匹配 */ {},};/* 传统匹配方式 ID 列表 ,即使不使用也要添加,不然probe匹配不成功 */staticconststructi2c_device_idxxx_id_table[] = { {"xxx", 0}, {},};staticstructi2c_driverxxx_driver = { .probe = _driver_probe, .remove = _driver_remove, .driver = { .name = xxx_DTS_COMPATIBLE, .owner = THIS_MODULE, .of_match_table = xxx_of_match_table, /* 通过设备树匹配 */ }, .id_table = xxx_id_table,};/* 入口函数 */staticint __init _driver_init(void){int ret = 0; ret = i2c_add_driver(&xxx_driver); /* 注册I2C驱动 */return ret;}/* 出口函数 */staticvoid __exit _driver_exit(void){ i2c_del_driver(&xxx_driver); /* 注销I2C驱动 */}module_init(_driver_init);module_exit(_driver_exit);MODULE_AUTHOR("Ares");MODULE_LICENSE("GPL");