通过对iic总线进行学习后,我发现i2c设备类似于独立于platform_driver 的设备,或者说是一种特殊的platform_driver ,它拥有自己的一套流程,与总线设备驱动模型是完全类似的。
在平台设备中我们是当platform_driver 中的compatible属性与设备树中compatible属性匹配时候使用probe函数,相同的在i2c驱动模型中我们对i2c_driver的结构体进行分配注册,当其中属性compatible符合设备树节点中compatible进行调用。
下面直接从代码上手会比较容易理解:
首先对i2c_driver结构体进行分配注册,其结构体成员类似于platform_driver ,但不可忽略的一点是,即使我们使用i2c_driver中的driver中的of_match_table中的compatible进行匹配设备节点时候,我们也需要构建并初始化一个id_table(可以胡乱填充),这点在韦东山老师的直播课程中有调试过,这是内核的bug,如果没有id_table即使compatible匹配也无法进入probe函数。
static const struct of_device_id at24c02_dri_of_match[] = {
{ .compatible = "fly,at24c02" },
{ /* Sentinel */ }
};
//iic设备必须提供id_table否则无法进入probe函数 可以随便写但不能没有
static const struct i2c_device_id at24c02_dri_id_table[] =
{
{"xxx",0},
{}
};
static struct i2c_driver at24c02_dri = {
.probe = at24c02_dri_probe,
.remove = at24c02_dri_remove,
.driver = {
.name = "at24c02_dri",
.of_match_table = at24c02_dri_of_match,
},
.id_table = at24c02_dri_id_table,
};
其次,我们需要对构建的i2c_driver结构体进行注册,那么注册和销毁分别放在驱动程序的入口函数和出口函数是比较合理的
static int at24c02_module_init(void)
{
//注册i2c_driver
printk("at24c02_dri register!\r\n");
i2c_add_driver(&at24c02_dri);
return 0;
}
//出口函数
static void at24c02_module_exit(void)
{
i2c_del_driver(&at24c02_dri);
}
我们只是填写了i2c_driver结构体但我们并未对实际的probe函数与remove函数进行编写,下面我们需要完善probe函数与remove函数,我们需要想一想这两个函数中需要做什么?
probe函数中我们需要创建字符驱动程序、创建类、创建设备,由于i2c驱动程序的probe函数有一个输入项为client,因此我们需要一个全局变量来接受这个client。client表示用户端,是由我们设备树节点转换的,其中包含了硬件信息。类似的remove函数用来删除probe的资源
static int at24c02_dri_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
printk("probe!\r\n");
at24c02_client = client;
major = register_chrdev(0,"at24c02chrdev", &at24c02_fop);
at24c02_class = class_create(THIS_MODULE, "at24c02class");
device_create(at24c02_class,NULL, MKDEV(major,0), NULL,"flyat24c02");
return 0;
}
static int at24c02_dri_remove(struct i2c_client *client)
{
device_destroy(at24c02_class, MKDEV(major,0));
class_destroy(at24c02_class);
unregister_chrdev(major, "at24c02chrdev");
return 0;
}
在probe函数中我们注册了一个字符驱动程序,那我们需要实现这个file_operation结构体,我们本次实验是读写eeprom模块,因此我们无论是使用write+read或光使用ioctl都是可以实现的,因为ioctl有点类似于结合了read write。本次我们使用ioctl。
static const struct file_operations at24c02_fop =
{
.owner = THIS_MODULE,
.unlocked_ioctl = at24c02_ioctl,
};
下面我们只需要实现ioctl即可:
1.这里的这个强制数据类型转换可以参考上一篇文章
2.首先我们把数据拷贝到内核态
3.判断读还是写
4a.写:我想写一个数据进eeprom但根据规则我需要写两个字节,因为第一个字节代表我需要写道哪个结构体,这里的传输数据方法是构造i2c_msg并使用i2c_transfer进行传输,需要构造的内容有:
写到i2c总线(总线是在设备树的时候就确定了,使用哪个总线就把设备节点放置在哪个i2c总线下)的哪个设备点(即我们需要却定设备地址,这里eeprom的设备地址为0x50)但这里不需要我们直接写,只需要使用客户端client指向的地址即可。
读操作还是写操作 0 写 1 读
写多少个字节?
写的内容是什么?(这里第四个内容是一个地址,因为我们不可能只写一个字节啊,因此一个变量肯定是不可以表示的,我们需要一个地址指向我们的字节,再配合长度完成写),需要注意的是数据是一个unsigned char类型。
4.b读:我们这里采用随机读(我也不知为什么取这个名字,随机读其实是指定地址的读),首先我们需要进行一次假写操作来确定地址,第二次在进行读。构造的消息如下
两次设备地址相同
第一次写,第二次读
长度均为1,因为我们这里只读一个字节
数据第一次是我们传输过来的地址,第二次是一个unsigned char类型的空变量,因为是读数据,因此我们是把数据读出来,最后再把数据拷贝至用户态即可
static long at24c02_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
unsigned int buffer[2];
unsigned int *temp_buffer =(unsigned int *)arg ;//把arg当作地址
struct i2c_msg msgs[2];
unsigned char addr;//可以进入查看msg的数据类型其中数据是u8即unsigned char
unsigned char data;//读到的数据
unsigned char write_buff[2];
copy_from_user(buffer, temp_buffer, 8);//8个字节一个是地址一个是数据
switch (cmd)
{
case MYWRITE :
{
//由于buf是unsigned char类型因此要转换一下
write_buff[0] = addr;
write_buff[1] = buffer[1];
//设备地址 读写 长度 内容
msgs[0].addr = at24c02_client ->addr;
msgs[0].flags = 0;
msgs[0].len = 2;
msgs[0].buf = write_buff;
//传输一次消息即可
i2c_transfer(at24c02_client->adapter, msgs, 1);
mdelay(20);
//整体的意思是写两个字符,其中第一个字符是寄存器地址,第二个字符是数据
break;
}
case MYREAD :
{
addr = buffer[0];
//先使用写来假确定地址,再通过读
msgs[0].addr = at24c02_client ->addr;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = &addr;
msgs[1].addr = at24c02_client ->addr;
msgs[1].flags = 1;
msgs[1].len = 1;
msgs[1].buf = &data;
i2c_transfer(at24c02_client->adapter, msgs, 2);
buffer[1] = data;
copy_to_user(temp_buffer, buffer, 8);//8个字节一个是地址一个是数据
break;
}
}
return 0;
}
总体驱动代码:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/log2.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/of.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>
#define MYWRITE 100
#define MYREAD 101
static struct i2c_client *at24c02_client;
static int major = 0;
static struct class *at24c02_class;
static long at24c02_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
unsigned int buffer[2];
unsigned int *temp_buffer =(unsigned int *)arg ;//把arg当作地址
struct i2c_msg msgs[2];
unsigned char addr;//可以进入查看msg的数据类型其中数据是u8即unsigned char
unsigned char data;//读到的数据
unsigned char write_buff[2];
copy_from_user(buffer, temp_buffer, 8);//8个字节一个是地址一个是数据
switch (cmd)
{
case MYWRITE :
{
//由于buf是unsigned char类型因此要转换一下
write_buff[0] = addr;
write_buff[1] = buffer[1];
//设备地址 读写 长度 内容
msgs[0].addr = at24c02_client ->addr;
msgs[0].flags = 0;
msgs[0].len = 2;
msgs[0].buf = write_buff;
//传输一次消息即可
i2c_transfer(at24c02_client->adapter, msgs, 1);
mdelay(20);
//整体的意思是写两个字符,其中第一个字符是寄存器地址,第二个字符是数据
break;
}
case MYREAD :
{
addr = buffer[0];
//先使用写来假确定地址,再通过读
msgs[0].addr = at24c02_client ->addr;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = &addr;
msgs[1].addr = at24c02_client ->addr;
msgs[1].flags = 1;
msgs[1].len = 1;
msgs[1].buf = &data;
i2c_transfer(at24c02_client->adapter, msgs, 2);
buffer[1] = data;
copy_to_user(temp_buffer, buffer, 8);//8个字节一个是地址一个是数据
break;
}
}
return 0;
}
static const struct file_operations at24c02_fop =
{
.owner = THIS_MODULE,
.unlocked_ioctl = at24c02_ioctl,
};
static int at24c02_dri_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
printk("probe!\r\n");
at24c02_client = client;
major = register_chrdev(0,"at24c02chrdev", &at24c02_fop);
at24c02_class = class_create(THIS_MODULE, "at24c02class");
device_create(at24c02_class,NULL, MKDEV(major,0), NULL,"flyat24c02");
return 0;
}
static int at24c02_dri_remove(struct i2c_client *client)
{
device_destroy(at24c02_class, MKDEV(major,0));
class_destroy(at24c02_class);
unregister_chrdev(major, "at24c02chrdev");
return 0;
}
static const struct of_device_id at24c02_dri_of_match[] = {
{ .compatible = "fly,at24c02" },
{ /* Sentinel */ }
};
//iic设备必须提供id_table否则无法进入probe函数 可以随便写但不能没有
static const struct i2c_device_id at24c02_dri_id_table[] =
{
{"xxx",0},
{}
};
static struct i2c_driver at24c02_dri = {
.probe = at24c02_dri_probe,
.remove = at24c02_dri_remove,
.driver = {
.name = "at24c02_dri",
.of_match_table = at24c02_dri_of_match,
},
.id_table = at24c02_dri_id_table,
};
static int at24c02_module_init(void)
{
//注册i2c_driver
printk("at24c02_dri register!\r\n");
i2c_add_driver(&at24c02_dri);
return 0;
}
//出口函数
static void at24c02_module_exit(void)
{
i2c_del_driver(&at24c02_dri);
}
module_init(at24c02_module_init);
module_exit(at24c02_module_exit);
MODULE_LICENSE("GPL");
总体应用代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include<sys/ioctl.h>
#include <stdlib.h>
#define MYWRITE 100
#define MYREAD 101
// ./at24c02_test /dev/flyat24c02 w/r addr data
int main(int argc ,char ** argv)
{
int fd;
unsigned int buff[2];
char status=0;
if((argc != 4 )&&(argc != 5))
{
printf("Usage %s dev wr addr data \r\n",argv[0]);
return -1;
}
fd = open(argv[1],O_RDWR);
buff[0] = strtol(argv[3],NULL,10);
if(argv[2][0] == 'w')
{
buff[1] = strtol(argv[4],NULL,10);
ioctl(fd,MYWRITE,buff);
}
else if(argv[2][0]=='r')
{
ioctl(fd,MYREAD,buff);
printf("read buff: 0x%x\r\n",buff[1]);
}
return 0;
}