linux驅動開發:24cxx一次讀、寫多個字節的編程實現

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

發佈了37 篇原創文章 · 獲贊 5 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章