Linux下EEPROM-24LC16驱动调试

  

1 概述

    Microchip公司的24AA16/24LC16B (24XX16*)是一个16kbit的电可擦PROM。该设备由8块(共256×8位)内存和2线串行接口组成。低压设计允许运行到1.7 v。24XX16还具有最多16字节数据的页写入功能。

    我们使用的24LC16封装类型如下图所示:在这里插入图片描述

2 引脚连接及I2C从器件地址、时序

2.1 引脚连接

    使用的硬件平台: Atmel SAMA5d4,Cortex-A5处理器。
    EEPROM 24LC16芯片与处理器引脚连接关系如下,其中地址位A0,A1,A2均置低电平

序号24LC16芯片引脚定义处理器引脚定义
1SCLPE30
2SDLPE29

    在A5处理器芯片手册中查找处理器引脚(PE30、PE29)相关的信息。可知,A5处理器有4条I2C总线,EEPROM被挂载到总线TWI1(i2c1)下。
在这里插入图片描述

2.2 I2C从器件地址、时序

    阅读EEPROM的芯片手册,查看从器件地址以及I2C读写时序。
在这里插入图片描述
    IIC总线是采用两线式串行通讯接口,分别为SDA数据线以及SCL时钟信号,其中每个I2C设备有个7位的从设备地址,7位的地址中前4位是设备识别地址(根据上图可知为:1010),后面3位为地址位,由电路上决定 (B0~B2对应2.1节的A0-A2,可知全部为0,即后3位为:000),由以上分析可知,24LC16 EEPROM的从设备地址为(0b1010000, 0x50)。

    最低位为读写标示位(R/W),写为0,读为1,整体8位来描述一个设备,也就是说相同的器件(前四位相同)最多只能有7个。所以可以确定24LC16的写地址为 0xA0(0b10100000)、读地址为0xA1(0x10100001)

     下图为写的过程时序图,具体描述可以阅读datasheet。
在这里插入图片描述
    

3 驱动调试

3.1 I2C子系统

    Linux下的I2C子系统架构如下图。
在这里插入图片描述
    可以看到在Linux下可以用两种方式来操作i2c。一种是透过/dev 下的设备文件直接透过i2c设备驱动来操作。另外一种是透过用户态驱动程序使用通用驱动 i2c-dev来操作。

3.2 驱动调试过程

3.2.1 使用内核源代码提供的at24.c驱动

    在linux内核里已经实现了i2c接口的eeprom设备驱动,我们直接使用内核源代码提供的at24.c驱动来操作。下面进行介绍。

  • 内核配置选项在:
            make menuconfig
                Device Drivers —>
                     Misc devices —>
                        EEPROM support —>
                             <*> I2C EEPROMs from most vendors
  • Driver驱动源码
        使用内核源代码提供的at24.c驱动来操作I2C的24LC16 EEPROM。drivers/misc/eeprom/at24.c文件支持大多数I2C接口的EEPROM,一个具体的I2C设备驱动由i2c_driver的形式进行组织,用于将设备挂载于I2C总线,组织好之后,再完成设备本身所属类型的驱动。对于EEPROM而言,设备本身的驱动以二进制sysfs节点形式呈现(我们的板子上节点路径:/sys/bus/i2c/devices/1-0050/eeprom)。

    以下代码给出了该驱动的框架。文件at24.c,逐步分析。
        首先看at24.c驱动支持的设备。

static const struct i2c_device_id at24_ids[] = {
    /* 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) },
    { "24cs01", AT24_DEVICE_MAGIC(16,
                AT24_FLAG_SERIAL | AT24_FLAG_READONLY) },
    { "24c02",  AT24_DEVICE_MAGIC(2048 / 8, 0) },
    { "24cs02", AT24_DEVICE_MAGIC(16,
                AT24_FLAG_SERIAL | AT24_FLAG_READONLY) },
    { "24mac402",   AT24_DEVICE_MAGIC(48 / 8,
                AT24_FLAG_MAC | AT24_FLAG_READONLY) },
    { "24mac602",   AT24_DEVICE_MAGIC(64 / 8,
                AT24_FLAG_MAC | AT24_FLAG_READONLY) },
    /* 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) },
    { "24cs04", AT24_DEVICE_MAGIC(16,
                AT24_FLAG_SERIAL | AT24_FLAG_READONLY) },
    /* 24rf08 quirk is handled at i2c-core */
    { "24c08",  AT24_DEVICE_MAGIC(8192 / 8, 0) },
    { "24cs08", AT24_DEVICE_MAGIC(16,
                AT24_FLAG_SERIAL | AT24_FLAG_READONLY) },
    { "24c16",  AT24_DEVICE_MAGIC(16384 / 8,    0) },   // here
    { "24cs16", AT24_DEVICE_MAGIC(16,
                AT24_FLAG_SERIAL | AT24_FLAG_READONLY) },
    { "24c32",  AT24_DEVICE_MAGIC(32768 / 8,    AT24_FLAG_ADDR16) },
    { "24cs32", AT24_DEVICE_MAGIC(16,
                AT24_FLAG_ADDR16 |
                AT24_FLAG_SERIAL |
                AT24_FLAG_READONLY) },
    { "24c64",  AT24_DEVICE_MAGIC(65536 / 8,    AT24_FLAG_ADDR16) },
    { "24cs64", AT24_DEVICE_MAGIC(16,
                AT24_FLAG_ADDR16 |
                AT24_FLAG_SERIAL |
                AT24_FLAG_READONLY) },
    { "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 */ }
};
MODULE_DEVICE_TABLE(i2c, at24_ids);

    我们使用的Microchip公司的24LC16和Atmel公司的at24c16存储及操作基本是一样的。在i2c_device_id结构中,有对24c16的描述。有一项{ “24c16”, AT24_DEVICE_MAGIC(16384/ 8, 0) }是EEPROM at24c16的id项,其中AT24_DEVICE_MAGIC 是在求at24c16这个设备独一无二的魔幻数,at24c16有 16384bits,所以16384/8的意思是2048个字节。

    初始化代码

static struct i2c_driver at24_driver = {
    .driver = {
        .name = "at24",
        .acpi_match_table = ACPI_PTR(at24_acpi_ids),
    },
    .probe = at24_probe,
    .remove = at24_remove,
    .id_table = at24_ids,
};

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);
}
module_exit(at24_exit);

    可以看到这是采用总线的驱动,总线的过程是当有设备加入总线,会调用驱动的 probe 方法来捕获设备,驱动已经实现了probe 方法。

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;
    u8 test_byte;

    //i2c设备可以在platform_data里提供eeprom芯片的信息
    if (client->dev.platform_data) {
        chip = *(struct at24_platform_data *)client->dev.platform_data;
    } else {
        if (id) {
            //如果没有在platform_data里提供,则使用匹配上的i2c_device_id对象里的driver_data
            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 >>= 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;

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

    /*
     * REVISIT: the size of the EUI-48 byte array is 6 in at24mac402, while
     * the call to ilog2() in AT24_DEVICE_MAGIC() rounds it down to 4.
     *
     * Eventually we'll get rid of the magic values altoghether in favor of
     * real structs, but for now just manually set the right size.
     */
    if (chip.flags & AT24_FLAG_MAC && chip.byte_len == 4)
        chip.byte_len = 6;

    /* Use I2C operations unless we're stuck with SMBus extensions. */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
//------是否具备I2C_FUNC_I2C,如果不具备则判断是否具备下面能力。然后决定是否使用SMBus。
        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;
        }

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

    if ((chip.flags & AT24_FLAG_SERIAL) && (chip.flags & AT24_FLAG_MAC)) {
        dev_err(&client->dev,
            "invalid device data - cannot have both AT24_FLAG_SERIAL & AT24_FLAG_MAC.");
        return -EINVAL;
    }

if (chip.flags & AT24_FLAG_SERIAL) {
//-----根据flags,以及是否使用SMBus,选择合适的read_func()/write_func()。
        at24->read_func = at24_eeprom_read_serial;
    } else if (chip.flags & AT24_FLAG_MAC) {
        at24->read_func = at24_eeprom_read_mac;
    } else {
        at24->read_func = at24->use_smbus ? at24_eeprom_read_smbus
                          : at24_eeprom_read_i2c;
    }

    if (at24->use_smbus) {
        if (at24->use_smbus_write == I2C_SMBUS_I2C_BLOCK_DATA)
            at24->write_func = at24_eeprom_write_smbus_block;
        else
            at24->write_func = at24_eeprom_write_smbus_byte;
    } else {
        at24->write_func = at24_eeprom_write_i2c;
    }

    writable = !(chip.flags & AT24_FLAG_READONLY);
if (writable) {
//------------------write_max决定后面i2c写大小,大于此数字会被拆分。
        if (!use_smbus || use_smbus_write) {

            unsigned write_max = chip.page_size;

            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);
            if (!at24->writebuf)
                return -ENOMEM;
        } 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;
        }
    }

    i2c_set_clientdata(client, at24);

    /*
     * Perform a one-byte test read to verify that the
     * chip is functional.
     */
    err = at24_read(at24, 0, &test_byte, 1); //----------读一字节进行测试。
    if (err) {
        err = -ENODEV;
        goto err_clients;
    }

    at24->nvmem_config.name = dev_name(&client->dev); //---注册nvmem设备,对应设备/sys/bus/i2c/devices/x-0050/eeprom。
    at24->nvmem_config.dev = &client->dev;
    at24->nvmem_config.read_only = !writable;
    at24->nvmem_config.root_only = true;
    at24->nvmem_config.owner = THIS_MODULE;
    at24->nvmem_config.compat = true;
    at24->nvmem_config.base_dev = &client->dev;
    at24->nvmem_config.reg_read = at24_read; //----对于非SMBus设备,对应at24_eeprom_read_i2c()。
    at24->nvmem_config.reg_write = at24_write; //----对非SMBus设备,对应at24_eeprom_write_i2c()。
    at24->nvmem_config.priv = at24;
    at24->nvmem_config.stride = 1;
    at24->nvmem_config.word_size = 1;
    at24->nvmem_config.size = chip.byte_len;

    at24->nvmem = nvmem_register(&at24->nvmem_config); ---注册nvmem设备

    if (IS_ERR(at24->nvmem)) {
        err = PTR_ERR(at24->nvmem);
        goto err_clients;
    }

    dev_info(&client->dev, "%u byte %s EEPROM, %s, %u bytes/write\n",
        chip.byte_len, 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->nvmem, 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;
}

    at24的probe主要做数据有效检查、根据i2c的capability决定读写函数、读1字节验证功能、最后注册对应的nvmem设备。
    注册对应的nvmem设备。展开nvmem_register()函数。
在这里插入图片描述
    nvmem_register()函数中的 device_add()。device_add就是将设备加入到Linux设备模型的关键,它的内部将找到它的bus,然后让它的bus给它找到它的driver。(可参考:https://blog.csdn.net/qq_20678703/article/details/52841706

在这里插入图片描述
在这里插入图片描述
     DTS 设备树

    以上对at24.c驱动源码进行了简单分析,at24.c不依赖于具体的CPU和I2C控制器。由于我们使用的 24LC16(at24c16)包含在at24.c的设备列表中,因此,只需要在 .dts 文件中添加一个24LC16的节点即可。

    在设备树\sama5d4.dtsi文件中查找处理器引脚(PE30、PE29)相关的信息。
在这里插入图片描述
    sama5d4.dtsi文件中对i2c1节点的描述如下。
在这里插入图片描述
    at91-sama5d4_xplained.dts文件中没有使能i2c1,故没有对i2c1节点的描述,只有i2c0节点的描述。
在这里插入图片描述
    为使能i2c1,参考sama5d4.dtsi.dtsi文件中对i2c1节点的描述信息,在at91-sama5d4_xplained.dts中添加i2c1节点。并在该节点下添加24lc16子节点。
在这里插入图片描述
    修改后,重新编译内核和设备树文件,烧至开发板,可到/sys/bus/i2c/devices/1-0050/eeprom目录下,对eeprom节点进行eeprom的读写测试。

3.2.2 使用内核提供的I2C通用设备驱动

后续更新。

4 EEPROM读写测试程序

  • 方法1:使用系统自带的 i2c-tools 进行eeprom测试;

(参考:https://blog.csdn.net/mantis_1984/article/details/18254767/
    若系统没有自带i2c-tools工具,需要下载并安装。

    1) 用i2cdetect检测有几组i2c总线在系统上,输入: i2cdetect -l

    下图显示了i2c总线1
在这里插入图片描述
    2)用i2cdetect检测挂载在i2c总线上器件,输入 i2cdetect -r -y 1(检测i2c-1上的挂载情况)
在这里插入图片描述
    3)用i2cdump查看器件所有寄存器的值,这个很有用,输入 i2cdump -f -y 1 0x50 (查看eeprom的寄存器值)
在这里插入图片描述
    4)用i2cset设置单个寄存器值,用i2cget读取单个寄存器值,
        i2cset和i2cget使用方法:

  •     i2cset -f -y 1 0x50 0x00 0x3f (设置i2c-1上0x50器件的0x00寄存器值为0x3f)
  •     i2cget -f -y 1 0x50 0x00 (读取i2c-1上0x50器件的0x00寄存器值)
    在这里插入图片描述
  • 方法2:通过sysfs文件系统访问I2C设备,对eeprom进行测试;

    eeprom的属性文件创建在 /sys/bus/i2c/devices/1-0050/eeprom(或/sys/devices/platform/ahb/ahb:apb/f8018000.i2c/i2c-1/1-0050/eeprom,这两个路径的eeprom节点是通过符号链接建立的,操作任何一个都可以)。

    因为eeprom已经映射为一个文件了,可以通过文件I/O写应用程序对其进行简单的访问测试。比如以下程序对特定地址(0x40)写入特定数据(Hi,this is an eeprom test!),然后再把写入的数据在此地址上读出来。注意连续页写或字节写的时候,时间间隔要在5ms以上。

    
    测试代码:

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>

int main(void){
    int fd, size, len, i;
    char buf[500]= {0};
    char *bufw="Hi,this is an eepromtest!";//要写入的数据

    len=strlen(bufw);//数据长度
    fd= open("/sys/devices/platform/ahb/ahb:apb/f8018000.i2c/i2c-1/1-0050/eeprom",O_RDWR);//打开文件
    if(fd< 0)
    {
        printf("#### i2c test device open failed ####/n");
        return(-1);
    }
    //写操作
    lseek(fd,0x50,SEEK_SET); //定位地址,地址是0x50
    if((size=write(fd,bufw, len))<0)//写入数据
    {
        printf("write error\n");
        return 1;
    }
    printf("write ok\n");
    //读操作
    lseek(fd,0x50, SEEK_SET);//准备读,首先定位地址,因为前面写入的时候更新了当前文件偏移量,所以这边需要重新定位到0x50.
    if((size=read(fd,buf,len))<0)//读数据
    {
        printf("readerror\n");
        return 1;
    }
    printf("readok\n");
    for(i=0; i< len; i++)
        printf("buff[%d]=%x\n",i, buf[i]);//打印数据
    close(fd);

    return 0;
}

    

  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

heat.huang

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

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

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

打赏作者

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

抵扣说明:

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

余额充值