这一篇主要是在友善的Smart210开发板上写一个符合linux的iic驱动模型的设备驱动程序,这样能有一个更感性的认识。
开发环境介绍:
主机linux版本:Ubuntu18.04
开发板:三星的Louis210
嵌入式linux版本:linux-3.10.46
交叉编译器:arm-linux-gcc-4.3.3
硬件简单介绍:
从图可以看出来EEPROM是和Louis210上的第0个iic适配器连接的。
注册设备信息
阅读linux下的Documentation/i2c/instantiating-devices 文档可以知道有两种方式可以注册,咱们只说前一种。打开:linux-3.10.46/arch/arm/mach-Louis210/mach-Louis210.c这个.c文件。就是在这个文件中填写咱们设备的信息的,这就是所说的bsp文件。首先添加头文件#include
因为linux专门问iic接口的eeprom提供了相应的数据结构,要是不加,肯定要报错。接下来添加如下信息:
static struct at24_platform_data at24c08 = {
.byte_len = SZ_2K / 8, //eeprom的容量大小(地址的总数)
.page_size = 8, //eeprom的一页中包含的字节数
};
然后添加如下的信息,主要把eeprom的信息包装成符合iic模型中的设备信息的格式
static struct i2c_board_info smdkv210_i2c_devs0[] __initdata = {
{
I2C_BOARD_INFO("24c02", 0x50),
*platform_data = at24c08,
}, /* add by Louis */
};
后边的0x50是eeprom的地址,可能有人说应该是0xa0,但linux中需要的是7bit的地址,所以向右移一位,
正好是0x50。当然了,最终会在linux的iic的读函数和写函数中变成0xa0和0xa1的格式
最后在Louis210_machine_init函数中把上面写的信息注册到iic总线上
static void __init Louis210_machine_init(void)
{
...
s3c_i2c0_set_platdata(NULL);
s3c_i2c1_set_platdata(NULL);
s3c_i2c2_set_platdata(NULL);
i2c_register_board_info(0, smdkv210_i2c_devs0, ARRAY_SIZE(smdkv210_i2c_devs0));
i2c_register_board_info(1, smdkv210_i2c_devs1, ARRAY_SIZE(smdkv210_i2c_devs1));
i2c_register_board_info(2, smdkv210_i2c_devs2, ARRAY_SIZE(smdkv210_i2c_devs2));
...
}
这就算把设备信息注册上了,重新编译一下你的linux内核吧,然后把编译好的内核烧进开发板,下面开始就是真真的驱动部分了。
设备驱动编写
首先咱们是用eeprom读写一些数据,数据量不会很大,所以它应该是个字符设备,尽管它从iic驱动模型的角度说,是iic设备,起始这并不矛盾。因为字符设备里包括了一部分的iic设备,下面就是整个驱动了
/*
* at24.c - handle most I2C EEPROMs
*
* Copyright (C) 2005-2007 David Brownell
* Copyright (C) 2008 Wolfram Sang, Pengutronix
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_NAME "24c02"
//#define DEBUG
#define MEMDEV_SIZE (2048 / 8)
#define AT24_SIZE_BYTELEN 5
#define AT24_SIZE_FLAGS 8
struct At24c02_dev
{
char name[30];
unsigned write_max;
struct mutex lock; //互斥锁
struct i2c_client *at24c02_client;
struct miscdevice at24c02_miscdev; //因为本身是一个字符设备,所以定义成一个杂项设备
};
struct At24c02_dev *At24c02_devp;
/*open函数将全局的At24c02_devp赋给file文件的私有数据,方便其他函数调用*/
static int at24c02_open(struct inode *inode, struct file *filp)
{
filp->private_data = At24c02_devp;
return 0;
}
static ssize_t at24_eeprom_read(struct file *filp, char *buf, loff_t offset, size_t count)
{
int transferred = 0;
struct At24c02_dev *dev = (struct At24c02_dev *)filp->private_data;
if (!i2c_check_functionality(dev->at24c02_client->adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA))
return -ENODEV;
while(transferred < count)
{
msleep(10);
//这里一定注意,要不这个延时加上,因为cpu速度比较快,eeprom速度比较慢,所以不加会出问题,我调试时就出问题了,后加的
buf[transferred++] = i2c_smbus_read_byte_data(dev->at24c02_client,offset + transferred);
if(transferred >= dev->write_max)
break;
}
return transferred;
}
static ssize_t at24_eeprom_write(struct file *filp, const char *buf, loff_t offset, size_t count)
{
int transferred = 0;
struct At24c02_dev *dev = (struct At24c02_dev *)filp->private_data;
if (!i2c_check_functionality(dev->at24c02_client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -ENODEV;
while(transferred < count)
{
msleep(10);
//这里一定注意,要不这个延时加上,因为cpu速度比较快,eeprom速度比较慢,所以不加会出问题,我调试时就出问题了,后加的
i2c_smbus_write_byte_data(dev->at24c02_client,offset + transferred, buf[transferred]);
//这个函数通过adapter的通信方法把一个字节的数据发送 到iic设备中去
transferred++;
if(transferred >= dev->write_max)
break;
}
return transferred;
}
static ssize_t at24_bin_read(struct file *filp, char *buf, size_t count, loff_t *ppos)
{
ssize_t retval = 0;
ssize_t status;
loff_t off;
struct At24c02_dev *dev;
if(unlikely(!count))
return count;
dev = (struct At24c02_dev *)filp->private_data;
off = filp->f_pos;
mutex_lock(&dev->lock); /* 访问设备前,加锁*/
while(count){
status = at24_eeprom_read(filp, buf, off, count);
if(status <= 0){
if(retval == 0)
retval = status;
break;
}
buf += status;
off += status;
count -= status;
retval += status;
}
mutex_unlock(&dev->lock);
return retval;
}
static ssize_t at24_bin_write(struct file *filp, const char *buf, size_t count, loff_t *ppos)
{
ssize_t retval = 0;
ssize_t status;
loff_t off;
struct At24c02_dev *dev;
if(unlikely(!count))
return count;
dev = (struct At24c02_dev *)filp->private_data;
off = filp->f_pos;
mutex_lock(&dev->lock); /* 访问设备前,加锁*/
while(count){
status = at24_eeprom_write(filp, buf, off, count);
if(status <= 0){
if(retval == 0)
retval = status;
break;
}
buf += status;
off += status;
count -= status;
retval += status;
}
mutex_unlock(&dev->lock);
return retval;
}
static loff_t at24_llseek(struct file *filp, loff_t offset, int whence)
{
loff_t newpos;
switch(whence) {
case 0: /* SEEK_SET */
newpos = offset;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + offset;
break;
case 2: /* SEEK_END */
newpos = MEMDEV_SIZE - 1 + offset;
break;
default: /* can't happen */
return -EINVAL;
}
if ((newpos<0) || (newpos > MEMDEV_SIZE))
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
static struct file_operations at24c02_fops =
{
.owner = THIS_MODULE,
.open = at24c02_open,
.write = at24_bin_write,
.read = at24_bin_read,
.llseek = at24_llseek,
.release = NULL,
};
static int at24c02_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret;
#ifdef DEBUG
printk(KERN_NOTICE"The routine of at24c02 probe!
");
#endif
At24c02_devp = kzalloc(sizeof(struct At24c02_dev), GFP_KERNEL);
if(!At24c02_devp)
return -ENOMEM;
memset(At24c02_devp, 0, sizeof(struct At24c02_dev));
At24c02_devp->at24c02_client = client; //把分配的i2c_client赋给定义的全局变量
//杂项设备定义
At24c02_devp->at24c02_miscdev.minor = MISC_DYNAMIC_MINOR;
At24c02_devp->at24c02_miscdev.name = DEVICE_NAME;
At24c02_devp->at24c02_miscdev.fops = &at24c02_fops;
At24c02_devp->write_max = 50;
mutex_init(&At24c02_devp->lock); /* 初始化设备锁 */
ret = misc_register(&At24c02_devp->at24c02_miscdev); //注册杂项设备
#ifdef DEBUG
printk(KERN_NOTICE"The driver of at24c02 has register!
");
#endif
return ret;
}
static int at24c02_remove(struct i2c_client *client)
{
misc_deregister(&At24c02_devp->at24c02_miscdev);
#ifdef DEBUG
printk(KERN_NOTICE"The routine of at24c02 remove!
");
#endif
return 0;
}
#define AT24_DEVICE_MAGIC(_len, _flags)
((1 << AT24_SIZE_FLAGS | (_flags))
<< AT24_SIZE_BYTELEN | ilog2(_len))
static const struct i2c_device_id at24_ids[] = {
/* old variants can't be handled with this generic entry! */
{ "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) }, //1K bit
{ "24c02", AT24_DEVICE_MAGIC(2048 / 8, 0) }, //2K bit
{ "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) }, //4K bit
/* 24rf08 quirk is handled at i2c-core */
{ "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) }, //8K bit
{ "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) }, //16K bit
{ "at24", 0 },
{ /* END OF LIST */ }
};
//当把设备挂接到总线上时,就调用这里面的名字和注册在总线里的名字比对,
//如果一样就会调用probe函数,同时给挂接的设备分配i2c_client结构体
MODULE_DEVICE_TABLE(i2c, at24_ids);
struct i2c_driver at24c02_driver =
{
.driver = {
.name = DEVICE_NAME,
.owner = THIS_MODULE,
},
.probe = at24c02_probe,
.remove = at24c02_remove,
.id_table = at24_ids,
};
static int __init at24c02_init(void)
{
printk(KERN_NOTICE"The driver of at24c02 is insmod!
");
return i2c_add_driver(&at24c02_driver); //把iic设备挂接到总线上
}
static void __exit at24c02_exit(void)
{
printk(KERN_NOTICE"at24c02 is remmod!
");
i2c_del_driver(&at24c02_driver);
}
MODULE_DESCRIPTION("AT24C02 eeprom driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LouisGou");
module_init(at24c02_init);
module_exit(at24c02_exit);
上面就是完整的eeprom驱动,当然驱动写完了,需要写个简单的Makefile来编译这个驱动,好吧,下面就是Makefile文件的内容
obj-m := at24.o
KDIR := /root/code/linux-stable-3.10.46
all:
@make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
clean:
@rm -rf *.o *.ko *.order *.symvers *.bak .*.cmd *.mod.o *.mod.c .tmp_versions
cp:
@make clean
@make
cp *.ko /root/code/rootfs/root/at24c02
上面的Makefile文件很是简单,就不做过多的解释了。当把驱动编译好了,用动态的方式挂载到了linux内核上后,你还得做个简单的测试程序,来验证咱们写的驱动工作是否正常,下面就直接贴出来吧。
#include
#include
#include
#include
#include
#include
#include
void print_usage(char *file)
{
printf("%s r addr
", file);
printf("%s w addr val
", file);
}
int main(int argc, char **argv)
{
int i;
int ret;
char value[19] = "eeprom-driver test!";
char backvalue[19];
unsigned char addr, data;
if ((argc != 3) && (argc != 4))
{
print_usage(argv[0]);
return -1;
}
int fd;
fd = open("/dev/24c02",O_RDWR);
if(fd<0){
printf("Open at24c02 device failed!
");
exit(1);
}
addr = strtoul(argv[2], NULL, 0);
ret = lseek(fd, addr, SEEK_SET);
if(ret == -1)
{
perror("");
}
if(strcmp(argv[1], "r") == 0)
{
read(fd, &data, 1);
printf("read data:%#x %#x %c
", addr, data, data);
}
else if((strcmp(argv[1], "w") == 0) && (argc == 4))
{
data = strtoul(argv[3], NULL, 0);
write(fd, &data, 1);
printf("write data:%#x %#x %c
", addr, data, data);
}
else
{
print_usage(argv[0]);
close(fd);
return -1;
}
close(fd);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
void print_usage(char *file)
{
printf("%s r addr
", file);
printf("%s w addr val
", file);
}
int main(int argc, char **argv)
{
int i;
int ret;
char value[19] = "eeprom-driver test!";
char backvalue[19];
unsigned char addr;
char data[100] = "0123456789qwertyuiopasdfghjklzxcvbnm,./;'[]/*-+?><";
if ((argc != 3) && (argc != 4))
{
print_usage(argv[0]);
return -1;
}
int fd;
fd = open("/dev/24c02",O_RDWR);
if(fd<0){
printf("Open at24c02 device failed!
");
exit(1);
}
addr = strtoul(argv[2], NULL, 0);
ret = lseek(fd, addr, SEEK_SET);
if(ret == -1)
{
perror("");
}
if(strcmp(argv[1], "r") == 0)
{
printf("read data:%#x
", addr);
read(fd, &data, 100);
printf("read data:%#x %#x %#s
", addr, data, data);
}
else if((strcmp(argv[1], "w") == 0) && (argc == 4))
{
//data = strtoul(argv[3], NULL, 0);
write(fd, &data, strlen(data));
printf("write data:%#x %#x %#s
", addr, data, data);
}
else
{
print_usage(argv[0]);
close(fd);
return -1;
}
close(fd);
return 0;
}
哈哈,驱动就写完了,我自己测试了,没问题,你可以试试,下一篇我们会分析iic总线驱动。