IIO Driver

This chapter covers implementing IIO device drivers for sensors and data acquisition devices.

Basic IIO Driver Structure

#include <linux/module.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/i2c.h>
#include <linux/regmap.h>

struct my_sensor {
    struct regmap *regmap;
    struct mutex lock;
};

static int my_sensor_read_raw(struct iio_dev *indio_dev,
                              struct iio_chan_spec const *chan,
                              int *val, int *val2, long mask);

static int my_sensor_write_raw(struct iio_dev *indio_dev,
                               struct iio_chan_spec const *chan,
                               int val, int val2, long mask);

static const struct iio_info my_sensor_info = {
    .read_raw = my_sensor_read_raw,
    .write_raw = my_sensor_write_raw,
};

Channel Definition

Single-Ended ADC Channels

#define MY_ADC_CHANNEL(num) {                          \
    .type = IIO_VOLTAGE,                               \
    .indexed = 1,                                      \
    .channel = (num),                                  \
    .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),     \
    .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
    .scan_index = (num),                              \
    .scan_type = {                                    \
        .sign = 'u',                                  \
        .realbits = 12,                               \
        .storagebits = 16,                            \
        .shift = 0,                                   \
        .endianness = IIO_BE,                         \
    },                                                \
}

static const struct iio_chan_spec my_adc_channels[] = {
    MY_ADC_CHANNEL(0),
    MY_ADC_CHANNEL(1),
    MY_ADC_CHANNEL(2),
    MY_ADC_CHANNEL(3),
    IIO_CHAN_SOFT_TIMESTAMP(4),  /* Timestamp channel */
};

Accelerometer Channels

#define MY_ACCEL_CHANNEL(axis, idx) {                  \
    .type = IIO_ACCEL,                                 \
    .modified = 1,                                     \
    .channel2 = IIO_MOD_##axis,                        \
    .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |    \
                          BIT(IIO_CHAN_INFO_CALIBBIAS),\
    .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
                                BIT(IIO_CHAN_INFO_SAMP_FREQ), \
    .scan_index = (idx),                               \
    .scan_type = {                                     \
        .sign = 's',                                   \
        .realbits = 16,                                \
        .storagebits = 16,                             \
        .endianness = IIO_LE,                          \
    },                                                 \
}

static const struct iio_chan_spec my_accel_channels[] = {
    MY_ACCEL_CHANNEL(X, 0),
    MY_ACCEL_CHANNEL(Y, 1),
    MY_ACCEL_CHANNEL(Z, 2),
    IIO_CHAN_SOFT_TIMESTAMP(3),
};

Complete ADC Driver Example

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>

#define REG_CONVERSION  0x00
#define REG_CONFIG      0x01
#define REG_LO_THRESH   0x02
#define REG_HI_THRESH   0x03

#define NUM_CHANNELS    4

struct my_adc {
    struct regmap *regmap;
    struct regulator *vref;
    struct mutex lock;
    int vref_mv;
};

static const struct regmap_config my_adc_regmap_config = {
    .reg_bits = 8,
    .val_bits = 16,
    .max_register = REG_HI_THRESH,
};

static int my_adc_read_channel(struct my_adc *adc, int channel, int *val)
{
    unsigned int config, data;
    int ret;

    mutex_lock(&adc->lock);

    /* Select channel and start conversion */
    ret = regmap_read(adc->regmap, REG_CONFIG, &config);
    if (ret)
        goto out;

    config = (config & 0x0FFF) | (channel << 12) | BIT(15);
    ret = regmap_write(adc->regmap, REG_CONFIG, config);
    if (ret)
        goto out;

    /* Wait for conversion (simplified - should poll or use IRQ) */
    usleep_range(1000, 2000);

    /* Read result */
    ret = regmap_read(adc->regmap, REG_CONVERSION, &data);
    if (ret)
        goto out;

    *val = data >> 4;  /* 12-bit result */

out:
    mutex_unlock(&adc->lock);
    return ret;
}

static int my_adc_read_raw(struct iio_dev *indio_dev,
                           struct iio_chan_spec const *chan,
                           int *val, int *val2, long mask)
{
    struct my_adc *adc = iio_priv(indio_dev);
    int ret;

    switch (mask) {
    case IIO_CHAN_INFO_RAW:
        ret = my_adc_read_channel(adc, chan->channel, val);
        if (ret)
            return ret;
        return IIO_VAL_INT;

    case IIO_CHAN_INFO_SCALE:
        /* Scale = Vref / 2^12 */
        *val = adc->vref_mv;
        *val2 = 12;
        return IIO_VAL_FRACTIONAL_LOG2;

    default:
        return -EINVAL;
    }
}

static const struct iio_chan_spec my_adc_channels[] = {
    {
        .type = IIO_VOLTAGE,
        .indexed = 1,
        .channel = 0,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
    },
    {
        .type = IIO_VOLTAGE,
        .indexed = 1,
        .channel = 1,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
    },
    {
        .type = IIO_VOLTAGE,
        .indexed = 1,
        .channel = 2,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
    },
    {
        .type = IIO_VOLTAGE,
        .indexed = 1,
        .channel = 3,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
    },
};

static const struct iio_info my_adc_info = {
    .read_raw = my_adc_read_raw,
};

static int my_adc_probe(struct i2c_client *client)
{
    struct my_adc *adc;
    struct iio_dev *indio_dev;
    int ret;

    indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*adc));
    if (!indio_dev)
        return -ENOMEM;

    adc = iio_priv(indio_dev);
    mutex_init(&adc->lock);

    adc->regmap = devm_regmap_init_i2c(client, &my_adc_regmap_config);
    if (IS_ERR(adc->regmap))
        return PTR_ERR(adc->regmap);

    /* Get reference voltage */
    adc->vref = devm_regulator_get(&client->dev, "vref");
    if (IS_ERR(adc->vref))
        return PTR_ERR(adc->vref);

    ret = regulator_enable(adc->vref);
    if (ret)
        return ret;

    adc->vref_mv = regulator_get_voltage(adc->vref) / 1000;

    /* Setup IIO device */
    indio_dev->name = "my-adc";
    indio_dev->info = &my_adc_info;
    indio_dev->modes = INDIO_DIRECT_MODE;
    indio_dev->channels = my_adc_channels;
    indio_dev->num_channels = ARRAY_SIZE(my_adc_channels);

    ret = devm_iio_device_register(&client->dev, indio_dev);
    if (ret) {
        regulator_disable(adc->vref);
        return ret;
    }

    i2c_set_clientdata(client, indio_dev);

    dev_info(&client->dev, "ADC registered: %d channels, Vref=%d mV\n",
             NUM_CHANNELS, adc->vref_mv);

    return 0;
}

static void my_adc_remove(struct i2c_client *client)
{
    struct iio_dev *indio_dev = i2c_get_clientdata(client);
    struct my_adc *adc = iio_priv(indio_dev);

    regulator_disable(adc->vref);
}

static const struct of_device_id my_adc_of_match[] = {
    { .compatible = "vendor,my-adc" },
    { }
};
MODULE_DEVICE_TABLE(of, my_adc_of_match);

static struct i2c_driver my_adc_driver = {
    .driver = {
        .name = "my-adc",
        .of_match_table = my_adc_of_match,
    },
    .probe = my_adc_probe,
    .remove = my_adc_remove,
};
module_i2c_driver(my_adc_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Example IIO ADC Driver");

Return Value Types

switch (mask) {
case IIO_CHAN_INFO_RAW:
    *val = raw_value;
    return IIO_VAL_INT;              /* Integer value in val */

case IIO_CHAN_INFO_SCALE:
    /* val.val2 = val / 2^val2 */
    *val = 3300;                      /* 3.3V in mV */
    *val2 = 12;                       /* 12-bit ADC */
    return IIO_VAL_FRACTIONAL_LOG2;   /* val / 2^val2 */

case IIO_CHAN_INFO_SAMP_FREQ:
    *val = 1000;
    return IIO_VAL_INT;              /* 1000 Hz */

case IIO_CHAN_INFO_OFFSET:
    *val = 0;
    *val2 = 500000;                   /* 0.5 */
    return IIO_VAL_INT_PLUS_MICRO;    /* val + val2/1000000 */

case IIO_CHAN_INFO_CALIBSCALE:
    *val = 1;
    *val2 = 234;                      /* 1.000234 */
    return IIO_VAL_INT_PLUS_NANO;     /* val + val2/1000000000 */
}

Available Attributes

static IIO_CONST_ATTR(sampling_frequency_available, "100 200 400 800");

static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("100 200 400 800");

static struct attribute *my_sensor_attrs[] = {
    &iio_const_attr_sampling_frequency_available.dev_attr.attr,
    NULL
};

static const struct attribute_group my_sensor_attrs_group = {
    .attrs = my_sensor_attrs,
};

static const struct iio_info my_sensor_info = {
    .read_raw = my_sensor_read_raw,
    .write_raw = my_sensor_write_raw,
    .attrs = &my_sensor_attrs_group,
};

Write Raw Implementation

static int my_sensor_write_raw(struct iio_dev *indio_dev,
                               struct iio_chan_spec const *chan,
                               int val, int val2, long mask)
{
    struct my_sensor *sensor = iio_priv(indio_dev);
    int ret;

    switch (mask) {
    case IIO_CHAN_INFO_SAMP_FREQ:
        if (val < 100 || val > 800)
            return -EINVAL;
        mutex_lock(&sensor->lock);
        ret = set_sampling_freq(sensor, val);
        mutex_unlock(&sensor->lock);
        return ret;

    case IIO_CHAN_INFO_CALIBBIAS:
        if (val < -32768 || val > 32767)
            return -EINVAL;
        return regmap_write(sensor->regmap,
                           REG_CALIBBIAS + chan->scan_index * 2,
                           val);

    default:
        return -EINVAL;
    }
}

Device Tree Binding

&i2c1 {
    adc@48 {
        compatible = "vendor,my-adc";
        reg = <0x48>;
        #io-channel-cells = <1>;
        vref-supply = <&vdd_3v3>;
    };
};

/* Consumer */
battery_monitor {
    compatible = "simple-battery-monitor";
    io-channels = <&adc 0>, <&adc 1>;
    io-channel-names = "voltage", "current";
};

Summary

  • Use devm_iio_device_alloc() with private data size
  • Define channels with iio_chan_spec array
  • Implement read_raw() and optionally write_raw()
  • Return appropriate IIO_VAL_* type
  • Use devm_iio_device_register() for registration
  • Handle reference voltage with regulator framework

Further Reading

Next

Learn about IIO Buffers for continuous data acquisition.


Back to top

Linux Driver Development Guide is a community resource for learning kernel driver development. Not affiliated with the Linux Foundation. Content provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.