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 with devm_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 to spi_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 or CONFIG_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