本节主要分析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()来实现的。
更多推荐
(6)LinuxI2C驱动--I2C设备驱动
发布评论