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

描述

前言

  • Linux的IIC驱动想必大家都耳熟能详,网上也有很多相关的教程。
  • 网上的教程总结,比如:
方法 问题描述
Linux 3.X.X版本之后,设备树+驱动 此方法是比较符合linux驱动的写法的。当对于不熟悉设备树的小伙伴,写起来比较棘手
使用 i2c-tools,并通过脚本或者应用程序编写设备驱动(简单粗暴) 此方法是将设备驱动丢到用户态中,对于一些的设备除了I2C通信还有一些引脚也要控制的,此方法写起来将非常痛苦
直接操作i2c总线驱动。(简单粗暴) 此方法是将设备驱动丢到用户态中,对于一些的设备除了I2C通信还有一些引脚也要控制的,此方法写起来将非常痛苦。他将会操作多个文件
  • 上面的做法都有些困难及弊端存在,经过摸索了一遍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 long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
 struct i2c_client *client = file->private_data;
 unsigned long funcs;

    ......

 switch (cmd) {
    .....

 case I2C_RDWR:
  return i2cdev_ioctl_rdwr(client, arg);

    ......
 }
 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 *)®_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_MODULECLASS_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!!

总结

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

审核编辑 黄昊宇


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分