I2C是咱们在单片机开发时时常会用到的通信接口,用来与一些字符型设备进行通讯,好比:陀螺仪、温度传感器等等,一样的在Linux下I2C驱动也是十分重要的。有了操做系统的加持,咱们不用像在32上那样去软件实现IIC协议,更多的是去学习Linux I2c的驱动框架,首先咱们先来了解一下它的驱动框架,以下图
Linux 的I2C体系结构主要分为3个部分node
i2c相关的代码主要存放在/driver/i2c
linux
I2C总线驱动重点是I2C适配器(也就是SOC的I2C接口控制器)
用到两个重要的数据结构:i2c_adapter和i2c_algorithm
一、i2c_adapterweb
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 */ struct rt_mutex bus_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; };
二、i2c_algorithm数据结构
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适配器的传输函数,能够经过此函数来完成与IIC设备之间的通讯
smbus_xfer就是SMBUS总线的传输函数app
I2C总线驱动,或者说 I2C适配器驱动的主要工做就是初始化 i2c_adapter结构体变量,而后设置 i2c_algorithm中的 master_xfer函数。完成之后经过 i2c_add_numbered_adapter或 i2c_add_adapter这两个函数向系统注册设置好的 i2c_adapter
注册函数:框架
int i2c_add_adapter(struct i2c_adapter *); int i2c_add_numbered_adapter(struct i2c_adapter *);
删除I2C适配器:ide
void i2c_del_adapter(struct i2c_adapter *);
通常SOC的I2C总线驱动都是由半导体厂商编写的,不须要用户去编写,对于咱们这些SOC使用者来讲是被屏蔽掉的,咱们主要专一于I2C设备驱动便可svg
主要关注两个数据结构: i2c_client和i2c_driver
一、i2c_client函数
struct i2c_client { unsigned short flags; /* div., see below */ unsigned short addr; /* chip address - NOTE: 7bit */ /* addresses are stored in the */ /* _LOWER_ 7 bits */ char name[I2C_NAME_SIZE]; struct i2c_adapter *adapter; /* the adapter we sit on */ struct device dev; /* the device structure */ int irq; /* irq issued by device */ struct list_head detected; #if IS_ENABLED(CONFIG_I2C_SLAVE) i2c_slave_cb_t slave_cb; /* callback for slave mode */ #endif };
二、i2c_driver
相似于platform_driver,是咱们编写设备驱动重点要处理的内容学习
struct i2c_driver { unsigned int class; /* Notifies the driver that a new bus has appeared. You should avoid * using this, it will be removed in a near future. */ int (*attach_adapter)(struct i2c_adapter *) __deprecated; /* Standard driver model interfaces */ int (*probe)(struct i2c_client *, const struct i2c_device_id *); int (*remove)(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"). */ 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; const struct i2c_device_id *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; };
这里面也有设备和驱动匹配成功后会执行的probe函数
id_table是传统的未使用设备树的设备匹配ID表
对于咱们I2C设备驱动编写人来讲,重点工做是构建i2c_driver
注册i2c_driver,使用i2c_register_driver
int i2c_register_driver(struct module *, struct i2c_driver *);
也能够使用i2c_add_driver
#define i2c_add_driver(driver) \ i2c_register_driver(THIS_MODULE, driver)
实际也是调用i2c_register_driver
注销i2c_driver,使用i2c_del_driver
void i2c_del_driver(struct i2c_driver *);
I2C设备和驱动的匹配过程是由I2C核心来完成的,\drivers\i2c\i2c-core.c,I2C核心提供了一些与具体硬件无关的API函数
一、 i2c_adapter注册 /注销函数
int i2c_add_adapter(struct i2c_adapter *adapter) int i2c_add_numbered_adapter(struct i2c_adapter *adap) void i2c_del_adapter(struct i2c_adapter * adap)
二、 i2c_driver注册 /注销函数
int i2c_register_driver(struct module *owner, struct i2c_driver *driver) int i2c_add_driver (struct i2c_driver *driver) void i2c_del_driver(struct i2c_driver *driver)
设备和驱动的匹配过程也是有I2C总线完成的,I2C总线的数据结构为i2c_bus_type
struct bus_type i2c_bus_type = { .name = "i2c", .match = i2c_device_match, .probe = i2c_device_probe, .remove = i2c_device_remove, .shutdown = i2c_device_shutdown, };
i2c_device_match函数就是总线额定设备和驱动匹配函数
I2C适配器就是SOC的I2C控制器驱动
在设备树中找到IMX6U的I2C1控制器节点
i2c1: i2c@021a0000 { #address-cells = <1>; #size-cells = <0>; compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c"; reg = <0x021a0000 0x4000>; interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6UL_CLK_I2C1>; status = "disabled"; };
重点关注compatible 属性值,适配器驱动将根据这个来进行驱动和设备的匹配
在在\drivers\i2c\busses\i2c-imx.c中有以下的代码
static struct platform_device_id imx_i2c_devtype[] = { { .name = "imx1-i2c", .driver_data = (kernel_ulong_t)&imx1_i2c_hwdata, }, { .name = "imx21-i2c", .driver_data = (kernel_ulong_t)&imx21_i2c_hwdata, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, imx_i2c_devtype); static const struct of_device_id i2c_imx_dt_ids[] = { { .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, }, { .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, }, { .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, i2c_imx_dt_ids); static struct platform_driver i2c_imx_driver = { .probe = i2c_imx_probe, .remove = i2c_imx_remove, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = i2c_imx_dt_ids, .pm = IMX_I2C_PM, }, .id_table = imx_i2c_devtype, };
其中的i2c_imx_dt_ids就是根据设备树进行匹配的匹配表
咱们能够看出imx6的i2c适配器驱动是一个典型的platform驱动
在probe函数中的主要工做
static struct i2c_algorithm i2c_imx_algo = { .master_xfer = i2c_imx_xfer, .functionality = i2c_imx_func, };
functionality用于返回此I2C适配器支持什么样的通讯协议,这里的就是 i2c_imx_func
重点关注i2c_imx_xfer,最终就是经过此函数来完成与I2C设备通讯
在BSP里面使用i2c_board_info结构体来描述一个具体的 I2C设备。
struct i2c_board_info { char type[I2C_NAME_SIZE]; unsigned short flags; unsigned short addr; void *platform_data; struct dev_archdata *archdata; struct device_node *of_node; struct fwnode_handle *fwnode; int irq; };
type和addr这两个成员变量必须设置,一个是I2C设备名字,一个是I2C器件地址
通常使用宏I2C_BOARD_INFO来设置
#define I2C_BOARD_INFO(dev_type, dev_addr) .type = dev_type, .addr = (dev_addr)
例子:static struct i2c_board_info mx27_3ds_i2c_camera = { I2C_BOARD_INFO("ov2640", 0x30), };
名字ov2640,期间地址0x30
I2C设备描述信息经过建立对应的节点
例子:
&i2c1 { clock-frequency = <100000>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c1>; status = "okay"; mag3110@0e { compatible = "fsl,mag3110"; reg = <0x0e>; position = <2>; }; ap3216c@1e { compatible = "ap3216c"; reg = <0x1e>; }; };
在I2C1上两个节点mag3110和ap3216c,重点关注compatible和reg,一个用于匹配驱动,一个用于设置器件地址
I2C设备驱动首先要作的就是初始化 i2c_driver并向 Linux内核注册。当设备和驱动匹配之后 i2c_driver里面的 probe函数就会执行
通常须要在probe函数中初始化I2C设备,要初始化I2C设备就必须可以对I2C设备寄存器进行读写操做,这里就要用到 i2c_transfer函数了
i2c_transfer函数最终会调用I2C适配器中的master_xfer函数,对于imx6u来讲就是 i2c_imx_xfer这个函数
原型以下:
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
adap:所使用的I2C适配器,i2c_client会保存其对应的i2c_adapter
msgs:I2C要发送的一个或多个消息
num:消息数量
重点关注msgs这个参数,这是一个i2c_msg类型的指针,I2C进行数据收发就是消息的传递,Linux内核使用i2c_msg结构体来描述一个消息,结构体内容以下
struct i2c_msg { __u16 addr; /* 从机地址*/ __u16 flags; /*标志位*/ #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */ #define I2C_M_RD 0x0001 /* read data, from slave to master */ #define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */ #define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */ __u16 len; /*消息长度(本msg)*/ __u8 *buf; /* 消息数据*/ };
还有两个API函数分别用于I2C数据的收发操做,这两个函数最终都会调 i2c_transfer
int i2c_master_send(const struct i2c_client *client, const char *buf, int count); int i2c_master_recv(const struct i2c_client *client, char *buf, int count);
注意count要小于64KB,由于i2c_msg的len成员变量是一个u16类型的数据
咱们要实现AP3216C的设备驱动, AP3216C是由DYNA IMAGE推出的一款传感器,其支持环境光强度 (ALS)、接近距离 (PS)和红外线强度 (IR)这三个环境参数检测。该芯片能够经过IIC接口与主控相连,并支持中断
一、pinctrl子系统
pinctrl_i2c1: i2c1grp { fsl,pins = < MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0 MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0 >; };
设置引脚为I2C的功能
二、i2c1节点的设置
&i2c1 { clock-frequency = <100000>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c1>; status = "okay"; mag3110@0e { compatible = "fsl,mag3110"; reg = <0x0e>; position = <2>; }; fxls8471@1e { compatible = "fsl,fxls8471"; reg = <0x1e>; position = <0>; interrupt-parent = <&gpio5>; interrupts = <0 8>; }; ap3216c@1e { compatible = "ap3216c"; reg = <0x1e>; }; };
在i2c1节点中添加ap3216c节点
clock-frequency属性为I2C的频率,这里设置为100KHZ
pinctrl-0属性指定pinctrl节点
1e为I2C设备的地址
一、ap3216c_reg.h
存放器件相关寄存器地址
#ifndef AP3216C_REG_H #define AP3216C_REG_H #define AP3216C_ADDR 0X1E /* AP3216C器件地址 */ /* AP3316C寄存器 */ #define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器 */ #define AP3216C_INTSTATUS 0X01 /* 中断状态寄存器 */ #define AP3216C_INTCLEAR 0X02 /* 中断清除寄存器 */ #define AP3216C_IRDATALOW 0x0A /* IR数据低字节 */ #define AP3216C_IRDATAHIGH 0x0B /* IR数据高字节 */ #define AP3216C_ALSDATALOW 0x0C /* ALS数据低字节 */ #define AP3216C_ALSDATAHIGH 0X0D /* ALS数据高字节 */ #define AP3216C_PSDATALOW 0X0E /* PS数据低字节 */ #define AP3216C_PSDATAHIGH 0X0F /* PS数据高字节 */ #endif
二、ap2116c_driver.c
#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/i2c.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #include "ap3216c_reg.h" #define AP3216C_CNT 1 #define AP3216C_NAME "ap3216c" struct ap3216c_dev { dev_t devid; struct cdev cdev; struct class *class; struct device *device; struct device_node *nd; int major; void *private_data; unsigned short ir,als,ps; }; static struct ap3216c_dev ap3216cdev; static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len) { int ret; struct i2c_msg msg[2]; struct i2c_client *client = (struct i2c_client *)dev->private_data; /*msg[0] addr to read*/ msg[0].addr = client->addr; msg[0].flags = 0; msg[0].buf = ® msg[0].len = 1; /*msg[1] read data*/ msg[1].addr = client->addr; 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(KERN_EMERG "i2c read failed=%d reg %06x len=%d \n", ret, reg, len); ret = -EREMOTEIO; } return ret; } static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, int len) { u8 b[256]; struct i2c_msg msg; struct i2c_client *client = (struct i2c_client *)dev->private_data; b[0] = reg; memcpy(&b[1], buf, len); msg.addr = client->addr; msg.flags = 0; msg.buf = b; msg.len = len + 1; return i2c_transfer(client->adapter, &msg, 1); } static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg) { u8 data = 0; ap3216c_read_regs(dev, reg, &data, 1); return data; #if 0 struct i2c_client *client = (struct i2c_client *)dev->private_data; return i2c_smbus_read_byte_data(client, reg); #endif } static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data) { u8 buf = 0; buf = data; ap3216c_write_regs(dev, reg, &buf, 1); } void ap3216c_readdata(struct ap3216c_dev *dev) { unsigned char i = 0; unsigned char buf[6]; for(i=0; i<6; i++) { buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i); } if(buf[0] & 0x80) dev->ir = 0; else //read IR data dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0x03); dev->als = ((unsigned short)buf[3] << 8) | buf[2]; //read ALS data if(buf[4] & 0x40) dev->ps = 0; else //read PS data dev->ps = ((unsigned short)(buf[5] & 0x3F) << 4) | (buf[4] & 0x0F); } static int ap3216c_open(struct inode *inode, struct file *filp) { filp->private_data = &ap3216cdev; /*init AP3216C*/ ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04); mdelay(50);//at lease 10ms ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x03); return 0; } static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off) { short data[3]; long err = 0; struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data; ap3216c_readdata(dev); data[0] = dev->ir; data[1] = dev->als; data[2] = dev->ps; err = copy_to_user(buf, data, sizeof(data)); return 0; } static int ap3216c_release(struct inode *inode, struct file *filp) { return 0; } static const struct file_operations ap3216c_ops = { .owner = THIS_MODULE, .open = ap3216c_open, .read = ap3216c_read, .release = ap3216c_release, }; /*ap3216c_probe*/ static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id) { /*1.get device id*/ if(ap3216cdev.major) { ap3216cdev.devid = MKDEV(ap3216cdev.major, 0); register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME); } else { alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME); ap3216cdev.major = MAJOR(ap3216cdev.devid); } /*2.register device*/ cdev_init(&ap3216cdev.cdev, &ap3216c_ops); cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT); /*3.create class*/ ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME); if(IS_ERR(ap3216cdev.class)) { return PTR_ERR(ap3216cdev.class); } /*4.create device*/ ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME); if(IS_ERR(ap3216cdev.device)) { return PTR_ERR(ap3216cdev.device); } ap3216cdev.private_data = client; return 0; } /*ap3216c_remove*/ static int ap3216c_remove(struct i2c_client *client) { /*delete device*/ cdev_del(&ap3216cdev.cdev); unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT); /*unregister class and device*/ device_destroy(ap3216cdev.class, ap3216cdev.devid); class_destroy(ap3216cdev.class); return 0; } /* 传统匹配方式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 */ } }; static struct i2c_driver ap3216c_driver = { .probe = ap3216c_probe, .remove = ap3216c_remove, .driver = { .owner = THIS_MODULE, .name = "ap3216c", .of_match_table = ap3216c_of_match, }, .id_table = ap3216c_id, }; static int __init ap3216c_init(void) { int ret = 0; ret = i2c_add_driver(&ap3216c_driver); return ret; } static void __exit ap3216c_exit(void) { i2c_del_driver(&ap3216c_driver); } module_init(ap3216c_init); module_exit(ap3216c_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("gyy");
#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "sys/ioctl.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" #include <poll.h> #include <sys/select.h> #include <sys/time.h> #include <signal.h> #include <fcntl.h> int main(int argc, char *argv[]) { int fd; char *filename; unsigned short databuf[3]; unsigned short ir, als, ps; int ret = 0; if (argc != 2) { printf("Error Usage!\r\n"); return -1; } filename = argv[1]; fd = open(filename, O_RDWR); if(fd < 0) { printf("can't open file %s\r\n", filename); return -1; } while (1) { ret = read(fd, databuf, sizeof(databuf)); if(ret == 0) { /* 数据读取成功 */ ir = databuf[0]; /* ir传感器数据 */ als = databuf[1]; /* als传感器数据 */ ps = databuf[2]; /* ps传感器数据 */ printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps); } usleep(200000); /*200ms */ } close(fd); /* 关闭文件 */ return 0; }
应用程序部分的逻辑比较简单就是循环调用read函数从模块读取数据并打印
在宋宝华老师的Linux设备驱动开发详解中有这么一张图指明了I2C设备各个结构体的关系
仔细阅读这个图你会发现I2C设备的驱动框架就在你的心中展开