文档章节

I2C EEPROM驱动实例分析

y
 yepanl
发布于 2018/12/11 23:00
字数 2909
阅读 26
收藏 0

    上篇分析了Linux Kernel中的I2C驱动框架,本篇举一个具体的I2C设备驱动(eeprom)来对I2C设备驱动有个实际的认识。

    s3c24xx系列集成了一个基于I2C的eeprom设备at24cxx系列。at24cxx系列芯片包含at24c01, at24c02, at24c04, at24c08, at24c16 等,其中xx代表芯片可寻址范围,如01代表1kB,02代表2kB,如此类推。at24xx系列芯片还支持多地址寻址功能,即,支持多个地址芯片。

    直接分析代码:drivers/misc/eeprom/at24.c

    1,驱动注册:

static const struct i2c_device_id at24_ids[] = {    // at24设备驱动程序支持at24cxx全系列驱动
    /* needs 8 addresses as A0-A2 are ignored */
    { "24c00", AT24_DEVICE_MAGIC(128 / 8, AT24_FLAG_TAKE8ADDR) },
    /* old variants can't be handled with this generic entry! */
    { "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) },
    { "24c02", AT24_DEVICE_MAGIC(2048 / 8, 0) },
    /* spd is a 24c02 in memory DIMMs */
    { "spd", AT24_DEVICE_MAGIC(2048 / 8,
        AT24_FLAG_READONLY | AT24_FLAG_IRUGO) },
    { "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) },
    /* 24rf08 quirk is handled at i2c-core */
    { "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) },
    { "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) },
    { "24c32", AT24_DEVICE_MAGIC(32768 / 8, AT24_FLAG_ADDR16) },
    { "24c64", AT24_DEVICE_MAGIC(65536 / 8, AT24_FLAG_ADDR16) },
    { "24c128", AT24_DEVICE_MAGIC(131072 / 8, AT24_FLAG_ADDR16) },
    { "24c256", AT24_DEVICE_MAGIC(262144 / 8, AT24_FLAG_ADDR16) },
    { "24c512", AT24_DEVICE_MAGIC(524288 / 8, AT24_FLAG_ADDR16) },
    { "24c1024", AT24_DEVICE_MAGIC(1048576 / 8, AT24_FLAG_ADDR16) },
    { "at24", 0 },
    { /* END OF LIST */ }
};

static struct i2c_driver at24_driver = {
    .driver = {
        .name = "at24",
        .acpi_match_table = ACPI_PTR(at24_acpi_ids),
    },
    .probe = at24_probe,    // i2c_bus_type match()之后,调用 i2c_driver的probe()
    .remove = at24_remove,
    .id_table = at24_ids,    // 驱动支持设备id列表
};

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);    // 注册at24cxx系列i2c设备驱动
}

    2,设备探测probe

    (1)at24cxx设备平台数据结构

struct at24_platform_data {
    u32        byte_len;        /* size (sum of all addr) */    // 设备支持的容量
    u16        page_size;        /* for writes */    // 设备支持一次读取或写入的大小
    u8        flags;    // 设备特性,如下
#define AT24_FLAG_ADDR16    0x80    /* address pointer is 16 bit */
#define AT24_FLAG_READONLY    0x40    /* sysfs-entry will be read-only */
#define AT24_FLAG_IRUGO        0x20    /* sysfs-entry will be world-readable */
#define AT24_FLAG_TAKE8ADDR    0x10    /* take always 8 addresses (24c00) */

    void        (*setup)(struct memory_accessor *, void *context);
    void        *context;
};
 

    (2)核心数据结构 struct at24_data

struct at24_data {
    struct at24_platform_data chip;    // 芯片平台数据
    struct memory_accessor macc;
    int use_smbus;
    int use_smbus_write;

    /*
     * Lock protects against activities from other Linux tasks,
     * but not from changes by other I2C masters.
     */
    struct mutex lock;
    struct bin_attribute bin;    // 通过sysfs提供给用户的接口

    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[];    // 多地址芯片实例
};

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

    // 获取eeprom设备平台相关数据,如

    if (client->dev.platform_data) {

        // 设备树或者平台相关代码已经初始化好平台相关数据
        chip = *(struct at24_platform_data *)client->dev.platform_data;
    } else {

        // 没有预先配置好,从驱动支持列表的 magic 中解析平台相关数据,如容量,特性等
        if (id) {
            magic = id->driver_data;
        } else {
            const struct acpi_device_id *aid;

            aid = acpi_match_device(at24_acpi_ids, &client->dev);
            if (aid)
                magic = aid->driver_data;
        }
        if (!magic)
            return -ENODEV;

        chip.byte_len = BIT(magic & AT24_BITMASK(AT24_SIZE_BYTELEN));    // 从magic解析eeprom设备容量
        magic >>= AT24_SIZE_BYTELEN;
        chip.flags = magic & AT24_BITMASK(AT24_SIZE_FLAGS);    // 从magic解析eeprom设备特性
        /*
         * 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;

        /* update chipdata if OF is present */
        at24_get_ofdata(client, &chip);

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

    if (!is_power_of_2(chip.byte_len))
        dev_warn(&client->dev,
            "byte_len looks suspicious (no power of 2)!\n");
    if (!chip.page_size) {
        dev_err(&client->dev, "page_size must not be 0!\n");
        return -EINVAL;
    }
    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. */
    if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
        if (chip.flags & AT24_FLAG_ADDR16)
            return -EPFNOSUPPORT;

        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 {
            return -EPFNOSUPPORT;
        }
    }

    /* Use I2C operations unless we're stuck with SMBus extensions. */
    if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
        if (i2c_check_functionality(client->adapter,
                I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {
            use_smbus_write = I2C_SMBUS_I2C_BLOCK_DATA;
        } else if (i2c_check_functionality(client->adapter,
                I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) {
            use_smbus_write = I2C_SMBUS_BYTE_DATA;
            chip.page_size = 1;
        }
    }

    if (chip.flags & AT24_FLAG_TAKE8ADDR)    // 明确指定支持芯片支持8地址
        num_addresses = 8;
    else
        num_addresses =    DIV_ROUND_UP(chip.byte_len,
            (chip.flags & AT24_FLAG_ADDR16) ? 65536 : 256);    // 根据芯片容量计算支持的地址数量

    at24 = devm_kzalloc(&client->dev, sizeof(struct at24_data) +
        num_addresses * sizeof(struct i2c_client *), GFP_KERNEL);    // 分配核心结构对象 at24_data

    if (!at24)
        return -ENOMEM;

    mutex_init(&at24->lock);
    at24->use_smbus = use_smbus;
    at24->use_smbus_write = use_smbus_write;
    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)
     */

    // sysfs接口初始化
    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;    // sysfs读取接口
    at24->bin.size = chip.byte_len;    // 芯片容量,即,sysfs文件大小

    at24->macc.read = at24_macc_read;

    writable = !(chip.flags & AT24_FLAG_READONLY);    // 设备特性是否可写
    if (writable) {
        if (!use_smbus || use_smbus_write) {

            unsigned write_max = chip.page_size;

            at24->macc.write = at24_macc_write;

            at24->bin.write = at24_bin_write;    // sysfs写入接口
            at24->bin.attr.mode |= S_IWUSR;

            if (write_max > 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 = devm_kzalloc(&client->dev,
                write_max + 2, GFP_KERNEL);    // 分配写入buffer

            if (!at24->writebuf)
                return -ENOMEM;
        } else {
            dev_warn(&client->dev,
                "cannot write due to controller restrictions.");
        }
    }

    // 对于支持多地址芯片,每个地址范围分配一个i2c_client实例

    at24->client[0] = client;    // 默认地址范围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);    // 多地址,其他地址范围每个分配一个dummy client

        if (!at24->client[i]) {
            dev_err(&client->dev, "address 0x%02x unavailable\n",
                    client->addr + i);
            err = -EADDRINUSE;
            goto err_clients;
        }
    }

    err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin);    // 创建sysfs文件,给用户提供访问接口
    if (err)
        goto err_clients;

    i2c_set_clientdata(client, at24);    // 方便通过 client 获取 at24 核心私有结构

    dev_info(&client->dev, "%zu byte %s EEPROM, %s, %u bytes/write\n",
        at24->bin.size, client->name,
        writable ? "writable" : "read-only", at24->write_max);
    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");
    }

    /* 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]);

    return err;
}
 

    3,sysfs读取操作

        at24cxx系列i2c芯片读取协议:先发送写命令,写入要访问eeprom的目标地址(8位或者16位,由芯片平台数据flag决定),再发送读数据命令,读取目标地址数据。因此,读取操作需要两个消息,一个写入消息,一个读取消息。

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;

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

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;    // 空闲buffer首地址更新
        off += status;    // 要读取的eeprom地址更新
        count -= status;    // 空闲buffer大小更新
        retval += status;    // 累计读取的大小更新
    }

    mutex_unlock(&at24->lock);

    return retval;
}
 

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,以及更新client地址范围内偏移offset

    if (count > io_limit)
        count = io_limit;

    if (at24->use_smbus) {
        /* Smaller eeproms can work given some SMBus extension calls */
        if (count > I2C_SMBUS_BLOCK_MAX)
            count = I2C_SMBUS_BLOCK_MAX;
    } else {
        /*
         * 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;    // 如果是16位目标地址,先发送高8位

        msgbuf[i++] = offset;    // 16位目标地址的低8位,或者8位目标地址

        // msg[0]是写命令,写入eeprom的目标地址

        msg[0].addr = client->addr;    // client->addr是eeprom的I2C地址,通常是0x50
        msg[0].buf = msgbuf;
        msg[0].len = i;

        // msg[1]是读命令,读取目标地址的数据

        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;
        if (at24->use_smbus) {
            status = i2c_smbus_read_i2c_block_data_or_emulated(client, offset,
                                       count, buf);
        } else {
            status = i2c_transfer(client->adapter, msg, 2);    // 调用i2c框架传输接口,执行实际数据传输操作
            if (status == 2)    // i2c_transfer返回执行成功的msg个数
                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;
}
 

    4,sysfs写入操作

        at24cxx系列i2c芯片写入协议:先发送写命令,写入要访问eeprom的目标地址(8位或者16位,由芯片平台数据flag决定),再发送写数据命令,读取目标地址数据。因此,写入操作只需要一个写入消息就可以了。

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

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

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 = 0;
    unsigned long timeout, write_time;
    unsigned next_page;

    /* Get corresponding I2C address and adjust offset */
    client = at24_translate_offset(at24, &offset);    // 多地址芯片,计算目标地址落在哪个client,以及更新client地址范围内偏移offset

    /* 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;    // 保证每次写入不会跨page

    /* If we'll use I2C calls for I/O, set up the message */
    if (!at24->use_smbus) {
        int i = 0;

        msg.addr = client->addr;
        msg.flags = 0;

        /* 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;    // 16位地址高8位

        msg.buf[i++] = offset;    // 16位地址低8位或者8位地址
        memcpy(&msg.buf[i], buf, count);    // 拷贝实际要写入的数据
        msg.len = i + count;    // 实际消息的长度: eeprom目标地址大小+数据大小
    }

    /*
     * 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);
    do {
        write_time = jiffies;
        if (at24->use_smbus_write) {
            switch (at24->use_smbus_write) {
            case I2C_SMBUS_I2C_BLOCK_DATA:
                status = i2c_smbus_write_i2c_block_data(client,
                        offset, count, buf);
                break;
            case I2C_SMBUS_BYTE_DATA:
                status = i2c_smbus_write_byte_data(client,
                        offset, buf[0]);
                break;
            }

            if (status == 0)
                status = count;
        } else {
            status = i2c_transfer(client->adapter, &msg, 1);    // 调用i2c框架传输接口,执行实际数据传输操作
            if (status == 1)    // i2c_transfer返回执行成功的msg个数
                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;
}
 

    I2C eeprom的设备驱动的读取和写入的协议都算是比较简单的。在实际中,也可能遇到更加复杂的i2c设备驱动,这就需要我们熟悉具体设备的业务协议,通过sysfs或者字符设备等,提供给用户操作的api接口。

    从这个例子中,我们看到了kernel中随处可见的抽象思想:I2C核心框架抽象出一套平台无关的接口,I2C adapter实现具体总线的操作algorithm,而各个具体的i2c设备又在此I2C总线的操作基础之上,实现各自的功能。

© 著作权归作者所有

共有 人打赏支持
y
粉丝 0
博文 58
码字总数 61083
作品 0
南京
程序员
私信 提问
二线制I2C CMOS串行EEPROM续

本篇文章主要介绍一下EEPROM读写器件的设计思路,以及未来测试此器件搭建的测试平台。 1、串行EEPROM读写器件 我们要设计一个串行EEPROM读写器件,这要求我们设计出能够综合的Verilog HDL代码...

许晴125
2018/05/05
0
0
S5PV210开发 -- I2C 你知道多少?(二)

上一篇主要是介绍了下芯片手册 I2C 部分,都应该看些什么,以及上拉电阻取值和传输速率模式选择。 这一篇该来点程序了,首先以 AT24C02 (EEPROM)为基础介绍一下I2C设备驱动编程,然后以 MT...

qq_29350001
2017/12/12
0
0
i2c-bus识别i2c设备从地址疑问

@韩大卫 你好,想跟你请教个问题:我看了您的 《Linux下使用IIC总线读写EEPROM》,写的很好,收益很多,但是有个疑问这个eeprom的从地址在内核中是已经挂载到i2c-bus了么? 比如说我现在有个...

揪揪凯
2013/11/09
660
1
Linux 嵌入式驱动开发:移植I2C-EEPROM 驱动

1 在内核中配置I2C 驱动 Linux-2.6.32.2 对S2C2440 的I2C 接口提供了完善的驱动,因此我们只需在内核中配置一下即可使用。 在内核源代码目录执行:make menuconfig,进入内核配置主菜单,依次...

宁宁爸
2015/12/11
183
0
SylixOS中EEPROM设备驱动实现

1.开发环境 操作系统:SylixOS 编程环境:RealEvo-IDE3.1.5 硬件平台:SAMA5D2 Xplained开发板 2.EEPROM简介 EEPROM,或写作E2PROM,全称电子抹除式可复写只读存储器 (英语:Electrically-...

炉yu
2017/04/11
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Windows 上安装 Scala

在安装 Scala 之前需要先安装 Java 环境,具体安装的详细方法就不在这里描述了。 您可以自行搜索我们网站中的内容获得其他网站的帮助来获得如何安装 Java 环境的方法。 接下来,我们可以从 ...

honeymose
今天
1
0
数据库篇多表操作

第1章 多表操作 实际开发中,一个项目通常需要很多张表才能完成。例如:一个商城项目就需要分类表(category)、商品表(products)、订单表(orders)等多张表。且这些表的数据之间存在一定的关系...

stars永恒
今天
3
0
nginx日志自动切割

1.日志配置(Nginx 日志) access.log----记录哪些用户,哪些页面以及用户浏览器,IP等访问信息;error.log------记录服务器错误的日志 #配置日志存储路径:location / {      a...

em_aaron
昨天
5
0
java 反射

基本概念 RTTI,即Run-Time Type Identification,运行时类型识别。RTTI能在运行时就能够自动识别每个编译时已知的类型。   要想理解反射的原理,首先要了解什么是类型信息。Java让我们在运...

细节探索者
昨天
2
0
推荐转载连接

https://www.cnblogs.com/ysocean/p/7409779.html#_label0

小橙子的曼曼
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部