i2c子系统-eeprom实例分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_27087571/article/details/55802627

两种访问eeprom的例子

在Linux应用层访问eeprom的方法,并给出示例代码方便大家理解。第一个方法是通过eeprom的设备文件进行访问。这两个方法分别对应了i2c设备驱动的两个不同的实现,第二个方法是通过sysfs文件系统对eeprom进行访问。

第一种通过devfs访问eeprom的方法是linux i2c提供的一种通用的方法,是有i2c子系统提供的,访问设备的能力有限,通过devfs访问eeprom的方法则需要了解eeprom的读写时序。对写应用程序开发人员的要求较高。

第二种通过sysfs文件系统的二进制结点访问eeprom的方法是由eeprom的设备驱动实现的,是一种专有的方法。大体来讲就是关于时序的操作已经由驱动程序完成了,并以bin_attribute的方式开放到用户空间,应用程序开发人员只按文件操作的方式操作设备;通过sysfs文件系统访问eeprom操作简单,无需了解eeprom的硬件特性以及访问时序。

通过devfs访问I2C设备

介绍这个之前,先来看一下:i2c-dev.c 文件完全可以被看作一个 I2C 设备驱动,它实现的一个 i2c_client 是虚拟、临时的,随着设备文件的打开而产生,并随设备文件的关闭而撤销,并没有被添加到 i2c_adapter 的 clients
链表中。i2c-dev.c 针对每个 I2C 适配器生成一个主设备号为 89 的设备文件,实现了i2c_driver 的成员函数以及文件操作接口,所以 i2c-dev.c 的主体是“i2c_driver 成员函数 + 字符设备驱动” 。

i2c-dev.c 中提供 i2cdev_read()、i2cdev_write()函数来对应用户空间要使用的 read()和 write()文件操作接口,这两个函数分别调用 I2C 核心的 i2c_master_recv()和i2c_master_send()函数来构造一条 I2C 消息并引发适配器 algorithm 通信函数的调用,完成消息的传输,对应于如图 15.7 所示的时序。但是,很遗憾,大多数稍微复杂一点I2C 设备的读写流程并不对应于一条消息,往往需要两条甚至更多的消息来进行一次读写周期,这种情况下,在应用层仍然调用 read()、write()文件 API 来读写 I2C 设备,将不能正确地读写。许多工程师碰到过类似的问题,往往经过相当长时间的调试都没法解决 I2C 设备的读写,连错误的原因也无法找到,显然是对 i2cdev_read()和 i2cdev_write()函数的作用有所误解。

鉴于上述原因,i2c-dev.c 中 i2cdev_read()和 i2cdev_write()函数不具备太强的通用性,没有太大的实用价值,只能适用于非 RepStart 模式的情况。对于两条以上消息组成的读写,在用户空间需要组织 i2c_msg 消息数组并调用 I2C_RDWR IOCTL 命令。代码清单 15.21 所示为 i2cdev_ioctl()函数的框架,其中详细列出了 I2C_RDWR 命令的处理过程。

#include <stdio.h>
#include <linux/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#define I2C_RETRIES 0x0701
#define I2C_TIMEOUT 0x0702
#define I2C_RDWR 0x0707 

#define I2C_ADDR 0x50

struct i2c_msg
{
    unsigned short addr;
    unsigned short flags;
#define I2C_M_TEN 0x0010
#define I2C_M_RD 0x0001
    unsigned short len;
    unsigned char *buf;
};

struct i2c_rdwr_ioctl_data
{
    struct i2c_msg *msgs;
    int nmsgs; 
};

/***********main***********/
int main(){
    int fd,ret;
    struct i2c_rdwr_ioctl_data e2prom_data;

    //disable WP
    system("echo 103 > /sys/class/gpio/export");
    system("echo \"out\" > /sys/class/gpio/gpio103/direction");
    system("echo 0 > /sys/class/gpio/gpio103/value");

    fd=open("/dev/i2c-0",O_RDWR);//打开eeprom设备文件结点

    if(fd<0){
        perror("open error");
    }
    e2prom_data.nmsgs=2; 

    e2prom_data.msgs=(struct i2c_msg*)malloc(e2prom_data.nmsgs*sizeof(struct i2c_msg));
    if(!e2prom_data.msgs)
    {
        perror("malloc error");
        exit(1);
    }
    ioctl(fd,I2C_TIMEOUT,1);/*超时时间*/
    ioctl(fd,I2C_RETRIES,2);/*重复次数*/

    /***write data to e2prom**/
    e2prom_data.nmsgs=1;//由前面eeprom读写分析可知,写eeprom需要一条消息
    (e2prom_data.msgs[0]).len=5;//此消息的长度为5个字节,第一,二个字节是要写入数据的地址,第三,四,五,字节是要写入的数据
    (e2prom_data.msgs[0]).addr=I2C_ADDR;//e2prom 设备地址
    (e2prom_data.msgs[0]).flags=0; //write
    (e2prom_data.msgs[0]).buf=(unsigned char*)malloc(5);
    (e2prom_data.msgs[0]).buf[0]=0x00;// e2prom addr[15:8] 
    (e2prom_data.msgs[0]).buf[1]=0x00;//e2prom addr[7:0] 
    (e2prom_data.msgs[0]).buf[2]=0x55;//the data to write byte0
    (e2prom_data.msgs[0]).buf[3]=0x66;//the data to write byte1
    (e2prom_data.msgs[0]).buf[4]=0x77;//the data to write byte2

        ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);//通过ioctl进行实际写入操作
    if(ret<0)
    {
        perror("ioctl error1");
    }
    sleep(1);

        /******read data from e2prom*******/
    e2prom_data.nmsgs=2;//读eeprom需要两条消息
    (e2prom_data.msgs[0]).len=1;//第一条消息实际是写eeprom,需要告诉eeprom需要读数据的地址,因此长度为1个字节
    (e2prom_data.msgs[0]).addr=I2C_ADDR; // e2prom 设备地址
    (e2prom_data.msgs[0]).flags=0;//write
    (e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);
    (e2prom_data.msgs[0]).buf[0]=0x00;// e2prom addr[15:8] 
    (e2prom_data.msgs[0]).buf[1]=0x00;//e2prom addr[7:0] 
    (e2prom_data.msgs[1]).len=6;
    (e2prom_data.msgs[1]).addr=I2C_ADDR;
    (e2prom_data.msgs[1]).flags=I2C_M_RD;
    (e2prom_data.msgs[1]).buf=(unsigned char*)malloc(6);
    (e2prom_data.msgs[1]).buf[0]=0;
    (e2prom_data.msgs[1]).buf[1]=0;
    (e2prom_data.msgs[1]).buf[2]=0;
    (e2prom_data.msgs[1]).buf[3]=0;
    (e2prom_data.msgs[1]).buf[4]=0;
    (e2prom_data.msgs[1]).buf[5]=0;

        ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);//通过ioctl进行实际的读操作
    if(ret<0)
    {
        perror("ioctl error2");
    }
    printf("Read back:0x%02X\n\r",(e2prom_data.msgs[1]).buf[0]);
    printf("Read back:0x%02X\n\r",(e2prom_data.msgs[1]).buf[1]);
    printf("Read back:0x%02X\n\r",(e2prom_data.msgs[1]).buf[2]);


    close(fd);
    system("echo 103 > /sys/class/gpio/unexport");

    return 0;
}

通过 通过sysfs文件系统访问I2C设备

eeprom的设备驱动在/sys/devices/44000000.ocp/44e0b000.i2c/i2c-0/0-0050/eeprom目录下把eeprom设备映射为一个二进制节点,文件名为eeprom。对这个eeprom文件的读写就是对eeprom进行读写。

#define EEPROM_DEVICE "/sys/devices/44000000.ocp/44e0b000.i2c/i2c-0/0-0050/eeprom"
#define TEST_STR "eeprom write/read test!"

int main(void)
{
    int fd;
    struct stat eeprom_stat;
    char read_buf[32] = { '\0' };

    fd = open(EEPROM_DEVICE, O_RDWR);
    if (fd < 0) {
        printf("open eeprom unsuccess\n");
        return;
    }

    if (stat(EEPROM_DEVICE, &eeprom_stat) < 0) {
        perror("failed to get eeprom status");
    } else {
        printf("\neeprom size: %d KB\n\n", eeprom_stat.st_size/1024);
    }

    printf("write \'%s\' to eeprom\n", TEST_STR);
    if (write(fd, TEST_STR, strlen(TEST_STR)) < 0) {
        perror("write to eeprom failed\n");
        return;
    }

    lseek(fd, 0, SEEK_SET);
    if (read(fd, read_buf, strlen(TEST_STR)) < 0) {
        printf("read back failed\n");
        return;
    }

    printf("\nget the following string from eeprom:\n%s\n\n", read_buf);

    return 0;
}

eeprom驱动分析

在drivers/misc/eeprom/at24.c中可以看到eeprom驱动对i2c_driver结构的实例化。

static struct i2c_driver at24_driver = {
    .driver = {
        .name = "at24",
        .owner = THIS_MODULE,
    },
    .probe = at24_probe,
    .remove = at24_remove,
    .id_table = at24_ids,
};

其中probe和remove会在模块初始化和卸载的时候被调用。

static int __init at24_init(void)
{
    if (!io_limit) {
        pr_err("at24: io_limit must not be 0!\n");
        return -EINVAL;
    }

    io_limit = rounddown_pow_of_two(io_limit);
    return i2c_add_driver(&at24_driver);
}
module_init(at24_init);

static void __exit at24_exit(void)
{
    i2c_del_driver(&at24_driver);
}

at24_probe()

static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct at24_platform_data chip;
    bool writable;
    int use_smbus = 0;
    struct at24_data *at24;
    int err;
    unsigned i, num_addresses;
    kernel_ulong_t magic;

    //获取板级设备信息
    if (client->dev.platform_data) {
        chip = *(struct at24_platform_data *)client->dev.platform_data;
    } else {
        if (!id->driver_data) {
            err = -ENODEV;
            goto err_out;
        }
        magic = id->driver_data;
        chip.byte_len = BIT(magic & AT24_BITMASK(AT24_SIZE_BYTELEN));
        magic >>= AT24_SIZE_BYTELEN;
        chip.flags = magic & AT24_BITMASK(AT24_SIZE_FLAGS);
        /*
         * This is slow, but we can't know all eeproms, so we better
         * play safe. Specifying custom eeprom-types via platform_data
         * is recommended anyhow.
         */
        chip.page_size = 1;

        chip.setup = NULL;
        chip.context = NULL;
    }

    //检查参数,必须为2的幂
    if (!is_power_of_2(chip.byte_len))
        dev_warn(&client->dev,
            "byte_len looks suspicious (no power of 2)!\n");
    if (!is_power_of_2(chip.page_size))
        dev_warn(&client->dev,
            "page_size looks suspicious (no power of 2)!\n");

    /* Use I2C operations unless we're stuck with SMBus extensions. */
    //检查是否支持I2C协议,如果不支持则检查是否支持SMBUS
    if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
        if (chip.flags & AT24_FLAG_ADDR16) {
            err = -EPFNOSUPPORT;
            goto err_out;
        }
        if (i2c_check_functionality(client->adapter,
                I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
            use_smbus = I2C_SMBUS_I2C_BLOCK_DATA;
        } else if (i2c_check_functionality(client->adapter,
                I2C_FUNC_SMBUS_READ_WORD_DATA)) {
            use_smbus = I2C_SMBUS_WORD_DATA;
        } else if (i2c_check_functionality(client->adapter,
                I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
            use_smbus = I2C_SMBUS_BYTE_DATA;
        } else {
            err = -EPFNOSUPPORT;
            goto err_out;
        }
    }

    if (chip.flags & AT24_FLAG_TAKE8ADDR)//检查时候使用8个地址
        num_addresses = 8;
    else
        num_addresses = DIV_ROUND_UP(chip.byte_len,//AT24C01使用一个地址
            (chip.flags & AT24_FLAG_ADDR16) ? 65536 : 256);

    at24 = kzalloc(sizeof(struct at24_data) +
        num_addresses * sizeof(struct i2c_client *), GFP_KERNEL);//为at24_data分配内存,同时根据地址个数分配i2c_client
    if (!at24) {
        err = -ENOMEM;
        goto err_out;
    }

    mutex_init(&at24->lock);
    //初始化at24_data,也就是填充此结构体
    at24->use_smbus = use_smbus;
    at24->chip = chip;
    at24->num_addresses = num_addresses;

    /*
     * Export the EEPROM bytes through sysfs, since that's convenient.
     * By default, only root should see the data (maybe passwords etc)
     */
    //以二进制结点的形式呈现eeprom的数据
    sysfs_bin_attr_init(&at24->bin);
    at24->bin.attr.name = "eeprom";//结点名字
    at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR;
    at24->bin.read = at24_bin_read;//绑定读函数
    at24->bin.size = chip.byte_len;

    at24->macc.read = at24_macc_read;

    //判断是否可写
    writable = !(chip.flags & AT24_FLAG_READONLY);
    if (writable) {//如果可写
        if (!use_smbus || i2c_check_functionality(client->adapter,
                I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {

            unsigned write_max = chip.page_size;

            at24->macc.write = at24_macc_write;

            at24->bin.write = at24_bin_write;//绑定写函数
            at24->bin.attr.mode |= S_IWUSR;//文件拥有者可写

            if (write_max > io_limit)//一次最多写io_limit个字节
                write_max = io_limit;
            if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX)
                write_max = I2C_SMBUS_BLOCK_MAX;
            at24->write_max = write_max;

            /* buffer (data + address at the beginning) */
            at24->writebuf = kmalloc(write_max + 2, GFP_KERNEL);//分配缓冲区,多余两个字节用于保存寄存器地址
            if (!at24->writebuf) {
                err = -ENOMEM;
                goto err_struct;
            }
        } else {
            dev_warn(&client->dev,
                "cannot write due to controller restrictions.");
        }
    }

    at24->client[0] = client;

    /* use dummy devices for multiple-address chips */
    for (i = 1; i < num_addresses; i++) {
        at24->client[i] = i2c_new_dummy(client->adapter,
                    client->addr + i);
        if (!at24->client[i]) {
            dev_err(&client->dev, "address 0x%02x unavailable\n",
                    client->addr + i);
            err = -EADDRINUSE;
            goto err_clients;
        }
    }

    //向sysfs文件系统注册二进制结点
    err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin);
    if (err)
        goto err_clients;

    //保存驱动数据
    i2c_set_clientdata(client, at24);

    dev_info(&client->dev, "%zu byte %s EEPROM %s\n",
        at24->bin.size, client->name,
        writable ? "(writable)" : "(read-only)");
    if (use_smbus == I2C_SMBUS_WORD_DATA ||
        use_smbus == I2C_SMBUS_BYTE_DATA) {
        dev_notice(&client->dev, "Falling back to %s reads, "
               "performance will suffer\n", use_smbus ==
               I2C_SMBUS_WORD_DATA ? "word" : "byte");
    }
    dev_dbg(&client->dev,
        "page_size %d, num_addresses %d, write_max %d, use_smbus %d\n",
        chip.page_size, num_addresses,
        at24->write_max, use_smbus);

    /* export data to kernel code */
    if (chip.setup)
        chip.setup(&at24->macc, chip.context);

    return 0;

err_clients:
    for (i = 1; i < num_addresses; i++)
        if (at24->client[i])
            i2c_unregister_device(at24->client[i]);

    kfree(at24->writebuf);
err_struct:
    kfree(at24);
err_out:
    dev_dbg(&client->dev, "probe error %d\n", err);
    return err;
}
如果你正在开发的设备驱动程序中需要与用户层的接口,一般可选的方法有:

注册虚拟的字符设备文件,以这个虚拟设备上的 read/write/ioctl 等接口与用户交互;但 read/write 一般只能做一件事情, ioctl 可以根据 cmd 参数做多个功能(使用i2c-dev.c中的函数就是这个道理),但其缺点是很明显的: ioctl 接口无法直接在 Shell 脚本中使用,为了使用 ioctl 的功能,还必须编写配套的 C语言的虚拟设备操作程序, ioctl 的二进制数据接口也是造成大小端问题 (big endian与little endian)、32位/64位不可移植问题的根源;
注册 proc 接口,接受用户的 read/write/ioctl 操作;同样的,一个 proc 项通常使用其 read/write/ioctl 接口,它所存在的问题与上面的虚拟字符设备的的问题相似;
注册 sysfs 属性;
最重要的是,添加虚拟字符设备支持和注册 proc 接口支持这两者所需要增加的代码量都并不少,最好的方法还是使用 sysfs 属性支持,一切在用户层是可见的透明,且增加的代码量是最少的,可维护性也最好;

at24_probe()函数主要的工作是在sys目录在创建bin结点文件,也就是前面通过sysfs文件系统访问i2c设备中提到的/sys/bus/i2c/devices/0-0050/eeprom文件,用户可以用此文件来操作eeprom,提供读/写操作方法,在probe里面读写操作已经与二进制结点绑定,读操作函数是at24_bin_read(),写操作函数是at24_bin_write()。

其中有个重要的结构体:
 struct at24_data {
     struct at24_platform_data chip;
     struct memory_accessor macc;
     int use_smbus;

     /*
      * Lock protects against activities from other Linux tasks,
      * but not from changes by other I2C masters.
      */
     struct mutex lock;
     struct bin_attribute bin;//二进制结点

     u8 *writebuf;//写缓冲区
     unsigned write_max;
     unsigned num_addresses;

     /* 
      * Some chips tie up multiple I2C addresses; dummy devices reserve
      * them for us, and we'll use them with SMBus calls.
      */
     struct i2c_client *client[];
 };

at24_data是此驱动的一些私有数据的封装,包括二进制结点,以及写缓冲区。

而对at24_bin_read(),at24_bin_write()的进一步追踪可知:at24_bin_read()—->at24_read—->at24_eeprom_read;at24_bin_write—->at24_write—->at24_eeprom_write—->2c_transfer i2c_transfer

猜你喜欢

转载自blog.csdn.net/qq_27087571/article/details/55802627
今日推荐