58 linux i2c设备驱动之eeprom驱动

eeprom是i2c接口的at24c04芯片, at24c04中的”04”表示容量是4k位.

58 linux i2c设备驱动之eeprom驱动_第1张图片

at24c04的设备地址:
58 linux i2c设备驱动之eeprom驱动_第2张图片
上图表示设备地址前4位是固定的”1010”, 后三位是由芯片的引脚A2、A1,A0的电平来决定。通常A0~A2脚都是接地址,表示设备地址为”0x50”.
4K容量则分成两个2K, P0脚用于区分是访问前2K, 还是后2K.
8K容量则分成4个2K, P0和P1脚则是用于区分是第几个2K

在内核里声明i2c设备(名为”at24c04”, 设备地址为0x50), 我把此模块连接到第0个i2c控制器上.
///////////////////////////////////////////////////////
在设备驱动里,主要是调用控制器的接口函数i2c_transfer,调用一次此函数只会在最后发出一个停止信号。在调用一次此函数时,可以发出多条消息,每条消息都会有一个开始信号。
eeprom主要功能就是存取数据,也就是读写数据。读写的操作需要按照芯片的时序要求来操作.

58 linux i2c设备驱动之eeprom驱动_第3张图片
上图是在eeprom指定第几个字节的位置上存放指定的值。
时序过程: 主机先发出开始信号,设备地址和读写位, 再发出第几个字节位置(word address), 然后再发出要存放的数据, 最后停止信号

/////////////////////
58 linux i2c设备驱动之eeprom驱动_第4张图片
上图是连续写多个字节(一页)的时序。注意不同容量大小,页大小也不同(8-Byte Page (2K), 16-Byte Page (4K, 8K))
时序过程: 主机先发出开始信号,设备地址和读写位, 再发出第几个字节位置(word address), 然后再发出要连续存放的数据, 最后停止信号.
如指定的位置为第16字节,则DATA(n)存放在eeprom的第16字节, DATA(n+1)则存放在第17字节…
注意: 指定存储的位置也必须是按页大小对齐

////////////////////////
58 linux i2c设备驱动之eeprom驱动_第5张图片
上图是读出eeprom指定位置上的数据。
时序过程: 主机先发出开始信号,设备地址和读写位(写模式),再发出位置. 接着主机重新发出开始信号,设备地址和读写位(读模式), 接收从机输出的数据,最后发出停止信号. 注意两个开始信号间没有停止信号.

/////////////////////////////////////
eeprom的测试代码:
test.c


#include 
#include 
#include 
#include 

int eeprom_write(struct i2c_client *cli, u8 waddr, u8 data)
{
//按 byte write时序写, 因时序要求只有一个开始信号和一个停止信号,所以只需一条消息和调用i2c_transfer函数一次
    struct i2c_msg msg;
    char buf[2] = {waddr, data};    
    int ret;

    msg.addr = cli->addr;
    msg.flags = 0;
    msg.len = 2;
    msg.buf = buf;


    ret =  (i2c_transfer(cli->adapter, &msg, 1) == 1) ? 0 : -1;
    msleep(1);
    return ret;
}

u8 eeprom_read(struct i2c_client *cli, u8 waddr)
{
//按random read时序来操作,因时序时求有两个开始信号和一个停止信号,所以需要两条消息(一写,一读), i2c_transfer应调用一次
    struct i2c_msg msgs[2];
    u8 data;    

    msgs[0].addr = cli->addr;   
    msgs[0].flags = 0;
    msgs[0].len = 1;
    msgs[0].buf = &waddr;

    msgs[1].addr = cli->addr;   
    msgs[1].flags = I2C_M_RD;
    msgs[1].len = 1;
    msgs[1].buf = &data;

    if (i2c_transfer(cli->adapter, msgs, 2) != 2)
    {
        printk("i2c read failed\n");
        return -1;
    }
    msleep(1);
    return data;
}

int myprobe(struct i2c_client *cli, const struct i2c_device_id *id)
{
    int i;
    printk("in myprobe ...%s, %x\n", cli->name, id->driver_data);

    //先存放26个字母
    for (i = 0; i < 26; i++)
    {
        if (eeprom_write(cli, i, i+'A') < 0)
            printk("transfer failed : %d\n", i);
    }

    //再读出来
    for (i = 0; i < 26; i++)
        printk("%d, %c\n", i, eeprom_read(cli, i));

    return 0;
}

int myremove(struct i2c_client *cli)
{
    printk("in myremove ...%s\n", cli->name);
    return 0;
}

struct i2c_device_id ids[] = {
    {"abcd", 0x1122},
    {"at24c04", 0x3344},
    {},
};

//i2c设备驱动不管接在哪个控制器上面的设备都会进行匹配,只要符合id_table里的名字即可
struct i2c_driver mydrv = {
    .driver = {
        .name = "mydrv",
    },
    .probe = myprobe,
    .remove = myremove,
    .id_table = ids,    
};

module_i2c_driver(mydrv);

MODULE_LICENSE("GPL");

在设备驱动里如需提供接口予应用程序调用, 可以在设备驱动里再实现字符设备接口,也可以实现proc目录下的文件接口或都 sys目录下的属性文件接口

////////////////////////////////////////////////////////////////////////////////////////////
在linux内核里其实也实现了i2c接口的eeprom设备驱动
内核配置选项在:

make menuconfig ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
 Device Drivers  --->
      Misc devices  --->
         EEPROM support  --->
             <*> I2C EEPROMs from most vendors

设备驱动源码在:”drivers/misc/eeprom/at24.c “

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

//可支持多种型号的设备, 每种设备容量大小的不同由struct i2c_device_id的driver_data成员值来区别
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) },
    { "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 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;

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

    sysfs_bin_attr_init(&at24->bin); //初始化sys目录下的属性文件
    at24->bin.attr.name = "eeprom";  //属性文件名为eeprom
    at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR;
    at24->bin.read = at24_bin_read; //属性文件的读函数
    ...
    at24->bin.write = at24_bin_write; //属性文件的写函数
    err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin); //属性文件会创建在/sys/bus/i2c/device/0-0050/eeprom
        //然后对属性文件cat/echo操作即可
    ....

你可能感兴趣的:(OrangePi,H3,Linux设备驱动开发)