上篇文章實現了了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個包。最多三個。但在這裏,我們寫入用了幾個包?