Rice我叫加饭? · 2022年02月23日

Linux驱动开发的IIC设备驱动的投机取巧

前言

  • Linux的IIC驱动想必大家都耳熟能详,网上也有很多相关的教程。
  • 网上的教程总结,比如:
    image.png
  • 上面的做法都有些困难及弊端存在,经过摸索了一遍Linux的I2C驱动框架,我发现可以很精简的写一个I2C设备的设备驱动。而且是放在内核态中,这样处理一下GPIO或者中断什么的都很方便。

投机取巧的I2C驱动

I2C设备驱动说明

  • 投机取巧的I2C驱动是参考I2C总线驱动代码实现的。
  • 投机取巧的I2C驱动不需要设备树,这也让一些不熟悉设备树的小伙伴能编写一个设备驱动。
  • 投机取巧的I2C驱动精简,方便理解。

分析I2C总线驱动说明

  • I2C总线驱动的代码在linux的源码中--i2c-dev.c中。
  • 在代码中可以看到他提供一套文件操作接口,open,read,write,close接口。实际在上面描述的直接操作i2c总线驱动的方法,最终就是调用到这里。
  • 通过整个源码的分析,我们主要看看open和ioctl接口。其中:
  • open接口,代码分析:通过inode获取设备子设备号,根据子设备号获取I2C适配器。然后申请一个从设备对象。并将I2C适配器句柄映射到从设备对象中。
static int i2cdev_open(struct inode *inode, struct file *file)
{
 unsigned int minor = iminor(inode);
 struct i2c_client *client;
 struct i2c_adapter *adap;

 adap = i2c_get_adapter(minor);
 if (!adap)
  return -ENODEV;

 /* This creates an anonymous i2c_client, which may later be
  * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
  *
  * This client is ** NEVER REGISTERED ** with the driver model
  * or I2C core code!!  It just holds private copies of addressing
  * information and maybe a PEC flag.
  */
 client = kzalloc(sizeof(*client), GFP_KERNEL);
 if (!client) {
  i2c_put_adapter(adap);
  return -ENOMEM;
 }
 snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);

 client->adapter = adap;
 file->private_data = client;

 return 0;
}
  • ioctl接口(只提取有用信息):  获取从设备对象句柄,然后将用户态传输的内容传输到i2cdev_ioctl_rdwr()接口。i2cdev_ioctl_rdwr()接口是i2c总线驱动对从设备操作的进一步封装,我们进一步看一下这个函数。
static int i2cdev_open(struct inode *inode, struct file *file)
{
 unsigned int minor = iminor(inode);
 struct i2c_client *client;
 struct i2c_adapter *adap;

 adap = i2c_get_adapter(minor);
 if (!adap)
  return -ENODEV;

 /* This creates an anonymous i2c_client, which may later be
  * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
  *
  * This client is ** NEVER REGISTERED ** with the driver model
  * or I2C core code!!  It just holds private copies of addressing
  * information and maybe a PEC flag.
  */
 client = kzalloc(sizeof(*client), GFP_KERNEL);
 if (!client) {
  i2c_put_adapter(adap);
  return -ENOMEM;
 }
 snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);

 client->adapter = adap;
 file->private_data = client;

 return 0;
}
  • i2cdev_ioctl_rdwr接口:通过接口可以看出,从用户态拷贝数据,然后通过i2c_transfer接口进入从设备数据读写,然后判断标志是否读操作,如果为读操作,将i2c_transfer接口接收回来的数据拷贝到用户态。
static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
  unsigned long arg)
{
 struct i2c_rdwr_ioctl_data rdwr_arg;
 struct i2c_msg *rdwr_pa;
 u8 __user **data_ptrs;
 int i, res;

 if (copy_from_user(&rdwr_arg,
      (struct i2c_rdwr_ioctl_data __user *)arg,
      sizeof(rdwr_arg)))
  return -EFAULT;

    ......
 
 res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs);
    while (i-- > 0) {
  if (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);
 }
    ......

 return res;
}

投机取巧的I2C驱动写法

  • 通过i2c总线驱动的源码分析,实际我们的设备驱动可以通过这种模仿这个总线驱动来写。
  • 代码模板如下:
#include "rice_i2c.h"

#define CLASS_NAME  "rice_i2c"
#define DEVICE_NAME "rice_i2c"

typedef struct {
    int major_number;
    struct device *device;
    struct class *class;
    struct i2c_client *client;
} Rice_Driver;

Rice_Driver rice_drv;

static int i2c_test(void)
{
    struct i2c_msg i2c_msg[2] = {0};
    uint8_t reg_addr = 0x75;
    uint8_t buff[2] = {0};

    i2c_msg[0].addr  = 0x69;
    i2c_msg[0].flags = 0;
    i2c_msg[0].len   = 1;
    i2c_msg[0].buf   = (uint8_t *)&reg_addr;
    i2c_msg[1].addr = 0x69;
    i2c_msg[1].flags = I2C_M_RD;
    i2c_msg[1].len = 1;
    i2c_msg[1].buf = buff;

    i2c_transfer(rice_drv.client->adapter, i2c_msg, 2);

    printk(KERN_ALERT "i2c read data: 0x%02x!!\n", buff[0]);
}

static int __init rice_i2c_init(void) {
    struct i2c_client *client;
 struct i2c_adapter *adap;

    rice_drv.major_number = register_chrdev(0, DEVICE_NAME, NULL);

    if (rice_drv.major_number < 0) {
        printk(KERN_ALERT "Register fail!!\n");
        return rice_drv.major_number;
    }

    printk(KERN_ALERT "Registe success, major number is %d\n", rice_drv.major_number);

    rice_drv.class = class_create(THIS_MODULE, CLASS_NAME);

    if (IS_ERR(rice_drv.class)) {
        unregister_chrdev(rice_drv.major_number, DEVICE_NAME);
        return PTR_ERR(rice_drv.class);
    }

    rice_drv.device = device_create(rice_drv.class, NULL, MKDEV(rice_drv.major_number, 0), NULL, DEVICE_NAME);

    if (IS_ERR(rice_drv.device)) {
        class_destroy(rice_drv.class);
        unregister_chrdev(rice_drv.major_number, DEVICE_NAME);
        return PTR_ERR(rice_drv.device);
    }

    // 1 为设备挂在的i2c总线的子设备号
 adap = i2c_get_adapter(1);
 if (!adap)
  return -ENODEV;

 rice_drv.client = kzalloc(sizeof(*rice_drv.client), GFP_KERNEL);
 if (!rice_drv.client) {
  i2c_put_adapter(adap);
  return -ENOMEM;
 }
 snprintf(rice_drv.client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);

 rice_drv.client->adapter = adap;

    i2c_test();

    printk(KERN_ALERT "rice i2c ko init!!\n");

    return 0;
}

static void __exit rice_i2c_exit(void) {
    device_destroy(rice_drv.class, MKDEV(rice_drv.major_number, 0));
    class_unregister(rice_drv.class);
    class_destroy(rice_drv.class);
    unregister_chrdev(rice_drv.major_number, DEVICE_NAME);
    i2c_put_adapter(rice_drv.client->adap);

    printk(KERN_ALERT "rice i2c ko exit!!\n");
}

module_init(rice_i2c_init);
module_exit(rice_i2c_exit);
MODULE_AUTHOR("RieChen");
MODULE_LICENSE("GPL");
  • 运行结果
Registe success, major number is 240
i2c read data: 0x67!!
rice i2c ko init!!

总结

  • 通过投机取巧的方法,不需要设备树的存在,就可以在内核态中编写设备驱动,而且很灵活。
  • 虽然这是一种可以让我们快速开发驱动的方法,但是还是建议大家要去了解框架的逻辑。这样不仅对自己的编码能力,以及开发很有帮助。
  • 希望本篇文章能够帮助到大家。
首发:Rice 嵌入式开发技术分享
作者:RiceDIY

推荐阅读

更多嵌入式技术干货请关注Rice 嵌入式开发技术分享
推荐阅读
关注数
1761
内容数
51
一个周末很无聊的嵌入式软件工程师,写写经验,写写总结。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息