一、I2C客户端驱动 —— bmp280

前言

在开始转Linux 驱动开发的时候,就计划着把约翰·马迪厄的Linux设备驱动开发看完,然后找一些器件把上面的驱动实验都做一下的。因为各种各样的原因,一直没有做这个事情。现在也算是做了一点项目,计划深入并复习一下。于是乎,专门开个专栏记录一下,适配各个器件驱动的过程,内核API使用,驱动开发注意事项,以及调试代码。方便自己使用的时候查看,也为正在学习的童鞋做一个分享。

Linux设备驱动开发并没有想象的那么难,设备主要分为控制通道和数据通道,数据通道主要是外设总线的驱动,这些都是由SOC厂商完成,比如MIPI。因此我们主要开发工作内容是设备的控制通道。大部分的设备在内核也是有驱动的,部分没有驱动的设备就需要我们自己开发。难点主要还是在对芯片的了解,芯片的使用上,主要工作量还是在阅读理解芯片手册上面。像实验用到的芯片,温湿度计,GPIO扩展芯片,ENC28J60等比较简单,芯片手册就几十页,复杂的像serdes芯片,成百上千页的内容,更是难以上手。因此,主要还是要静下心来,好好阅读理解datasheet,主要依赖的就是英文阅读能力。在反复的英文内容阅读,近两年来,datasheet的阅读流畅度已经是进步非常大了。

I2C Client Driver

驱动开发步骤

驱动注册

主要是将我们的驱动注册到I2C总线驱动上。I2C总线上挂了很多驱动和设备,总线会在有设备或者驱动注册时,进行匹配比较,将能够进行匹配的驱动和设备进行绑定。这样,在设备树种申明的设备就能使用我们开发好的驱动了。

I2C总线提供快速注册宏:

static struct i2c_driver bmp280_driver = {
   
    .probe = bmp280_probe,
    .remove = bmp280_remove,
    .driver = {
   
        .owner = THIS_MODULE,
        .name = "bmp280_drv",
        .of_match_table = of_match_ptr(bmp280_of_match),
    },
    .id_table = bmp280_id_table,
};

module_i2c_driver(bmp280_driver);

申明一个i2c_driver,然后填充我们必须支持的方法,probe,remove,如果驱动在整个申明周期都不需要卸载的话,甚至可以不需要remove。然后将这个驱动注册到I2C总线驱动上即可。使用 module_i2c_driver 宏能够减轻工作量,不需要再写init 和 exit,分别实现注册和取消注册。

设备树匹配

匹配有很多种机制,我们一般使用设备树匹配的方式,匹配驱动和设备。在 struct i2c_driver 结构种,of_match_table 是使用设备树的匹配方式,id_table 是老式的id匹配机制。可以在代码中同时使用,已兼容较新老版本的内核。设备是有设备树进行申明定义的,这里是通过 compatible 属性,进行匹配的,只要驱动的compatible 属性能和设备的compatible属性匹配上,那么驱动和设备就匹配成功了。
当使用 id_table 时,并不需要做什么,只要注册驱动,并在设备树中添加设备,并填写正确的compatible属性。当使用of_match_table时,在低版本的内核中,仍然需要定义id_table(也可仅定义id_table),否则,驱动检测到id_table是NULL,将返回-ENODEV错误导致probe不执行,比如4.14版本内核。在高版本的内核中,已经不存在这个问题了,可以仅使用of_match_table,如果检测到id_table是NULL,将使用of_match_table进行匹配。

driver方法填充

一般需要我们完成probe函数和remove函数的编写。尽量不要使用单例模式,由于可能驱动会被多个设备使用,尽量还是不要使用单例模式。动态分配内存,将实例保存到驱动的数据指针中。在使用时,调出来即可。一般是放到 struct device 结构中的: dev->driver_data 。I2C设备驱动,可以调用 i2c_set_clientdatai2c_get_clientdata API,保存和获取驱动实例。如果是虚拟平台设备驱动,可以使用 platform_set_drvdataplatform_get_drvdata 进行设置。

在probe中初始化和注册的调用,大多数都需要我们在remove接口中去去初始化和取消注册。需要支持remove的驱动,一定不要忘记做这个事情。因此,在我们申请和初始化时,尽量使用 devm_ 前缀风格的API,这些API支持在设备驱动移出时,自行管理。

完成函数实现后,填充到 struct i2c_driver 结构中去。

设备实例申明

最关键的,我们需要申明我们的设备结构。所有的方法填充和操作,都是围绕这个结构进行展开的,设备数据也将保存到这个结构种。另外,还需要将内核设备实例关联到我们自己的结构种,这样才能随时在两个结构之间进行切换,找到我们需要的设备实例数据,以及内核设备的数据及方法。

这里由于是I2C client设备,因此可以存放 struct i2c_client * 指针,然后通过i2c_set_clientdata 来反向绑定我们自己的设备实例。这将使得我们在内核定义的方法中,通过其提供的设备实例获得我们自己的数据结构。

开发注意事项

错误处理

保证使用内核标准的错误码和处理流程。主要使用 IS_ERR,PTR_ERR,ERR_PTR。

IS_ERR:判断API返回的指针是否是一个错误。这里指针会被设置为特殊值,而不是NULL,这样可以通过预定义的特殊值,指导返回的错误消息。

PTR_ERR: 将上述的包含错误信息的指针值,转化为long类型的值,方便我们对照errno。

ERR_PTR:我们自己在实现函数时,当返回值是指针时,我们可以通过这个将我们需要的错误信息放到指针值中进行返回。

错误码的定义,尽量要和错误的具体情况符合。让我看一眼,就能准确知道是什么类型的错误,不要随便返回与错误无关的错误码。错误码主要定义在以下几个文件中:

uapi/linux/errno.h
asm-generic/errno.h
asm-generic/errno-base.h

日志信息

推荐使用 dev_err 风格的错误日志输出API,好处是,我们能清楚的知道是那个设备和驱动报出来的错误信息。这里输出的前缀是 “driver_name device_name”,如果没有 driver_name,那么将输出 “bus_name device_name” 。日志中不需要添加换行符。pr_err 风格的API也是不需要添加换行符的。

BMP280驱动代码

设备树:

&i2c4 {
    status = "okay";

    bmp280: bmp280@76 {
        status = "okay";
        compatible = "bosch,bmp280";
        reg = <0x76>;
    };
}

注意:reg属性是必须的。

较新的内核中包含了BME280/BMP280传感器的驱动的。这里的驱动只是作为练习,实际项目中可以直接启动内核中的驱动。

#include "asm-generic/errno-base.h"
#include "asm-generic/errno.h"
#include "linux/device.h"
#include "linux/i2c.h"
#include "linux/kernel.h"
#include "linux/slab.h"
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/delay.h>

struct bmp280_compensation {
   
    unsigned short dig_T1;
    signed short dig_T2;
    signed short dig_T3;
    unsigned short dig_P1;
    signed short dig_P2;
    signed short dig_P3;
    signed short dig_P4;
    signed short dig_P5;
    signed short dig_P6;
    signed short dig_P7;
    signed short dig_P8;
    signed short dig_P9;

    s32 t_fine;
};

struct bmp280_dev {
   
    struct i2c_client *client;
    struct bmp280_compensation compensation;

    s32 adc_T;
    s32 adc_P;

    s32 temperature;
    u32 pressure;
};

/* auto increase */
struct bmp280_i2c_reg_ctrl_r {
   
    u8 reg;
    u8 *val;
    u16 len;
};

/* can't auto increase */
struct bmp280_i2c_reg_ctrl_w {
   
    u8 reg;
    u8 val;
};

static int bmp280_i2c_write_reg(struct i2c_client *client, u16 chip, u8 reg, u8 value)
{
   
    u8 data[] = {
   reg, value};
    struct i2c_msg msg[] = {
   
        {
   
            .flags = I2C_M_STOP,
            .addr = chip,
            .buf = data,
            .len = ARRAY_SIZE(data),
        },
    };

    if (i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)) < 0) {
   
        dev_err(&client->dev, "i2c write reg error, chip=0x%02x, reg=0x%02x, val=0x%02x\n",
                chip, reg, value);
    }

    return 0;
}

static int bmp280_i2c_read_reg(struct i2c_client *client, u16 chip, u8 reg, u8 *value)
{
   
    struct i2c_msg msg[] = {
   
        {
   
            .flags = 0,
            .addr = chip,
            .buf = &reg,
            .len = 1,
        },
        {
   
            .flags = I2C_M_RD | I2C_M_STOP,
            .addr = chip,
            .buf = value,
            .len = 1,
        },
    };

    if (i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)) < 0) {
   
        dev_err(&client->dev, "i2c read reg error, chip=0x%02x, reg=0x%02x",
                chip, reg);
        return -EIO;
    }

    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值