eeprom是i2c接口的at24c04芯片, at24c04中的”04”表示容量是4k位.
at24c04的设备地址:
上图表示设备地址前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主要功能就是存取数据,也就是读写数据。读写的操作需要按照芯片的时序要求来操作.
上图是在eeprom指定第几个字节的位置上存放指定的值。
时序过程: 主机先发出开始信号,设备地址和读写位, 再发出第几个字节位置(word address), 然后再发出要存放的数据, 最后停止信号
/////////////////////
上图是连续写多个字节(一页)的时序。注意不同容量大小,页大小也不同(8-Byte Page (2K), 16-Byte Page (4K, 8K))
时序过程: 主机先发出开始信号,设备地址和读写位, 再发出第几个字节位置(word address), 然后再发出要连续存放的数据, 最后停止信号.
如指定的位置为第16字节,则DATA(n)存放在eeprom的第16字节, DATA(n+1)则存放在第17字节…
注意: 指定存储的位置也必须是按页大小对齐
////////////////////////
上图是读出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操作即可
....