闵行做网站建设,北海住房和城乡建设局官方网站,建设银行官网登录入口,长春公司做网站参考#xff1a;Linux之I2C驱动_linux i2c驱动_风间琉璃•的博客-CSDN博客
目录
一、I2C驱动框架简介
1.1 I2C总线驱动
1.2 I2C设备驱动
二、I2C总线-设备-驱动模型
2.1 i2c_driver
2.2 i2c_client
2.3 I2C 设备数据收发和处理
三、Linux I2C驱动程序模板… 参考Linux之I2C驱动_linux i2c驱动_风间琉璃•的博客-CSDN博客
目录
一、I2C驱动框架简介
1.1 I2C总线驱动
1.2 I2C设备驱动
二、I2C总线-设备-驱动模型
2.1 i2c_driver
2.2 i2c_client
2.3 I2C 设备数据收发和处理
三、Linux I2C驱动程序模板 一、I2C驱动框架简介
在 Linux 内核中 I2C 的体系结构分为 3 个部分
I2C 核心 I2C 核心提供了 I2C 总线驱动和设备驱动的注册、 注销方法I2C 总线驱动 I2C 总线驱动是对 I2C 硬件体系结构中适配器端的实现 适配器可由CPU 控制 甚至可以直接集成在 CPU 内部。I2C 总线驱动就是 SOC 的 I2C 控制器驱动也叫做 I2C 适配器驱动。I2C 设备驱动 I2C 设备驱动是对 I2C 硬件体系结构中设备端的实现 设备一般挂接在受 CPU 控制的 I2C 适配器上 通过 I2C 适配器与 CPU 交换数据。
1.1 I2C总线驱动
I2C 总线和 platform 总线类似 区别在于platform 总线是虚拟的一条总线 而 I2C 总线是实际 存在的。 对于使用 I2C 通信的设备 在驱动中直接使用 I2C 总结即可。 I2C 总线驱动的重点是 I2C 适配器驱动 主要涉及到两个结构体 i2c_adapter 和 i2c_algorithm。 在 Linux 内核中用 i2c_adapter 结构体来表示 I2C 适配器。 i2c_adapter 结构体定义在 include/linux/i2c.h 文件中
struct i2c_adapter {struct module *owner;unsigned int class; /* classes to allow probing for */const struct i2c_algorithm *algo; /* 总线访问算法 */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 类型的指针变量algo 对于一个 I2C 适配器 要对外提供读写 API 函数 设备驱动程序可以使用这些 API 函数来完成读写操作。 i2c_algorithm 是 I2C 适配器与 IIC 设备进行通信的方法。i2c_algorithm 结构体定义在 include/linux/i2c.h 文件中 struct i2c_algorithm
{......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 *);......
};
一般 SOC 的 I2C 总线驱动都是由半导体厂商编写的所以大多数只要专注于 I2C 设备驱动即可。
1.2 I2C设备驱动
在 I2C 设备驱动中主要有两个重要的结构体 i2c_client 和 i2c_driver。 i2c_client 是描述设备信息的i2c_driver 描述驱动内容。
当驱动和设备匹配成功后每检测到一个 I2C 设备就会给这个 I2C 设备分配一个i2c_client这个ic_client存储着这个设备的所有信息如芯片地址。i2c_client 结构体定义在include/linux/i2c.h 文件中
struct i2c_client {unsigned short flags; /* 标志 */unsigned short addr; /* 芯片地址 7 位存在低 7 位*/......char name[I2C_NAME_SIZE]; /* 名字 */struct i2c_adapter *adapter; /* 对应的 I2C 适配器 */struct device dev; /* 设备结构体 */int irq; /* 中断 */struct list_head detected;......
};
i2c_driver 类似 platform_driver是编写 I2C 设备驱动重点要处理的内容 i2c_driver 结构体定义在 include/linux/i2c.h 文件中
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 dont 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 responses low bit (eventflag). */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;
};
当 I2C 设备和驱动匹配成功以后 probe 函数就会执行。device_driver 驱动结构体如果使用设备树需要设置 device_driver 的of_match_table 成员变量即驱动的兼容(compatible)性。 未使用设备树的设备需要设置id_table 设备匹配 ID 表。
构造i2c_driver结构体里面会表明支持那些设备入口函数里注册i2c_driver结构体如果i2c_driver发现能够支持的i2c_client的话probe函数就被调用在probe函数里记录client信息、注册字符设备、注册file_operations、自动创建设备节点
I2C驱动程序与普通的字符设备驱动程序没有本质差别唯一差别发起数据传输时用到i2c_transfer该函数需要用到适配器i2c控制器probe函数被调用时内核会传入i2c_clienti2c_client里含有i2c控制器。
二、I2C总线-设备-驱动模型 2.1 i2c_driver
2c_driver表明能支持哪些设备 使用of_match_table来判断 设备树中某个I2C控制器节点下可以创建I2C设备的节点 如果I2C设备节点的compatible属性跟of_match_table的某项兼容则匹配成功 i2c_client.name跟某个of_match_table[i].compatible值相同则匹配成功 使用id_table来判断 i2c_client.name跟某个id_table[i].name值相同则匹配成功
i2c_driver跟i2c_client匹配成功后就调用i2c_driver.probe函数。
/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id xxx_id[] {{xxx, 0},{}
};/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] {{ .compatible xxx },{ /* Sentinel */ }
};/* i2c 驱动结构体 */
static struct i2c_driver xxx_driver {.probe xxx_probe,.remove xxx_remove,.driver {.owner THIS_MODULE,.name xxx,.of_match_table xxx_of_match, //使用设备树},.id_table xxx_id, //未使用设备树
};
2.2 i2c_client
i2c_client表示一个I2C设备创建i2c_client的方法有4种
1方法1通过设备树来创建常用
i2c1: i2c400a0000 {/* ... master properties skipped ... */clock-frequency 100000;flash50 {compatible atmel,24c256;reg 0x50;};pca9532: gpio60 {compatible nxp,pca9532;gpio-controller;#gpio-cells 2;reg 0x60;};};
向 i2c1 添加 flash 子节点flash50是子节点名字 后面的50是I2C 器件地址。compatible 属性值为atmel,24c256。reg属性也是设置I2C的器件地址的。I2C 设备节点的创建主要是 compatible 属性和 reg属性的设置 一个用于匹配驱动 一个用于设置器件地址。
2方法2
有时候无法知道该设备挂载哪个I2C bus下无法知道它对应的I2C bus number。 但是可以通过其他方法知道对应的i2c_adapter结构体。 可以使用下面两个函数来创建i2c_client
i2c_new_device static struct i2c_board_info sfe4001_hwmon_info {I2C_BOARD_INFO(max6647, 0x4e),};int sfe4001_init(struct efx_nic *efx){(...)efx-board_info.hwmon_client i2c_new_device(efx-i2c_adap, sfe4001_hwmon_info);(...)}
i2c_new_probed_device
static const unsigned short normal_i2c[] { 0x2c, 0x2d, I2C_CLIENT_END };static int usb_hcd_nxp_probe(struct platform_device *pdev){(...)struct i2c_adapter *i2c_adap;struct i2c_board_info i2c_info;(...)i2c_adap i2c_get_adapter(2);memset(i2c_info, 0, sizeof(struct i2c_board_info));strscpy(i2c_info.type, isp1301_nxp, sizeof(i2c_info.type));isp1301_i2c_client i2c_new_probed_device(i2c_adap, i2c_info,normal_i2c, NULL);i2c_put_adapter(i2c_adap);(...)}
差别 i2c_new_device会创建i2c_client即使该设备并不存在 i2c_new_probed_device 它成功的话会创建i2c_client并且表示这个设备肯定存在 I2C设备的地址可能发生变化比如AT24C02的引脚A2A1A0电平不一样时设备地址就不一样 可以罗列出可能的地址 i2c_new_probed_device使用这些地址判断设备是否存在
3方法3(不推荐)由i2c_driver.detect函数来判断是否有对应的I2C设备并生成i2c_client
4方法4通过用户空间(user-space)生成 调试时、或者不方便通过代码明确地生成i2c_client时可以通过用户空间来生成。
// 创建一个i2c_client, .name eeprom, .addr0x50, .adapter是i2c-3# echo eeprom 0x50 /sys/bus/i2c/devices/i2c-3/new_device// 删除一个i2c_client# echo 0x50 /sys/bus/i2c/devices/i2c-3/delete_device
2.3 I2C 设备数据收发和处理
在 I2C 设备驱动中首先要完成 i2c_driver 结构体的创建、 初始化和注册 当设备和驱动匹配成功后就会执行 probe 函数 probe 函数中就是执行字符设备驱动的一套流程。
一般需要在probe函数里面初始化 I2C 设备要初始化 I2C 设备需要使用 i2c_transfer 函数对 I2C 设备寄存器进行读写操作。 i2c_transfer 函数会调用 I2C 适配器中 i2c_algorithm里面的 master_xfer 函数 对于 I.MX6U 而言是 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 的数量 返回值 负值失败其他非负值发送的 msgs 数量 msgs参数是一个 i2c_msg 类型的指针参数Linux内核使用 i2c_msg 结构体 描述一个消息。
struct i2c_msg {__u16 addr; /* 从机地址 */__u16 flags; /* 标志 */#define I2C_M_TEN 0x0010#define I2C_M_RD 0x0001#define I2C_M_STOP 0x8000#define I2C_M_NOSTART 0x4000#define I2C_M_REV_DIR_ADDR 0x2000#define I2C_M_IGNORE_NAK 0x1000#define I2C_M_NO_RD_ACK 0x0800#define I2C_M_RECV_LEN 0x0400__u16 len; /* 消息(本 msg)长度 */__u8 *buf; /* 消息数据 */
};
使用i2c_transfer函数发送数据前要构建好 i2c_msg使用 i2c_transfer 进行 I2C 数据收发的模板
/* 设备结构体 */
struct xxx_dev {......void *private_data; /* 私有数据一般会设置为 i2c_client */
};/*
* description : 读取 I2C 设备多个寄存器数据
* param – dev : I2C 设备
* param – reg : 要读取的寄存器首地址
* param – val : 读取到的数据
* param – len : 要读取的数据长度
* return : 操作结果
*/
static int xxx_read_regs(struct xxx_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]第一条写消息发送要读取的寄存器首地址 */msg[0].addr client-addr; /* I2C 器件地址 */msg[0].flags 0; /* 标记为发送数据 */msg[0].buf reg; /* 读取的首地址 */msg[0].len 1; /* reg 长度 *//* msg[1]第二条读消息读取寄存器数据 */msg[1].addr client-addr; /* I2C 器件地址 */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 {ret -EREMOTEIO;}return ret;
}/*
* description : 向 I2C 设备多个寄存器写入数据
* param – dev : 要写入的设备结构体
* param – reg : 要写入的寄存器首地址
* param – buf : 要写入的数据缓冲区
* param – len : 要写入的数据长度
* return : 操作结果
*/
static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf,u8 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); /* 将要发送的数据拷贝到数组 b 里面 */msg.addr client-addr; /* I2C 器件地址 */msg.flags 0; /* 标记为写数据 */msg.buf b; /* 要发送的数据缓冲区 */msg.len len 1; /* 要发送的数据长度 */return i2c_transfer(client-adapter, msg, 1);
}
在设备结构体里面添加一个执行void的指针成员变量private_data此成员变量用于保存设备的私有数据在 I2C 设备驱动中一般将其指向 I2C 设备对应的i2c_client。 xxx_read_regs 函数用于读取 I2C 设备多个寄存器数据然后定义了一个i2c_msg 数组 2 个数组元素。如图I2C写时序图因为 I2C 读取数据的时候要先发送要读取的寄存器地址然后再读取数据所以需要准备两个 i2c_msg。一个用于发送寄存器地址一个用于读取寄存器值。
对于 msg[0] 将 flags设置为 0 表示写数据。 msg[0]的 addr 是 I2C 设备的器件地址 msg[0]的 buf 成员变量就是要读取的寄存器地址。 对于 msg[1] 将 flags 设置为 I2C_M_RD 表示读取数据。 msg[1]的 buf 成员变量用于保存读取到的数据 len 成员变量就是要读取的数据长度。 调用 i2c_transfer 函数完成 I2C 数据读操作。 xxx_write_regs 函数用于向 I2C 设备多个寄存器写数据。 数组 b 用于存放寄存器首地址和要发送的数据msg 的 addr设置为 I2C 器件地址。然后设置 msg 的 flags 为 0 也就是写数据。设置要发送的数据 也就是数组 b。设置 msg 的 len 为 len1 因为要加上一个字节的寄存器地址。 最后通过 i2c_transfer 函数完成向 I2C 设备的写操作。
另外还有两个API函数分别用于I2C数据的收发操作这两个函数都会调用i2c_transfer。
I2C 数据发送函数 i2c_master_send
int i2c_master_send(const struct i2c_client *client,const char *buf,int count) client I2C 设备对应的 i2c_client buf要发送的数据 count 要发送的数据字节数要小于 64KB以为 i2c_msg 的 len 成员变量是一个 u16(无 符号 16 位)类型的数据。 返回值 负值失败其他非负值发送的字节数 I2C 数据接收函数为 i2c_master_recv
int i2c_master_recv(const struct i2c_client *client,char *buf,int count) client I2C 设备对应的 i2c_client buf要接收的数据 count 要接收的数据字节数要小于 64KB以为 i2c_msg 的 len 成员变量是一个 u16(无 符号 16 位)类型的数据 返回值 负值失败其他非负值发送的字节数 三、Linux I2C驱动程序模板
i2c_drv.c
#include linux/i2c.h
#include linux/module.h
#include linux/poll.h#include linux/fs.h
#include linux/errno.h
#include linux/miscdevice.h
#include linux/kernel.h
#include linux/major.h
#include linux/mutex.h
#include linux/proc_fs.h
#include linux/seq_file.h
#include linux/stat.h
#include linux/init.h
#include linux/device.h
#include linux/tty.h
#include linux/kmod.h
#include linux/gfp.h
#include linux/gpio/consumer.h
#include linux/platform_device.h
#include linux/of_gpio.h
#include linux/of_irq.h
#include linux/interrupt.h
#include linux/irq.h
#include linux/slab.h
#include linux/fcntl.h
#include linux/timer.h/* 主设备号 */
static int major 0;
static struct class *my_i2c_class;struct i2c_client *g_client;static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);
struct fasync_struct *i2c_fasync;/* 实现对应的open/read/write等函数填入file_operations结构体 */
static ssize_t i2c_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int err;struct i2c_msg msgs[2];/* 初始化i2c_msg */err i2c_transfer(g_client-adapter, msgs, 2);/* copy_to_user */return 0;
}static ssize_t i2c_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;/* copy_from_user */struct i2c_msg msgs[2];/* 初始化i2c_msg */err i2c_transfer(g_client-adapter, msgs, 2);return 0;
}static unsigned int i2c_drv_poll(struct file *fp, poll_table * wait)
{//printk(%s %s line %d\n, __FILE__, __FUNCTION__, __LINE__);poll_wait(fp, gpio_wait, wait);//return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;return 0;
}static int i2c_drv_fasync(int fd, struct file *file, int on)
{if (fasync_helper(fd, file, on, i2c_fasync) 0)return 0;elsereturn -EIO;
}/* 定义自己的file_operations结构体 */
static struct file_operations i2c_drv_fops {.owner THIS_MODULE,.read i2c_drv_read,.write i2c_drv_write,.poll i2c_drv_poll,.fasync i2c_drv_fasync,
};static int i2c_drv_probe(struct i2c_client *client,const struct i2c_device_id *id)
{// struct device_node *np client-dev.of_node; 从client获取设备节点// struct i2c_adapter *adapter client-adapter; 从client获取控制器/* 记录client */g_client client;/* 注册字符设备 *//* 注册file_operations */major register_chrdev(0, 100ask_i2c, i2c_drv_fops); /* /dev/gpio_desc */my_i2c_class class_create(THIS_MODULE, 100ask_i2c_class);if (IS_ERR(my_i2c_class)) {printk(%s %s line %d\n, __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, 100ask_i2c);return PTR_ERR(my_i2c_class);}device_create(my_i2c_class, NULL, MKDEV(major, 0), NULL, myi2c); /* /dev/myi2c */return 0;
}static int i2c_drv_remove(struct i2c_client *client)
{/* 反注册字符设备 */device_destroy(my_i2c_class, MKDEV(major, 0));class_destroy(my_i2c_class);unregister_chrdev(major, 100ask_i2c);return 0;
}static const struct of_device_id myi2c_dt_match[] {{ .compatible 100ask,i2cdev },{},
};
static struct i2c_driver my_i2c_driver {.driver {.name 100ask_i2c_drv,.owner THIS_MODULE,.of_match_table myi2c_dt_match,},.probe i2c_drv_probe,.remove i2c_drv_remove,
};static int __init i2c_drv_init(void)
{/* 注册i2c_driver */return i2c_add_driver(my_i2c_driver);
}static void __exit i2c_drv_exit(void)
{/* 反注册i2c_driver */i2c_del_driver(my_i2c_driver);
}/* 7. 其他完善提供设备信息自动创建设备节点 */module_init(i2c_drv_init);
module_exit(i2c_drv_exit);MODULE_LICENSE(GPL);