i2c子系統-eeprom例項分析
兩種訪問eeprom的例子
在Linux應用層訪問eeprom的方法,並給出示例程式碼方便大家理解。第一個方法是通過eeprom的裝置檔案進行訪問。這兩個方法分別對應了i2c裝置驅動的兩個不同的實現,第二個方法是通過sysfs檔案系統對eeprom進行訪問。
第一種通過devfs訪問eeprom的方法是linux i2c提供的一種通用的方法,是有i2c子系統提供的,訪問裝置的能力有限,通過devfs訪問eeprom的方法則需要了解eeprom的讀寫時序。對寫應用程式開發人員的要求較高。
第二種通過sysfs檔案系統的二進位制結點訪問eeprom的方法是由eeprom的裝置驅動實現的,是一種專有的方法。大體來講就是關於時序的操作已經由驅動程式完成了,並以bin_attribute的方式開放到使用者空間,應用程式開發人員只按檔案操作的方式操作裝置;通過sysfs檔案系統訪問eeprom操作簡單,無需瞭解eeprom的硬體特性以及訪問時序。
通過devfs訪問I2C裝置
介紹這個之前,先來看一下:i2c-dev.c 檔案完全可以被看作一個 I2C 裝置驅動,它實現的一個 i2c_client 是虛擬、臨時的,隨著裝置檔案的開啟而產生,並隨裝置檔案的關閉而撤銷,並沒有被新增到 i2c_adapter 的 clients
連結串列中。i2c-dev.c 針對每個 I2C 介面卡生成一個主裝置號為 89 的裝置檔案,實現了i2c_driver 的成員函式以及檔案操作介面,所以 i2c-dev.c 的主體是“i2c_driver 成員函式 + 字元裝置驅動” 。
i2c-dev.c 中提供 i2cdev_read()、i2cdev_write()函式來對應使用者空間要使用的 read()和 write()檔案操作介面,這兩個函式分別呼叫 I2C 核心的 i2c_master_recv()和i2c_master_send()函式來構造一條 I2C 訊息並引發介面卡 algorithm 通訊函式的呼叫,完成訊息的傳輸,對應於如圖 15.7 所示的時序。但是,很遺憾,大多數稍微複雜一點I2C 裝置的讀寫流程並不對應於一條訊息,往往需要兩條甚至更多的訊息來進行一次讀寫週期,這種情況下,在應用層仍然呼叫 read()、write()檔案 API 來讀寫 I2C 裝置,將不能正確地讀寫。許多工程師碰到過類似的問題,往往經過相當長時間的除錯都沒法解決 I2C 裝置的讀寫,連錯誤的原因也無法找到,顯然是對 i2cdev_read()和 i2cdev_write()函式的作用有所誤解。
鑑於上述原因,i2c-dev.c 中 i2cdev_read()和 i2cdev_write()函式不具備太強的通用性,沒有太大的實用價值,只能適用於非 RepStart 模式的情況。對於兩條以上訊息組成的讀寫,在使用者空間需要組織 i2c_msg 訊息陣列並呼叫 I2C_RDWR IOCTL 命令。程式碼清單 15.21 所示為 i2cdev_ioctl()函式的框架,其中詳細列出了 I2C_RDWR 命令的處理過程。
#include <stdio.h>
#include <linux/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#define I2C_RETRIES 0x0701
#define I2C_TIMEOUT 0x0702
#define I2C_RDWR 0x0707
#define I2C_ADDR 0x50
struct i2c_msg
{
unsigned short addr;
unsigned short flags;
#define I2C_M_TEN 0x0010
#define I2C_M_RD 0x0001
unsigned short len;
unsigned char *buf;
};
struct i2c_rdwr_ioctl_data
{
struct i2c_msg *msgs;
int nmsgs;
};
/***********main***********/
int main(){
int fd,ret;
struct i2c_rdwr_ioctl_data e2prom_data;
//disable WP
system("echo 103 > /sys/class/gpio/export");
system("echo \"out\" > /sys/class/gpio/gpio103/direction");
system("echo 0 > /sys/class/gpio/gpio103/value");
fd=open("/dev/i2c-0",O_RDWR);//開啟eeprom裝置檔案結點
if(fd<0){
perror("open error");
}
e2prom_data.nmsgs=2;
e2prom_data.msgs=(struct i2c_msg*)malloc(e2prom_data.nmsgs*sizeof(struct i2c_msg));
if(!e2prom_data.msgs)
{
perror("malloc error");
exit(1);
}
ioctl(fd,I2C_TIMEOUT,1);/*超時時間*/
ioctl(fd,I2C_RETRIES,2);/*重複次數*/
/***write data to e2prom**/
e2prom_data.nmsgs=1;//由前面eeprom讀寫分析可知,寫eeprom需要一條訊息
(e2prom_data.msgs[0]).len=5;//此訊息的長度為5個位元組,第一,二個位元組是要寫入資料的地址,第三,四,五,位元組是要寫入的資料
(e2prom_data.msgs[0]).addr=I2C_ADDR;//e2prom 裝置地址
(e2prom_data.msgs[0]).flags=0; //write
(e2prom_data.msgs[0]).buf=(unsigned char*)malloc(5);
(e2prom_data.msgs[0]).buf[0]=0x00;// e2prom addr[15:8]
(e2prom_data.msgs[0]).buf[1]=0x00;//e2prom addr[7:0]
(e2prom_data.msgs[0]).buf[2]=0x55;//the data to write byte0
(e2prom_data.msgs[0]).buf[3]=0x66;//the data to write byte1
(e2prom_data.msgs[0]).buf[4]=0x77;//the data to write byte2
ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);//通過ioctl進行實際寫入操作
if(ret<0)
{
perror("ioctl error1");
}
sleep(1);
/******read data from e2prom*******/
e2prom_data.nmsgs=2;//讀eeprom需要兩條訊息
(e2prom_data.msgs[0]).len=1;//第一條訊息實際是寫eeprom,需要告訴eeprom需要讀資料的地址,因此長度為1個位元組
(e2prom_data.msgs[0]).addr=I2C_ADDR; // e2prom 裝置地址
(e2prom_data.msgs[0]).flags=0;//write
(e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);
(e2prom_data.msgs[0]).buf[0]=0x00;// e2prom addr[15:8]
(e2prom_data.msgs[0]).buf[1]=0x00;//e2prom addr[7:0]
(e2prom_data.msgs[1]).len=6;
(e2prom_data.msgs[1]).addr=I2C_ADDR;
(e2prom_data.msgs[1]).flags=I2C_M_RD;
(e2prom_data.msgs[1]).buf=(unsigned char*)malloc(6);
(e2prom_data.msgs[1]).buf[0]=0;
(e2prom_data.msgs[1]).buf[1]=0;
(e2prom_data.msgs[1]).buf[2]=0;
(e2prom_data.msgs[1]).buf[3]=0;
(e2prom_data.msgs[1]).buf[4]=0;
(e2prom_data.msgs[1]).buf[5]=0;
ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);//通過ioctl進行實際的讀操作
if(ret<0)
{
perror("ioctl error2");
}
printf("Read back:0x%02X\n\r",(e2prom_data.msgs[1]).buf[0]);
printf("Read back:0x%02X\n\r",(e2prom_data.msgs[1]).buf[1]);
printf("Read back:0x%02X\n\r",(e2prom_data.msgs[1]).buf[2]);
close(fd);
system("echo 103 > /sys/class/gpio/unexport");
return 0;
}
通過 通過sysfs檔案系統訪問I2C裝置
eeprom的裝置驅動在/sys/devices/44000000.ocp/44e0b000.i2c/i2c-0/0-0050/eeprom目錄下把eeprom裝置對映為一個二進位制節點,檔名為eeprom。對這個eeprom檔案的讀寫就是對eeprom進行讀寫。
#define EEPROM_DEVICE "/sys/devices/44000000.ocp/44e0b000.i2c/i2c-0/0-0050/eeprom"
#define TEST_STR "eeprom write/read test!"
int main(void)
{
int fd;
struct stat eeprom_stat;
char read_buf[32] = { '\0' };
fd = open(EEPROM_DEVICE, O_RDWR);
if (fd < 0) {
printf("open eeprom unsuccess\n");
return;
}
if (stat(EEPROM_DEVICE, &eeprom_stat) < 0) {
perror("failed to get eeprom status");
} else {
printf("\neeprom size: %d KB\n\n", eeprom_stat.st_size/1024);
}
printf("write \'%s\' to eeprom\n", TEST_STR);
if (write(fd, TEST_STR, strlen(TEST_STR)) < 0) {
perror("write to eeprom failed\n");
return;
}
lseek(fd, 0, SEEK_SET);
if (read(fd, read_buf, strlen(TEST_STR)) < 0) {
printf("read back failed\n");
return;
}
printf("\nget the following string from eeprom:\n%s\n\n", read_buf);
return 0;
}
eeprom驅動分析
在drivers/misc/eeprom/at24.c中可以看到eeprom驅動對i2c_driver結構的例項化。
static struct i2c_driver at24_driver = {
.driver = {
.name = "at24",
.owner = THIS_MODULE,
},
.probe = at24_probe,
.remove = at24_remove,
.id_table = at24_ids,
};
其中probe和remove會在模組初始化和解除安裝的時候被呼叫。
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);
}
at24_probe()
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;
}
如果你正在開發的裝置驅動程式中需要與使用者層的介面,一般可選的方法有:
註冊虛擬的字元裝置檔案,以這個虛擬裝置上的 read/write/ioctl 等介面與使用者互動;但 read/write 一般只能做一件事情, ioctl 可以根據 cmd 引數做多個功能(使用i2c-dev.c中的函式就是這個道理),但其缺點是很明顯的: ioctl 介面無法直接在 Shell 指令碼中使用,為了使用 ioctl 的功能,還必須編寫配套的 C語言的虛擬裝置操作程式, ioctl 的二進位制資料介面也是造成大小端問題 (big endian與little endian)、32位/64位不可移植問題的根源;
註冊 proc 介面,接受使用者的 read/write/ioctl 操作;同樣的,一個 proc 項通常使用其 read/write/ioctl 介面,它所存在的問題與上面的虛擬字元裝置的的問題相似;
註冊 sysfs 屬性;
最重要的是,新增虛擬字元裝置支援和註冊 proc 介面支援這兩者所需要增加的程式碼量都並不少,最好的方法還是使用 sysfs 屬性支援,一切在使用者層是可見的透明,且增加的程式碼量是最少的,可維護性也最好;
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是此驅動的一些私有資料的封裝,包括二進位制結點,以及寫緩衝區。
而對at24_bin_read(),at24_bin_write()的進一步追蹤可知:at24_bin_read()—->at24_read—->at24_eeprom_read;at24_bin_write—->at24_write—->at24_eeprom_write—->2c_transfer i2c_transfer