linux下i2c设备驱动程序,Linux I2C 设备驱动

I2C 设备驱动要使用 i2c_driver 和 i2c_client 数据结构并填充其中的成员函数。i2c_client 一般被包含在设备的私有信息结构体yyy_data 中,而 i2c_driver 则适合被定义为全局变量并初始化,下面代码所示为已被初始化的 i2c_driver。

static struct i2c_driver yyy_driver =

{

.driver ={

.name = "yyy",

} ,

.attach_adapter = yyy_attach_adapter,

.detach_client = yyy_detach_client,

.command = yyy_command,

};Linux I2C 设备驱动的模块加载与卸载

I2C 设备驱动的模块加载函数通用的方法是在 I2C 设备驱动的模块加载函数中完成两件事。

(1)通过 register_chrdev()函数将 I2C 设备注册为一个字符设备。

(2)通过 I2C 核心的 i2c_add_driver()函数添加 i2c_driver。

在模块卸载函数中需要做相反的两件事。

(1)通过 I2C 核心的 i2c_del_driver()函数删除 i2c_driver。

(2)通过 unregister_chrdev()函数注销字符设备。

下面代码所示为 I2C 设备驱动的加载与卸载函数模板。

static int _ _init yyy_init(void)

{

int res;

/*注册字符设备*/

res = register_chrdev(YYY_MAJOR, "yyy", &yyy_fops); //老内核接口

if (res)

goto out;

/*添加 i2c_driver*/

res = i2c_add_driver(&yyy_driver);

if (res)

goto out_unreg_class;

return 0;

out_unreg_chrdev:

unregister_chrdev(I2C_MAJOR, "i2c");

out:

printk(KERN_ERR "%s: Driver Initialisation failed\n", _ _FILE__);

return res;

}

static void _ _exit yyy_exit(void)

{

i2c_del_driver(&i2cdev_driver);

unregister_chrdev(YYY_MAJOR, "yyy");

}Linux I2C 设备驱动的 i2c_driver 成员函数

i2c_add_driver(&yyy_driver)的执行会引发 i2c_driver 结构体中 yyy_attach_adapter()函数的执行,我们可以在 yyy_attach_adapter()函数里探测物理设备。为了实现探测,yyy_attach_adapter()函数里面也只需简单地调用 I2C 核心的i2c_probe()函数,

如代码清单如下 所示

static int yyy_attach_adapter(struct i2c_adapter *adapter)

{

return i2c_probe(adapter, &addr_data, yyy_detect);

}i2c_probe传递的第 1 个参数是 i2c_adapter 指针,第 2 个参数是要探测的地址数据,第 3 个参数是具体的探测函数。要探测的地址实际列表在一个16 位无符号整型数组中,这个数组以

I2C_CLIENT_END 为最后一个元素。i2c_probe()函数会引发 yyy_detect()函数的调用,\可以在 yyy_detect()函数中初始化i2c_client.

如代码 所示:

static int yyy_detect(struct i2c_adapter *adapter, int address, int kind)

{

struct i2c_client *new_client;

struct yyy_data *data;

int err = 0;

if (!i2c_check_functionality(adapter, I2C_FUNC_XXX)

goto exit;

if (!(data = kzalloc(sizeof(struct yyy_data), GFP_KERNEL)))//分配私有信息结构体的内存,i2c_client 也被创建

{

err = - ENOMEM;

goto exit;

}

//对新创建的 i2c_client 进行初始化。

new_client = &data->client;

new_client->addr = address;

new_client->adapter = adapter;

new_client->driver = &yyy_driver;

new_client->flags = 0;

/* 将新的 client 将依附于 adapter */

if ((err = i2c_attach_client(new_client)))

goto exit_kfree;

yyy_init_client(new_client);//初始化 i2c_client 对应的 I2C 设备,这个函数是硬件相关的。

return 0;

exit_kfree:

kfree(data);

exit:

return err;

}下面列出i2c设备驱动加载的一系列流程:

0818b9ca8b590ca3270a3433284dd417.png

Linux I2C 设备驱动的文件操作接口

作为一种字符类设备,Linux I2C 设备驱动的文件操作接口与普通的设备驱动是完全一致的,但是在其中要使用 i2c_client、i2c_driver、i2c_adapter 和 i2c_algorithm 结构体和 I2C 核心,并且对设备的读写和控制需要借助体系结构中各组成部分的协同合作。

下面代码为一个 I2C 设备写函数的例子。

static ssize_t yyy_write(struct file *file, char *buf, size_t count,loff_t off)

{

struct i2c_client *client = (struct i2c_client*)file->private_data;

i2c_msg msg[1];

char *tmp;

int ret;

tmp = kmalloc(count, GFP_KERNEL);

if (tmp == NULL)

return - ENOMEM;

if (copy_from_user(tmp, buf, count))

{

kfree(tmp);

return - EFAULT;

}

msg[0].addr = client->addr;//地址

msg[0].flags = 0;//0 为写

msg[0].len = count;//要写的字节数

msg[0].buf = tmp;//要写的数据

ret = i2c_transfer(client->adapter, msg, 1); //传输 I2C 消息

return (ret == 1) ? count : ret;

}上述程序给出的仅仅是一个写函数的例子,具体的写操作与设备密切相关。下面详细讲解 I2C 设备读写过程中数据的流向和函数的调用关系。I2C 设备的写操作经历了如下几个步骤。

(1)从用户空间到字符设备驱动写函数接口,写函数构造 I2C 消息数组。

(2)写函数把构造的 I2C 消息数组传递给 I2C 核心的传输函数 i2c_transfer()。

(3)I2C 核心的传输函数 i2c_transfer()找到对应适配器 algorithm 的通信方法函数 master_xfer()去最终完成 I2C 消息的处理。

下图所示为从用户空间发起读写操作到 algorithm 进行消息传输的流程。

0818b9ca8b590ca3270a3433284dd417.png

通常,如果 I2C 设备不是一个输入/输出设备或存储设备,就并不需要给 I2C 设备提供读写函数。许多 I2C 设备只是需要被设置以某种方式工作,而不是被读写。另外,I2C 设备驱动的文件操作接口也不是必需的,甚至极少被需要。大多数情况下,我们只需要通过

i2c-dev.c 文件提供的 I2C 适配器设备文件接口就可完成对 I2C 设备的读写。

Linux 的 i2c-dev.c 文件分析

i2c-dev.c 文件完全可以被看作一个 I2C 设备驱动,它实现的一个 i2c_client 是虚拟、临时的,随着设备文件的打开而产生,并随设备文件的关闭而撤销,并没有被添加到 i2c_adapter 的 clients链表中。i2c-dev.c 针对每个 I2C 适配器生成一个主设备号为 89 的设备文件,实现了i2c_driver 的成员函数以及文件操作接口,所以i2c-dev.c 的主体是“i2c_driver 成员函数 + 字符设备驱动”。

i2c-dev.c 中提供 i2cdev_read()、i2cdev_write()函数来对应用户空间要使用的 read()和 write() 文件 操作 接 口, 这两 个函 数分 别 调用 I2C 核 心的 i2c_master_recv() 和i2c_master_send()函数来构造一条 I2C 消息并引发适配器 algorithm 通信函数的调用,完成消息的传输,对应于如图 15.7 所示的时序。但是,很遗憾,大多数稍微复杂一点I2C 设备的读写流程并不对应于一条消息,往往需要两条甚至更多的消息来进行一次读写周期(即如图 15.8 所示的重复开始位 RepStart 模式),这种情况下,在应用层仍然调用 read()、write()文件 API 来读写 I2C 设备,将不能正确地读写。

0818b9ca8b590ca3270a3433284dd417.png

许多工程师碰到过类似的问题,往往经过相当长时间的调试都没法解决 I2C 设备的读写,连错误的原因也无法找到,显然是对 i2cdev_read()和 i2cdev_write()函数的作用有所误解。

鉴于上述原因,i2c-dev.c 中 i2cdev_read()和 i2cdev_write()函数不具备太强的通用性,没有太大的实用价值,只能适用于非 RepStart 模式的情况。对于两条以上消息组成的读写,在用户空间需要组织 i2c_msg 消息数组并调用I2C_RDWR IOCTL 命令。

下面代码所示为 i2cdev_ioctl()函数的框架,其中详细列出了 I2C_RDWR 命令的处理过程。

static int i2cdev_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg)

{

struct i2c_client *client = (struct i2c_client *)file->private_data;

...

switch ( cmd ) {

case I2C_SLAVE:

case I2C_SLAVE_FORCE:

.../*设置从设备地址*/

case I2C_TENBIT:

...

case I2C_PEC:

...

case I2C_FUNCS:

...

case I2C_RDWR:

if(copy_from_user(&rdwr_arg,(struct i2c_rdwr_ioctl_data _ _user *)arg,sizeof(rdwr_arg)))

return -EFAULT;

/* 一次传入的消息太多 */

if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)

return -EINVAL;

/*获得用户空间传入的消息数组

rdwr_pa = (struct i2c_msg *)kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg),GFP_KERNEL);

if (rdwr_pa == NULL)

return -ENOMEM;

if (copy_from_user(rdwr_pa, rdwr_arg.msgs,rdwr_arg.nmsgs * sizeof(struct i2c_msg))) {

kfree(rdwr_pa);

return -EFAULT;

}

data_ptrs = kmalloc(rdwr_arg.nmsgs * sizeof(u8 _ _user *),GFP_KERNEL);

if (data_ptrs == NULL) {

kfree(rdwr_pa);

return -ENOMEM;

}

res = 0;

for( i=0; i

/* 限制消息的长度 */

if (rdwr_pa[i].len > 8192) {

res = -EINVAL;

break;

}

data_ptrs[i] = (u8 _ _user *)rdwr_pa[i].buf;

rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL);

if(rdwr_pa[i].buf == NULL) {

res = -ENOMEM;

break;

}

if(copy_from_user(rdwr_pa[i].buf, data_ptrs[i],rdwr_pa[i].len)) {

++i; /* Needs to be kfreed too */

res = -EFAULT;

break;

}

}

if (res < 0) {

int j;

for (j = 0; j < i; ++j)

kfree(rdwr_pa[j].buf);

kfree(data_ptrs);

kfree(rdwr_pa);

return res;

}

/*把这些消息交给通信方法去处理*/

res = i2c_transfer(client->adapter, rdwr_pa,rdwr_arg.nmsgs);

while(i-- > 0) {

/*如果是读消息,把值复制到用户空间*/

( res>=0 && (rdwr_pa[i].flags & I2C_M_RD)) {

if(copy_to_user(data_ptrs[i],rdwr_pa[i].buf,rdwr_pa[i].len)) {

res = -EFAULT;

}

}

kfree(rdwr_pa[i].buf);

}

kfree(data_ptrs);

kfree(rdwr_pa);

return res;

case I2C_SMBUS:

...

default:

return i2c_control(client,cmd,arg);

}

return 0;

}常用的 IOCTL 包括 I2C_SLAVE(设置从设备地址)、I2C_RETRIES(没有收到设备 ACK 情况下的重试次数,默认为 1)、I2C_TIMEOU(超时)以及 I2C_RDWR。

由第 17~19 行可以看出,应用程序需要通过 i2c_rdwr_ioctl_data 结构体来给内核传递 I2C 消息,这个结构体定义如代码清单 15.22 所示,i2c_msg 数组指针及消息数量就被包含在 i2c_rdwr_ ioctl_data 结构体中。

struct i2c_rdwr_ioctl_data {

struct i2c_msg _ _user *msgs; /* I2C 消息指针 */

_ _u32 nmsgs; /* I2C 消息数量 */

};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值