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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章