当前位置: 首页 > article >正文

一、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时,还需要在probe函数中,主动调用 of_match_device进行匹配。

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;
    }

    return 0;
}

static int bmp280_i2c_multi_bytes_write(struct i2c_client *client, u16 chip,
                                        struct bmp280_i2c_reg_ctrl_w *reg_ctrl, u16 len)
{
    struct i2c_msg msg;
    int i = 0;
    u8 *data;

    data = kzalloc(sizeof(u8) * len * 2, GFP_KERNEL);
    if (IS_ERR(data)) {
        return PTR_ERR(data);
    }

    for (i = 0; i < len; i++) {
        data[i * 2] = reg_ctrl[i].reg;
        data[1 * 2 + 1] = reg_ctrl[i].val;
    }

    msg.flags = I2C_M_STOP;
    msg.addr = chip;
    msg.buf = data;
    msg.len = len * 2;

    if (i2c_transfer(client->adapter, &msg, 1) < 0) {
        dev_err(&client->dev, "i2c multi bytes write error, chip=0x%02x\n", chip);
        kfree(data);
        return -EIO;
    }

    kfree(data);
    return 0;
}

static int bmp280_i2c_multi_bytes_read(struct i2c_client *client, u16 chip,
                                        struct bmp280_i2c_reg_ctrl_r *reg_ctrl)
{
    struct i2c_msg msg[] = {
        {
            .flags = 0,
            .addr = chip,
            .buf = &reg_ctrl->reg,
            .len = 1,
        },
        {
            .flags = I2C_M_RD | I2C_M_STOP,
            .addr = chip,
            .buf = reg_ctrl->val,
            .len = reg_ctrl->len,
        },
    };

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

    return 0;
}

typedef u32 BMP280_U32_t;
typedef s32 BMP280_S32_t;
typedef s64 BMP280_S64_t;

// Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123”
// equals 51.23 DegC. t_fine carries fine temperature as global value
BMP280_S32_t bmp280_compensate_T_int32(BMP280_S32_t adc_T,
                                       struct bmp280_compensation *com)
{
    BMP280_S32_t var1, var2, T;
    var1 = ((((adc_T >> 3) - ((BMP280_S32_t)com->dig_T1 << 1))) *
            ((BMP280_S32_t)com->dig_T2)) >>
           11;
    var2 = (((((adc_T >> 4) - ((BMP280_S32_t)com->dig_T1)) *
              ((adc_T >> 4) - ((BMP280_S32_t)com->dig_T1))) >>
             12) *
            ((BMP280_S32_t)com->dig_T3)) >>
           14;
    com->t_fine = var1 + var2;
    T = (com->t_fine * 5 + 128) >> 8;
    return T;
}

// Returns pressure in Pa as unsigned 32 bit integer. Output value of “96386”
// equals 96386 Pa = 963.86 hPa
BMP280_U32_t bmp280_compensate_P_int32(BMP280_S32_t adc_P,
                                        struct bmp280_compensation *com) 
{
    BMP280_S32_t var1, var2;
    BMP280_U32_t p;
    var1 = (((BMP280_S32_t)com->t_fine) >> 1) - (BMP280_S32_t)64000;
    var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * ((BMP280_S32_t)com->dig_P6);
    var2 = var2 + ((var1 * ((BMP280_S32_t)com->dig_P5)) << 1);
    var2 = (var2 >> 2) + (((BMP280_S32_t)com->dig_P4) << 16);
    var1 = (((com->dig_P3 * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) +
            ((((BMP280_S32_t)com->dig_P2) * var1) >> 1)) >>
           18;
    var1 = ((((32768 + var1)) * ((BMP280_S32_t)com->dig_P1)) >> 15);
    if (var1 == 0) {
        return 0; // avoid exception caused by division by zero
    }
    p = (((BMP280_U32_t)(((BMP280_S32_t)1048576) - adc_P) - (var2 >> 12))) *
        3125;
    if (p < 0x80000000) {
        p = (p << 1) / ((BMP280_U32_t)var1);
    } else {
        p = (p / (BMP280_U32_t)var1) * 2;
    }
    var1 = (((BMP280_S32_t)com->dig_P9) *
            ((BMP280_S32_t)(((p >> 3) * (p >> 3)) >> 13))) >>
           12;
    var2 = (((BMP280_S32_t)(p >> 2)) * ((BMP280_S32_t)com->dig_P8)) >> 13;
    p = (BMP280_U32_t)((BMP280_S32_t)p + ((var1 + var2 + com->dig_P7) >> 4));
    return p;
}

static int bmp280_compensation_load(struct bmp280_dev *bmp280)
{
    u8 data[24] = {0};
    struct i2c_client *client = bmp280->client;
    struct bmp280_i2c_reg_ctrl_r reg_ctrl = {
        .reg = 0x88,
        .val = data,
        .len = ARRAY_SIZE(data),
    };
    int ret = 0;
    struct bmp280_compensation *com = &bmp280->compensation;

    ret = bmp280_i2c_multi_bytes_read(client, client->addr, &reg_ctrl);
    if (ret) {
        return ret;
    }

    com->dig_T1 = data[0] | (data[1] << 8);
    com->dig_T2 = data[2] | (data[3] << 8);
    com->dig_T3 = data[4] | (data[5] << 8);
    com->dig_P1 = data[6] | (data[7] << 8);
    com->dig_P2 = data[8] | (data[9] << 8);
    com->dig_P3 = data[10] | (data[11] << 8);
    com->dig_P4 = data[12] | (data[13] << 8);
    com->dig_P5 = data[14] | (data[15] << 8);
    com->dig_P6 = data[16] | (data[17] << 8);
    com->dig_P7 = data[18] | (data[19] << 8);
    com->dig_P8 = data[20] | (data[21] << 8);
    com->dig_P9 = data[22] | (data[23] << 8);

    dev_info(&bmp280->client->dev,
            "dig_T1=0x%x\n"
            "dig_T2=0x%x\n"
            "dig_T3=0x%x\n"
            "dig_P1=0x%x\n"
            "dig_P2=0x%x\n"
            "dig_P3=0x%x\n"
            "dig_P4=0x%x\n"
            "dig_P5=0x%x\n"
            "dig_P6=0x%x\n"
            "dig_P7=0x%x\n"
            "dig_P8=0x%x\n"
            "dig_P9=0x%x\n",
            com->dig_T1, com->dig_T2, com->dig_T3,
            com->dig_P1, com->dig_P2, com->dig_P3,
            com->dig_P4, com->dig_P5, com->dig_P6,
            com->dig_P7, com->dig_P8, com->dig_P9);
    return 0;
}

static int bmp280_read_adc(struct bmp280_dev *bmp280)
{
    u8 data[6] = {0};
    struct bmp280_i2c_reg_ctrl_r reg_ctrl = {
        .reg = 0xF7,
        .val = data,
        .len = ARRAY_SIZE(data),
    };
    int ret = 0;
    struct i2c_client *client = bmp280->client;

    /* write config for sample, oversampling x1, force mode */
    bmp280_i2c_write_reg(client, client->addr, 0xF4, (0x07 << 5) | (0x07 << 2) | 0x01);

    /* measure time Max 6.4ms: T and P oversampling x1 */
    msleep(7);

    ret = bmp280_i2c_multi_bytes_read(client, client->addr, &reg_ctrl);
    if (ret) {
        return ret;
    }

    bmp280->adc_P = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);
    bmp280->adc_T = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4);

    dev_info(&client->dev, "adc_P: 0x%x, adc_T: 0x%x\n", bmp280->adc_P, bmp280->adc_T);
    return 0;
}

static int bmp280_read_temp_hum_press(struct bmp280_dev *bmp280)
{
    int ret = 0;

    ret = bmp280_read_adc(bmp280);
    if (ret) {
        return ret;
    }

    bmp280->temperature = bmp280_compensate_T_int32(bmp280->adc_T,
                                                    &bmp280->compensation);
    bmp280->pressure = bmp280_compensate_P_int32(bmp280->adc_P,
                                                &bmp280->compensation);

    dev_info(&bmp280->client->dev, "temperature: %d, pressure: %u\n",
            bmp280->temperature, bmp280->pressure);
    return 0;
}

static ssize_t bmp280_temperature_show(struct device *dev,
                                        struct device_attribute *attr,
                                        char *buf)
{
    struct i2c_client *client = container_of(dev, struct i2c_client, dev);
    struct bmp280_dev *bmp280 = i2c_get_clientdata(client);

    if (IS_ERR(bmp280)) {
        dev_err(dev, "i2c get cleintdata error: 0x%lx\n", PTR_ERR(bmp280));
    }

    bmp280_read_temp_hum_press(bmp280);
    return sprintf(buf, "%d\n", bmp280->temperature);
}

static ssize_t bmp280_temperature_store(struct device *dev,
                                        struct device_attribute *attr,
                                        const char *buf, size_t count)
{
    return 0;
}
static DEVICE_ATTR(temperature, 0644, bmp280_temperature_show,
                    bmp280_temperature_store);

static ssize_t bmp280_pressure_show(struct device *dev,
                                    struct device_attribute *attr,
                                    char *buf)
{
    struct i2c_client *client = container_of(dev, struct i2c_client, dev);
    struct bmp280_dev *bmp280 = i2c_get_clientdata(client);

    if (IS_ERR(bmp280)) {
        dev_err(dev, "i2c get cleintdata error: 0x%lx\n", PTR_ERR(bmp280));
    }

    bmp280_read_temp_hum_press(bmp280);
    return sprintf(buf, "%u\n", bmp280->pressure);
}

static ssize_t bmp280_pressure_store(struct device *dev,
                                        struct device_attribute *attr,
                                        const char *buf, size_t count)
{
    return 0;
}
static DEVICE_ATTR(pressure, 0644, bmp280_pressure_show, bmp280_pressure_store);

static int bmp280_init(struct bmp280_dev *bmp280)
{
    struct i2c_client *client = bmp280->client;
    u8 id = 0;
    int ret = 0;

    ret = bmp280_i2c_read_reg(client, client->addr, 0xD0, &id);
    if (ret < 0) {
        return -EREMOTEIO;
    }

    dev_info(&client->dev, "Read BMP280 Device ID: 0x%02x\n", id);

    /* Reset chip */
    ret = bmp280_i2c_write_reg(client, client->addr, 0xE0, 0xB6);
    if (ret) {
        return ret;
    }

    /* startup time: 2ms */
    msleep(2);

    ret = bmp280_compensation_load(bmp280);
    if (ret) {
        return ret;
    }

    return 0;
}

static int bmp280_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct bmp280_dev *bmp280;

    if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
        dev_err(&client->dev, "i2c_check_functionality not support I2C_FUNC_I2C\n");
        return -EIO;
    }

    bmp280 = devm_kzalloc(&client->dev, sizeof(*bmp280), GFP_KERNEL);
    if (IS_ERR(bmp280)) {
        dev_err(&client->dev, "kzalloc fail: 0x%lx\n", PTR_ERR(bmp280));
        return PTR_ERR(bmp280);
    }

    i2c_set_clientdata(client, bmp280);
    bmp280->client = client;

    if (bmp280_init(bmp280)) {
        dev_err(&client->dev, "bmp280 init error\n");
        return -EIO;
    }

    device_create_file(&client->dev, &dev_attr_temperature);
    device_create_file(&client->dev, &dev_attr_pressure);

    (void)bmp280_i2c_multi_bytes_write;
    (void)bmp280_i2c_multi_bytes_read;

    dev_info(&client->dev, "bmp280 probe success\n");
    return 0;
}

static int bmp280_remove(struct i2c_client *client)
{
    i2c_set_clientdata(client, NULL);
    device_remove_file(&client->dev, &dev_attr_temperature);
    device_remove_file(&client->dev, &dev_attr_pressure);

    dev_info(&client->dev, "bmp280 remove");
    return 0;
}

static struct i2c_device_id bmp280_id_table[] = {
    {"bosch,bmp280", 0},
    {},
};
MODULE_DEVICE_TABLE(i2c, bmp280_id_table);

static const struct of_device_id bmp280_of_match[] = {
    {.compatible = "bosch,bmp280"},
    { },
};
MODULE_DEVICE_TABLE(of, bmp280_of_match);

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);

MODULE_AUTHOR("duapple <duapple2@gmail.com>");
MODULE_LICENSE("GPL");


http://www.kler.cn/a/506276.html

相关文章:

  • tomcat文件目录讲解
  • 网络安全技术深度解析与实践案例
  • 2Spark Core
  • SDK调用文心一言如何接入,文心一言API接入教程
  • 【Python基础篇】——第3篇:从入门到精通:掌握Python数据类型与数据结构
  • o3模型重大突破:引领推理语言模型新纪元,展望2025年AI发展新格局
  • 智能化交易的新时代:中阳模型的突破与应用
  • 鸿蒙面试 2025-01-13
  • 申论对策类【2020国考地市第四题】
  • 迅为RK3576开发板Android 多屏显示
  • 深入 Flutter 和 Compose 在 UI 渲染刷新时 Diff 实现对比
  • 【Golang/nacos】nacos配置的增删查改,以及服务注册的golang实例及分析
  • 【PCIE734-1 】基于 PCIe 总线架构的 XCKU060 FPGA 4 路 SFP+光纤通道处理平台
  • 一路相伴,非凸科技助力第49届ICPC亚洲区决赛
  • 蓝桥杯2020年国赛C/C++C组第7题 重复字符串(思维与贪心)
  • 软件授权管理中的软件激活向导示例
  • 图论1-问题 C: 算法7-6:图的遍历——广度优先搜索
  • 高级Python Web开发:FastAPI的前后端集成与API性能优化
  • 计算机网络 (46)简单网络管理协议SNMP
  • AV1视频编解码简介、码流结构(OBU)
  • 【Idea启动项目报错NegativeArraySizeException】
  • ASP.NET Core WebApi接口IP限流实践技术指南
  • 基于springboot+mybatis-plus的线上订餐系统项目
  • ubuntu开机自启,其他方式
  • 二、学习SpringMVC
  • 微软徽标认证WHQL