跳到主要内容位置

I2C驱动程序模板

I2C驱动程序模板#

AT24C02

1.修改设备树#

  • 放在哪个I2C控制器(总线)下面
  • AT24C02的I2C设备地址
  • compatible属性:用来寻找驱动程序

修改:arch/arm/boot/dts/100ask_imx6ull-14x14.dts

&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
at24c02{
compatible = "100ask,i2cdev";
reg = <0x50>;
};
};

2.编写驱动#

入口函数与出口函数,注册和注销i2c设备:

/* 在入口函数 */
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);
}
static const struct of_device_id myi2c_dt_match[] = {
{ .compatible = "100ask,i2cdev" },
{},
};
static const struct i2c_device_id i2c_drv_id[] = {
{"xxxyyy", 0},
{},
};
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,
.id_table = i2c_drv_id,
};

当有对应的设备匹配上drv时,系统就会调用probe函数:

在probe函数中,我们记录client结构体,client结构体中有I2C总线(client->adapter)、设备树节点(client->dev.of_node)等信息;然后一样的套路,注册字符设备程序,注册设备节点myi2c。

static struct i2c_client *g_client;
static int i2c_drv_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
// struct i2c_adapter *adapter = client->adapter; //获取I2C总线
// struct device_node *np = client->dev.of_node; //获取设备树节点
/* 记录client */
g_client = client;
/* 注册字符设备 */
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;
}

接着就说file_operations结构体,操作具体硬件,这里是以EEPROM AT24C02为例

/* 定义自己的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,
};

读函数:APP发起一次read操作,调用i2c_drv_read,我们没使用偏移量,默认从0地址开始读

  • 先给内核数组kern_buf申请空间
  • 然后读,分为两步
    • 先发送要读的地址0,构造msgs[0]
    • 再进行读,构造msgs[1]
  • 调用i2c_transfer进行上述两步的传输
  • copy_to_user
static ssize_t i2c_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int err;
unsigned char *kern_buf;
struct i2c_msg msgs[2];
/* 从0读取size字节 */
kern_buf = kmalloc(size, GFP_KERNEL);
/* 初始化i2c_msg
* 1.发起一次写操作:把0发给AT24C02,表示要从0地址读数据
* 2.发起一次读操作:得到数据
*/
msgs[0].addr = g_client->addr; //从设备地址
msgs[0].flags = 0; //写操作
msgs[0].buf = kern_buf;
kern_buf[0] = 0;
msgs[0].len = 1;
msgs[1].addr = g_client->addr;
msgs[1].flags = I2C_M_RD; //读操作
msgs[1].buf = kern_buf;
msgs[1].len = size;
err = i2c_transfer(g_client->adapter, msgs, 2); //发起2次I2C传输操作
/* copy_to_user */
err = copy_to_user(buf, kern_buf, size);
kfree(kern_buf);
return size;
}

写操作:

  • 先构造内核数组kern_buf,大小为size+1,是因为用第一位(kern_buf[0])表示需要写入的寄存器地址
  • 然后,按每8位循环写入AT24C02
    • 先从copy_from_user
    • 再构造msgs[0],发起一次写操作,把kern_buf写入
    • 然后等待硬件完成写入

AT24C02一次不能写入超过8个字节的数据,即一页数据,需要分多次写入;且写一页数据也不是一次写入的,需要等待至少10ms

static ssize_t i2c_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
size_t len;
unsigned char write_addr = 0;
unsigned char *kern_buf;
struct i2c_msg msgs[1];
kern_buf = kmalloc(size+1, GFP_KERNEL);
/* 把size字节写入地址0 */
/* copy_from_user */
while (size > 0)
{
if(size > 8)
{
len = 8;
}
else
{
len = size;
}
size -= len;
err = copy_from_user(kern_buf+1, buf, len);
buf += len;
/* 初始化i2c_msg
* 1.发起一次写操作:把kern_buf都发给AT24C02,kern_buf[0]表示要写的地址,其余位表示写入的数据
*/
msgs[0].addr = g_client->addr; //设备地址
msgs[0].flags = 0; //写操作
msgs[0].buf = kern_buf;
kern_buf[0] = write_addr; /* 用kern_buf[0]表示写入的寄存器地址 */
msgs[0].len = len+1;
write_addr += len;
err = i2c_transfer(g_client->adapter, msgs, 1); //发起1次I2C传输操作
mdelay(20);
}
kfree(kern_buf);
return size;
}

3.编写APP#

APP很简单:./i2c_test /dev/myi2c string表示写入string

./i2c_test /dev/myi2c表示读取,从地址0

static int fd;
/*
* ./i2c_test /dev/myi2c string
* ./i2c_test /dev/myi2c
*
*/
int main(int argc, char **argv)
{
int ret;
char buf[100];
/* 1. 判断参数 */
if (argc < 2)
{
printf("Usage:\n", argv[0]);
printf(" %s <dev>, read at24c02\n", argv[0]);
printf(" %s <dev><string>, write at24c02\n", argv[0]);
return -1;
}
/* 2. 打开文件 */
fd = open(argv[1], O_RDWR | O_NONBLOCK);
if (fd == -1)
{
printf("can not open file %s\n", argv[1]);
return -1;
}
if (argc == 3)
{
ret = write(fd, argv[2], strlen(argv[2]) + 1);
}
else
{
ret = read(fd, buf, 100);
printf("read: %s\n", buf);
}
close(fd);
return 0;
}

4.开发板试验#

设备树里面有at24c02

系统总线I2C总线下面有设备:

装载驱动程序后,也存在/sys/bus/i2c/drivers/100ask_i2c_drv

驱动程序没有和设备匹配上


请点击左侧菜单(移动端为右下角)选择要查看的所有笔记吧。

最近更新于