注册驱动时会检测是否有匹配的设备,有则将相应的设备挂接到正确的adapter
i2c_add_driver(&eeprom_driver); //i2c_driver *driver=&eeprom_driver
driver_register(&driver->driver)//新式驱动注册方法把设备驱动eeprom_driver挂入链表
list_for_each_entry(adapter, &adapters, list) //老式驱动注册方法,遍历adapter链表中的所有已经注册的adapter
driver->attach_adapter(adapter); //相当于调用eeprom_attach_adapter匹配eeprom和adapter
eeprom_attach_adapter(eeprom_driver)
i2c_probe(adapter, &addr_data, eeprom_detect)
i2c_probe_address //使用adapter里面的master_xfer发送设备地址,如果能够收到ACK,标识发现一个支持的设备
i2c_smbus_xfer
i2c_smbus_xfer_emulated
i2c_transfer
adap->algo->master_xfer /*master_xfer 该函数即s3c24xx_i2c_xfer发信号确定是否有 该I2C设备,如果有调用 eeprom_detect found_proc=eeprom_detect*/
err = found_proc(adapter, addr, kind);
err = i2c_attach_client(new_client)//将新设备挂接在dapter
部分示例代码和总结来源于他人博客
LinuxI2C驱动-从两个访问eeprom的例子开始
1. 该驱动实际只是提供了i2c通信接口,实际的驱动在用户层,应用层人员必须清楚与设备交互的时序。 应用层可以使用该方法访问几乎所有i2c设备。该方法一次只能实现一个方向的通信。比如读eeprom数据必须分两次操作,先写地址,再读数据。linux的i2c驱动会针对每个i2c适配器在/dev/目录下生成一个主设备号为89的设备文件,简单的来说,对于本例的eeprom驱动,/dev/i2c/0就是它的设备文件,因此接下来的eeprom的访问就变为了对此设备文件的访问。
我们需要用到两个结构体i2c_msg和i2c_rdwr_ioctl_data。
struct i2c_rdwr_ioctl_data {
struct i2c_msg __user *msgs; /* 指向一个i2c消息 */
__u32 nmsgs; /* i2c消息的数量 */
};
struct i2c_rdwr_ioctl_data {
struct i2c_msg __user *msgs; /* 指向一个i2c消息 */
__u32 nmsgs; /* i2c消息的数量 */
};
对一个eeprom上的特定地址(0x10)写入特定数据(0x58)并在从此地址读出写入数据的示例程序如下所示。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int fd,ret;
struct i2c_rdwr_ioctl_data e2prom_data;
fd=open("/dev/i2c/0",O_RDWR);//打开eeprom设备文件结点
if(fd<0)
{
perror("open error");
}
e2prom_data.nmsgs=2;
e2prom_data.msgs(structi2c_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);/*重复次数*/
/*写eeprom*/
e2prom_data.nmsgs=1;//由前面eeprom读写分析可知,写eeprom需要一条消息
(e2prom_data.msgs[0]).len=2; //此消息的长度为2个字节,第一个字节是要写入数据的地址,第二个字节是要写入的数据
(e2prom_data.msgs[0]).addr=0x50;//e2prom 设备地址
(e2prom_data.msgs[0]).flags=0; //写
(e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);
(e2prom_data.msgs[0]).buf[0]=0x10;// e2prom 写入目标的地址
(e2prom_data.msgs[0]).buf[1]=0x58;//写入的数据
ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);//通过ioctl进行实际写入操作,后面会详细分析
if(ret<0)
{
perror("ioctl error1");
}
sleep(1);
/*读eeprom*/
e2prom_data.nmsgs=2;//读eeprom需要两条消息
(e2prom_data.msgs[0]).len=1; //第一条消息实际是写eeprom,需要告诉eeprom需要读数据的地址,因此长度为1个字节
(e2prom_data.msgs[0]).addr=0x50; // e2prom 设备地址
(e2prom_data.msgs[0]).flags=0;//先是写
(e2prom_data.msgs[0]).buf[0]=0x10;//e2prom上需要读的数据的地址
(e2prom_data.msgs[1]).len=1;//第二条消息才是读eeprom,
(e2prom_data.msgs[1]).addr=0x50;// e2prom 设备地址
(e2prom_data.msgs[1]).flags=I2C_M_RD;//然后是读
(e2prom_data.msgs[1]).buf=(unsigned char*)malloc(1);//存放返回值的地址。
(e2prom_data.msgs[1]).buf[0]=0;//初始化读缓冲,读到的数据放到此缓冲区
ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);//通过ioctl进行实际的读操作
if(ret<0)
{
perror("ioctl error2");
}
printf("buff[0]=%x\n",(e2prom_data.msgs[1]).buf[0]);
/***打印读出的值,没错的话,就应该是前面写的0x58了***/
close(fd);
return 0;
}
eeprom的设备驱动在/sys/bus/i2c/devices/0-0050/目录下把eeprom设备映射为一个二进制节点,文件名为eeprom。对这个eeprom文件的读写就是对eeprom进行读写。对应系统内核中的eeprom.c。
# echo "test" > eeprom
# cat eeprom
test
当然,因为eeprom已经映射为一个文件了,我们还可以通过文件I/O写应用程序对其进行简单的访问测试。比如以下程序对特定地址(0x40)写入特定数据(Hi,this is an eepromtest!),然后再把写入的数据在此地址上读出来。
#include
#include
#include
#include
#include
#include
int main(void){
int fd, size, len, i;
char buf[50]= {0};
char *bufw="Hi,this is an eepromtest!";//要写入的数据
len=strlen(bufw);//数据长度
fd= open("/sys/bus/i2c/devices/0-0050/eeprom",O_RDWR);//打开文件
if(fd< 0)
{
printf("####i2c test device open failed####/n");
return(-1);
}
//写操作
lseek(fd,0x40,SEEK_SET); //定位地址,地址是0x40
if((size=write(fd,bufw, len))<0)//写入数据
{
printf("write error\n");
return 1;
}
printf("writeok\n");
//读操作
lseek(fd,0x40, SEEK_SET);//准备读,首先定位地址,因为前面写入的时候更新了当前文件偏移量,所以这边需要重新定位到0x40.
if((size=read(fd,buf,len))<0)//读数据
{
printf("readerror\n");
return 1;
}
printf("readok\n");
for(i=0; i< len; i++)
printf("buff[%d]=%x\n",i, buf[i]);//打印数据
close(fd);
return 0;
}
/********************************************************
驱动代码,代码来源于韦东山教程
*********************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 */
/* 改为0x60的话, 由于不存在设备地址为0x60的设备, 所以at24cxx_detect不被调用 */
static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short * forces[] = {force_addr, NULL};
static struct i2c_client_address_data addr_data = {
.normal_i2c = normal_addr, /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
.probe = ignore,
.ignore = ignore,
//.forces = forces, /* 强制认为存在这个设备 */
};
static struct i2c_driver at24cxx_driver;
static int major;
static struct class *cls;
struct i2c_client *at24cxx_client;
static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
unsigned char address;
unsigned char data;
struct i2c_msg msg[2];
int ret;
/* address = buf[0]
* data = buf[1]
*/
if (size != 1)
return -EINVAL;
copy_from_user(&address, buf, 1);
/* 数据传输三要素: 源,目的,长度 */
/* 读AT24CXX时,要先把要读的存储空间的地址发给它 */
msg[0].addr = at24cxx_client->addr; /* 目的 */
msg[0].buf = &address; /* 源 */
msg[0].len = 1; /* 地址=1 byte */
msg[0].flags = 0; /* 表示写 */
/* 然后启动读操作 */
msg[1].addr = at24cxx_client->addr; /* 源 */
msg[1].buf = &data; /* 目的 */
msg[1].len = 1; /* 数据=1 byte */
msg[1].flags = I2C_M_RD; /* 表示读 */
ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
if (ret == 2)
{
copy_to_user(buf, &data, 1);
return 1;
}
else
return -EIO;
}
static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
unsigned char val[2];
struct i2c_msg msg[1];
int ret;
/* address = buf[0]
* data = buf[1]
*/
if (size != 2)
return -EINVAL;
copy_from_user(val, buf, 2);
/* 数据传输三要素: 源,目的,长度 */
msg[0].addr = at24cxx_client->addr; /* 目的 */
msg[0].buf = val; /* 源 */
msg[0].len = 2; /* 地址+数据=2 byte */
msg[0].flags = 0; /* 表示写 */
ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
if (ret == 1)
return 2;
else
return -EIO;
}
static struct file_operations at24cxx_fops = {
.owner = THIS_MODULE,
.read = at24cxx_read,
.write = at24cxx_write,
};
static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
printk("at24cxx_detect\n");
/* 构构一个i2c_client结构体: 以后收改数据时会用到它 */
at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
at24cxx_client->addr = address;
at24cxx_client->adapter = adapter;
at24cxx_client->driver = &at24cxx_driver;
strcpy(at24cxx_client->name, "at24cxx");
i2c_attach_client(at24cxx_client);
major = register_chrdev(0, "at24cxx", &at24cxx_fops);
cls = class_create(THIS_MODULE, "at24cxx");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */
return 0;
}
static int at24cxx_attach(struct i2c_adapter *adapter)
{
return i2c_probe(adapter, &addr_data, at24cxx_detect);
}
static int at24cxx_detach(struct i2c_client *client)
{
printk("at24cxx_detach\n");
class_device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "at24cxx");
i2c_detach_client(client);
kfree(i2c_get_clientdata(client));
return 0;
}
/* 1. 分配一个i2c_driver结构体 */
/* 2. 设置i2c_driver结构体 */
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = "at24cxx",
},
.attach_adapter = at24cxx_attach,
.detach_client = at24cxx_detach,
};
static int at24cxx_init(void)
{
i2c_add_driver(&at24cxx_driver);
return 0;
}
static void at24cxx_exit(void)
{
i2c_del_driver(&at24cxx_driver);
}
module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_LICENSE("GPL");
/**************************************************************
应用层测试代码
**************************************************************/
#include
#include
#include
#include
#include
#include
/* i2c_test r addr
* i2c_test w addr val
*/
void print_usage(char *file)
{
printf("%s r addr\n", file);
printf("%s w addr val\n", file);
}
int main(int argc, char **argv)
{
int fd;
unsigned char buf[2];
if ((argc != 3) && (argc != 4))
{
print_usage(argv[0]);
return -1;
}
fd = open("/dev/at24cxx", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/at24cxx\n");
return -1;
}
if (strcmp(argv[1], "r") == 0)
{
buf[0] = strtoul(argv[2], NULL, 0);
read(fd, buf, 1);
printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
}
else if (strcmp(argv[1], "w") == 0)
{
buf[0] = strtoul(argv[2], NULL, 0);
buf[1] = strtoul(argv[3], NULL, 0);
write(fd, buf, 2);
}
else
{
print_usage(argv[0]);
return -1;
}
return 0;
}
mach-smdk2440.c添加AT24C02设备的板级信息如下:
添加一个i2c设备
mach-smdk2440.c添加AT24C02设备的板级信息如下:
static struct at24_platform_data at24c02= {
6. .byte_len = SZ_2K / 8,
7. .page_size = 8,
8. .flags = AT24_FLAG_ADDR8,
9. };
10.
11. static struct i2c_board_info __initdata smdk2440_i2c_devs[] = {
12. {
13. I2C_BOARD_INFO("24c02", 0x50),
14. .platform_data = &at24c02,
15. },
16. /* more devices can be added using expansion connectors */
17. };
18. static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg);
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
/*添加i2c设备*/
i2c_register_board_info(0,smdk2440_i2c_devs,ARRAY_SIZE(smdk2440_i2c_devs));
smdk_machine_init();
}