i2c设备驱动1

i2c设备驱动,在i2c子系统中的实现分为三部分,最上层是i2c驱动,中间层是i2c核心层,底层是i2c的总线,如果细分,底层i2c的总线还可以分离出i2c的适配器,所以也可以将i2c设备驱动的子系统分为四个部分,i2c驱动,i2c核心层,i2c的总线,i2c适配器。

 I2C 核心提供了I2C 总线驱动和设备驱动的注册、注销方法,I2C 通信方法;I2C 总线驱动主要包含了I2C 适配器数据结构i2c_adapter、I2C适配器的algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数;

在i2c子系统中所有的i2c驱动都挂在i2c的总线上,实现i2c的驱动的时候,分为左右两个部分,左边链表挂i2c设备的客户端即实现了i2c_client,右边实现i2c_driver,左右两边通过通过id_table中的name参数实现匹配,然后调用probe。

本文主要主要以一个简单的例子说明i2c驱动的实现,首先我实现了i2c_driver部分,具体的代码如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/mutex.h>
static const struct i2c_device_id at24cxx_i2c_id[] = {
{ "at24c08", 0},
{}
};
static int at24c08_i2c_probe(struct i2c_client *i2c,const struct i2c_device_id *id)
{
printk("%s %s %d \n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int at24c08_i2c_remove(struct i2c_client *i2c)
{
printk("%s %s %d \n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static struct i2c_driver at24cxx_i2c_driver = {
.driver = {
.name = "xxxx",
.owner = THIS_MODULE,
},
.probe = at24c08_i2c_probe,
.remove = at24c08_i2c_remove,
.id_table = at24cxx_i2c_id,
};
/* 分配设置i2c_driver*/
static int at24cxx_drv_init(void)
{
/* 注册一个i2c_driver*/
i2c_add_driver(&at24cxx_i2c_driver);
return 0;
}
static void at24cxx_drv_exit(void)
{
i2c_del_driver(&at24cxx_i2c_driver);
}

module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("I2C /dev entries driver");

此时编译出来i2c_drv.ko,进行实验,通过insmod的加载,你会发现在此时并没有调用probe函数。所以你要实现i2c_client,不然他不可能在i2c_client中找到其对应的设备

这就引出了我们的第一种创建i2c设备的方法,通过注册板级文件,轮询左边设备没找到对应的i2c设备。这样就可以使用i2c设备了。在Linux2.6版本之后的i2c子系统中为我们提供了两个创建设备的函数接口i2c_new_device()和i2c_new_probed_device(),直接拿过来用就好了。具体的实现代码如下

#include <linux/kernel.h>  
#include <linux/module.h>  
#include <linux/device.h>  
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/mutex.h>

static struct i2c_board_info at24cxx_info = {
I2C_BOARD_INFO("at24c08", 0x1c),
};
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");
MODULE_DESCRIPTION("I2C /dev entries driver");

i2c_new_device 创建一个i2c设备,然后通过i2c_board_info 中注册一个"at24c08"。这个名字和i2c_drv中注册的i2c设备的name是相同,如果这两个名字不匹配,那么就不会调用probe函数。0x1c是i2c设备的地址。

实现左右两边,编译得到了i2c_drv.ko和i2c_dev.ko,拷贝到设备中,insmod这个两ko文件,便可以加载成功i2c设备。

在上述中提到创建i2c设备有多种方法,第一种方法是i2c_new_device和i2c_new_probed_device,那么这两个函数有什么区别呢

查看i2c子系统的代码这两个函数的实现源码如下:

struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client*client;
int status;

client = kzalloc(sizeof *client, GFP_KERNEL);
if (!client)
return NULL;
client->adapter = adap;
client->dev.platform_data = info->platform_data;
if (info->archdata)
client->dev.archdata = *info->archdata;
client->flags = info->flags;
client->addr = info->addr;
client->irq = info->irq;
strlcpy(client->name, info->type, sizeof(client->name));
/* Check for address validity */
status = i2c_check_client_addr_validity(client);
if (status) {
dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
goto out_err_silent;
}
/* Check for address business */
status = i2c_check_addr_busy(adap, client->addr);
if (status)
goto out_err;
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = info->of_node;
ACPI_HANDLE_SET(&client->dev, info->acpi_node.handle);
/* For 10-bit clients, add an arbitrary offset to avoid collisions */
dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
    client->addr | ((client->flags & I2C_CLIENT_TEN)
    ? 0xa000 : 0));
status = device_register(&client->dev);
if (status)
goto out_err;
dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
client->name, dev_name(&client->dev));
return client;
out_err:
dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x "
"(%d)\n", client->name, client->addr, status);
out_err_silent:
kfree(client);
return NULL;
}

从上述代码分析可知,只要传入两个正确的参数,i2c_new_device就会创建一个i2c设备出来,client->adapter = adap;直接赋值,只要是正确的i2c适配器,他就会在内核中创建这个i2c设备。再来看i2c_new_probed_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);
}

probe = i2c_default_probe;为我们提供了一个默认的函数,当这个参数为NULL时。之后去轮询地址链表,检测你提供的地址链表是否合法,之后调用i2c_check_addr_busy(adap, addr_list[i])检测适配器是否和地址匹配,之后找到匹配的addr_list,调用i2c_new_device。所以从上述分析我们可以得到至少三个结论

1、i2c_new_probed_device创建设备之前会去检查提供的地址表中的地址是否合法,所以他只会去创建一个已经识别出来的设备

2、创建i2c设备实际由i2c_new_device创建

3、只要提供的适配器正确,i2c_new_device是强制认为i2c设备是存在的,一定会创建一个i2c设备的

在进行i2c_new_probed_device的实验时,i2c_drv.c代码不动,只需要更改i2c_dev代码即可,具体实现如下

#include <linux/kernel.h>  
#include <linux/module.h>  
#include <linux/device.h>  
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/mutex.h>

static const unsigned short addr_list[] = {0x6b,0x1c,I2C_CLIENT_END};
static struct i2c_client *at24cxx_client;
static int at24cxx_dev_init(void)
{
struct i2c_adapter *i2c_adap;
struct i2c_board_info at24cxx_info;
memset(&at24cxx_info,0x00, sizeof(at24cxx_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 -1;
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");
MODULE_DESCRIPTION("I2C /dev entries driver");

接下来是创建设备的第二种方法介绍

第二种方式是直接在用户空间操作,通etho命令直接写入,在用户空间进入 /sys/bus/i2c/devices/i2c-0/,你会找到两个字段new_device和delete_device,new_device用来创建设备,delete_device删除设备,在内核代码中搜索new_device和delete_device就会发现他们分别对应是驱动函数中的i2c_new_device和i2c_unregister_device函数,

new_device---》i2c_sysfs_new_device--》i2c_new_device 

delete_device--》i2c_sysfs_delete_device--》i2c_unregister_device

具体的实现使用:

创建设备

echo at240c08 0x50 > /sys/bus/i2c/devices/i2c-0/new_device
最终导致i2c_new_device被调用

删除设备
echo 0x50 > /sys/bus/i2c/devices/i2c-0/delete_device
导致i2c_unregister_device被调用

at240c08 这个名字对应i2c_drv.c中id_table中name,地址是i2c的设备地址,,所以第二种方法只要加载i2c_drv.ko,在启动文件中写入上述命令即可。

第三种方式是直接在i2c_drv.c中实现,这样就不需要在左边重新创建一个设备,而是直接去查找适配器,在适配器中探测到设备就可以了

static struct i2c_driver at24cxx_driver = {
.class = I2C_CLASS_HWMON, //去哪一类i2c适配器去查找设备
.driver = {
.name = "xxxx",
},
.probe = at24cxx_probe,
.remove = at24cxx_remove,
.alert = at24cxx_alert,
.id_table = at24cxx_id,
.detect = at24cxx_detect,//y用这个函数去检测能否找到设备
.address_list = addr_list, //这些设备的地址
};

这种方法是去“class”表示的这一类i2c适配器,用at24cxx_detect函数来确定能否找到.address_list里面的设备,

如果找到了这个设备就调用i2c_new_device来注册i2c_client,这会和i2c_driver的id_table比较,如果匹配,则调用probe。

具体的代码实现如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/mutex.h>
static const struct i2c_device_id at24cxx_i2c_id[] = {
{ "at24c08", 0},
{}
};
static const unsigned short addr_list[] = {0x1c,0x50,I2C_CLIENT_END };

static int at24cxx_detect(struct i2c_client *client, struct i2c_board_info *info)
{
/* 能运行到这里,说明该地址的设备是存在的
* 但是有些设备单凭这些还无法分辨
* 还需要进一步读写i2c来分辨设备是否存在
* detect函数就是进一步判断这是哪一款芯片的,并且设置info->type
*/
printk("at24cxx_detect add: %x \n",client->addr);
strlcpy(info->type, "at24c08", I2C_NAME_SIZE);
return 0;
}
static int at24c08_i2c_probe(struct i2c_client *i2c,const struct i2c_device_id *id)
{
printk("%s %s %d \n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int at24c08_i2c_remove(struct i2c_client *i2c)
{
printk("%s %s %d \n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}

static struct i2c_driver at24cxx_i2c_driver = {
.class = I2C_CLASS_HWMON,
.driver    = {
.name = "xxxx",
.owner = THIS_MODULE,
},
.probe    = at24c08_i2c_probe,
.remove   = at24c08_i2c_remove,
.detect   = at24cxx_detect,
.id_table = at24cxx_i2c_id,
.address_list = addr_list, 
};
/* 分配设置i2c_driver*/
static int at24cxx_drv_init(void)
{
/* 注册一个i2c_driver*/
i2c_add_driver(&at24cxx_i2c_driver);
return 0;
}
static void at24cxx_drv_exit(void)
{
i2c_del_driver(&at24cxx_i2c_driver);
}
module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("I2C /dev entries driver");

能运行到at24cxx_detect这里,说明该地址的设备是存在的, 但是有些设备单凭这些还无法分辨(例如A芯片和B芯片,当他们的设备地址都是0x50,),还需要进一步读写i2c来分辨设备是否存在,detect函数就是进一步判断这是哪一款芯片的,并且设置info->type

以上就是i2c创建设备的三种方式,在内核代码中,推荐使用第一种方法的第二个方式,无论理解还是在其运行效率上都比较高效。

在上述代码中只是关于i2c驱动,i2c核心,i2c总线的模型,之后余下的i2c具体的功能实现主要使用的还是字符设备的方式,通过file_operations结构的填充,实现具体的i2c读写功能,以及i2c设备的优化工作,如如何调用i2c中的是时钟,测试等等。
 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值