最全的IIC介绍及其设备驱动编写

参考资料:

Linux2.6之IIC驱动_月月鸟呀的博客-CSDN博客

Linux3.4.2之IIC驱动_月月鸟呀的博客-CSDN博客

1、IIC介绍

IIC是通信协议中的一种,为一主多从的结构,对于主从,所有的数据都是从主机这边发起,从机只能接受,不能主动引起数据传输,只有两条总线线路:一条串行数据线(SDA),一条串行时钟线(SCL),IIC有硬件IIC和软件IIC,这里简单解释,硬件IIC为硬件构成的IIC,一般只需要操作相关寄存器即可,对于软件IIC,可以由IO口来模拟IIC总线进行对IIC设备的通信。

对于多个IIC设备来说,每个连接到总线的器件都可以使用软件根据它的唯一地址来识别,当我们要使用一个IIC设备时,主机通过SDA线向各IIC设备寻址,等待地址对应的IIC设备的回应即ACK信号。

IIC是半双工的,即SDA总线是双向传输的。两条线在硬件上都使用了开极电路,且都接有上拉电阻(即空闲时,SDA和SCL都为高电平)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uCKvuXrc-1628932720637)(C:\Users\liang\AppData\Roaming\Typora\typora-user-images\image-20210812203900555.png)]

  • 传输过程:

当主机A要发送数据时,A拉低SDA总线,稍后向SCL输送时钟信号,开始传输数据(在每个时钟周期中,当SCL为高电平时,从机从SDA线上采样(获得)数据;当SCL为低电平时,主机向SDA传送(更新)数据)。主机每传完8个比特位,就将SDA释放(SDA恢复到高电平),若从机正常接收完8个比特位,就将SDA线拉低,以表示向主机回送一个ACK信号,若没有接收到8个比特位,就不会拉低SDA线,主机就收不到ACK信号,所以主机会发送一个终止或开始(重传)信号给从机。当主机已经完成数据传输时,会先释放SCL线,然后再释放SDA线。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3XsSO80t-1628932720640)(https://i.loli.net/2021/08/12/ieORwNA9xzGIbyW.png)]

并非每传输8位数据之后,都会有ACK信号,有以下3种例外:

  1. 当从机不能响应从机地址时(例如它正忙于其他事而无法响应I²总线的操作,或者这个地址没有对应的从机),在第9个SCL周期内SDA线没有被拉低,即没有ACK信号。这时,主机发出一个Р信号终止传输或者重新发出一个S信号开始新的传输.
  2. 如果从机接收器在传输过程中不能接收更多的数据时,它也不会发出ACK信号。这样,主机就可以意识到这点,从而发出一个Р信号终止传输或者重新发出一个S信号开始新的传输.
  3. 主机接收器在接收到最后一个字节后,也不会发出ACK信号。于是,从机发送器释放SDA线,以允许主机发出Р信号结束传输
  • 使用IIC协议的芯片介绍—AT24C02/04/08/16

AT24Cxx系列芯片是采用IIC协议的EEPROM芯片,其读写过程如下:

写过程:主机先向芯片发出设备地址,再发出写地址后就可以开始传输数据;

读过程:主机先向芯片发出设备地址,再发出读地址,然后需要再发出一次设备地址,才能读数据,是因为AT24Cxx容量各有不同,有1K,2K,4K,8K,16K等大小,对于4K芯片来说其架构为512*8bit,而512为2的9次方,因此只传输一次读地址是不够的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fUFMLsrP-1628932720642)(https://i.loli.net/2021/08/12/3aHObDrZy2GxMLC.png)]

2、IIC驱动框架

I²C驱动框架:

App:open()、 read() 、write()
IIC设备驱动程序:drv_open()、 drv_read() 、drv_write() 知道数据含义
IIC总线驱动程序:1.识别设备; 2.提供读写函数 。 知道怎么收发数据
IIC硬件:如AT24C02/AT24C08
  • 这三层通过总线设备驱动模型联系在一块,在内核中有很多虚拟的总线例如platfoem_bus_type,对于IIC来说是i2c_bus_type总线,在这条总线中有i2c_clienti2c_driver两条链表,当i2c_client加到链表中后,会先跟i2c_driver链表的每一项比较,如果能匹配的话就调用相对应的i2c_driver的probe函数;对于i2c_driver加到链表中后,一样会先跟i2c_client链表的每一项比较,如果能匹配的话就调用i2c_driver的probe函数。匹配是调用i2c_bus_type总线中的match函数进行比较,其中比较的是id_table,而在id_table中比较的是两者链表中的name,因此简单描述如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-49sBhfqD-1628932720646)(C:\Users\liang\AppData\Roaming\Typora\typora-user-images\image-20210813194017090.png)]

  1. 左边注册一个设备:i2c_client

  2. 右边注册一个驱动:i2c_driver

  3. 比较他们的名字,如果相同,则调用probe函数

  4. probe函数里,可以注册字符设备驱动

在i2c_bus_type总线中,dev链表不仅有i2c_client还有i2c_adapter,后面再解释。

  • 对于裸板程序来说,可以直接对寄存器进行映射使用,但在内核中有完善的IIC总线驱动程序,会帮我们去发出起始信号,识别IIC设备参考i2c-s3c2410.c,在入口函数中注册了平台总线,在probe函数中设置adap并注册添加了adap(IIC适配器:i2c_add_adapter>i2c_register_adapter),在设置adap中的algo(算法)设置s3c24xx_i2c_xfer函数,这个函数实现了识别IIC设备的功能(发S信号,设备地址,等待应答)
  • IIC总线驱动程序在内核源码中drivers\i2c\busses中参考i2c-s3c2410.c,在入口函数中注册了平台总线,在probe函数中设置adap并注册添加了adap(IIC适配器:i2c_add_adapter>i2c_register_adapter),在设置adap中的algo(算法)设置s3c24xx_i2c_xfer函数,这个函数实现了识别IIC设备的功能(发S信号,设备地址,等待应答)
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
	.master_xfer		= s3c24xx_i2c_xfer,
	.functionality		= s3c24xx_i2c_func,
};
 
static struct s3c24xx_i2c s3c24xx_i2c = {
	.lock		= __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
	.wait		= __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
	.tx_setup	= 50,
	.adap		= {
		.name			= "s3c2410-i2c",
		.owner			= THIS_MODULE,
		.algo			= &s3c24xx_i2c_algorithm,
		.retries		= 2,
		.class			= I2C_CLASS_HWMON,
	},
};
...
 
static int __init i2c_adap_s3c_init(void)
{
	int ret;
 
	ret = platform_driver_register(&s3c2410_i2c_driver);
	if (ret == 0) {
		ret = platform_driver_register(&s3c2440_i2c_driver);
		if (ret)
			platform_driver_unregister(&s3c2410_i2c_driver);
	}
 
	return ret;
}
 
...
 
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
...
 
	ret = s3c24xx_i2c_init(i2c);
	if (ret != 0)
		goto err_iomap;
 
	/* find the IRQ for this unit (note, this relies on the init call to
	 * ensure no current IRQs pending 
	 */
 
	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (res == NULL) {
		dev_err(&pdev->dev, "cannot find IRQ\n");
		ret = -ENOENT;
		goto err_iomap;
	}
 
	ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,
			  pdev->name, i2c);
 
	if (ret != 0) {
		dev_err(&pdev->dev, "cannot claim IRQ\n");
		goto err_iomap;
	}
 
	i2c->irq = res;
		
	dev_dbg(&pdev->dev, "irq resource %p (%lu)\n", res,
		(unsigned long)res->start);
 
	ret = i2c_add_adapter(&i2c->adap);
...
}
 
...
static int __init i2c_adap_s3c_init(void)
{
	int ret;
 
	ret = platform_driver_register(&s3c2410_i2c_driver);
	if (ret == 0) {
		ret = platform_driver_register(&s3c2440_i2c_driver);
		if (ret)
			platform_driver_unregister(&s3c2410_i2c_driver);
	}
 
	return ret;
}
  • 参考IIC设备驱动\drivers\i2c\chips\eeprom.c,在入口函数中i2c_add_driver添加driver设备,attach_adapter会加到适配器中去,在i2c_add_driver过程中总线为i2c_bus_type,会先把i2c_driver放入总线的dri链表,从adap链表中取出"适配器"并调用attach_adapter,而attach_adapter会调用i2c_probe,其中会使用master_xfer函数发S信号,发设备地址,如果能收到ACK信号,则会调用i2c_probe参数中的function函数,这其中我们可以注册字符设备驱动来完善IIC设备驱动程序,对于总线驱动程序中i2c_add_adapter函数会把adap(适配器)放入链表,一样会调用dri的attach_adapter然后调用master_xfer函数。
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
	int res;
 
...
	if (driver->attach_adapter) {
		struct i2c_adapter *adapter;
 
		list_for_each_entry(adapter, &adapters, list) {
			driver->attach_adapter(adapter);
		}
	}
 
	mutex_unlock(&core_lists);
	return 0;
}
 
static int eeprom_attach_adapter(struct i2c_adapter *adapter)
{
	return i2c_probe(adapter, &addr_data, eeprom_detect);
}
 
static struct i2c_driver eeprom_driver = {
	.driver = {
		.name	= "eeprom",
	},
	.id		= I2C_DRIVERID_EEPROM,
	.attach_adapter	= eeprom_attach_adapter,
	.detach_client	= eeprom_detach_client,
};
...
static int __init eeprom_init(void)
{
	return i2c_add_driver(&eeprom_driver);
}

i2c_probe函数过程如下

  • i2c_probe(adapter, &addr_data, eeprom_detect);
    • i2c_probe_address // 发出S信号,发出设备地址(来自addr_data)
      • i2c_smbus_xfer
        • i2c_smbus_xfer_emulated
          • i2c_transfer
          • adap->algo->master_xfer // s3c24xx_i2c_xfer
  • i2c_add_driver:

    • 1.把i2c_driver放入链表;
    • 2.从adap链表里取出适配器,调用drv的attch_adapter函数;
    • 3.调用i2c_probe(adapter, &addr_data, function),master_xfer函数用adapter参数发信号,确定有无设备,有则调用function;
    • 4.构造i2c_client结构体(.address .adapter(指向左边的adap) .driver(指向右边的dri) )
  • i2c_add_adapter:

    • 1.把adap适配器放入链表;
    • 2.调用drv的attch_adapter函数;
    • 3.同上

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sU9Thfsj-1628932720648)(https://i.loli.net/2021/08/12/bEoTn5YNORfzluM.png)]

3、自己编写IIC驱动程序(Linux2.6)

## 3.1基本框架

以I2C设备(EEPROM芯片)AT24CXX为例,编写其驱动程序的基本步骤如下:

  • 1 分配一个i2c_driver结构体
  • 2 设置
    • attach_adapter // 它直接调用 i2c_probe(adap, 设备地址, 发现这个设备后要调用的函数);
    • detach_client // 卸载这个驱动后,如果之前发现能够支持的设备,则调用它来清理
  • 3 注册:i2c_add_driver

参考设备m41t00.c中设备地址怎么写(由AT24CXX的数据手册查得其地址为0xA0),在normal_i2c参数中地址值只需要7位,若识别设备先加入打印语句测试:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
 
static unsigned short ignore[]      = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位,取0xA0的高7位 */
 
static struct i2c_client_address_data addr_data = {
	.normal_i2c	= normal_addr,  /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
	.probe		= ignore,
	.ignore		= ignore,
};
 
static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
	printk("at24cxx_detected.\n");
	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_detached.\n");
	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");

编译加载驱动,若出现以下打印语句,则表示框架正确!

# insmod at24cxx.ko

at24cxx_detected.

3.2 强制发现IIC设备

当设备还未发现后面才发现,我们可以强制发现设备,添加forces属性,参考i2c_probe函数,ANY_I2C_BUS为任何i2c总线,0x60是没有设备的地址,修改addr_data:

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	= ignore,  /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
	.probe		= ignore,
	.ignore		= ignore,
	.forces     = forces, /* 强制认为存在这个设备 */
};

编译测试,得到一样的情况:

# insmod at24cxx.ko
at24cxx_detected.

# rmmod at24cxx

3.3 添加client(设备)

上面在卸载IIC驱动的时候,没有打印出”at24cxx_detached.”。参考eeprom.c得知,需要发现设备后,在detect函数中分配/设置/注册client结构体,收发数据时会用到。

所以现在修改调用函数at24cxx_detect,并且在at24cxx_detach中卸载client,设备地址重新改回0x50:

static unsigned short ignore[]      = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END };
static struct i2c_driver at24cxx_driver; 	//声明一下

static struct i2c_client_address_data addr_data = {
    .normal_i2c    = normal_addr,  /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
    .probe        = ignore,
    .ignore        = ignore,
};

static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
    struct i2c_client *new_client;
    
    printk("at24cxx_detected.\n");

    /* 构造一个i2c_client结构体: 以后收发数据时会用到它 */
    new_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
    new_client->addr    = address;
    new_client->adapter = adapter;
    new_client->driver  = &at24cxx_driver;
    strcpy(new_client->name, "at24cxx");
    
    i2c_attach_client(new_client); 
    return 0;
}

static int at24cxx_detach(struct i2c_client *client)
{
    printk("at24cxx_detached.\n");
    i2c_detach_client(client);
    kfree(i2c_get_clientdata(client));

    return 0;
}

编译测试,卸载驱动打印出信息

# insmod at24cxx.ko
at24cxx_detect
# rmmod at24cxx
at24cxx_detach

3.4 完善驱动程序

  • 在调用函数at24cxx_detect()中注册字符设备,修改at24cxx_detect函数,在at24cxx_detach函数中卸载,并添加相应头文件:
#include <linux/fs.h>

static int major;

static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
    return 0;
}

static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    return 0;
}


static struct file_operations at24cxx_fops = {
    .owner = THIS_MODULE,
    .read  = at24cxx_read,
    .write = at24cxx_write,
};

static struct class *cls;
struct i2c_client *at24cxx_client;  //修改为全局变量

static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{    
    printk("at24cxx_detected.\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_detach(struct i2c_client *client)
{
    printk("at24cxx_detached.\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;
}
  • 利用i2c_transfer进行传输,i2c_transfer参数一位client中的适配器,以i2c_msg结构体传输,在i2c_msg结构体中含有数据传输三要素,其中addr为设备地址,完整代码如下:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
 
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);/* 2代表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);/* 1代表一个数组项 */
	if (ret == 1) /* msg一项,返回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");

3.5 测试程序

 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
 
/* 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;
}

4、编写IIC驱动(Linux3.4.2)

需要理解bus-drv-dev模型的含义,然后知道设备的4种构建方法(在Linux3.4.2内核目录中有个文档Documentation\i2c\instantiating-devices里面介绍了四种方法怎么去构造IIC设备)。

首先,需要i2c_bus_type总线模型的左边,注册并且设置一个i2c_client。

4.1 方法一:Declare the I2C devices by bus number

Method 1: Declare the I2C devices by bus number
-----------------------------------------------
    
This method is appropriate when the I2C bus is a system bus as is the case
for many embedded systems. On such systems, each I2C bus has a number
which is known in advance. It is thus possible to pre-declare the I2C
devices which live on this bus. This is done with an array of struct
i2c_board_info which is registered by calling i2c_register_board_info().
    
Example (from omap2 h4):

static struct i2c_board_info __initdata h4_i2c_board_info[] = {
	{
		I2C_BOARD_INFO("isp1301_omap", 0x2d),
		.irq		= OMAP_GPIO_IRQ(125),
	},
	{	/* EEPROM on mainboard */
		I2C_BOARD_INFO("24c01", 0x52),
		.platform_data	= &m24c01,
	},
	{	/* EEPROM on cpu card */
		I2C_BOARD_INFO("24c01", 0x57),
		.platform_data	= &m24c01,
	},
};

static void __init omap_h4_init(void)
{
	(...)
	i2c_register_board_info(1, h4_i2c_board_info,
			ARRAY_SIZE(h4_i2c_board_info));
	(...)
}

The above code declares 3 devices on I2C bus 1, including their respective
addresses and custom data needed by their drivers. When the I2C bus in
question is registered, the I2C devices will be instantiated automatically
by i2c-core.

The devices will be automatically unbound and destroyed when the I2C bus
they sit on goes away (if ever.)
  • 该方法通过总线number来声明

    • 首先定义一个i2c_board_info结构体(结构体中包含有名字和设备地址);

    • 然后,根据i2c_board_info结构体构造一个i2c_client。即在入口函数中通过i2c_register_board_info(busnum, ...)函数注册设备,其中的list_add_tail()函数会把它们放入到__i2c_board_list链表中去,其函数定义如下:

    • int __init i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
      {
      	int status;
       
      	down_write(&__i2c_board_lock);
       
      	/* dynamic bus numbers will be assigned after the last static one */
      	if (busnum >= __i2c_first_dynamic_bus_num)
      		__i2c_first_dynamic_bus_num = busnum + 1;
       
      	for (status = 0; len; len--, info++) {
      		struct i2c_devinfo	*devinfo;
       
      		devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
      		if (!devinfo) {
      			pr_debug("i2c-core: can't register boardinfo!\n");
      			status = -ENOMEM;
      			break;
      		}
       
      		devinfo->busnum = busnum;
      		devinfo->board_info = *info;
      		list_add_tail(&devinfo->list, &__i2c_board_list);   //把它们放入i2c_board_list链表中去
      	}
       
      	up_write(&__i2c_board_lock);
       
      	return status;
      }
      
    • 搜索__i2c_board_list链表,在drivers\i2c\i2c-core.c中,在list_for_each_entry函数中链表被用到:(i2c_register_adapter > i2c_scan_static_board_info > i2c_new_device)。

    • 对链表的每一个成员都调用一个i2c_new_device,通过i2c_new_device构造一个i2c_client放到链表中去。

    • static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
      {
      	struct i2c_devinfo	*devinfo;
       
      	down_read(&__i2c_board_lock);
      	list_for_each_entry(devinfo, &__i2c_board_list, list) {  
      		if (devinfo->busnum == adapter->nr && !i2c_new_device(adapter,&devinfo->board_info))
      			dev_err(&adapter->dev, "Can't create device at 0x%02x\n", devinfo->board_info.addr);
      	}
      	up_read(&__i2c_board_lock);
      }
      
  • 使用此方法的前提使用前,必须在注册适配器(i2c_register_adapter)之前 i2c_scan_static_board_info,所以不适合我们动态加载(insmod)

4.2 方法二:Instantiate the devices explicitly

Method 2: Instantiate the devices explicitly
--------------------------------------------

This method is appropriate when a larger device uses an I2C bus for
internal communication. A typical case is TV adapters. These can have a
tuner, a video decoder, an audio decoder, etc. usually connected to the
main chip by the means of an I2C bus. You won't know the number of the I2C
bus in advance, so the method 1 described above can't be used. Instead,
you can instantiate your I2C devices explicitly. This is done by filling
a struct i2c_board_info and calling i2c_new_device().

Example (from the sfe4001 network driver):

static struct i2c_board_info sfe4001_hwmon_info = {
	I2C_BOARD_INFO("max6647", 0x4e),
};

int sfe4001_init(struct efx_nic *efx)
{
	(...)
	efx->board_info.hwmon_client =
		i2c_new_device(&efx->i2c_adap, &sfe4001_hwmon_info);

	(...)
}

The above code instantiates 1 I2C device on the I2C bus which is on the
network adapter in question.

A variant of this is when you don't know for sure if an I2C device is
present or not (for example for an optional feature which is not present
on cheap variants of a board but you have no way to tell them apart), or
it may have different addresses from one board to the next (manufacturer
changing its design without notice). In this case, you can call
i2c_new_probed_device() instead of i2c_new_device().

Example (from the nxp OHCI driver):

static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END };

static int __devinit usb_hcd_nxp_probe(struct platform_device *pdev)
{
	(...)
	struct i2c_adapter *i2c_adap;
	struct i2c_board_info i2c_info;

	(...)
	i2c_adap = i2c_get_adapter(2);
    
	memset(&i2c_info, 0, sizeof(struct i2c_board_info));
	strlcpy(i2c_info.type, "isp1301_nxp", I2C_NAME_SIZE);
    
	isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info, normal_i2c, NULL);
	i2c_put_adapter(i2c_adap);
	(...)
}

The above code instantiates up to 1 I2C device on the I2C bus which is on
the OHCI adapter in question. It first tries at address 0x2c, if nothing
is found there it tries address 0x2d, and if still nothing is found, it
simply gives up.

The driver which instantiated the I2C device is responsible for destroying
it on cleanup. This is done by calling i2c_unregister_device() on the
pointer that was earlier returned by i2c_new_device() or
i2c_new_probed_device().
  • 直接i2c_new_device 或 i2c_new_probe_device
    • 先完善i2c_driver,在probe和remove加上打印语句:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>
 
 
static int __devinit at24cxx_probe(struct i2c_client *client,
				  const struct i2c_device_id *id)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
static int __devexit at24cxx_remove(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
static const struct i2c_device_id at24cxx_id_table[] = {
	{ "at24c08", 0 },
	{}
};
 
 
/* 1. 分配/设置i2c_driver */
static struct i2c_driver at24cxx_driver = {
	.driver	= {
		.name	= "darkbirds",
		.owner	= THIS_MODULE,
	},
	.probe		= at24cxx_probe,
	.remove		= __devexit_p(at24cxx_remove),
	.id_table	= at24cxx_id_table,
};
 
static int at24cxx_drv_init(void)
{
	/* 2. 注册i2c_driver */
	i2c_add_driver(&at24cxx_driver);
	
	return 0;
}
 
static void at24cxx_drv_exit(void)
{
	i2c_del_driver(&at24cxx_driver);
}
 
 
module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);
MODULE_LICENSE("GPL");
  • 完善i2c_device

    • 先尝试直接i2c_new_device方式(认为设备肯定存在):

    • #include <linux/kernel.h>
      #include <linux/module.h>
      #include <linux/platform_device.h>
      #include <linux/i2c.h>
      #include <linux/err.h>
      #include <linux/regmap.h>
      #include <linux/slab.h>
       
       
      static struct i2c_board_info at24cxx_info = {	
      	I2C_BOARD_INFO("at24c08", 0x50),  //0x50是芯片地址,取7位
      };
       
      static struct i2c_client *at24cxx_client;
       
      static int at24cxx_dev_init(void)
      {
      	struct i2c_adapter *i2c_adap;
       
      	i2c_adap = i2c_get_adapter(0);
      	at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info);
      	i2c_put_adapter(i2c_adap);
      	
      	return 0;
      }
       
      static void at24cxx_dev_exit(void)
      {
      	i2c_unregister_device(at24cxx_client);
      }
       
       
      module_init(at24cxx_dev_init);
      module_exit(at24cxx_dev_exit);
      MODULE_LICENSE("GPL");
      

      编译执行测试程序,若把设备地址改成0x60,也是一样的结果。因为对于i2c_new_device,会认为设备肯定存在。

    • insmod at24cxx_drv.ko
      insmod at24cxx_dev.ko
      /work/drivers_and_test_new/i2c/at24cxx_drv.c at24cxx_probe 13
      rmmod at24cxx dev
      /work/drivers_and_test_new/i2c/at24cxx drv.c at24cxx remove 19
      
  • 再尝试使用i2c_new_probe_device:对于"已经能识别出来的设备probe_device,才会创建(“new”)。

    • 其参数中的probe会被调用,否则调用默认的probe来识别设备,我们用默认的probe识别,写为NULL就行。在probe函数中,判断addr_list的每一项,看设备是否真正存在,只有存在的情况下才调用i2c_new_device函数。
    • i2c_new_probed_device
      • probe(adap, addr_list[i]) /* 确定设备是否真实存在 */
      • info->addr = addr_list[i];
      • i2c_new_device(adap, info);
struct i2c_client * i2c_new_probed_device(struct i2c_adapter *adap,
		      struct i2c_board_info *info,
		      unsigned short const *addr_list,
		      int (*probe)(struct i2c_adapter *, unsigned short addr))
{
	int i;
 
	if (!probe)
		probe = i2c_default_probe;
 
	for (i = 0; addr_list[i] != I2C_CLIENT_END; i++) {
		/* Check address validity */
		if (i2c_check_addr_validity(addr_list[i]) < 0) {
			dev_warn(&adap->dev, "Invalid 7-bit address "
				 "0x%02x\n", addr_list[i]);
			continue;
		}
 
		/* Check address availability */
		if (i2c_check_addr_busy(adap, addr_list[i])) {
			dev_dbg(&adap->dev, "Address 0x%02x already in "
				"use, not probing\n", addr_list[i]);
			continue;
		}
 
		/* Test address responsiveness */
		if (probe(adap, addr_list[i]))
			break;
	}
 
	if (addr_list[i] == I2C_CLIENT_END) {
		dev_dbg(&adap->dev, "Probing failed, no device found\n");
		return NULL;
	}
 
	info->addr = addr_list[i];
	return i2c_new_device(adap, info);
}

参考文档中的例子是先把i2c_board_info结构体清零,再设置名字和设备地址,参数addr_list含有设备地址,可以放多个设备地址,若第一个设备地址不存在,则判断第二个设备地址。

修改i2c_devic:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>
 
static struct i2c_client *at24cxx_client;
 
static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };
 
static int at24cxx_dev_init(void)
{
	struct i2c_adapter *i2c_adap;
	struct i2c_board_info at24cxx_info;
 
	memset(&at24cxx_info, 0, sizeof(struct i2c_board_info));	
	strlcpy(at24cxx_info.type, "at24c08", I2C_NAME_SIZE);
 
	i2c_adap = i2c_get_adapter(0);
	at24cxx_client = i2c_new_probed_device(i2c_adap, &at24cxx_info, addr_list, NULL);
	i2c_put_adapter(i2c_adap);
 
	if (at24cxx_client)
		return 0;
	else
		return -ENODEV;
}
 
static void at24cxx_dev_exit(void)
{
	i2c_unregister_device(at24cxx_client);
}
 
 
module_init(at24cxx_dev_init);
module_exit(at24cxx_dev_exit);
MODULE_LICENSE("GPL");

加载驱动,会先识别0x60再识别0x50,对于0x50才是真正存在的设备,因此装载会打印出来:

insmod at24cxx_drv.ko
insmod at24cxx_dev.ko
/work/drivers_and_test_new/i2c/at24cxx_drv.c at24cxx_probe 13
rmmod at24cxx dev
/work/drivers_and_test_new/i2c/at24cxx drv.c at24cxx remove 19

4.3 方法三:Probe an I2C bus for certain devices

Method 3: Probe an I2C bus for certain devices
----------------------------------------------

Sometimes you do not have enough information about an I2C device, not even
to call i2c_new_probed_device(). The typical case is hardware monitoring
chips on PC mainboards. There are several dozen models, which can live
at 25 different addresses. Given the huge number of mainboards out there,
it is next to impossible to build an exhaustive list of the hardware
monitoring chips being used. Fortunately, most of these chips have
manufacturer and device ID registers, so they can be identified by
probing.

In that case, I2C devices are neither declared nor instantiated
explicitly. Instead, i2c-core will probe for such devices as soon as their
drivers are loaded, and if any is found, an I2C device will be
instantiated automatically. In order to prevent any misbehavior of this
mechanism, the following restrictions apply:
* The I2C device driver must implement the detect() method, which
  identifies a supported device by reading from arbitrary registers.
* Only buses which are likely to have a supported device and agree to be
  probed, will be probed. For example this avoids probing for hardware
  monitoring chips on a TV adapter.

Example:
See lm90_driver and lm90_detect() in drivers/hwmon/lm90.c

I2C devices instantiated as a result of such a successful probe will be
destroyed automatically when the driver which detected them is removed,
or when the underlying I2C bus is itself destroyed, whichever happens
first.

Those of you familiar with the i2c subsystem of 2.4 kernels and early 2.6
kernels will find out that this method 3 is essentially similar to what
was done there. Two significant differences are:
* Probing is only one way to instantiate I2C devices now, while it was the
  only way back then. Where possible, methods 1 and 2 should be preferred.
  Method 3 should only be used when there is no other way, as it can have
  undesirable side effects.
* I2C buses must now explicitly say which I2C driver classes can probe
  them (by the means of the class bitfield), while all I2C buses were
  probed by default back then. The default is an empty class which means
  that no probing happens. The purpose of the class bitfield is to limit
  the aforementioned undesirable side effects.

Once again, method 3 should be avoided wherever possible. Explicit device
instantiation (methods 1 and 2) is much preferred for it is safer and
faster.

其它三种方法都要事先确定适配器(IIC总线,IIC控制器)。

那如果事先并不知道IIC设备在哪个适配器上,怎么办?答:得去class表示的所有的适配器上查找。

因为一些I2C设备的上层地址是一样,怎么继续分配它是哪一款?答:要用到detect函数

与内核2.6的IIC驱动相似,在文档中让我们参考lm90.c,文档中建议让我们用前三种方法,这种方法在万不得已的情况再用。

在lm90.c中,class表示去哪些适配器上找设备,detect用这个函数来检测设备确实存在,而在address_list中含有设备的地址。首先去"class表示的这一类"I2C适配器,用"detect函数"来确定能否找到"address_list里的设备",
如果能找到就调用i2c_new_device来注册i2c_client, 这会和i2c_driver的id_table比较,如果匹配,则调用probe。

static struct i2c_driver lm90_driver = {
	.class		= I2C_CLASS_HWMON,		 /* 表示去哪些适配器上找设备 */
	.driver = {
		.name	= "lm90",
        .owner	= THIS_MODULE,
	},
	.probe		= lm90_probe,
	.remove		= lm90_remove,
	.alert		= lm90_alert,
	.id_table	= lm90_id_table,
	.detect		= lm90_detect,		 /* 用这个函数来检测设备确实存在 */
	.address_list	= normal_i2c,	 /* 这些设备的地址 */
};

以下是i2c_add_driver注册driver的过程,除了从dev链表里取出能匹配的i2c_client,若没有合适的i2c_client,在dev链表还存在着适配器,其中含有type来区别是否为适配器,对于每一个适配器,会调用它的函数确定address_list里的设备是否存在,如果存在,再调用detect进一步确定、设置,最终i2c_new_device

i2c_add_driver
    i2c_register_driver
        a. 将at24cxx_driver放入i2c_bus_type的drv链表
           并且从dev链表里取出能匹配的i2c_client并调用probe
        driver_register
               
        b. 对于每一个适配器,调用__process_new_driver
           对于每一个适配器,调用它的函数确定address_list里的设备是否存在
           如果存在,再调用detect进一步确定、设置,然后i2c_new_device
    
/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver);
    __process_new_driver
        i2c_do_add_adapter
            /* Detect supported devices on that bus, and instantiate them */
            i2c_detect(adap, driver);
				/* 
				 * 第一次为0x60,这个设备不存在,因此不会调用detect 
				 * 第二次为0x50,会调用到detect函数,因此能运行到detect说明设备已经存在
				 */
                for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {  
                     err = i2c_detect_address(temp_client, driver);             
                           
                    
                    /* 判断这个设备是否存在:简单的发出S信号确定有ACK */
                    if (!i2c_default_probe(adapter, addr))
                         return 0;
                                        
                    memset(&info, 0, sizeof(struct i2c_board_info));
                    info.addr = addr;    
                                      
                    // 设置info.type
                    err = driver->detect(temp_client, &info);
                    
                    i2c_new_device //需要addr和info.type

因此只需要写出i2c_driver

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>
 
 
static int __devinit at24cxx_probe(struct i2c_client *client,
				  const struct i2c_device_id *id)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
static int __devexit at24cxx_remove(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
static const struct i2c_device_id at24cxx_id_table[] = {
	{ "at24c08", 0 },
	{}
};
 
static int at24cxx_detect(struct i2c_client *client, struct i2c_board_info *info)
{
	/* 能运行到这里, 表示该addr的设备是存在的
	 * 但是有些设备单凭地址无法分辨(A芯片的地址是0x50, B芯片的地址也是0x50)
	 * 还需要进一步读写I2C设备来分辨是哪款芯片
	 * detect就是用来进一步分辨这个芯片是哪一款,并且设置info->type
	 */
	
	printk("at24cxx_detect : addr = 0x%x\n", client->addr);
 
	/* 进一步判断是哪一款 */
	
	strlcpy(info->type, "at24c08", I2C_NAME_SIZE);
	return 0;
	/* 返回0之后, 会创建一个新的I2C设备
	 * i2c_new_device(adapter, &info), 其中的info->type = "at24c08",
	 * 它和i2c_device_id中的名字进行比较,一致则调用probe
	 */
}
 
static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };
 
/* 1. 分配/设置i2c_driver */
static struct i2c_driver at24cxx_driver = {
	.class  = I2C_CLASS_HWMON,			/* 表示去哪些适配器上找设备 */
	.driver	= {
		.name	= "DarkBirds",
		.owner	= THIS_MODULE,
	},
	.probe		= at24cxx_probe,
	.remove		= __devexit_p(at24cxx_remove),
	.id_table	= at24cxx_id_table,
	.detect     = at24cxx_detect,  		/* 用这个函数来检测设备确实存在 */
	.address_list	= addr_list,   		/* 这些设备的地址 */
};
 
static int at24cxx_drv_init(void)
{
	/* 2. 注册i2c_driver */
	i2c_add_driver(&at24cxx_driver);
	
	return 0;
}
 
static void at24cxx_drv_exit(void)
{
	i2c_del_driver(&at24cxx_driver);
}
 
 
module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);
MODULE_LICENSE("GPL");

执行结果:

insmod at24cxx drv.ko
at24cxx_detect : addr = 0x50
i2c i2c-0: DarkBirds detection fuction provided no name for 0x50

这种方法,事先并不知道这个I2C设备在哪个适配器上,去class表示的所有的适配器上查找,如果和某适配器上的一些I2C设备的地址是一样,则继续用detect函数分辨它。

4.4 方法四:Instantiate from user-space

Method 4: Instantiate from user-space
-------------------------------------

In general, the kernel should know which I2C devices are connected and
what addresses they live at. However, in certain cases, it does not, so a
sysfs interface was added to let the user provide the information. This
interface is made of 2 attribute files which are created in every I2C bus
directory: new_device and delete_device. Both files are write only and you
must write the right parameters to them in order to properly instantiate,
respectively delete, an I2C device.

File new_device takes 2 parameters: the name of the I2C device (a string)
and the address of the I2C device (a number, typically expressed in
hexadecimal starting with 0x, but can also be expressed in decimal.)

File delete_device takes a single parameter: the address of the I2C
device. As no two devices can live at the same address on a given I2C
segment, the address is sufficient to uniquely identify the device to be
deleted.

Example:
# echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device

While this interface should only be used when in-kernel device declaration
can't be done, there is a variety of cases where it can be helpful:
* The I2C driver usually detects devices (method 3 above) but the bus
  segment your device lives on doesn't have the proper class bit set and
  thus detection doesn't trigger.
* The I2C driver usually detects devices, but your device lives at an
  unexpected address.
* The I2C driver usually detects devices, but your device is not detected,
  either because the detection routine is too strict, or because your
  device is not officially supported yet but you know it is compatible.
* You are developing a driver on a test board, where you soldered the I2C
  device yourself.

This interface is a replacement for the force_* module parameters some I2C
drivers implement. Being implemented in i2c-core rather than in each
device driver individually, it is much more efficient, and also has the
advantage that you do not have to reload the driver to change a setting.
You can also instantiate the device before the driver is loaded or even
available, and you don't need to know what driver the device needs.
  • 从用户空间创建设备:
    • 创建设备:echo at24c08 0x50 > /sys/class/i2c-adapter/i2c-0/new_device,导致i2c_new_device被调用;
    • 删除设备:echo 0x50 > /sys/class/i2c-adapter/i2c-0/delete_device,导致i2c_unregister_device被调用 。
创建设备:
# echo at24c08 0x50 > /sys/class/i2c-adapter/i2c-0/new_device
/work/drivers_and_test_new/i2c/at24cxx_drv.c at24cxx_probe 13	//i2c_new_device被调用

删除设备:
# echo 0x50 > /sys/class/i2c-adapter/i2c-0/delete_device
/work/drivers_and_test_new/i2c/at24cxx drv.c at24cxx remove 19	//i2c_unregister_device被调用

5、完善设备驱动

5.1 用户态直接访问

不需要自己写驱动程序,在内核有人完善了IIC驱动程序,可以让我们直接,在内核文档中dev-interface有介绍怎么访问,在C例子中含有i2c_smbus_write_word_data等函数,这些函数在i2c工具包中得到(i2c-tools package),在网上下载个i2c工具包。

Each registered i2c adapter gets a number, counting from 0. You can
examine /sys/class/i2c-dev/ to see what number corresponds to which adapter.
Alternatively, you can run "i2cdetect -l" to obtain a formated list of all
i2c adapters present on your system at a given time. i2cdetect is part of
the i2c-tools package.

C example
=========

So let's say you want to access an i2c adapter from a C program. The
first thing to do is "#include <linux/i2c-dev.h>". Please note that
there are two files named "i2c-dev.h" out there, one is distributed
with the Linux kernel and is meant to be included from kernel
driver code, the other one is distributed with i2c-tools and is
meant to be included from user-space programs. You obviously want
the second one here.

Now, you have to decide which adapter you want to access. You should
inspect /sys/class/i2c-dev/ or run "i2cdetect -l" to decide this.
Adapter numbers are assigned somewhat dynamically, so you can not
assume much about them. They can even change from one boot to the next.

Next thing, open the device file, as follows:

  int file;
  int adapter_nr = 2; /* probably dynamically determined */
  char filename[20];
  
  snprintf(filename, 19, "/dev/i2c-%d", adapter_nr);
  file = open(filename, O_RDWR);
  if (file < 0) {
    /* ERROR HANDLING; you can check errno to see what went wrong */
    exit(1);
  }

When you have opened the device, you must specify with what device
address you want to communicate:

  int addr = 0x40; /* The I2C address */

  if (ioctl(file, I2C_SLAVE, addr) < 0) {
    /* ERROR HANDLING; you can check errno to see what went wrong */
    exit(1);
  }

Well, you are all set up now. You can now use SMBus commands or plain
I2C to communicate with your device. SMBus commands are preferred if
the device supports them. Both are illustrated below.

  __u8 register = 0x10; /* Device register to access */
  __s32 res;
  char buf[10];

  /* Using SMBus commands */
  res = i2c_smbus_read_word_data(file, register);
  if (res < 0) {
    /* ERROR HANDLING: i2c transaction failed */
  } else {
    /* res contains the read word */
  }

  /* Using I2C Write, equivalent of 
     i2c_smbus_write_word_data(file, register, 0x6543) */
  buf[0] = register;
  buf[1] = 0x43;
  buf[2] = 0x65;
  if (write(file, buf, 3) ! =3) {
    /* ERROR HANDLING: i2c transaction failed */
  }

  /* Using I2C Read, equivalent of i2c_smbus_read_byte(file) */
  if (read(file, buf, 1) != 1) {
    /* ERROR HANDLING: i2c transaction failed */
  } else {
    /* buf[0] contains the read byte */
  }

Note that only a subset of the I2C and SMBus protocols can be achieved by
the means of read() and write() calls. In particular, so-called combined
transactions (mixing read and write messages in the same transaction)
aren't supported. For this reason, this interface is almost never used by
user-space programs.

IMPORTANT: because of the use of inline functions, you *have* to use
'-O' or some variation when you compile your program!

i2c-tools-3.1.0\include\linux\i2c-dev.h中的函数对应着内核中的i2c-dev.c驱动程序,直接写出测试程序,包含工具包的"i2c-dev.h"。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "i2c-dev.h"
 
 
/* 
 * i2c_usr_test </dev/i2c-0> <dev_addr> r addr
 * i2c_usr_test </dev/i2c-0> <dev_addr> w addr val
 */
 
void print_usage(char *file)
{
	printf("%s </dev/i2c-0> <dev_addr> r addr\n", file);
	printf("%s </dev/i2c-0> <dev_addr> w addr val\n", file);
}
 
int main(int argc, char **argv)
{
	int fd;
	unsigned char addr, data;
	unsigned int dev_addr;
	
	if ((argc != 5) && (argc != 6))
	{
		print_usage(argv[0]);
		return -1;
	}
 
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf("can't open %s\n", argv[1]);
		return -1;
	}
 
	dev_addr = strtoul(argv[2], NULL, 0);
	if (ioctl(fd, I2C_SLAVE, dev_addr) < 0)		//判断地址是否被使用
	{    
		/* ERROR HANDLING; you can check errno to see what went wrong */    
		printf("set addr error!\n");
		return -1;
	}
 
	if (strcmp(argv[3], "r") == 0)
	{
		addr = strtoul(argv[4], NULL, 0);
		
		data = i2c_smbus_read_word_data(fd, addr);
			
		printf("data: %c, %d, 0x%2x\n", data, data, data);
	}
	else if ((strcmp(argv[3], "w") == 0) && (argc == 6))
	{
		addr = strtoul(argv[4], NULL, 0);
		data = strtoul(argv[5], NULL, 0);
		i2c_smbus_write_byte_data(fd, addr, data);		
	}
	else
	{
		print_usage(argv[0]);
		return -1;
	}
	
	return 0;
}

i2c-dev.c需要编译进内核或者以insmod加载(重新配置内核:Device Drivers——> I2C support——><*> I2C device interface 或者在/drivers/i2c/Makefile中,添加配置项CONFIG_I2C_CHARDEV),修改内核并重新加载内核,执行测试程序。

在文档中建议不用read、write函数是因为读写混合,例如需要想读地址,需要把地址先写个IIC设备,然后再去写,但是利用i2c_smbus_write_word_data等函数可以一次性操作完成。

执行测试程序,当我们加载之前的驱动,重新去读就没办法了,因为地址0x50的这个设备已经被dev和drv占用了(在内核i2c-dev.c中的ioctl有判断地址是否被使用,为了防止破坏别人的驱动程序)。

# ./i2c_usr_test
./i2c_usr_test </dev/i2c-0> <dev_addr> r addr
./i2c_usr_test </dev/i2c-0> <dev_addr> w addr val
# 
# ./i2c_usr_test /dev/i2c-0 0x50 r 0
data: 2, 50, 0x32
# 
# ./i2c_usr_test /dev/i2c-0 0x50 w 0 0x61
# ./i2c_usr_test /dev/i2c-0 0x50 r 0
data: a, 97, 0x61
# 
# insmod at24cxx_drv.ko
# insmod at24cxx_dev.ko
# ./i2c_usr_test /dev/i2c-0 0x50 r 0 
set addr error!

5.2 重编I2C总线驱动

在总线模型中匹配成功后会调用i2c_driver的probe函数,在probe注册字符设备驱动,写出at24c08的读写函数。怎么读写?利用核心层提供的操作函数(SMBus系统管理总线、i2c_transfer)。

SMBus是IIC协议中的一小部分即协议的子集,文档中建议我们用SMBus,因为有些适配器只支持SMBus,之前的内核2.6的IIC驱动是以i2c_transfer来传输数据。

在smbus-protocol文档中,i2c_smbus_read_byte_datai2c_smbus_write_byte_data的格式对应着at24c08的读写格式,因此使用这两个函数:

SMBus Read Byte:  i2c_smbus_read_byte_data()
============================================

This reads a single byte from a device, from a designated register.
The register is specified through the Comm byte.

S Addr Wr [A] Comm [A] S Addr Rd [A] [Data] NA P                 //NA没有应答信号

........................................................................................................

SMBus Write Byte:  i2c_smbus_write_byte_data()
==============================================

This writes a single byte to a device, to a designated register. The
register is specified through the Comm byte. This is the opposite of
the Read Byte operation.

S Addr Wr [A] Comm [A] Data [A] P
  • 完整驱动程序

修改方法二中的i2c_driver,i2c_device不变:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
 
 
static int major;
static struct class *class;
static struct i2c_client *at24cxx_client;
 
/* 传入: buf[0] : addr
 * 输出: buf[0] : data
 */
static ssize_t at24cxx_read(struct file * file, char __user *buf, size_t count, loff_t *off)
{
	unsigned char addr, data;
	
	copy_from_user(&addr, buf, 1);
	data = i2c_smbus_read_byte_data(at24cxx_client, addr);
	copy_to_user(buf, &data, 1);
	return 1;
}
 
/* buf[0] : addr
 * buf[1] : data
 */
static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t count, loff_t *off)
{
	unsigned char ker_buf[2];
	unsigned char addr, data;
 
	copy_from_user(ker_buf, buf, 2);
	addr = ker_buf[0];
	data = ker_buf[1];
 
	printk("addr = 0x%02x, data = 0x%02x\n", addr, data);
 
	if (!i2c_smbus_write_byte_data(at24cxx_client, addr, data))
		return 2;
	else
		return -EIO;	
}
 
static struct file_operations at24cxx_fops = {
	.owner = THIS_MODULE,
	.read  = at24cxx_read,
	.write = at24cxx_write,
};
 
static int __devinit at24cxx_probe(struct i2c_client *client,
				  const struct i2c_device_id *id)
{
	at24cxx_client = client;
		
	//printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, "at24cxx", &at24cxx_fops);
	class = class_create(THIS_MODULE, "at24cxx");
	device_create(class, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */
	
	return 0;
}
 
static int __devexit at24cxx_remove(struct i2c_client *client)
{
	//printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(class, MKDEV(major, 0));
	class_destroy(class);
	unregister_chrdev(major, "at24cxx");
		
	return 0;
}
 
static const struct i2c_device_id at24cxx_id_table[] = {
	{ "at24c08", 0 },
	{}
};
 
 
/* 1. 分配/设置i2c_driver */
static struct i2c_driver at24cxx_driver = {
	.driver	= {
		.name	= "100ask",
		.owner	= THIS_MODULE,
	},
	.probe		= at24cxx_probe,
	.remove		= __devexit_p(at24cxx_remove),
	.id_table	= at24cxx_id_table,
};
 
static int at24cxx_drv_init(void)
{
	/* 2. 注册i2c_driver */
	i2c_add_driver(&at24cxx_driver);
	
	return 0;
}
 
static void at24cxx_drv_exit(void)
{
	i2c_del_driver(&at24cxx_driver);
}
 
 
module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);
MODULE_LICENSE("GPL");
  • 测试程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
 
/* 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) && (argc == 4))
	{
		buf[0] = strtoul(argv[2], NULL, 0);
		buf[1] = strtoul(argv[3], NULL, 0);
		if (write(fd, buf, 2) != 2)
			printf("write err, addr = 0x%02x, data = 0x%02x\n", buf[0], buf[1]);
	}
	else
	{
		print_usage(argv[0]);
		return -1;
	}
	
	return 0;
}
  • 先去掉内核自带总线驱动:“Device Drivers——>I2C support——>I2C Hardware Bus support——>< > S3C2410 I2C Driver”,重新编译、加载内核。
  • 先加载drv驱动,再加载dev驱动,两者匹配之后,调用i2c_driver中的probe函数,才会出现字符设备驱动。
# lsmod
at24cxx_drv 1746 0 -Live 0xbf024000 (o)
# rmmod at24cxx_drv
# insmod at24cxx_drv.ko
# ls /dev/at*
ls: /dev/at*: No such file or directory
# insmod at24cxx_dev.ko
# ls /dev/at*
/dev/at24cxx
# 
# ./i2c_test
./i2c_test r addr
./i2c_test w addr val
#
# ./i2c_test r 0
data: 1, 49, 0x31
#
# ./i2c_test w 0x32
./i2c_test r addr
./i2c_test w addr val
#
# ./i2c_test w 0x32

del_driver(&at24cxx_driver);
}

module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);
MODULE_LICENSE(“GPL”);


* **测试程序**

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
 
/* 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) && (argc == 4))
	{
		buf[0] = strtoul(argv[2], NULL, 0);
		buf[1] = strtoul(argv[3], NULL, 0);
		if (write(fd, buf, 2) != 2)
			printf("write err, addr = 0x%02x, data = 0x%02x\n", buf[0], buf[1]);
	}
	else
	{
		print_usage(argv[0]);
		return -1;
	}
	
	return 0;
}
  • 先去掉内核自带总线驱动:“Device Drivers——>I2C support——>I2C Hardware Bus support——>< > S3C2410 I2C Driver”,重新编译、加载内核。
  • 先加载drv驱动,再加载dev驱动,两者匹配之后,调用i2c_driver中的probe函数,才会出现字符设备驱动。
# lsmod
at24cxx_drv 1746 0 -Live 0xbf024000 (o)
# rmmod at24cxx_drv
# insmod at24cxx_drv.ko
# ls /dev/at*
ls: /dev/at*: No such file or directory
# insmod at24cxx_dev.ko
# ls /dev/at*
/dev/at24cxx
# 
# ./i2c_test
./i2c_test r addr
./i2c_test w addr val
#
# ./i2c_test r 0
data: 1, 49, 0x31
#
# ./i2c_test w 0x32
./i2c_test r addr
./i2c_test w addr val
#
# ./i2c_test w 0x32
  • 6
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_George

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值