转载至:https://blog.csdn.net/ZhengNice/article/details/50070435
这篇文章主要是总结一下eeprom的驱动制作以及测试程序的编写。
开发环境
开发环境:Centos6.5
内核版本:Linux3.0
交叉编译器版本: buildroot-2012.08
以下为旧内核,新版内核需要修改设备树。
原理图部分
上面的图中,左图是eeprom的底板原理图,从图中我们得知,此开发板的eeprom使用的是AT2402的芯片,结合右图可以看出eeprom的时钟和数据信号与开发板的i2c总线的scl和sda接口连接在一起,说明此eeprom是挂载在i2c总线下。
移植
开发板内核配置
首先启用i2c
Generic Driver Options --->
<*> I2C support --->
然后启用eeprom
[*] Misc devices --->
EEPROM support ---> The new
添加硬件信息
在内核源程序中i2c总线eeprom驱动程序添加
修改linux/arch/arm/mach-s3c2440/mach-smdk2440.c文件
在其中添加
#include <linux/i2c/at24.h>
static struct at24_platform_data at24c02 = {
.byte_len = SZ_2K / 8,
.page_size = 8,
.flags = 0,
};
static struct i2c_board_info __initdata smdk2440_i2c_devs[] = {
{
I2C_BOARD_INFO("24c02", 0x50),
.platform_data = &at24c02,
},
/* more devices can be added using expansion connectors */
};
另外在smdk2440_machine_init函数中添加
i2c_register_board_info(0, smdk2440_i2c_devs, ARRAY_SIZE(smdk2440_i2c_devs));
完后上面的步骤之后,我们重新编译内核,烧录到开发板上,我们进入开发板,进入目录/sys/devices/platform/s3c2440-i2c/i2c-0/0-0050/下,我们可以看到name和eeprom显示如下
到这里我们就完成了Linux内核中i2c总线下eeprom的驱动制作。
eeprom测试程序
对于i2c设备,我们有两种常见的操作方式,一种是通过设备节点操作,一种是i2c总线操作。所有的i2c设备,都可以通过i2c总线设备驱动来操作。
设备驱动操作
这个测试程序的主要功能是能够在eeprom中读出和写入开发板的sn 、mac以及owner信息,程序具体显示要求如下:
程序编译后的名字叫eeprom, 其使用方法为:
向eeprom中写入信息:
$ eeprom -s FL2440-abc
$ eeprom -m 00-11-22-33-44-55
$ eeprom -o Michael
从eeprom中读出信息:
$ eeprom -r
sn= FL2440-abc
mac= 00-11-22-33-44-55
owner= Michael
测试程序如下:
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/io.h>
#include <errno.h>
#define LEN 50
#define SN_OFS 0
#define SN_LEN 50
#define MAC_OFS 50
#define MAC_LEN 50
#define OWN_OFS 100
#define OWN_LEN 50
/****** help information *********/
void help()
{
printf("This program is used to read or write MAC information to eeprom.\n");
printf("Mandatory arguments to long options are mandatory for short options too:\n");
printf(" -r [read] read the information of board in eeprom;\n");
printf(" -s [serial] write the serial of board to eeprom ;\n");
printf(" -m [mac] write the mac adress of board to eeprom ;\n");
printf(" -o [owner] write the owner of board to eeprom ;\n");
printf(" -h [help] printf the help information.\n");
}
/**** the struct of board_information ******/
struct board_info_s
{
char sn[SN_LEN];
char mac[MAC_LEN];
char owner[OWN_LEN];
}board_info_t;
/**** write the board information into eeprom ******/
void write_eeprom_sn(char *a)
{
int fd = -1;
int ret = -1;
fd = open("/sys/devices/platform/s3c2440-i2c/i2c-0/0-0050/eeprom",O_WRONLY);
if(fd < 0)
{
printf("open file eeprom failed;%s\n",strerror(errno));
}
strncpy(board_info_t.sn, a, sizeof(board_info_t.sn));
lseek(fd, SN_OFS, SEEK_SET);
ret = write(fd, board_info_t.sn, sizeof(board_info_t.sn));
if(ret < 0)
{
printf("error: %d:%s\n", errno, strerror(errno));
}
printf("the sn information of board %s is writen into eeprom!\n", board_info_t.sn);
}
void write_eeprom_mac(char *b)
{
int fd = -1;
int ret = -1;
fd = open("/sys/devices/platform/s3c2440-i2c/i2c-0/0-0050/eeprom", O_WRONLY);
if(fd < 0)
{
printf("open file eeprom failed;%s\n", strerror(errno));
}
strncpy(board_info_t.mac, b, sizeof(board_info_t.mac));
lseek(fd, MAC_OFS, SEEK_SET);
ret = write(fd, board_info_t.mac, sizeof(board_info_t.mac));
if(ret < 0)
{
printf("error: %d:%s\n", errno, strerror(errno));
}
printf("the sn information of board %s is writen into eeprom!\n", board_info_t.mac);
}
void write_eeprom_owner(char *c)
{
int fd = -1;
int ret = -1;
fd = open("/sys/devices/platform/s3c2440-i2c/i2c-0/0-0050/eeprom",O_WRONLY);
if(fd < 0)
{
printf("open file eeprom failed;%s\n", strerror(errno));
}
strncpy(board_info_t.owner, c, sizeof(board_info_t.owner));
lseek(fd, OWN_OFS, SEEK_SET);
ret = write(fd, board_info_t.owner, sizeof(board_info_t.owner));
if(ret < 0)
{
printf("error: %d:%s\n", errno, strerror(errno));
}
printf("the sn information of board %s is writen into eeprom!\n", board_info_t.owner);
}
struct board_write
{
void (*write_sn)(char *);
void (*write_mac)(char *);
void (*write_owner)(char *);
}board_write_cb;
/**** read the board_information from eeprom ****/
void board_read()
{
int fd = -1;
int ret = -1;
fd = open("/sys/devices/platform/s3c2440-i2c/i2c-0/0-0050/eeprom", O_RDONLY);
if(fd<0)
{
printf("open file eeprom failed;%s\n", strerror(errno));
}
lseek(fd, SN_OFS, SEEK_SET);
ret = read(fd, board_info_t.sn, SN_LEN);
if(ret < 0)
{
printf("read from eeprom eerror:%s !\n", strerror(errno));
}
printf("the sn of board_information in eeprom is SN=%s !\n", board_info_t.sn);
lseek(fd, MAC_OFS, SEEK_SET);
ret=read(fd, board_info_t.mac, MAC_LEN);
if(ret < 0)
{
printf("read from eeprom eerror:%s !\n", strerror(errno));
}
printf("the sn of board_information in eeprom is MAC=%s !\n", board_info_t.mac);
lseek(fd, MAC_OFS, SEEK_SET);
ret = read(fd, board_info_t.owner, MAC_LEN);
if(ret < 0)
{
printf("read from eeprom eerror:%s !\n", strerror(errno));
}
printf("the sn of board_information in eeprom is OWNER=%s !\n", board_info_t.owner);
}
/********************************************************************************
* Description:
* Input Args:
* Output Args:
* Return Value:
********************************************************************************/
int main (int argc, char **argv)
{
int opt=-1;
int ret=-1;
if(argc < 2)
{
help();
}
if(sizeof(argv[2]) > LEN)
{
printf("you putin too much characters!\n");
}
board_write_cb.write_sn=write_eeprom_sn;
board_write_cb.write_mac=write_eeprom_mac;
board_write_cb.write_owner=write_eeprom_owner;
struct option long_options[] ={
{"read", no_argument, NULL, 'r'},
{"sn", required_argument, NULL, 's'},
{"mac", required_argument, NULL, 'm'},
{"owner", required_argument, NULL, 'o'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
while((opt=getopt_long(argc,argv, ":rs:m:o:h", long_options, NULL)) != -1)
{
switch(opt)
{
case 'r':
board_read();
break;
case 's':
board_write_cb.write_sn(optarg);
break;
case 'm':
board_write_cb.write_mac(optarg);
break;
case 'o':
board_write_cb.write_owner(optarg);
break;
case 'h':
help();
break;
case '?':
printf("Invalid character constant !\n");
break;
}
}
return 0;
} /* ----- End of main() ----- */
测试结果:
总线驱动操作
下面是通过操作总线驱动设备来操作at24的。
所有的i2c设备都可以通过操作总线驱动设备来操作,i2ctool就是这样实现的,更高级的操作可以查看i2ctool的源码。
// 写入的结构体
struct i2c_at24_w
{
unsigned char addr;
unsigned char wdata[8];
};
// 读出的结构体
struct i2c_at24_r
{
unsigned char addr;
unsigned char rdata[128];
};
int main()
{
int fd =open("/dev/i2c-0", O_RDWR);
if (fd< 0) {
printf("open /dev/i2c-0 failed\n");
goto exit;
}
struct i2c_msg msg;
struct i2c_at24_r rd = {0};
struct i2c_at24_w wd = {0};
struct i2c_rdwr_ioctl_data ioctl_data;
struct i2c_msg msgs;
// 要写入的消息
ioctl_data.nmsgs= 1;
ioctl_data.msgs= &msgs;
// 0地址写入8Byte 0x33,AT24C02一次最多能写入8byte
for (int i = 0; i < 8;i++) {
wd.wdata[i] = 0x33;
}
wd.addr = 0x00;
msgs.addr = 0x50;
msgs.flags = 0;
msgs.len = sizeof(struct i2c_at24_w);
msgs.buf = (unsigned char*)&wd;
printf("ioctl write addr 0, return :%d\n", ioctl(fd, I2C_RDWR, &ioctl_data));
ioctl_data.nmsgs= 1;
ioctl_data.msgs= &msgs;
// 写入要读的地址
msgs.addr = 0x50;
msgs.flags = 0;
msgs.len = sizeof(rd.addr);
msgs.buf = (unsigned char*)&rd.addr;
printf("ioctl write address, return :%d\n", ioctl(fd, I2C_RDWR, &ioctl_data));
// 连续读取128byte
msgs.addr = 0x50;
msgs.flags |= I2C_M_RD;
msgs.len = sizeof(rd.rdata);
msgs.buf = (unsigned char*)&rd.rdata[0];
printf("ioctl read, return :%d\n", ioctl(fd, I2C_RDWR, &ioctl_data));
close(fd);
}
思考
问题1
I2C设备驱动的设备节点在哪?
驱动编译为模块:.ko文件。
加载驱动模块:`insmod at24.ko`
加载驱动之后,通过find找到接口:`find / -name "at24"` 、`find / -name "eeprom"`
sys api接口:`/sys/devices/platform/s3c2440-i2c/i2c-0/0-0050/eeprom`
问题2
AT24的地址怎么变成0x50了?
数据手册里AT24C02/04/08,他们的地址都是0xA0,而我看网上的例子都是用0x50地址,用扫描工具看到确实有个0x50地址的设备
[root@EmbedSky nfs]# i2cdetect -y -r 0 0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
内核里linux-2.6.30.4/drivers/misc/eeprom/at24.c有相关介绍。
这个50是什么貌似是内核专门为eeprom而分配的。那么问题来了,以后我自己写一个内核没有的I2C设备驱动,我怎么知道该设备的地址变成多少?
/* * However, misconfiguration can lose data. "Set 16-bit memory address"
* to a part with 8-bit addressing will overwrite data. Writing with too
* big a page size also loses data. And it's not safe to assume that the
* conventional addresses 0x50..0x57 only hold eeproms; a PCF8563 RTC
* uses 0x51, for just one example.
*/
是0x50 可以在手册中看到 A2 A1 A0都接地
至于为什么是0x50 涉及到I2C总线层
发设备地址时,左移了1位发送的
0x50(0x01010000 << 1) 实际设备地址是0xa0 (1010 0000)
i2c设备的地址一般在芯片手册中可以找到(当然还要根据原理图来确定),不同的设备有不同的地址域,这是ieee定好的标准。