上篇文章实现了了24cxx eeprom驱动的编写,但是其实很鸡肋,很难用。当我一次性需要写入/读出多个字节时,这一切变的很难操作.那么本篇文章来实现更进阶的一次读取/写入多个数据的操作.
我们的驱动列表里面使同时支持24c02,24c04,24c08.只做简单的功能实现.不考虑并发,竟态的状况.所以也不加锁,基本不做出错处理.只是写一个最简单的框架以供参考.
根据24cxx的spec,我们可以知道:
读操作有三种方式:当前读,随机读,顺序读.
这里我们一次读多个字节肯定需要用顺序读方式。顺序读发生在当前/随机读之后,直接读取下一个字节,不发送stop信号,直到最后一个数据取出为止。
读操作的最大长度为:最大容量 - 当前读地址.读操作对于slave设备来讲不需要缓冲区.比如从24c02的第0个位置取256个数据都是可以的.
写操作有两种方式:字节写,按页写.
按页写:开始和字节写一样,但是当第一个字节传送完成后,不发送stop信号.直到写完所有数据为止.
按页写需要 slave设备的缓冲区。这个和IC相关。我们知道spec上有说明24c02 缓冲区8 byte.24c04和24c08缓冲区 16byte.
也就是说24c08一次最多可以写入16个字节.当然这边需要特别注意:即使是24c08,即使你一次传送16个字节,但也不是每次都会发送成功.
这边说明了原因.当按页写时,IC会根据 IC型号 来自动进行subadr++操作。但是这边又有限制,这个一定要注意
1.24c02:subadr低三位自增.
2.24c04,24c08:subadr 低四位自增.
那么这边有一个问题:如果我要往 0x1e的位置上按页写入16个字节.理论上没错,可以实现。但是实际上0x1e的低四位是1110.
页就是说你只能在这边写2个byte.(0x1e,0x1f)。因为只有第四位会auto inc,当地址自增到0x1f后,需要我们自己再去指定新的地址是0x20后,再去写.否则,数据写的位置就不是你想要的.
所以这边需要进行一次逻辑判断,你需要将要发送的数据分成几个包进行传送?
当然最少一个包,最多两个(如果最多发送16个字节的话),这里针对24c04和24c08来讲。
如果是24c02,最少需要2个包,最多3个包,如果一次发送16个字节的话
那么还有一个问题:24c08的size是1024.那么访问255以上的地址该怎么办?
我们知道 24cxx的地址是 8bit的,但是很明显只能访问到0-255的地址空间。那么1024的空间该如何访问?
我们来看这边:24c08的dev adr里面的bit1,bit2位的作用.
这边是用来切换页/块block的,如果
P1 P0 访问地址空间
0 0 0-255
0 1 256-511
1 0 512-767
1 1 768-1023
所以这边,当我们访问地址超过255时,要记得进行地址空间重定义.因为默认地址中 P1,P0是0,也就是访问地址空间是0-255.
所以访问24c04和24c08时需要特别注意.
当然,按页写的时序:
到此为止,IC的spec已经大概分析清楚.为了这些资讯,可能你要不断的翻芯片手册.我自己大概看了不下20遍.当然你需要一个很好地英文阅读理解水平,但往往这只是最基础的。你还要把得到的资讯翻译成代码.
坦白讲,多次写入/读出数据这个程序在原先的框架上修改就花费了我一天的时间。如果算上写笔记的事情,也快两天了.以前也玩过51下的iic。还FW模拟过IIC的波形。觉得也还好。
但是真的反而是这次,感觉以前好多东西都漏掉了。顺带一句:虽然linux下的驱动都是有的,但怎么用还真是一门学问,需要下很多功夫。驱动开发的路真是任重道远呢!只为那短短几十行代码,我们要try n次,猜n次,翻看datasheet n次。我觉得吧我们的职业应该改为 翻译官比较好。将书面上的东西翻译成 scaler可以读懂的代码——–翻译官
驱动程序:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <mach/gpio.h>
#include <linux/gpio.h>
#include <asm/io.h>
#include <linux/irq.h>
#include <asm/bitops.h>
#define _PAGE_SIZE 256
#define BUF_SIZE 32
#define _SIZE_256 256
#define _SIZE_512 512
#define _SIZE_1024 1024
#define _WBUF_SIZE8 8//24c02
#define _WBUF_SIZE16 16//24c04 24c08
static const struct i2c_device_id at24cxx_id[] =
{
{"24c02",_SIZE_256},
{"24c04",_SIZE_512},
{"24c08",_SIZE_1024},
{}
};
/*read /write 16 bytes per time*/
struct trans_buf
{
unsigned int subadr;//sub adr to rw
unsigned char len; // how many want to get
unsigned char buf[BUF_SIZE]; //buf
};
static struct trans_buf at_buf;
static const struct i2c_device_id *g_id =NULL;
//device number
static dev_t at_dev_num;
struct at24cxx_dev
{
struct cdev at_dev;//char device
struct i2c_client * at_client;
};
static struct at24cxx_dev *at24cxx_struct = NULL;
//auto set up the device node file use
static struct class *at_class =NULL;
static struct device *at_device =NULL;
static int at24cxx_check_range(struct trans_buf *buf,size_t size)
{
printk("2.addr:0x%2x,len:%d.\n",buf->subadr,buf->len);
if((buf->subadr + buf->len) > g_id->driver_data)
{
printk("r/w over flow!!!\n");
return -EINVAL;
}
if(buf->len == 0 || size == 0 || buf->len != size)
{
printk("please check the len or size.\n");
return -EINVAL;
}
return 0;
}
static void at_24cxx_set_data(unsigned char *slaadr,unsigned char *subadr)
{
unsigned char page =0;
page = at_buf.subadr/_PAGE_SIZE;
*subadr = at_buf.subadr%_PAGE_SIZE;
*slaadr = at24cxx_struct->at_client->addr | page;
printk("slave adr :0x%2x,subadr :0x%2x.\n",*slaadr,*subadr);
}
static void at_24cxx_transfer(struct i2c_msg *msg,unsigned char *wpos,unsigned char *wlen)
{
unsigned char subadr=0,slaveadr=0,wbufsize =0;
unsigned char len=0,tmp;
//get the wbuf size by ic type
if(strcmp(g_id->name,"24c02") == 0)
{
wbufsize = _WBUF_SIZE8;
}
else
{
wbufsize = _WBUF_SIZE16;
}
//update the subadr
at_buf.subadr = at_buf.subadr + *wlen;
at_24cxx_set_data(&slaveadr,&subadr);
//the max length can be write current time
tmp = wbufsize -1;
len = wbufsize -(subadr&tmp);
printk("5.max len:%d.\n",len);
//get the actual len
len =((len > (at_buf.len -*wpos)) ? (at_buf.len -*wpos): len);
printk("5.actual len:%d.\n",len);
//slave dev addr
msg->addr =slaveadr;
msg->flags =0;//W cmd
msg->buf =&subadr; //sub adr
msg->len =1;
(msg+1)->addr = slaveadr;
(msg+1)->flags = 0|I2C_M_NOSTART;
(msg+1)->buf = &(at_buf.buf[*wpos]);
(msg+1)->len = len;
*wlen =len;
}
/*****ssize_t (*read) (struct file *, char __user *, size_t, loff_t *)*****/
static ssize_t at24cxx_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
int ret=0;
unsigned char subadr=0,slaveadr=0;
struct i2c_msg msgs[2];
memset(msgs,0,sizeof(msgs));
//get the buf form user,to store in at_buf
ret = copy_from_user(&at_buf,buf,sizeof(struct trans_buf));
//check range
at24cxx_check_range(&at_buf,size);
//set the slave adr and sub adr
at_24cxx_set_data(&slaveadr,&subadr);
msgs[0].addr = slaveadr;
msgs[0].flags = 0;//W cmd
msgs[0].buf = &subadr;
msgs[0].len = 1;
msgs[1].addr = slaveadr;
msgs[1].flags = 1;//R cmd
msgs[1].buf = &(at_buf.buf[0]);
msgs[1].len = at_buf.len;
i2c_transfer(at24cxx_struct->at_client->adapter,msgs,ARRAY_SIZE(msgs));
ret = copy_to_user(buf,&at_buf,sizeof(struct trans_buf));
return size;
}
/*****ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *)*****/
static ssize_t at24cxx_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
int ret=0;
unsigned char subadr=0,slaveadr=0,wpos=0,wlen=0;
struct i2c_msg msgs[2];
memset(msgs,0,sizeof(msgs));
//get the buf form user,to store in at_buf
ret = copy_from_user(&at_buf,buf,sizeof(struct trans_buf));
//check range
at24cxx_check_range(&at_buf,size);
//set the slave adr and sub adr
at_24cxx_set_data(&slaveadr,&subadr);
while(wpos < at_buf.len)
{
at_24cxx_transfer(msgs,&wpos,&wlen);
wpos +=wlen;
printk("4.now the pos:%d\n",wpos);
i2c_transfer(at24cxx_struct->at_client->adapter,msgs,ARRAY_SIZE(msgs));
mdelay(10);//must do this ,but can't find reason
}
return size;
}
static struct file_operations at24cxx_fops =
{
.owner =THIS_MODULE,
.read =at24cxx_read,
.write =at24cxx_write,
};
/*****int (*probe)(struct i2c_client *, const struct i2c_device_id *)*****/
static int at24cxx_probe(struct i2c_client *uc_i2c_client, const struct i2c_device_id * uc_i2c_id_table)
{
static unsigned int probe_count =0;
probe_count++;
g_id =uc_i2c_id_table;
printk("iic driver probe time:%d.\n ",probe_count);
/* alloc the dev number*/
alloc_chrdev_region(&at_dev_num,0,1,"at24cxx");
/* get the buf for at24cxx_struct */
at24cxx_struct = kzalloc(sizeof(struct at24cxx_dev),GFP_KERNEL);
at24cxx_struct->at_client = uc_i2c_client;
/* initial cdev struct,add cdev to kernel */
cdev_init(&(at24cxx_struct->at_dev),&at24cxx_fops);
cdev_add(&(at24cxx_struct->at_dev),at_dev_num,1);
/*create device node file use*/
at_class =class_create(THIS_MODULE,"at24cxx");
at_device =device_create(at_class,NULL,at_dev_num,NULL,"at24cxx");
return 0;
}
/*****int (*remove)(struct i2c_client *)*****/
static int at24cxx_remove(struct i2c_client *uc_i2c_client)
{
device_destroy(at_class,at_dev_num);
class_destroy(at_class);
cdev_del(&(at24cxx_struct->at_dev));
kfree(at24cxx_struct);
unregister_chrdev_region(at_dev_num,1);
return 0;
}
static struct i2c_driver at24cxx_drv =
{
.driver =
{
.name ="at24cxx",
.owner= THIS_MODULE,
},
.probe = at24cxx_probe,
.remove = at24cxx_remove,
//match use
.id_table = at24cxx_id,
};
int __init at24cxx_init(void)
{
i2c_add_driver(&at24cxx_drv);
return 0;
}
void __exit at24cxx_exit(void)
{
i2c_del_driver(&at24cxx_drv);
}
module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_LICENSE("GPL");
IIC总线不是一条虚拟总线,它是实实在在存在的,而且s5pv210有自己的IIC控制器。我们在编写我们的驱动之前,实则kernel已经帮我们初始化好了IIC控制器。所以我们可以直接调用i2c_transfer()这个函数来直接传送数据.
s5pv210的IIC控制器的驱动在24cxx_driver.c文件中实现.挂载在IIC总线上.
i2c-core.c
i2c_transfer
{
--->__i2c_transfer
{
---> adap->algo->master_xfer(s3c24xx_i2c_xfer)
{
--->s3c24xx_i2c_doxfer
{
--->s3c24xx_i2c_message_start
{
...
--->int
}
}
}
}
}
--->s3c24xx_i2c_irq
{
--->i2c_s3c_irq_nextbyte
{
--->s3c24xx_i2c_message_start
.
.
.
--->s3c24xx_i2c_stop
}
}
IIC 总线实则在i2c-core.c中实现。s5pv210的IIC控制器驱动就注册到这根总线上.
底层,IIC 控制器实际上操作 IICCON,IICSDA,IICDS这几个寄存器。
具体对应到芯片手册上,如下:
这里我们不再看寄存器内容,具体需要对照i2c-s3c2410.c来看.
写完驱动,我们再来写测试程序:
当然驱动程序和测试程序是要匹配的。有时候你可以先想想测试程序如何操作你的设备,那么反而你的驱动程序会好写。这两者是密切相关的.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUF_SIZE 32
#define _USER_DEBUG 0
/*read /write 16 bytes per time*/
struct trans_buf
{
unsigned int subadr;//sub adr to rw
unsigned char len; // how many want to get
unsigned char buf[BUF_SIZE]; //buf
};
struct trans_buf at_buf;
void print_usage(char *file)
{
printf("%s w addr val0 val1 val2 ... valn\n 1<=n<=%d\n",file,BUF_SIZE);
printf("%s r addr size \n 1<=size<=%d\n",file,BUF_SIZE);
}
//write: a.out w adr data0 data1 data2 data3 ...
//read: a.out r adr size
int main(int argc ,char *argv[])
{
int fd,i;
unsigned char len;
if(argc < 4)
{
print_usage(argv[0]);
return -1;
}
else if(argc ==4 && strcmp(argv[1],"r") == 0)
{
//read lenth
len = strtoul(argv[3],NULL,0);
if(len > BUF_SIZE|| len ==0)
{
print_usage(argv[0]);
return -1;
}
at_buf.len = len;
//read pos/adr
at_buf.subadr = strtoul(argv[2],NULL,0);
}
else if(strcmp(argv[1],"w") == 0 && argc >=4)
{
//write len
len = argc - 3;
if(len > BUF_SIZE || len ==0)
{
print_usage(argv[0]);
return -1;
}
at_buf.len = len;
//write pos/adr
at_buf.subadr = strtoul(argv[2],NULL,0);
//get the data to wrire to
for(i=3;i < argc;i++)
{
sscanf(argv[i], "%d", &at_buf.buf[i-3]);
#if(_USER_DEBUG)
printf("data[%i]=%d.\n",i-3,at_buf.buf[i-3]);
#endif
}
}
else
{
print_usage(argv[0]);
return -1;
}
#if(_USER_DEBUG)
printf("len:%d,subadr:0x%2x.\n",len,at_buf.subadr);
#endif
fd =open("/dev/at24cxx",O_RDWR);
if(strcmp(argv[1],"r") == 0)
{
read(fd,&at_buf,len);
printf("read data form addr 0x%2x:",at_buf.subadr);
for(i=0;i <len;i++)
{
printf("%d ",*(at_buf.buf+i));
}
printf("\n");
}
else if(strcmp(argv[1],"w") == 0)
{
write(fd,&at_buf,len);
}
close(fd);
return 0;
}
24cxx的操作分为读和写。所以简单的字符设备完全可以满足和实现。所以采用字符设备驱动来实现.
实验结果:
这里我们先从subadr:932上顺序读32个字节。然后再从932开始按页写32个字节。然后再读出来与之前的作比较.
这里讲一点:按照前面的分析,24c08一次传送32个字节需要至少2个包。最多三个。但在这里,我们写入用了几个包?