(6)LinuxI2C驱动--I2C设备驱动

本节主要分析eeprom的所属的i2c设备驱动,此驱动主要实现了能够通过sysfs文件系统访问eeprom。

1. eeprom板级设备资源

因为原开发板的eeprom驱动还没调试好,板级资源还没写好,所以需要自己加进去。
修改arch/arm/mach-s5pv210/mach-smdkc110.c文件。

 static struct at24_platform_data at24c01 = {
     .byte_len = SZ_8K / 8,/*eeprom大小*/
     .page_size = 8,/*页大小*/
 };
 /* I2C0 */
 static struct i2c_board_info i2c_devs0[] __initdata = {
    {
         I2C_BOARD_INFO("24c01",0x50),//0x50是eeprom的设备地址
         .platform_data=&at24c01,
     },
}

这样以后后面调用smdkc110_machine_init就会把资源注册进去。

 static void __init smdkc110_machine_init(void)
 {
….
     i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
….
}

2. AT24C01A EEPROM 的I2C设备驱动

2.1 at24_driver

前面讲i2c驱动架构的时候,说到I2C设备驱动主要由i2c_driver来描述。

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

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

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

 static int __init at24_init(void)//模块初始化
 {
     io_limit = rounddown_pow_of_two(io_limit);//io_limit是写eeprom时允许一次写入的最大字节,默认128Byte,是驱动模块参数。
     return i2c_add_driver(&at24_driver);//添加i2c_driver,在i2c核心中实现,会调用at24_probe.
 }
 module_init(at24_init);

 static void __exit at24_exit(void)//模块卸载
 {
     i2c_del_driver(&at24_driver);//删除i2c_driver,会调用at24_remove
 }
 module_exit(at24_exit);

2.2 at24_probe() / at24_remove()

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;
}

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是此驱动的一些私有数据的封装,包括二进制结点,以及写缓冲区。

static int __devexit at24_remove(struct i2c_client *client)
{
    struct at24_data *at24;
    int i;

    at24 = i2c_get_clientdata(client);
    sysfs_remove_bin_file(&client->dev.kobj, &at24->bin);

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

    kfree(at24->writebuf);
    kfree(at24);
    return 0;
}

at24_remove()基本就是at24_probe()的反操作。

2.3 at24_bin_read()

static ssize_t at24_bin_read(struct file *filp, struct kobject *kobj,
         struct bin_attribute *attr,
         char *buf, loff_t off, size_t count)
 {
     struct at24_data *at24;

     //通过kobj获得device,再获取driver_data
     at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
     return at24_read(at24, buf, off, count);//调用at24_read()
 }

at24_bin_read()通过dev_get_drvdata()获取at24_data结构体数据。然后调用at24_read()。

 static ssize_t at24_read(struct at24_data *at24,
         char *buf, loff_t off, size_t count)
 {
     ssize_t retval = 0;

     if (unlikely(!count))
         return count;

     /*
      * Read data from chip, protecting against concurrent updates
      * from this host, but not from other I2C masters.
      */
     mutex_lock(&at24->lock);//访问设备前加锁

     while (count) {
         ssize_t status;

         status = at24_eeprom_read(at24, buf, off, count);
         if (status <= 0) {
             if (retval == 0)
                 retval = status;
             break;
         }
         buf += status;
         off += status;
         count -= status;
         retval += status;
     }

     mutex_unlock(&at24->lock);//访问结束后解锁

     return retval;
 }

at24_read()传入的参数,at24是驱动私有数据结构体at24_data,buf是读eeprom后读到的数据存储的缓冲区,off是数据的偏移地址,count是要读数据的大小。at24_read()主要调用at24_eeprom_read()去读,但是此函数读eeprom能读到的数据个数有限制,不一定一次就把count个数据都读到,所以用while来读,并且读到status个数据后更新count,表示还剩多少个数据没读到,同时也要更新数据偏移off,和读入缓冲buf。

static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf,
        unsigned offset, size_t count)
{
    struct i2c_msg msg[2];
    u8 msgbuf[2];
    struct i2c_client *client;
    unsigned long timeout, read_time;
    int status, i;

    memset(msg, 0, sizeof(msg));

    /*
     * REVISIT some multi-address chips don't rollover page reads to
     * the next slave address, so we may need to truncate the count.
     * Those chips might need another quirk flag.
     *
     * If the real hardware used four adjacent 24c02 chips and that
     * were misconfigured as one 24c08, that would be a similar effect:
     * one "eeprom" file not four, but larger reads would fail when
     * they crossed certain pages.
     */

    /*
     * Slave address and byte offset derive from the offset. Always
     * set the byte address; on a multi-master board, another master
     * may have changed the chip's "current" address pointer.
     */
    client = at24_translate_offset(at24, &offset);//获得client

    if (count > io_limit)
        count = io_limit;

    switch (at24->use_smbus) {//如果使用SMBUS
    case I2C_SMBUS_I2C_BLOCK_DATA:
        /* Smaller eeproms can work given some SMBus extension calls */
        if (count > I2C_SMBUS_BLOCK_MAX)
            count = I2C_SMBUS_BLOCK_MAX;
        break;
    case I2C_SMBUS_WORD_DATA:
        count = 2;
        break;
    case I2C_SMBUS_BYTE_DATA:
        count = 1;
        break;
    default://使用I2C协议
        /*
         * When we have a better choice than SMBus calls, use a
         * combined I2C message. Write address; then read up to
         * io_limit data bytes. Note that read page rollover helps us
         * here (unlike writes). msgbuf is u8 and will cast to our
         * needs.
         */
        i = 0;
        if (at24->chip.flags & AT24_FLAG_ADDR16)
            msgbuf[i++] = offset >> 8;
        msgbuf[i++] = offset;

        //由前小节读eeprom的时序可知,需要2条消息,第一条消息是写eeprom
        msg[0].addr = client->addr;//设备地址,即0x50
        msg[0].buf = msgbuf;
        msg[0].len = i;

        //第二条消息才是读eeprom,读到的数据存储在buf中。
        msg[1].addr = client->addr;//设备地址
        msg[1].flags = I2C_M_RD;//读
        msg[1].buf = buf;//读缓冲区
        msg[1].len = count;//要读数据的长度
    }

    /*
     * Reads fail if the previous write didn't complete yet. We may
     * loop a few times until this one succeeds, waiting at least
     * long enough for one entire page write to work.
     */
    timeout = jiffies + msecs_to_jiffies(write_timeout);
    do {
        read_time = jiffies;
        switch (at24->use_smbus) {
        case I2C_SMBUS_I2C_BLOCK_DATA:
            status = i2c_smbus_read_i2c_block_data(client, offset,
                    count, buf);
            break;
        case I2C_SMBUS_WORD_DATA:
            status = i2c_smbus_read_word_data(client, offset);
            if (status >= 0) {
                buf[0] = status & 0xff;
                buf[1] = status >> 8;
                status = count;
            }
            break;
        case I2C_SMBUS_BYTE_DATA:
            status = i2c_smbus_read_byte_data(client, offset);
            if (status >= 0) {
                buf[0] = status;
                status = count;
            }
            break;
        default://使用I2C协议去读
            status = i2c_transfer(client->adapter, msg, 2);//实际的数据传输,
            if (status == 2)
                status = count;
        }
        dev_dbg(&client->dev, "read %zu@%d --> %d (%ld)\n",
                count, offset, status, jiffies);

        if (status == count)//已经全部读取,则返回
            return count;

        /* REVISIT: at HZ=100, this is sloooow */
        msleep(1);
    } while (time_before(read_time, timeout));

    return -ETIMEDOUT;
}

at24_eeprom_read()根据读eeprom所需要的时序,填充两个i2c消息结构体,第一个i2c消息结构体是写eeprom,告诉eeprom要读的数据是哪个,第二个i2c消息才是真正的读eeprom。最后把这两个i2c消息结构体传给i2c_transfer()进行实际的消息传输。i2c_transfer()是i2c核心的函数,用于i2c设备与i2c适配器直接的消息传递,后面会分析。这里我们看到了i2c设备驱动通过i2c核心向i2c总线驱动传递消息的主要途径,i2c总线驱动接收到i2c消息后就会控制i2c适配器根据传入的i2c消息,通过SDA和SCL与eeprom进行交互。

2.4 at24_bin_write()

 static ssize_t at24_bin_write(struct file *filp, struct kobject *kobj,
         struct bin_attribute *attr,
         char *buf, loff_t off, size_t count)
 {
     struct at24_data *at24;

     at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
     return at24_write(at24, buf, off, count);
 }

at24_bin_write()与at24_bin_read()一样操作,获得at24_data后调用at24_write().

 static ssize_t at24_write(struct at24_data *at24, const char *buf, loff_t off,
               size_t count)
 {
     ssize_t retval = 0;

     if (unlikely(!count))
         return count;

     /*
      * Write data to chip, protecting against concurrent updates
      * from this host, but not from other I2C masters.
      */
     mutex_lock(&at24->lock);

     while (count) {
         ssize_t status;

         status = at24_eeprom_write(at24, buf, off, count);
         if (status <= 0) {
             if (retval == 0)
                 retval = status;
             break;
         }
         buf += status;
         off += status;
         count -= status;
         retval += status;
     }

     mutex_unlock(&at24->lock);

     return retval;
 }

at24_write()的操作也是类似的,通过调用at24_eeprom_write()来实现。

static ssize_t at24_eeprom_write(struct at24_data *at24, const char *buf,
        unsigned offset, size_t count)
{
    struct i2c_client *client;
    struct i2c_msg msg;
    ssize_t status;
    unsigned long timeout, write_time;
    unsigned next_page;

    /* Get corresponding I2C address and adjust offset */
    client = at24_translate_offset(at24, &offset);//获得对应的client

    /* write_max is at most a page */
    //检查写入字数
    if (count > at24->write_max)
        count = at24->write_max;

    /* Never roll over backwards, to the start of this page */
    //写入不会越过页边界(下一页)
    next_page = roundup(offset + 1, at24->chip.page_size);
    if (offset + count > next_page)
        count = next_page - offset;

    /* If we'll use I2C calls for I/O, set up the message */
    if (!at24->use_smbus) {//使用i2c协议,则填充i2c消息结构体
        int i = 0;

        //由前小节分析,写eeprom只需一条i2c消息
        msg.addr = client->addr;//设备地址
        msg.flags = 0;//写eeprom

        /* msg.buf is u8 and casts will mask the values */
        msg.buf = at24->writebuf;//写缓冲区
        if (at24->chip.flags & AT24_FLAG_ADDR16)
            msg.buf[i++] = offset >> 8;

        msg.buf[i++] = offset;
        memcpy(&msg.buf[i], buf, count);//复制需要发送的数据
        msg.len = i + count;//发送传读为要发送的数据长度,加上地址长度
    }

    /*
     * Writes fail if the previous one didn't complete yet. We may
     * loop a few times until this one succeeds, waiting at least
     * long enough for one entire page write to work.
     */
    timeout = jiffies + msecs_to_jiffies(write_timeout);//超时时间,为驱动模块参数,默认25ms
    do {
        write_time = jiffies;
        if (at24->use_smbus) {
            status = i2c_smbus_write_i2c_block_data(client,
                    offset, count, buf);
            if (status == 0)
                status = count;
        } else {//i2c传输
            status = i2c_transfer(client->adapter, &msg, 1);//实际传输
            if (status == 1)
                status = count;
        }
        dev_dbg(&client->dev, "write %zu@%d --> %zd (%ld)\n",
                count, offset, status, jiffies);

        if (status == count)//已经全部写入,返回
            return count;

        /* REVISIT: at HZ=100, this is sloooow */
        msleep(1);
    } while (time_before(write_time, timeout));

    return -ETIMEDOUT;
}

与at24_eeprom_read()类似,at24_eeprom_write()因为写eeprom需要1条i2c消息,最后实际的传输也是通过i2c_transfer()实现。

3. 总结

由上面简单的分析可知,通过sysfs文件系统访问eeprom,对/sys/bus/i2c/devices/0-0050/eeprom的读写是通过at24_bin_read()/at24_bin_write() ==> at24_eeprom_read()/at24_eeprom_write() ==>i2c_transfer()来实现的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hello2mao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值