In this tutorial I intend to give a brief introduction to the Industrial I/O (IIO) subsystem and show how to create a simple IIO driver from scratch. My aproach will be preaty much practical. I’ll use AD7292 as study case to show how to create a simple IIO device driver from scratch. This tutorial makes part of the Introduction to IIO driver development series of tutorials.
To make things easier to understand, I’ll incrementally add code as if sending patches to the IIO susbsystem. A patch is nothing more than a set of changes that is intended to add a new functionality, solve a specific issue or bug. Good practices say that a patch should be small and self contained such that it does not depends on additional changes nor breacks the project if applied alone. The way contributions are made to the Linux kernel is through patches so, it is good to get used with this way of organizing changes if you intend to contribute in the future.
A nice post talking about the patch philosophy may be seen at kernel newbies site.
So, let’s get started. =p
Industrial Input Output (IIO) (coming soon)
PATCH 1 - Basic Driver for AD7292
The first patch just adds a base driver structure. However, since it is desired that our code can be at least compiled and used (otherwise we would not be adding it), this patch also adds some configuration symbols and rules to allow the Kernel Build System (kbuild) to compile our driver (details were explained at the Raspberry Pi kernel compilation) tutorial)
The complete patch can be downloaded as a patch file or seen at github on this pull request to Analog Devices Linux kernel.
The patch / commit message and description are as follows:
iio: adc: ad7292: add driver support for AD7292
The AD7292 contains all the functionality required for general-purpose
monitoring of analog signals and control of external devices, integrated
into a single-chip solution.
Datasheet:
Link: https://www.analog.com/media/en/technical-documentation/data-sheets/AD7292.PDF
This commit adds a skeleton driver for the AD7292.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt1@gmail.com>
The title (first line) follows a pattern to indicate where the changes are
being made. In this particular case, they are made inside the
drivers/iio/adc/
directory, in a file named ad7292
(that is being
created). The paragraphs below it describe the changes in a concise, yet
informative, way. So to speak, this patch adds a basic driver for the AD7292
part which is a general-purpose Integrated Circuit (IC) designed to monitor
analog signals and control external devices. The tecnical specification of the
hardware (datasheet) is also linked for reference. Finally, a signed-off-by tag
is added with the commiter’s full name and e-mail to provide autorship for the
patch.
More about commit messages and how to write them may be seen this How to Write a Git Commit Message
Basic Driver Source Code
In this first driver version, we have an state
struct which holds instance
specific data for AD7292 devices. For now, this struct only has a spi
attribute that will store the information related to the SPI slave device.
struct ad7292_state {
struct spi_device *spi;
};
See the Linux kernel SPI documentation for details.
Next, I added the skeleton for some common functions in IIO device drivers.
They are: setup
, read_raw
, and write_raw
. The setup
function is
commonly used to initialize hardware registers during device probe. By the
moment I was finishing this tutorial I had not implemented a truly setup
function, so I won’t talk about it anymore xD. The read_raw
is responsible
for requesting values from the device. The write_raw
is alanogous for pushing
data to the device. Both will be commented out in more details in latter parts
of this set of tutorials.
static int ad7292_setup(struct ad7292_state *st)
{
return 0;
}
static int ad7292_read_raw(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
int *val, int *val2, long info)
{
return 0;
}
static int ad7292_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long info)
{
return 0;
}
Note: Inside the Linux kernel people usualy try to avoid adding code before it
is really needed. Hence, the best aproach would probably be to not add the above
functions (iio_info
and iio_chan_spec
structs as well) in this first patch.
For more info on read_raw
and write_raw
, see: include/linux/iio/iio.h
Then we have the setup of the iio_info
struct for our driver. This struct
holds constant information about the device such as the device’s module
structure, read/write function pointers, list of events and triggers, list of
attributes exposed to user space, etc.. For now, the ad7292_info
only
contains pointers to the read and write functions.
static const struct iio_info ad7292_info = {
.read_raw = ad7292_read_raw,
.write_raw = &ad7292_write_raw,
};
For more information about the iio_info
struct, see: include/linux/iio/iio.h
An empty array for the data channels. IIO channels will also be tackled later.
static const struct iio_chan_spec ad7292_channels[] = {
};
The most significant function for now is the probe
function. This function is
responsible for initializing a new device instance. Among the things it usualy
does are:
- Memory alocation for the device
state
data withdevm_iio_device_alloc
- Set
state
as private for the device instance (iio_priv
). Learn more about this with Matheus’ & Renato’s presentation about OO programing in C - Store the spi device pointer for later reference (
st->spi = spi
) - Bind
iio_device
tospi_device
(spi_set_drvdata
) - Initialize IIO device data (
indio_dev->...
) - Setup
- Register device whithin the IIO API
static int ad7292_probe(struct spi_device *spi)
{
struct ad7292_state *st;
struct iio_dev *indio_dev;
int ret;
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
if (!indio_dev)
return -ENOMEM;
st = iio_priv(indio_dev);
st->spi = spi;
spi_set_drvdata(spi, indio_dev);
indio_dev->dev.parent = &spi->dev;
indio_dev->name = spi_get_device_id(spi)->name;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = ad7292_channels;
indio_dev->num_channels = ARRAY_SIZE(ad7292_channels);
indio_dev->info = &ad7292_info;
ret = ad7292_setup(st);
if (ret)
return ret;
return devm_iio_device_register(&spi->dev, indio_dev);
}
For more comments on device probing, take a look at IIO dummy driver.
See drivers/iio/dummy/iio_simple_dummy.c
inside the kernel source.
Finally, for SPI devices, SPI device id table and Open Firmware (of) entries may be set. The snippet bellow does that for the AD7292 driver. The of table entry allows the driver to match device tree node entries. The device tree is a data structure that describes the hardware available to the system. It is based on device tree entries that the kernel loads the drivers needed to handle device operation. In this particular case, AD7292 driver will be loaded if the system device tree indicates the existance of a device compatible with “adi,ad7292”.
static const struct spi_device_id ad7292_id_table[] = {
{ "ad7292", 0 },
{}
};
MODULE_DEVICE_TABLE(spi, ad7292_id_table);
static const struct of_device_id ad7292_of_match[] = {
{ .compatible = "adi,ad7292" },
{ },
};
MODULE_DEVICE_TABLE(of, ad7292_of_match);
static struct spi_driver ad7292_driver = {
.driver = {
.name = "ad7292",
.of_match_table = ad7292_of_match,
},
.probe = ad7292_probe,
.id_table = ad7292_id_table,
};
module_spi_driver(ad7292_driver);
There is plenty of information about device tree on the Internet. But let’s just let it for the next parts of this tutorial set and concentrate on other changes that need to be made to get our driver compiled.
Adding Compilation Symbols and Makefile Rules
To get a driver compiled along with the rest of the Linux kernel, we need to set up a compilation symbol for it as well as a Makefile rule. This rule will take care of compiling the driver during kernel compilation process when our symbol has a ‘y’ or ‘m’ value assigned to it.
To define a compilation symbol for a driver, one may add a
config <driver_name>
entry to the Kconfig file present in the same directory
of the driver. For instance, to add a compilation symbol for the AD7292 driver,
the following lines were added to the Kconfig file at
drivers/iio/adc/Kconfig
:
config AD7292
tristate "Analog Devices AD7292 ADC driver"
depends on SPI
help
Say yes here to build support for Analog Devices AD7292
8 Channel ADC with temperature sensor.
To compile this driver as a module, choose M here: the
module will be called ad7292.
The Makefile rule is added to the Makefile in the driver’s directory too. For
our AD7292 example, the rule was added in the Makefile at drivers/iio/adc
as
follows:
obj-$(CONFIG_AD7292) += ad7292.o
To better understand the details of why this is needed, see the Raspberry Pi kernel compilation tutorial.
Updating the MAINTEINERS File
The MAINTAINERS file at the kernel root contains the list of developers accounted for developing and/or giving support for some artifact within the Linux kernel. To include information about the AD7292 driver, the following entry was added:
ANALOG DEVICES INC AD7292 DRIVER
M: Marcelo Schmitt <marcelo.schmitt1@gmail.com>
L: linux-iio@vger.kernel.org
W: http://ez.analog.com/community/linux-device-drivers
S: Supported
F: drivers/iio/adc/ad7292.c
The meaning of each tag can be found at the beginning of the MAINTAINERS file.
- M: Mail patches to: FullName <address@domain>
- L: Mailing list that is relevant to this area
- W: Web-page with status/info
- S: Status supported means that someone is actually paid to look after this.
- F: Files and directories with wildcard patterns.
Make Kbuild Compile the AD7292 Driver
To get kbuild to compile a device driver, we need to assign a ‘y’ or ‘m’ value
to the associated configuration symbol (usually CONFIG_<driver_name>
. For the
AD7292 driver this can be done by some ways:
- Manually setting
CONFIG_AD7292=y
orCONFIG_AD7292=m
whitin the .config file - With the aid of programs like xconfig, menuconfig, or nconfig that also have embedded help text
- Loading configuration values from a defconfig file that contains the above assignment
Once this have been set, the next kbuild run will use this value within the local Makefile at to form a rule that will take care of compiling the driver C file into a .o object file containg the symbols needed for further compilation steps to generate machine code. These .o files persist after kernel compilation, allowing next compilations to be performed only on modified objects. Thus, subsequent build processes can perform faster.
make zImage -j4
Note: If you’re cross compiling to other architecture (like ARM), don’t forget to set the relevant environment variabels (usually ARM and CROSS_COMPILE).
If everything goes well, the last output lines should look like this:
LD arch/arm/boot/compressed/vmlinux
OBJCOPY arch/arm/boot/zImage
Kernel: arch/arm/boot/zImage is ready
And, somewhere in the midle of the compilation log, it should appear a line indicating that the driver was compiled. In the AD7292 example, it will show something like:
CHK include/generated/compile.h
CC drivers/iio/adc/ad7292.o
AR drivers/iio/adc/built-in.o
After that, you will be able to compile just the files inside the driver
directory. This is useful when willing to just test if the driver compiles. Use
make
plus the directory path to do this.
make <dir path>
For instance:
make M=drivers/iio/adc
Note: This last way of compiling is just for compilation testing. To produce
new kernel images you will need to run an image rule (like bzImage
, zImage
,
uImage
, etc.) anyway.
If you can see an output line indicating that the compiler (CC
) produced the
.o file for the driver (and there’s no error messages following it), than you
may feel happy and know that your code does not break the kernel (at least at
compilation time).
For further references on kernel compilation, see the Raspberry Pi kernel compilation and the Kernel Compilation and Installation tutorials.
—————————————————— —————————————————— ——————————————————
Revision History:
- Rev1 (2019-09-04): Release
- Rev2 (2019-09-06):
- Added
Make Kbuild Compile the AD7292 Driver
section; - Rewritten TODO
- Added