一、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_clientdata
和 i2c_get_clientdata
API,保存和获取驱动实例。如果是虚拟平台设备驱动,可以使用 platform_set_drvdata
和 platform_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 = ®,
.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 = ®_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, ®_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, ®_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");