Linux IIO Driver Complete Example
Free Linux Kernel Development Course — IIO Framework, Lecture 5
free linux device drivers course
free embedded systems course
free linux development course
linux iio driver
This lecture is part of our free linux kernel development course and brings together every IIO
building block we studied earlier into one working linux iio driver. If you have followed this
free linux device drivers course from the beginning, you already know about channels, the
iio_chan_spec array, indexed channels, and modified channels. Today we combine all of that into a
single, original, modern linux iio driver you can build and test on a real kernel.
What You Will Learn
Writing read_raw() and write_raw()
Modern devm_iio_device_register()
Reading sysfs output after loading a linux iio driver
Common mistakes and fixes
Prerequisites
Linux kernel module basics (insmod/rmmod)
Platform driver concept
Earlier lectures in this IIO framework series
Why Build a Complete IIO Driver Example?
In the previous lectures we looked at the channel model, the iio_dev structure, and how indexed and
modified channels change the sysfs attribute names. Reading about each piece separately is useful, but a
linux iio driver only makes sense once you see all the pieces working together in one file. That
is exactly what this lecture does: one small, original driver that a beginner can read top to bottom in a few
minutes.
Designing the Channel Table
Our example driver, ep_iio_sensor_demo, exposes four sensor readings using a mix of indexed and
modified channels, which is the most common pattern you will see in a real linux iio driver:
Here is the channel array itself. Notice the EP_VOLTAGE_CHANNEL() macro, which avoids repeating the
same fields three times — a common technique in a real linux iio driver:
#define EP_VOLTAGE_CHANNEL(idx) \
{ \
.type = IIO_VOLTAGE, \
.indexed = 1, \
.channel = (idx), \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\
}
static const struct iio_chan_spec ep_sensor_channels[] = {
EP_VOLTAGE_CHANNEL(0),
EP_VOLTAGE_CHANNEL(1),
EP_VOLTAGE_CHANNEL(2),
{
.type = IIO_INTENSITY,
.modified = 1,
.channel2 = IIO_MOD_LIGHT_IR,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
},
{
.type = IIO_LIGHT,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
},
};
Private Data for the linux iio driver
A real linux iio driver needs somewhere to keep its state, protected by a lock. We keep this
simple with a mutex and a few integers:
struct ep_sensor_data {
struct mutex lock;
int voltage[3];
int light_ir;
int light_both;
int scale;
};
Implementing read_raw() and write_raw()
These two callbacks are the heart of any linux iio driver. read_raw() answers
every sysfs read, and write_raw() answers every sysfs write. We use iio_priv() to get
back our private structure and a mutex to keep access safe:
static int ep_sensor_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct ep_sensor_data *data = iio_priv(indio_dev);
int ret = 0;
mutex_lock(&data->lock);
switch (mask) {
case IIO_CHAN_INFO_RAW:
switch (chan->type) {
case IIO_VOLTAGE:
*val = data->voltage[chan->channel];
ret = IIO_VAL_INT;
break;
case IIO_INTENSITY:
*val = data->light_ir;
ret = IIO_VAL_INT;
break;
case IIO_LIGHT:
*val = data->light_both;
ret = IIO_VAL_INT;
break;
default:
ret = -EINVAL;
}
break;
case IIO_CHAN_INFO_SCALE:
*val = data->scale;
*val2 = 0;
ret = IIO_VAL_INT;
break;
default:
ret = -EINVAL;
}
mutex_unlock(&data->lock);
return ret;
}
static int ep_sensor_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct ep_sensor_data *data = iio_priv(indio_dev);
if (mask != IIO_CHAN_INFO_SCALE)
return -EINVAL;
mutex_lock(&data->lock);
data->scale = val;
mutex_unlock(&data->lock);
return 0;
}
static const struct iio_info ep_sensor_info = {
.read_raw = ep_sensor_read_raw,
.write_raw = ep_sensor_write_raw,
};
Modernization note: older books also set a .driver_module = THIS_MODULE field
inside struct iio_info. That field was removed from the kernel years ago, so a correct
linux iio driver written for a current kernel must leave it out completely, as shown above.
Probe: Allocating and Registering the linux iio driver
The probe function ties the channel table, the callbacks, and the private data together. We use the
devm_ managed allocation and registration calls, which is the recommended modern style because the
kernel automatically cleans everything up when the device is removed — no custom remove()
function is even required:
static int ep_sensor_probe(struct platform_device *pdev)
{
struct iio_dev *indio_dev;
struct ep_sensor_data *data;
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
mutex_init(&data->lock);
data->voltage[0] = 1200;
data->voltage[1] = 2400;
data->voltage[2] = 3300;
data->light_ir = 45;
data->light_both = 120;
data->scale = 1;
indio_dev->name = "ep_iio_sensor";
indio_dev->info = &ep_sensor_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = ep_sensor_channels;
indio_dev->num_channels = ARRAY_SIZE(ep_sensor_channels);
platform_set_drvdata(pdev, indio_dev);
return devm_iio_device_register(&pdev->dev, indio_dev);
}
static const struct of_device_id ep_sensor_of_match[] = {
{ .compatible = "ep,iio-sensor-demo" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_sensor_of_match);
static struct platform_driver ep_sensor_driver = {
.probe = ep_sensor_probe,
.driver = {
.name = "ep_iio_sensor",
.of_match_table = ep_sensor_of_match,
},
};
module_platform_driver(ep_sensor_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala complete linux iio driver example");
Building and Loading the linux iio driver
Build the module against your running kernel headers, then load it like any other kernel module:
$ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
$ sudo insmod ep_iio_sensor_demo.ko
Because this demo driver is a platform driver, in a real board it would be instantiated by the device tree.
For quick testing on a development machine you can bind it manually:
$ echo ep_iio_sensor > /sys/bus/platform/devices/ep_iio_sensor.0/driver_override
$ sudo modprobe platform_device_register 2>/dev/null; true
Expected sysfs Output
Once the linux iio driver is bound successfully, list the new IIO device:
$ ls /sys/bus/iio/devices/
iio:device0
$ ls /sys/bus/iio/devices/iio:device0/
in_voltage0_raw in_voltage1_raw in_voltage2_raw in_voltage_scale
in_intensity_ir_raw in_illuminance_input name uevent power
$ cat /sys/bus/iio/devices/iio:device0/name
ep_iio_sensor
$ cat /sys/bus/iio/devices/iio:device0/in_voltage1_raw
2400
$ echo 2 > /sys/bus/iio/devices/iio:device0/in_voltage_scale
$ cat /sys/bus/iio/devices/iio:device0/in_voltage_scale
2
Every attribute name above comes directly from the channel table: indexed channels produce a number in the
name, modified channels produce a word, and the processed light channel produces _input instead of
_raw. This is the whole point of studying the channel model before writing a linux iio
driver.
Indexed vs Modified: Quick Recap Table
| Channel Style | Struct Fields Used | Example sysfs Name |
|---|---|---|
| Indexed | .indexed = 1, .channel = N | in_voltage1_raw |
| Modified | .modified = 1, .channel2 = IIO_MOD_* | in_intensity_ir_raw |
| Plain (shared) | none of the above | in_voltage_scale |
| Processed | info_mask_separate = IIO_CHAN_INFO_PROCESSED | in_illuminance_input |
Real-World Use Case
A battery-monitoring board is a good real example. It might expose a raw battery voltage as an indexed
IIO_VOLTAGE channel, a modified IIO_TEMP channel for the battery pack temperature, and a
processed IIO_CURRENT channel showing calibrated charge current. Any application that already reads
standard IIO sysfs files — including iio_info and libiio based tools — will
work with this linux iio driver without any custom parsing code.
Common Mistakes and Troubleshooting
| Mistake | Symptom | Fix |
|---|---|---|
| Forgetting BIT() around info mask | Build error or wrong attribute missing | Always wrap with BIT(IIO_CHAN_INFO_*) |
| Using both .channel and .channel2 without .modified | Wrong or duplicate sysfs name | Set .modified = 1 whenever .channel2 is used |
| Missing mutex around private data | Race condition under concurrent reads | Lock in read_raw()/write_raw() |
| Setting driver_module in iio_info | Build failure on modern kernels | Remove the field — it no longer exists |
Best Practices
- Always use
devm_allocation and registration in a new linux iio driver. - Group repeated channel definitions behind a macro, as shown with
EP_VOLTAGE_CHANNEL(). - Return
IIO_VAL_INT(or the correctIIO_VAL_*constant) fromread_raw(). - Keep the private data struct small and protect it with a single mutex unless you have a measured reason not to.
Performance Considerations
For a sysfs-only linux iio driver like this one, each read is a single function call, so
overhead is minimal. If you later need high-rate sampling, move to the buffer and trigger APIs covered in the
earlier lecture on IIO buffer setup instead of polling sysfs in a tight loop.
Security Considerations
Any writable attribute, such as in_voltage_scale here, is reachable by any user with sysfs write
permission. Validate every value inside write_raw() and never trust it blindly, especially if the
value is later used for a hardware register write.
Summary and Key Takeaways
- A complete linux iio driver combines a channel table, read_raw()/write_raw(), and a simple probe().
- Indexed and modified channels can be mixed freely in the same channel array.
- devm_iio_device_register() removes the need to write a manual remove() function.
- The driver_module field in struct iio_info no longer exists on current kernels.
Conclusion
You have now seen a full, working, modern linux iio driver from channel table to sysfs output,
built entirely from the concepts covered earlier in this free linux kernel development course.
This same pattern — channel array, read_raw/write_raw, devm_ registration — is exactly what you will
find inside real IIO drivers in the upstream kernel tree, which makes it a solid foundation before moving on to
IIO events in the next lecture of this free linux device drivers course.
FAQ
What does a linux iio driver actually register with the kernel?
It registers an iio_dev structure describing its channels, callbacks, and name, which the IIO core
then exposes under /sys/bus/iio/devices/.
Do I need a custom remove() function for a linux iio driver?
Not when you use devm_iio_device_alloc() and devm_iio_device_register(); the kernel
automatically unregisters the device when the underlying device is removed.
Why does one channel produce in_voltage1_raw and another in_intensity_ir_raw?
The first uses an indexed channel (.indexed with a number), and the second uses a modified channel (.channel2
with an IIO_MOD_* constant), which the IIO core turns into a word instead of a number.
Is struct iio_info.driver_module still needed?
No. That field was removed from the kernel, so a modern linux iio driver must not set it.
Can I test this driver without real hardware?
Yes. Since read_raw()/write_raw() only touch simple in-memory variables here, you can build and load the
module on any development machine or virtual machine.
What tool can I use to read this linux iio driver’s channels easily?
Standard sysfs commands like cat and echo work directly, and libiio-based tools such as iio_info can also read
and list the channels automatically.
What should I learn next after this lecture?
The next lecture in this free linux kernel development course covers IIO events, which let a linux iio driver
notify user space when a threshold is crossed instead of waiting to be polled.
Continue the Free Linux Kernel Development Course
Keep building real skills with our free linux device drivers course and free embedded systems course.
