目录
一、IIC驱动框架介绍
框架分为三层:app、设备驱动、总线驱动
app | open、read | |
设备驱动 | 例如at24cxx_open、at24cxx_read | |
总线驱动 | 提供统一的IIC操作函数 | |
例如2440的IIC硬件操作 | Atmel某个芯片的IIC硬件操作 |
怎么把这三层联系在一块:涉及到总线设备驱动模型,在内核中有很多虚拟的总线例如platfoem_bus_type
对于IIC来说是i2c_bus_type总线,在这条总线中有i2c_client和i2c_driver两条链表,当i2c_client加到链表中去会先跟i2c_driver链表每一项比较如果能匹配的话就调用i2c_driver的probe函数,对于i2c_driver加到链表中去一样会先跟i2c_client链表每一项比较如果能匹配的话就调用i2c_driver的probe函数,匹配是调用i2c_bus_type总线中的match函数进行比较,其中比较的是id_table,而在id_table中比较的是两者链表中的name,因此简单描述如下:
1.左边注册一个设备:i2c_client
2.右边注册一个驱动:i2c_driver
3.比较他们的名字,如果相同,则调用probe函数
4.probe函数里,可以注册字符设备驱动
在i2c_bus_type总线中,dev链表不仅有i2c_client还有i2c_adapter,后面解释
二、搭建驱动框架
在内核目录中有个文档Documentation\i2c\instantiating-devices里面介绍了四种方法怎么去构造IIC设备
需要i2c_bus_type总线模型的左边,注册并且设置一个i2c_client
2.1 方法一
(Method 1: Declare the I2C devices by bus number)是通过总线number来声明,定义一个i2c_board_info结构体,在结构体中包含有名字和设备地址,根据这些信息来构造一个i2c_client,参考参考mach-mini2440.c,i2c_board_info 在入口函数中注册,对于i2c_register_board_info函数,第一参数对于2440来说,第0个就是i2c总线就是i2c控制器,其中list_add_tail会把它们放入链表中去
static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
{
I2C_BOARD_INFO("24c08", 0x50),
.platform_data = &at24c08,
},
};
-----------------------------------
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); //把它们放入链表中去
}
up_write(&__i2c_board_lock);
return status;
}
搜索__i2c_board_list链表,在drivers\i2c\i2c-core.c中,在list_for_each_entry函数中链表在这里被用到,对链表的每一个成员都调用一个i2c_new_device,通过i2c_new_device构造一个i2c_client放到链表中去,在总线右边是i2c_add_driver,而i2c_scan_static_board_info被i2c_register_adapter(注册适配器)调用
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)
2.2 方法二
(Method 2: Instantiate the devices explicitly):直接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 = "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");
完善i2c_device,先试直接i2c_new_device方式,在内核文档中有举例取出适配器i2c_get_adapter,取完之后i2c_put_adapter
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);
(...)
}
#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就行,其中(adap, addr_list[i])对于addr_list的每一项调用probe函数来判断设备是否真正存在,如果存在的话才i2c_new_device
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含有设备地址,可以放多个设备地址,若第一个设备地址不存在,则判断第二个设备地址
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);
(...)
}
修改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
2.3 方法三
在文档中是第四种方法(Method 4: Instantiate from user-space),从用户空间创建设备
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
创建设备,导致i2c_new_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
2.4 方法四
(Method 3: Probe an I2C bus for certain devices),前面的三种都要事先确定适配器(IIC总线,IIC控制器),如果事先并不知道IIC设备在哪个适配器上,就用这种方法,与内核2.6.22.6的IIC驱动相似,在文档中让我们参考lm90.c,文档中建议让我们用前三种方法,这种方法在万不得已的情况再用
Example:
See lm90_driver and lm90_detect() in drivers/hwmon/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",
},
.probe = lm90_probe,
.remove = lm90_remove,
.alert = lm90_alert,
.id_table = lm90_id,
.detect = lm90_detect,
.address_list = normal_i2c,
};
因此只需要写出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 = "100ask",
.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: 100ask detection fuction provided no name for 0x50
这种方法,事先并不知道这个I2C设备在哪个适配器上,去class表示的所有的适配器上查找,如果适配器上一些I2C设备的地址是一样,用detect函数继续分辨它,以下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);
for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) { //第一次为0x60,这个设备不存在,因此不会调用detect
err = i2c_detect_address(temp_client, driver); //第二次为0x50,会调用到detect函数,因此能运行到detect说明设备已经存在
/* 判断这个设备是否存在:简单的发出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的probe函数,在probe注册字符设备驱动,写出at24c08的读写的函数,怎么读写,利用核心层提供的操作函数1.SMBus系统管理总线;2.i2c_transfer;SMBus是IIC协议中的一小部分即协议的子集,文档中建议我们用SMBus,因为有些适配器只支持SMBus,之前的内核2.6.22.6的IIC驱动是以i2c_transfer来传输数据
在smbus-protocol文档中,i2c_smbus_read_byte_data和i2c_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;
}
先加载drv驱动,再加载dev驱动,两者匹配之后,调用i2c_driver中的probe函数,才会出现字符设备驱动
四、用户态直接访问
不需要自己写驱动程序,在内核有人完善了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;
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加载,在/drivers/i2c/Makefile中知道需要配置CONFIG_I2C_CHARDEV这一项,修改内核并重新加载内核,执行测试程序,在文档中建议不用read、write函数是因为读写混合,例如需要想读地址,需要把地址先写个IIC设备,然后再去写,但是利用i2c_smbus_write_word_data等函数一次性操作完成
执行测试程序,当我们加载之前的驱动,重新去读就没办法了,是因为50地址这个设备被dev和drv占用了,在内核i2c-dev.c中的ioctl有判断地址是否被使用,为了防止破坏别人的驱动程序