项目:I2C 传感器驱动

编写一个完整的 I2C 设备驱动,读写传感器。

一、目标

二、硬件:HTU21D

三、设备树

&i2c1 {
    clock-frequency = <400000>;
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";

    htu21d: htu21d@40 {
        compatible = "myvendor,htu21d";
        reg = <0x40>;
    };
};

四、驱动

// drivers/i2c/chips/htu21d.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/delay.h>

struct htu21d_data {
    struct i2c_client *client;
    struct mutex lock;
};

static int htu21d_read(struct i2c_client *client, u8 cmd, u16 *value)
{
    int ret;
    u8 buf[3];

    ret = i2c_smbus_write_byte(client, cmd);
    if (ret < 0) return ret;

    msleep(50);   // 等待转换

    ret = i2c_master_recv(client, buf, 3);
    if (ret < 0) return ret;

    if (buf[2] & 0x02) {
        // CRC 错误(简化:忽略)
    }

    *value = ((u16)buf[0] << 8) | buf[1];
    *value &= 0xFFFC;   // 状态位清零

    return 0;
}

static int htu21d_read_temperature(struct htu21d_data *data, int *temp)
{
    u16 raw;
    int ret = htu21d_read(data->client, 0xF3, &raw);
    if (ret) return ret;
    // T = -46.85 + 175.72 * (raw / 65536)
    *temp = (-46850 + 175720 * raw / 65536);   // 0.01°C
    return 0;
}

static int htu21d_read_humidity(struct htu21d_data *data, int *hum)
{
    u16 raw;
    int ret = htu21d_read(data->client, 0xF5, &raw);
    if (ret) return ret;
    // RH = -6 + 125 * (raw / 65536)
    *hum = (-600 + 125000 * raw / 65536);     // 0.01%
    return 0;
}

// sysfs 接口
static ssize_t temp_show(struct device *dev,
                         struct device_attribute *attr, char *buf)
{
    struct i2c_client *client = to_i2c_client(dev);
    struct htu21d_data *data = i2c_get_clientdata(client);
    int t;

    mutex_lock(&data->lock);
    htu21d_read_temperature(data, &t);
    mutex_unlock(&data->lock);

    return sprintf(buf, "%d.%02d\n", t / 100, abs(t % 100));
}

static ssize_t hum_show(struct device *dev,
                        struct device_attribute *attr, char *buf)
{
    struct i2c_client *client = to_i2c_client(dev);
    struct htu21d_data *data = i2c_get_clientdata(client);
    int h;

    mutex_lock(&data->lock);
    htu21d_read_humidity(data, &h);
    mutex_unlock(&data->lock);

    return sprintf(buf, "%d.%02d\n", h / 100, abs(h % 100));
}

static DEVICE_ATTR_RO(temp);
static DEVICE_ATTR_RO(hum);

static int htu21d_probe(struct i2c_client *client,
                       const struct i2c_device_id *id)
{
    struct htu21d_data *data;
    int ret;

    if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
        return -ENODEV;

    data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
    if (!data) return -ENOMEM;
    data->client = client;
    mutex_init(&data->lock);
    i2c_set_clientdata(client, data);

    // 软复位
    i2c_smbus_write_byte(client, 0xFE);
    msleep(20);

    // 创建设备属性
    ret = device_create_file(&client->dev, &dev_attr_temp);
    if (ret) return ret;
    ret = device_create_file(&client->dev, &dev_attr_hum);
    if (ret) { device_remove_file(&client->dev, &dev_attr_temp); return ret; }

    dev_info(&client->dev, "HTU21D probed\n");
    return 0;
}

static int htu21d_remove(struct i2c_client *client)
{
    device_remove_file(&client->dev, &dev_attr_temp);
    device_remove_file(&client->dev, &dev_attr_hum);
    return 0;
}

static const struct i2c_device_id htu21d_id[] = {
    { "htu21d", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, htu21d_id);

static const struct of_device_id htu21d_of[] = {
    { .compatible = "myvendor,htu21d" },
    { }
};
MODULE_DEVICE_TABLE(of, htu21d_of);

static struct i2c_driver htu21d_driver = {
    .driver = {
        .name = "htu21d",
        .of_match_table = htu21d_of,
    },
    .probe    = htu21d_probe,
    .remove   = htu21d_remove,
    .id_table = htu21d_id,
};
module_i2c_driver(htu21d_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("HTU21D temperature/humidity sensor driver");

五、Makefile

obj-m += htu21d.o

六、用户态测试

# 加载驱动
insmod htu21d.ko

# 看 sysfs
ls /sys/bus/i2c/devices/1-0040/

# 读温度
cat /sys/bus/i2c/devices/1-0040/temp
# 输出:23.45

# 读湿度
cat /sys/bus/i2c/devices/1-0040/hum
# 输出:56.78

七、IIO 子系统(更专业)

实际产品中建议用 IIO(Industrial I/O)框架:

#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>

static const struct iio_chan_spec htu21d_channels[] = {
    {
        .type = IIO_TEMP,
        .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
        .output = 0,
        .indexed = 1,
        .channel = 0,
    },
    {
        .type = IIO_HUMIDITYRELATIVE,
        .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
        .indexed = 1,
        .channel = 0,
    },
};

static int htu21d_read_raw(struct iio_dev *indio_dev,
                            struct iio_chan_spec const *chan,
                            int *val, int *val2, long mask)
{
    struct htu21d_data *data = iio_priv(indio_dev);
    int tmp;

    switch (mask) {
    case IIO_CHAN_INFO_PROCESSED:
        switch (chan->type) {
        case IIO_TEMP:
            htu21d_read_temperature(data, &tmp);
            *val = tmp;
            *val2 = 10000;
            return IIO_VAL_INT_PLUS_MICRO;
        case IIO_HUMIDITYRELATIVE:
            htu21d_read_humidity(data, &tmp);
            *val = tmp;
            *val2 = 10000;
            return IIO_VAL_INT_PLUS_MICRO;
        }
        break;
    }
    return -EINVAL;
}

static const struct iio_info htu21d_info = {
    .read_raw = htu21d_read_raw,
};

用户态:

# IIO 工具
iio_info
iio_readdev -u local:htu21d -s

# libiio / iio-tools