写文章的目的是想通过记录自己的学习过程,以便以后使用到相关的知识点可以回顾和参考。
对于 I2C驱动,分为两个部分:i2c总线驱动和i2c设备驱动,总线驱动一旦编写完成就不需要再做修改,其他的 I2C 设备直接调用主机驱动提供的 API 函数完成读写操作即可。这个正好符合 Linux 的驱动分离与分层的思想。下面简单介绍这两部分驱动的区别:
1、I2C 总线驱动,I2C 总线驱动就是 SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动。
2、I2C 设备驱动,I2C 设备驱动就是针对具体的 I2C 设备而编写的驱动。
本次实验主要编写i2c设备驱动,因为i2c总线驱动已经由soc厂家编写好了,不过我们还是要学习一下总线驱动的原理,所以讲解总线驱动的流程会在下一遍博客中分析,本次先完成稍微简单的i2c设备驱动实验。
I2C 设备驱动重点关注两个数据结构:i2c_client 和 i2c_driver,i2c_client 就是描述设备信息的,i2c_driver 描述驱动内容,类似于 platform_driver。
i2c_client 结构体定义在 include/linux/i2c.h 文件中,内容如下:
struct i2c_client {
unsigned short flags; /* 标志 */
unsigned short addr; /* 芯片地址,7 位,存在低 7 位 */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE]; /* 名字 */
struct i2c_adapter *adapter; /* 对应的 I2C 适配器 */
struct i2c_driver *driver; /* i2c_driver 驱动结构体 */
struct device dev; /* 设备结构体 */
int irq; /* 中断 */
struct list_head detected;
};
一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个i2c_client。
i2c_driver 类似 platform_driver,是我们编写 I2C 设备驱动重点要处理的内容,i2c_driver 结构体定义在 include/linux/i2c.h 文件中,内容如下:
struct i2c_driver {
unsigned int class;
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
int (*detach_adapter)(struct i2c_adapter *) __deprecated;
/* probe 和 remove函数,跟platform平台设备驱动一样,
* 设备和驱动匹配成功就会执行probe函数。
*/
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 *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
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);
/* device_driver 驱动结构体,如果使用设备树的话,
* 需要设置 device_driver 的of_match_table 成员变量,
* 也就是驱动的兼容(compatible)属性。
*/
struct device_driver driver;
/* id匹配表,用来跟设备进行配对的 */
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 设备驱动编写人来说,重点工作就是构建 i2c_driver,构建完成以后需要向Linux 内核注册这个 i2c_driver。i2c_driver 注册函数为i2c_register_driver。
i2c_driver 注册函数为 int i2c_register_driver,此函数原型如下:
int i2c_register_driver(struct module owner, struct i2c_driver driver)
函数参数和返回值含义如下:
owner:一般为 THIS_MODULE。
driver:要注册的 i2c_driver。
返回值:0,成功;负值,失败。
另外 i2c_add_driver 也常常用于注册 i2c_driver,i2c_add_driver 是一个宏,定义如下:
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
i2c_add_driver 就是对 i2c_register_driver 做了一个简单的封装,只有一个参数,就是要注册的 i2c_driver。
注销 I2C 设备驱动的时候需要将前面注册的 i2c_driver 从 Linux 内核中注销掉,需要用到i2c_del_driver 函数,此函数原型如下:
void i2c_del_driver(struct i2c_driver driver)
函数参数和返回值含义如下:
driver:要注销的 i2c_driver。
返回值:无。
要使用i2c进行读取和写入数据之前,必须先定义一个 i2c_msg 类型的变量,并完善 i2c_msg 变量里面的成员,其实可以把 i2c_msg 变量理解为一个数据包,它可以存将要写入的数据,或者存读取到的数据,之后把它作为参数使用 i2c_transfer 函数进行写入和读取。i2c_msg 结构体定义在include/linux/i2c.h 文件中,内容如下:
struct i2c_msg {
__u16 addr; /* slave address i2c设备地址 */
__u16 flags; /* 标记,最常用:0为写,I2C_M_RD 为读 */
#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_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#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 length 数据的长度 */
__u8 *buf; /* pointer to msg data 数据缓存区 */
};
把 i2c_msg 类型变量完善好后,使用 i2c_transfer 函数进行数据写入和读取,i2c_transfer 函数最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数,i2c_transfer 函数原型如下:
int i2c_transfer(struct i2c_adapter adap, struct i2c_msg msgs, int num)
函数参数和返回值含义如下:
adap:所使用的 I2C 适配器,i2c_client 会保存其对应的 i2c_adapter。
msgs:I2C 要发送的一个或多个消息。
num:消息数量,也就是 msgs 的数量。
返回值:负值,失败,其他非负值,发送的 msgs 数量。
·
编写i2c设备驱动前,先完善好i2c设备的信息,比如设备的名字等等,驱动程序就是通过name字段(名字)来进行驱动与设备的配对。在arch/arm/plat-s5p6818/device.c 中使用 i2c_board_info 结构体来描述一个具体的 I2C 设备。i2c_board_info 结构体如下:
struct i2c_board_info {
char type[I2C_NAME_SIZE]; /* I2C 设备名字 */
unsigned short flags;
unsigned short addr; /* I2C 器件地址 */
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
int irq;
};
type 和 addr 这两个成员变量是必须要设置的,一个是 I2C 设备的名字,一个是 I2C 设备的器件地址。
完善好 i2c_board_info 结构体后使用 i2c_register_board_info 函数把设备注册到i2c设备链表中。
i2c_register_board_info 函数定义在drivers/i2c/i2c-boardinfo.c文件中,内容如下:
int __init
i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
{
int status;
down_write(&__i2c_board_lock);
/* dynamic bus numbers will be assigned after the last static one */
if (busnum >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = busnum + 1;
for (status = 0; len; len--, info++) {
struct i2c_devinfo *devinfo; /* i2c设备信息结构体,一个设备对应一个 i2c_devinfo */
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) {
pr_debug("i2c-core: can't register boardinfo!\n");
status = -ENOMEM;
break;
}
devinfo->busnum = busnum;
devinfo->board_info = *info;
/* 把i2c设备(即i2c_devinfo)加入到 __i2c_board_list 链表中 */
list_add_tail(&devinfo->list, &__i2c_board_list);
}
up_write(&__i2c_board_lock);
return status;
}
所以,这里可以猜测一下设备和驱动的匹配方式,可能就是通过遍历__i2c_board_list 链表,找到一个i2c_board_info的type成员跟驱动的i2c_device_id的name成员内容一样的,表示配对成功,这个配对过程就是内核中的i2c-core.c核心代码完成的。
/* i2c 驱动的 probe 函数 */
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
/* 函数具体程序 */
return 0;
}
/* i2c 驱动的 remove 函数 */
static int ap3216c_remove(struct i2c_client *client)
{
/* 函数具体程序 */
return 0;
}
/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id xxx_id[] = {
{"xxx", 0},
{}
};
/* i2c 驱动结构体 */
static struct i2c_driver xxx_driver = {
.probe = xxx_probe,
.remove = xxx_remove,
.driver = {
.owner = THIS_MODULE,
.name = "xxx",
},
.id_table = xxx_id,
};
/* 驱动入口函数 */
static int __init xxx_init(void)
{
int ret = 0;
ret = i2c_add_driver(&xxx_driver);
return ret;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
i2c_del_driver(&xxx_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
打开arch/arm/plat-s5p6818/device.c文件,找到合适位置添加以下内容;
#define EEPROM_I2C_BUS (2) /* 使用i2c2总线 */
static struct i2c_board_info __initdata s5p6818_eeprom_i2c_bdi = {
I2C_BOARD_INFO("s5p6818_eeprom", 0x50), /* at24c04设备地址0x50 */
.platform_data = &s5p6818_eeprom,
};
i2c_register_board_info(EEPROM_I2C_BUS, &s5p6818_eeprom_i2c_bdi, 1);
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LEDDEV_NAME "s5p6818_eeprom" /* 设备名字 */
/* 定义一个设备信息的结构体 */
struct eeprom_dev{
struct i2c_client *client; /* 描述设备信息的结构体 */
void *private_data; /* 私有数据 */
};
struct eeprom_dev eepromdev;
/* 从 at24 读取多个寄存器数据 */
static int eeprom_read_regs(struct eeprom_dev *dev, u8 reg, void *dbuf, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = dev->client;
msg[0].addr = client->addr; /* at24epprom 地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg 长度 */
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
msg[1].buf = dbuf; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度 */
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2){
ret = 0;
}else{
printk(KERN_EMERG "i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
/* 向 at24 多个寄存器写入数据 */
static int eeprom_write_regs(struct eeprom_dev *dev, u8 reg, void *dbuf, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = dev->client;
msg[0].addr = client->addr; /* at24epprom 地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg 长度 */
msg[1].addr = client->addr;
msg[1].flags = 0 | I2C_M_NOSTART; /* 标记为发送数据,不带start信号 */
msg[1].buf = dbuf; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度 */
ret = i2c_transfer(client->adapter, msg, 2);
return ret;
}
#if 0
/* 向 at24 指定寄存器写入指定的值,写一个寄存器
* return : 无
*/
static void epprom_write_reg(struct eeprom_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
eeprom_write_regs(dev, reg, &buf, 1);
}
/* 读取 at24 指定寄存器值, 读一个寄存器
* return : 读取到的寄存器值
*/
static unsigned char epprom_read_reg(struct eeprom_dev *dev, u8 reg)
{
u8 data = 0;
eeprom_read_regs(dev, reg, &data, 1);
return data;
}
#endif
/* 接口函数open,read,write,release */
static int eeprom_open(struct inode *inode, struct file *filp)
{
/* 把设备结构体设置为私有数据 */
filp->private_data = &eepromdev;
printk(KERN_EMERG "s5p6818_epprom open dev ok!\r\n");
return 0;
}
static ssize_t eeprom_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int ret;
char wbuf[256] = {0}; /* 一次最多写 256-1 个字节数据 */
u8 reg = 0;
struct eeprom_dev *dev = (struct eeprom_dev *)filp->private_data;
if(cnt > sizeof(wbuf)-1)
return -EINVAL;
ret = copy_from_user(wbuf, buf, cnt+1); /* 从应用层拷贝过来的数据放进wbuf, wbuf[0]为寄存器地址 */
if(ret < 0)
return ret;
reg = wbuf[0];
ret = eeprom_write_regs(dev, reg, &wbuf[1], cnt); /* 调用上面编写的写寄存器的函数 */
if(ret < 0)
return ret;
return cnt;
}
static ssize_t epprom_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
int ret;
u8 reg = 0;
char rbuf[256] = {0};
struct eeprom_dev *dev = (struct eeprom_dev *)filp->private_data;
ret = copy_from_user(rbuf, buf, 1); /* 从应用层拷贝过来的寄存器地址放进rbuf[0] */
if(ret < 0)
return ret;
reg = rbuf[0];
ret = eeprom_read_regs(dev, reg, &rbuf[1], cnt); /* 调用上面编写的读寄存器值的函数 */
if(ret < 0)
return ret;
ret = copy_to_user(buf, rbuf, cnt+1);
if(ret < 0)
return ret;
return cnt;
}
static int eeprom_release(struct inode *inode, struct file *filp)
{
printk(KERN_EMERG "s5p6818_epprom release ok!\r\n");
return 0;
}
/* 字符设备提供给应用层的接口函数 */
static struct file_operations eeprom_fops = {
.owner = THIS_MODULE,
.open = eeprom_open,
.write = eeprom_write,
.read = epprom_read,
.release = eeprom_release,
};
/* misc设备结构体 */
static struct miscdevice eeprom_miscdev = {
.minor = MISC_DYNAMIC_MINOR, /* 动态分配子设备号 */
.name = LEDDEV_NAME, //设备节点的名字 /dev/xxx
.fops = &eeprom_fops,
};
/* probe函数 */
static int s5p6818_eeprom_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret;
/* 保存 client 指针*/
eepromdev.client = client;
/* 检查是否支持i2c功能 */
if(!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)){
printk(KERN_EMERG "i2c_check_functionality fail!\r\n");
}
/* 注册misc设备驱动 */
ret = misc_register(&eeprom_miscdev);
if(ret < 0){
printk(KERN_EMERG "register misc device failed!\r\n");
return ret;
}
return 0;
}
/* remove函数 */
static int s5p6818_eeprom_remove(struct i2c_client *client)
{
/* 注销misc设备驱动 */
misc_deregister(&eeprom_miscdev);
return 0;
}
/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id s5p6818_eeprom_id[]={
{"s5p6818_eeprom", 0}, //设备类型(名字),id号
{},
};
/* 添加到内核i2c设备列表 */
MODULE_DEVICE_TABLE(i2c, s5p6818_eeprom_id);
/* i2c 驱动结构体 */
static struct i2c_driver s5p6818_eeprom_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "s5p6818_eeprom",
},
.probe = s5p6818_eeprom_probe,
.remove = s5p6818_eeprom_remove,
.id_table = s5p6818_eeprom_id,
};
/* 驱动入口函数 */
static int __init s5p6818_eeprom_init(void)
{
return i2c_add_driver(&s5p6818_eeprom_driver);
}
/* 驱动出口函数 */
static void __exit s5p6818_eeprom_exit(void)
{
i2c_del_driver(&s5p6818_eeprom_driver);
}
module_init(s5p6818_eeprom_init);
module_exit(s5p6818_eeprom_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xzj");
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
int main(int argc , char *argv[])
{
int fd, ret, len;
char *filename;
char dbuf[2];
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){
printf("read or write at24_eeprom: 1.write 2.read :");
scanf("%d", &ret);
switch(ret){
case 1:
printf("addr without 0x:"); scanf("%x",&dbuf[0]);
printf("data without 0x:"); scanf("%x",&dbuf[1]);
len = write(fd, dbuf, 1); /* dbuf[0]为写入地址,写入一个字节, 写入内容放入dbuf[1]*/
if(len > 0){
printf("write ok!\r\n");
}else{
printf("write fail!\r\n");
}
memset(dbuf, 0, sizeof(dbuf));
printf("\r\n");
break;
case 2:
printf("addr without 0x:"); scanf("%x",&dbuf[0]);
len = read(fd, dbuf, 1); /* dbuf[0]为读取地址,读取一个字节,读取内容放入dbuf[1] */
if(len > 0){
printf("read data at 0x%x , data: 0x%x\r\n", dbuf[0], dbuf[1]);
}else{
printf("read fail!\r\n");
}
memset(dbuf, 0, sizeof(dbuf));
printf("\r\n");
break;
default:
break;
}
}
/* 关闭设备 */
ret = close(fd);
if(ret < 0){
printf("Can't close file %s\r\n", filename);
return -1;
}
return 0;
}