Linux Driver Development With Raspberry Pi: Practical Labs
Linux Driver Development With Raspberry Pi: Practical Labs
Development with
Raspberry Pi®
Practical Labs
Copyright © 2021, Alberto Liberal. All rights reserved.
Linux is a registered trademark of Linus Torvalds. Other trademarks within this book are the
property of their respective owners.
The kernel modules examples provided with this book are released under the GPL license. This
license gives you the right to use, study, modify and share the software freely. However, when
the software is redistributed, modified or unmodified, the GPL requires that you redistribute the
software under the same license, with the source code.
Published by:
Alberto Liberal
[email protected]
[1]
Table of Contents
Preface..................................................................................................................13
Chapter 1: Building the System..........................................................................19
Bootloader..............................................................................................................20
Linux kernel............................................................................................................22
System call interface and C runtime library............................................................25
System shared libraries..........................................................................................26
Root filesystem.......................................................................................................27
Linux boot process..................................................................................................28
Building an embedded Linux system for the Raspberry Pi.....................................29
Raspberry Pi OS.....................................................................................................30
Connect and set up hardware................................................................................32
Setting up an ethernet communication...................................................................36
Copying files to your Raspberry Pi.........................................................................36
Building the Linux kernel........................................................................................37
Chapter 2: The Linux Device and Driver Model.................................................41
Bus core drivers......................................................................................................42
Bus controller drivers..............................................................................................44
Device drivers.........................................................................................................44
Devices...................................................................................................................45
The sysfs filesystem...............................................................................................47
The kobject infrastructure.......................................................................................49
Introduction to the Device Tree...............................................................................53
Chapter 3: The Simplest Drivers.........................................................................61
Licensing.................................................................................................................62
LAB 3.1: "helloworld" module.................................................................................62
Listing 3-1: helloworld_rpi3.c......................................................................63
Listing 3-2: Makefile....................................................................................63
helloworld_rpi3.ko demonstration...............................................................64
LAB 3.2: "helloworld with parameters" module.......................................................65
Listing 3-3: helloworld_rpi3_with_parameters.c..........................................66
Listing 3-4: Makefile....................................................................................66
[3]
Table of Contents
helloworld_rpi3_with_parameters.ko demonstration...................................67
Chapter 4: Character Drivers..............................................................................69
LAB 4.1: "helloworld character" module.................................................................71
Registration and unregistration of character devices.............................................72
Listing 4-1: helloworld_rpi3_char_driver.c...................................................76
Listing 4-2: Makefile....................................................................................78
Listing 4-3: ioctl_test.c................................................................................78
helloworld_rpi3_char_driver.ko demonstration...........................................79
Creating device files with devtmpfs........................................................................79
LAB 4.2: "class character" module.........................................................................80
Listing 4-4: helloworld_rpi3_class_driver.c.................................................82
helloworld_rpi3_class_driver.ko demonstration..........................................84
Miscellaneous character driver...............................................................................85
Registering a minor number...................................................................................85
LAB 4.3: "miscellaneous character" module...........................................................86
Listing 4-5: misc_rpi3_driver.c....................................................................87
misc_rpi3_driver.ko demonstration.............................................................88
Chapter 5: Platform Drivers.................................................................................89
LAB 5.1: "platform device" module.........................................................................91
Listing 5-1: hellokeys_rpi3.c........................................................................93
hellokeys_rpi3.ko demonstration................................................................95
Documentation to interact with the hardware.........................................................96
Hardware naming convention.................................................................................96
Pin control subsystem.............................................................................................97
GPIO controller driver...........................................................................................106
GPIO descriptor consumer interface....................................................................110
Obtaining and disposing GPIOs........................................................................... 110
Using GPIOs......................................................................................................... 110
GPIOs mapped to IRQs........................................................................................ 111
GPIOs in the Device Tree..................................................................................... 112
Exchanging data between kernel and user spaces..............................................112
MMIO (Memory-Mapped I/O) device access........................................................113
LAB 5.2: "RGB LED platform device" module......................................................115
[4]
Table of Contents
[5]
Table of Contents
[6]
Table of Contents
keyled_rpi3_class.ko demonstration.........................................................259
LAB 7.4: "GPIO expander device" module...........................................................262
LAB 7.4 hardware description..............................................................................263
LAB 7.4 Device Tree description..........................................................................264
LAB 7.4 GPIO controller driver description...........................................................265
Listing 7-4: CY8C9520A_rpi3.c ................................................................271
LAB 7.4 GPIO child driver description..................................................................283
Listing 7-5: int_rpi3_gpio.c .......................................................................285
LAB 7.4 GPIO based IRQ application..................................................................286
Listing 7-6: gpio_int.c ...............................................................................287
LAB 7.4 driver demonstration...................................................................289
LAB 7.5: "GPIO-PWM-PINCTRL expander device" module.................................293
LAB 7.5 GPIO irqchip description.........................................................................293
LAB 7.5 pin controller driver description...............................................................295
LAB 7.5 PWM controller driver description...........................................................299
Listing 7-7: CY8C9520A_pwm_pinctrl.c ..................................................302
LAB 7.5 driver demonstration...................................................................323
LAB 7.6: CY8C9520A Device Tree overlay..........................................................325
LAB 7.6 driver demonstration...................................................................332
Chapter 8: Allocating Kernel Memory..............................................................333
Linux address types..............................................................................................333
User process virtual to physical memory mapping...............................................335
Kernel virtual to physical memory mapping..........................................................336
Kernel memory allocators.....................................................................................337
Page allocator.......................................................................................................338
Page allocator API................................................................................................338
SLAB allocator......................................................................................................339
SLAB allocator API...............................................................................................342
Kmalloc allocator..................................................................................................343
LAB 8.1: "linked list memory allocation" module........................................................344
Listing 8-1: linkedlist_rpi3_platform.c..............................................................346
linkedlist_rpi3_platform.ko demonstration......................................................351
Chapter 9: DMA in Device Drivers....................................................................353
Cache coherency..................................................................................................353
Linux DMA Engine API..........................................................................................355
Types of DMA mappings.......................................................................................357
[7]
Table of Contents
[8]
Table of Contents
LAB 10.4 code description of the "SPI accel input device" module......................420
Listing 10-6: adxl345_rpi3.c......................................................................430
adxl345_rpi3.ko demonstration.................................................................442
Chapter 11: Industrial I/O Subsystem...............................................................445
IIO device sysfs interface......................................................................................447
IIO device channels..............................................................................................447
The iio_info structure............................................................................................449
Buffers..................................................................................................................450
IIO buffer sysfs interface.......................................................................................450
IIO buffer setup.....................................................................................................450
Triggers.................................................................................................................452
Triggered buffers..................................................................................................452
Industrial I/O events..............................................................................................454
Delivering IIO events to user space......................................................................456
IIO utils..................................................................................................................458
LAB 11.1: "IIO Mixed-Signal I/O Device" module.................................................458
LAB 11.1 hardware description.............................................................................461
LAB 11.1 Device Tree description........................................................................463
LAB 11.1 driver description...................................................................................468
Listing 11-1: max11300-base.h.................................................................483
Listing 11-2: maxim,max11300.h..............................................................485
Listing 11-3: max11300.c..........................................................................486
Listing 11-4: max11300-base.c.................................................................489
LAB 11.1 driver demonstration..................................................................507
GPIO control through a character device............................................................. 511
Listing 11-5: gpio_device_app.c................................................................512
GPIO control through the gpiolibd library.............................................................513
Listing 11-6: libgpiod_max11300_app.c....................................................514
LAB 11.2: “Nunchuk provider and consumer” modules........................................515
Nunchuck provider module...................................................................................515
Listing 11-7: nunchuk_accel.c...................................................................519
Nunchuck consumer module................................................................................522
Listing 11-8: nunchuk_consumer.c............................................................523
LAB 11.2 Device Tree description........................................................................527
LAB 11.2 driver demonstration..................................................................529
[9]
Table of Contents
[ 10 ]
Table of Contents
[ 11 ]
Preface
Embedded systems have become an integral part of our daily life. They are deployed in mobile
devices, networking infrastructure, home and consumer devices, digital signage, medical imaging,
automotive infotainment and many other industrial applications. The use of embedded systems
is growing exponentially. Many of these embedded systems are powered by an inexpensive yet
powerful system-on-chip (SoC) that is running a Linux operating system. The BCM2837 from
Broadcom is one of these SoCs, running quad ARM Cortex A53 cores at 1.2GHz. This is the SoC
used in the popular Raspberry Pi 3 boards.
This book follows the learning by doing approach, so you will be playing with your Raspberry
Pi since the first chapter. Besides the Raspberry Pi board, you will use several low-cost boards to
develop the hands-on examples. In the labs, it is described what each step means in detail so that
you can use your own hardware components adapting the content of the book to your needs.
You will learn how to develop Linux drivers for the Raspberry Pi boards. You will start with
the simplest ones that do not interact with any external hardware, then you will develop Linux
drivers that manage different kind of devices: Accelerometer, DAC, ADC, RGB LED, Buttons,
Joystick controller, Multi-Display LED controller and I/O expanders controlled via I2C and SPI
buses. You will also develop DMA drivers, USB device drivers, drivers that manage interrupts
and drivers that write and read on the internal registers of the SoC to control its GPIOs. To ease
the development of some of these drivers, you will use different types of Linux kernel subsystems:
Miscellaneous, LED, UIO, USB, Input and Industrial I/O. More than 30 kernel modules have been
written (besides several user applications), which can be downloaded from the book's GitHub
repository.
This book uses the Long Term Support (LTS) Linux kernel 5.4, which was released on November
2019 and will be maintained until December 2025.
This book is a learning tool to start developing drivers without any previous knowledge about
this field, so the intention during its writing has been to develop drivers without a high level of
complexity that both serve to reinforce the main driver development concepts and can be a starting
point to help you to develop your own drivers. And, remember that the best way to develop a
driver is not to write it from scratch. You can reuse free code from similar Linux kernel mainline
drivers. All the drivers written throughout this book are GPL licensed, so you can modify and
redistribute them under the same license.
[ 13 ]
Preface
[ 14 ]
Preface
several I2C client drivers throughout this chapter. You will also see how to add "sysfs" support to
control the hardware from user space via sysfs entries.
Chapter 7, Handling Interrupts in Device Drivers, introduces interrupt hardware and software
operation on embedded processors running Linux. You will learn about the different structures
used to create Linux drivers for interrupt controllers and the different types of GPIO irqchips. You
will develop several drivers that manage interrupts coming from the GPIOs of the Raspberry Pi
SoC. In one of these drivers, you will see how a user application is put to sleep by using a "wait
queue" and woken up later via an interrupt. You will also develop a GPIO controller driver with
interrupt capabilities for an off-chip GPIO expander and a user application that requests interrupts
from the GPIO expander by using GPIOlib APIs. In this chapter, you will see how to introduce
new hardware support by using the Raspberry Pi Device Tree overlay mechanism.
Chapter 8, Allocating Kernel Memory, explains the different types of addresses used in Linux.
This chapter also describes the different kernel memory allocators.
Chapter 9, DMA in Device Drivers, describes the "Linux DMA Engine Subsystem" and the
different types of DMA mappings. You will develop a DMA driver for the Raspberry Pi that
manages memory to memory transactions without CPU intervention.
Chapter 10, Input Subsystem, introduces the use of frameworks to provide a coherent user space
interface for every type of device, regardless of the drivers. The chapter explains in the relationship
between the physical and logical parts of a driver that uses a kernel framework. It focuses on
the Input subsystem, which takes care of the input events coming from the human user. In this
chapter, you will develop a driver for the WII Nunchuk controller and several Python applications
that interact with the events generated by the Nunchuk driver. One of these applications will use
the Pygame Python modules, drawing the Nunchuk Joystick values on a screen connected to the
Raspberry Pi board. This chapter also describes the Linux SPI subsystem, which is based on the
Linux device model. You will develop an SPI client driver for an accelerometer device using the
Input subsystem.
Chapter 11, Industrial I/O Subsystem, describes the IIO subsystem, which provides support for
ADCs, DACs, gyroscopes, accelerometers, magnetometers, proximity sensors, etc. The set up of
IIO triggered buffers and Industrial I/O events will be explained in detail. You will create an IIO
driver to control the Maxim MAX11300 device, which integrates a PIXI™, 12-bit, multichannel,
analog-to-digital converter (ADC) and a 12-bit, multichannel, buffered digital-to-analog converter
(DAC) in a single integrated circuit (IC). In this driver, you will create a GPIO controller, which
configures and controls the MAX11300 ports. You will also develop one application that controls
a GPIO of the MAX11300 device through a character device and another application that controls
the same GPIO using the functions of the gpiolibd library. In this chapter, you will also develop
two drivers using a provider-consumer implementation: the first one is an IIO provider driver
[ 15 ]
Preface
which will read the 3-axis data from the Nunchuk´s accelerometer, and the second one is an Input
subsystem consumer driver which will read the IIO channel values from the IIO provider driver
and report them to the Input subsystem.
Chapter 12, Using the Regmap API in Device Drivers, provides an overview of the regmap API
and explains how it takes care of calling the relevant calls of the SPI or I2C subsystem. You will
transform the SPI Input subsystem driver of the Chapter 10, which uses specific SPI core APIs, into
an SPI IIO subsystem driver which uses the regmap API, keeping the same functionality between
both drivers. Finally, you will dive into the "IIO tools" applications to test the driver.
Chapter 13, USB Device Drivers, describes the Linux USB subsystem, which is based on the Linux
device model. You will learn how to create custom USB HID devices based on the Microchip
PIC32MX microcontroller that will send/receive data to/from a Raspberry Pi Linux USB device
driver. You will learn about main Linux USB data structures and functions and develop several
Linux USB device drivers throughout this chapter.
[ 16 ]
Preface
Questions
If you have a problem with any aspect of this book, you can contact us at
[email protected], and we will do our best to address the problem.
[ 17 ]
1
Building the System
The Linux kernel is one of the largest and most successful open source projects that has ever come
about. The huge rate of change and the number of individual contributors shows that it has a
vibrant and active community, constantly stimulating evolution of the kernel. This rate of change
continues to increase, as does the number of developers and companies involved in the process.
The development process has proved that it is able to scale up to higher speeds without trouble.
The Linux kernel together with GNU software and many other open-source components provides
a completely free operating system, GNU/Linux. Embedded Linux is the usage of the Linux kernel
and various open-source components in embedded systems.
Embedded Linux is used in embedded systems such as consumer electronics (e.g., set-top boxes,
smart TVs, PVRs (personal video recorders), IVI (in-vehicle infotainment), networking equipment
(such as routers, switches, WAPs (wireless access points) or wireless routers), machine control,
industrial automation, navigation equipment, spacecraft flight software, and medical instruments
in general).
There are many advantages of using Linux in embedded systems. The following list shows some
of these benefits:
• The main advantage of Linux is the ability to reuse components. Linux provides scalability
due to its modularity and configurability.
• Open source. No royalties or licensing fees.
• Ported to a broad range of hardware architectures, platforms and devices.
• Broad support of applications and communication protocols (e.g., TCP/IP stack, USB stack,
graphical toolkit libraries).
• Large support coming from an active community of developers.
These are the main components of an embedded Linux system: Bootloader, Kernel, System
call interface, C-Runtime library, System shared libraries and Root filesystem. Each of these
components will be described in more detail in the next sections.
[ 19 ]
Building the System Chapter 1
Bootloader
Linux cannot be started on an embedded device without a small amount of machine specific code
to initialize the system. Linux requires the bootloader code to do very little, although several
bootloaders do provide extensive additional functionality. The minimal requirements are:
• Configuration of the memory system.
• Loading of the kernel image and the Device Tree at the correct addresses.
• Optional loading of an initial RAM disk at the correct memory address.
• Setting of the kernel command-line and other parameters (e.g., Device Tree, machine type).
It is also usually expected that the bootloader initializes a serial console for the kernel in addition
to these basic tasks.
There are different bootloader options that come in all shapes and sizes. U-Boot is the standard
bootloader for ARM Linux. U-Boot source code is located at https://source.denx.de/u-boot/u-boot.
These are some of the main U-Boot features:
1. Small: U-Boot is a bootloader, and its primary purpose in the system is to load an
operating system. That means that U-Boot is necessary to perform a certain task, but it is
not worth spending significant resources on. Typically, U-Boot is stored in the relatively
small NOR flash memory, which is expensive compared to the much larger NAND
devices normally used to store the operating system and the application. A usable and
useful configuration of U-Boot, including a basic interactive command interpreter, support
for download over Ethernet and the capability to program the flash should fit in no more
than 128 KB.
2. Fast: The end user is not interested in running U-Boot. In most embedded systems, they
are not even aware that U-Boot exists. The user wants to run some application code, and
they want to do that as soon as possible after switching on the device. Initialize devices
only when they are needed within U-Boot, i.e., don't initialize the Ethernet interface(s)
unless U-Boot performs a download over Ethernet; don't initialize any IDE or USB devices
unless U-Boot actually tries to load files from these, etc.
3. Portable: U-Boot is a bootloader, but it is also a tool used for board bring-up, for
production testing and for other activities that are very closely related to hardware
development. So far, it has been ported to several hundreds of different boards on about
30 different processor families.
4. Configurable: U-Boot is a powerful tool with many, many extremely useful features.
The maintainer or user of each board will have to decide which features are important
[ 20 ]
Chapter 1 Building the System
and what shall be included with their specific board configuration to meet the current
requirements and restrictions.
5. Debuggable: U-Boot is not only a tool in itself, it is often also used for hardware bring-up,
so debugging U-Boot often means that you don't know if you are tracking down a problem
in the U-Boot software or in the hardware you are running on. Code that is clean and easy
to understand and debug is all the more important to everyone. One important feature of
U-Boot is to enable output to the (usually serial) console as soon as possible in the boot
process, even if this causes tradeoffs in other areas like memory footprint. All initialization
steps shall print some "begin doing this" message before they actually start and some
"done" message when they complete. The purpose of this is that you can always see which
initialization step was running if any problems occur. This is important not only during
software development, but also for the service people dealing with broken hardware in the
field. U-Boot should be debuggable with simple JTAG or BDM equipment. It should use a
simple, single-threaded execution model.
[ 21 ]
Building the System Chapter 1
Linux kernel
Linux is a clone of the operating system Unix, written from scratch by Linus Torvalds with
assistance from a loosely-knit team of hackers across the Net. It aims towards POSIX and Single
UNIX Specification compliance.
It has all the features you would expect in a modern, fully-fledged Unix implementation, including
true multitasking, virtual memory, shared libraries, demand loading, shared copy-on-write
executables, proper memory management and multistack networking including IPv4 and IPv6.
Although originally developed for 32-bit x86-based PCs (386 or higher), today Linux also runs on a
multitude of other processor architectures, in both 32-bit and 64-bit variants.
The Linux kernel is the lowest level of software running on a Linux system. It is charged with
managing the hardware, running user programs and maintaining the overall security and integrity
of the whole system. It is this kernel, which, after its initial release by Linus Torvalds in 1991,
jump-started the development of Linux as a whole. The kernel is a relatively small part of the
software on a full Linux system (many other large components come from the GNU project, the
GNOME and KDE desktop projects, the X.org project and many other sources), but the kernel is
the core which determines how well the system will work and is the piece which is truly unique to
Linux.
The kernel, which forms the core of the Linux system, is the result of one of the largest cooperative
software projects ever attempted. Regular two-to-three month releases deliver stable updates to
Linux users, each with significant new features, added device support and improved performance.
The rate of change in the kernel is high and increasing, with over 10,000 patches going into each
recent kernel release. Each of these releases contains the work of more than 1,600 developers
representing over 200 corporations.
As kernels move from the mainline into the stable category, two things can happen:
1. They can reach End of Life after a few bugfix revisions, which means that kernel
maintainers will release no more bugfixes for this kernel version, or
2. They can be put into longterm maintenance, which means that maintainers will provide
bugfixes for this kernel revision for a much longer period of time.
If the kernel version you are using is marked EOL, you should consider upgrading to the next
major version, as there will be no more bugfixes provided in the kernel version you are using.
Linux kernel is released under GNU GPL version 2 and is therefore Free Software as defined by
the Free Software Foundation. You may read the entire copy of the license in the COPYING file
distributed with each release of the Linux kernel.
[ 22 ]
Chapter 1 Building the System
[ 23 ]
Building the System Chapter 1
In addition to the official versions of the kernel, there are many third-parties (chip-vendors,
sub-communities) that supply and maintain their own version of the kernel sources by forking
from the official kernel tree. The intent is to separately develop support for a particular piece of
hardware or subsystem and to integrate this support to the official kernel at a later point. This
process is called mainlining and describes the task to integrate the new feature or hardware
support to the upstream (official) kernel. These are called Distribution kernels.
It is easy to tell if you are running a Distribution kernel. Unless you downloaded, compiled and
installed your own version of kernel from www.kernel.org, you are running a Distribution kernel. To
find out the version of your kernel, run uname -r after booting the processor.
[ 24 ]
Chapter 1 Building the System
You will work with the longterm kernel 5.4.y releases to develop all the drivers throughout this
book.
The C runtime library (C-standard library) defines macros, type definitions and functions for
string handling, mathematical functions, input/output processing, memory allocation and several
other functions that rely on OS services. The runtime library provides applications with access to
OS resources and functions by abstracting the OS System call interface.
Several C runtime libraries are available: glibc, uClibc, eglibc, dietlibc, newlib. The choice of the C
library must be made at the time of the cross-compiling toolchain generation, as the GCC compiler
is compiled against a specific C library.
The GNU C library, glibc, is the default C library used for example in the Yocto project. The
GNU C Library is primarily designed to be a portable and high performance C library. It follows
all relevant standards including ISO C11 and POSIX.1-2008. It is also internationalized and has
[ 25 ]
Building the System Chapter 1
one of the most complete internationalization interfaces known. You can find the glibc manual at
https://www.gnu.org/software/libc/manual/.
[ 26 ]
Chapter 1 Building the System
Root filesystem
The root filesystem is where all the files contained in the file hierarchy (including device nodes) are
stored. The root filesystem is mounted as /, containing all the libraries, applications and data.
The folder structure of the root filesystem is defined by FHS (Filesystem-Hierarchy-Standard). The
FHS defines the names, locations and permissions for many file types and directories. It thereby
ensures compatibility between different Linux distributions and allows applications to make
assumptions about where to find specific system files and configurations.
An embedded Linux root filesystem, usually includes the following:
• /bin: Commands needed during bootup that might be used by normal users (probably
after bootup).
[ 27 ]
Building the System Chapter 1
• /sbin: Like /bin, but the commands are not intended for normal users, although they may
use them if necessary and allowed; /sbin is not usually in the default path of normal users,
but will be in root's default path.
• /etc: Configuration files specific to the machine.
• /home: Like My Documents in Windows.
• /root: The home directory for user root. This is usually not accessible to other users on the
system.
• /lib: Essential shared libraries and kernel modules.
• /dev: Device files. These are special virtual files that help the user interface with the
various devices on the system.
• /tmp: Temporary files. As the name suggests, programs running often store temporary files
in here.
• /boot: Files used by the bootstrap loader. Kernel images are often kept here instead of in
the root directory. If there are many kernel images, the directory can easily grow too large,
and it might be better to keep it in a separate filesystem.
• /mnt: Mount point for mounting a filesystem temporarily.
• /opt: Add-on application software packages.
• /usr: Secondary hierarchy.
• /var: Variable data.
• /sys: Exports information about devices and drivers from the kernel device model to user
space, and it is also used for configuration.
• /proc: Represent the current state of the kernel.
[ 28 ]
Chapter 1 Building the System
4. The kernel runs low level kernel initialization, enabling MMU, creating the initial table of
memory pages and setting up caches. This is done in arch/arm/kernel/head.s. The file head.s
contains CPU architecture specific, but platform independent initialization code. Then, the
system switches to the non architecture specific kernel startup function start_kernel().
5. The kernel runs start_kernel() located in init/main.c that:
• Initializes the kernel core (e.g., memory, scheduling, interrupts).
• Initializes statically compiled drivers.
• Mounts the root filesystem based on bootargs passed to the kernel from U-Boot.
• Executes the first user process, init. The init process, by default, is /init for initramfs
and /sbin/init for a regular filesystem. The three init programs that you usually find on
embedded Linux devices are BusyBox init, System V init and systemd.
[ 29 ]
Building the System Chapter 1
Raspberry Pi OS
Raspberry Pi OS is the recommended operating system for normal use on a Raspberry Pi. Raspberry
Pi OS is a free operating system based on Debian, optimised for the Raspberry Pi hardware.
Raspberry Pi OS comes with over 35,000 packages: precompiled software bundled in a nice format
for easy installation on your Raspberry Pi. Raspberry Pi OS is a community project under active
development, with an emphasis on improving the stability and performance of as many Debian
packages as possible.
You will install on a Micro SD a Raspberry Pi OS image based on kernel 5.4.y. Go to https://
downloads.raspberrypi.org/raspios_full_armhf/images/raspios_full_armhf-2020-12-04/ and download the
2020-12-02-raspios-buster-armhf-full.zip image.
To write the compressed image on the Micro SD card, you will download and install Etcher. This
tool, which is an Open Source software, is useful since it allows to get a compressed image as
input. More information and extra help is available on the Etcher website at https://etcher.io/.
Once the image is installed on the Micro SD card, you will insert the Micro SD into the SD card
reader on your host PC, then you will enable UART, SPI and I2C peripherals in the programmed
Micro SD. Open a terminal application on your host PC, and type the following commands:
~$ lsblk
~$ mkdir ~/mnt
~$ mkdir ~/mnt/fat32
~$ mkdir ~/mnt/ext4
~$ sudo mount /dev/mmcblk0p1 ~/mnt/fat32
See the files in the fat32 partition. Check that config.txt is included:
~$ ls -l ~/mnt/fat32/
[ 30 ]
Chapter 1 Building the System
You can also update previous settings (after booting the Raspberry Pi board) through the
Raspberry Pi Configuration application found in Preferences on the menu.
The Interfaces tab is where you turn these different connections on or off so that the Pi recognizes
that you’ve linked something to it via a particular type of connection.
[ 31 ]
Building the System Chapter 1
[ 32 ]
Chapter 1 Building the System
2. Connect your screen to the single Raspberry Pi’s HDMI port. You can also connect a
mouse to a USB port and keyboard in the same way.
3. Connect the Ethernet port on Raspberry Pi to an Ethernet socket on your host PC.
[ 33 ]
Building the System Chapter 1
4. The serial console is a helpful tool for debugging your board and reviewing system log
information. To access the serial console, connect a USB to TTL Serial Cable to the device
UART pins as shown below.
5. Plug the USB power supply into a socket and connect it to your Raspberry Pi’s power port.
[ 34 ]
Chapter 1 Building the System
You should see a red LED light up, which indicates that the Raspberry Pi board is connected to
power. As it starts up, you will see raspberries appear in the top left-hand corner of your screen.
After a few seconds the Raspberry Pi OS Desktop will appear.
For the official Raspberry Pi OS, the default user name is pi, with password raspberry.
To find out the version of the booted kernel, run uname -r on the Raspberry Pi terminal:
pi@raspberrypi:~$ uname -r
5.4.79-v7+
Reset the board. You can disconnect your screen from the Raspberry Pi’s HDMI port during the
development of the labs (you will only need to connect a screen in the LAB 10.3).
pi@raspberrypi:~$ sudo reboot
To see Linux boot messages on the console, add loglevel=8 in the file cmdline.txt under /boot.
pi@raspberrypi:~$ sudo nano /boot/cmdline.txt
For example:
pi@raspberrypi:~$ echo 8 > /proc/sys/kernel/printk
Now, every kernel message will appear on your console, as all priority higher than 8 (lower loglevel
values) will be displayed. Please note that after reboot, this configuration is reset. To keep the
[ 35 ]
Building the System Chapter 1
configuration permanently, just append the following kernel.printk value to the /etc/sysctl.conf file,
then reboot the processor:
kernel.printk = 8 4 1 3
pi@raspberrypi:~$ sudo nano /etc/sysctl.conf
pi@raspberrypi:~$ sudo reboot
Raspbian has the SSH server disabled by default. You have to start the service on the Pi:
pi@raspberrypi:~# sudo /etc/init.d/ssh restart
Now, verify that you can ping your Linux host machine from the Raspberry Pi. Exit the ping
command by typing "Ctrl-c":
pi@raspberrypi:~# ping 10.0.0.1
You can also ping from Linux host machine to the target. Exit the ping command by typing
"Ctrl-c".
~$ ping 10.0.0.10
[ 36 ]
Chapter 1 Building the System
By default, the root account is disabled, but you can enable it by using this command and giving it
a password, for example "pi":
pi@raspberrypi:~$ sudo passwd root
Now, you can log into your pi as the root user. Open the sshd_config file, and change
PermitRootLogin to yes (also comment the line out). After editing the file, type "Ctrl+x", then type
"yes", and press "enter" to exit:
pi@raspberrypi:~$ sudo nano /etc/ssh/sshd_config
On your host PC, create the linux_rpi3 folder, where you are going to download the kernel sources:
~$ mkdir linux_rpi3
~$ cd linux_rpi3/
Get the kernel sources. The git clone command below will download the current active branch
without any history. Omitting the --depth=1 will download the entire repository, including the full
history of all branches, but this takes much longer and occupies much more storage.
~/linux_rpi3$ git clone --depth=1 -b rpi-5.4.y https://github.com/raspberrypi/linux
Compile the kernel, modules and Device Tree files. First, apply the default configuration:
~/linux_rpi3/linux$ KERNEL=kernel7
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig
Configure the following kernel settings that will be needed during the development of the labs:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
Device drivers >
[*] SPI support --->
<*> User mode SPI device driver support
[ 37 ]
Building the System Chapter 1
For the LAB 12.1, you will need the functions that enable the triggered buffer support. If they are
not defined accidentally by another driver, there's an error thrown out while linking. To solve
this problem, you can select, for example, the HTS221 driver, which includes this triggered buffer
support:
Device drivers >
<*> Industrial I/O support > Humidity sensors --->
<*> STMicroelectronics HTS221 sensor Driver
Having built the kernel, you need to copy it onto your Raspberry Pi device and also install the
modules. Insert the previously programmed Micro SD into the SD card reader on your host PC,
and execute the following commands:
~$ lsblk
~$ mkdir ~/mnt
~$ mkdir ~/mnt/fat32
~$ mkdir ~/mnt/ext4
~$ sudo mount /dev/mmcblk0p1 ~/mnt/fat32
~$ sudo mount /dev/mmcblk0p2 ~/mnt/ext4/
~$ cd linux_rpi3/linux
~/linux_rpi3/linux$ sudo env PATH=$PATH make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
INSTALL_MOD_PATH=~/mnt/ext4 modules_install
[ 38 ]
Chapter 1 Building the System
Insert the Micro SD card you’ve set up in the Micro SD card slot on the underside of your
Raspberry Pi, and connect the Raspberry Pi´s UART (through a USB to serial adapter) to a Linux
PC's USB. On Linux PC, launch Minicom utility as shown below (for debugging purpose):
~$ sudo minicom –s
If you modify and compile the kernel or Device Tree files later, you can copy them to the
Raspberry Pi remotely using the secure copy protocol (SCP). You need to connect previously an
Ethernet cable between the Raspberry Pi board and your host PC.
~/linux_rpi3/linux$ scp arch/arm/boot/zImage [email protected]:/boot/kernel7.img
Copy the following .dtb file if you are using the Raspberry Pi 3 Model B board:
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
Copy the following .dtb file if you are using the Raspberry Pi 3 Model B+ board:
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b-plus.dtb [email protected]:/boot/
[ 39 ]
2
The Linux Device and Driver
Model
Understanding the Linux device and driver model is central to developing device drivers in
Linux. A unified device model was added in Linux kernel 2.6 to provide a single mechanism for
representing devices and describing their topology in the system. The Linux device and driver
model is a universal way of organizing devices and drivers into buses. Such a system provides
several benefits:
• Minimization of code duplication.
• Clean code organization with the device drivers separated from the controller drivers, the
hardware description separated from the drivers themselves, etc.
• Capability to determine all the devices in the system, view their status and power state, see
what bus they are attached to, and determine which driver is responsible for them.
• The capability to generate a complete and valid tree of the entire device structure of the
system, including all buses and interconnections.
• The capability to link devices to their drivers and vice versa.
• Categorization of devices by their type (classes), such as input devices, without the need to
understand the physical device topology.
The device model involves terms like "device", "driver" and "bus":
• device: A physical or virtual object which attaches to a bus.
• driver: A software entity which may probe for and be bound to devices and which can
perform certain management functions.
• bus: A device which serves as an attachment point for other devices.
The device model is organized around three main data structures:
1. The bus_type structure, which represents one type of bus (e.g., USB, PCI, I2C).
2. The device_driver structure, which represents one driver capable of handling certain
devices on a certain bus.
3. The device structure, which represents one device connected to a bus.
[ 41 ]
The Linux Device and Driver Model Chapter 2
A new bus must be registered via a call to bus_register(), which is defined in drivers/base/platform.c
in the kernel source tree:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
[ 42 ]
Chapter 2 The Linux Device and Driver Model
early_platform_cleanup();
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}
The bus_register() function registers the bus with the kobject infrastructure and creates a /sys/bus/
platform/ directory that consists of two directories: devices and drivers.
One of the struct bus_type members is a pointer to the subsys_private structure, which is declared in
drivers/base/base.h in the kernel source tree:
struct subsys_private {
struct kset subsys;
struct kset *devices_kset;
struct list_head interfaces;
struct mutex mutex;
The klist_devices member of the subsys_private structure is a list of devices in the system that reside
on this particular type of bus. This list is updated by the device_register() function, which is called
when the bus is scanned for devices by the bus controller driver (during initialization or when a
device is hot plugged).
The klist_drivers member of the subsys_private structure is a list of drivers that can handle devices
on that bus. This list is updated by the driver_register() function, which is called when a driver
initializes itself.
When a new device is plugged into the system, the bus controller driver detects the device and
calls device_register(). When a device is registered by the bus controller driver, the parent member
[ 43 ]
The Linux Device and Driver Model Chapter 2
of the device structure is pointed to the bus controller device to build the physical device list. The
list of drivers associated with the bus is iterated over to find out if there are any drivers that can
handle the device. The match function provided in the bus_type structure is used to check if a given
driver can handle a given device. When a driver is found that can handle the device, the driver
member of the device structure is pointed to the corresponding device driver.
When a kernel module is inserted into the kernel and the driver calls driver_register(), the list of
devices associated with the bus is iterated over to find out if there are any devices that the driver
can handle by using the match function. When a match is found, the device is associated with the
device driver, and the driver´s probe() function is called. This is what we call binding.
When does a driver attempt to bind a device?
1. When the driver is registered (if the device already exits).
2. When the device is created (if the driver is already registered).
Summarizing, the bus driver registers a bus in a system and:
1. Allows registration of bus controller drivers, whose role is to detect devices and configure
their resources.
2. Allows registration of device drivers.
3. Matches devices and drivers.
Device drivers
Every device driver registers itself with the bus core driver by using driver_register(). After that, the
device model core tries to bind it with a device. When a device that can be handled by a particular
driver is detected, the probe() member of the driver is called, and the device configuration data can
be retrieved from the Device Tree.
[ 44 ]
Chapter 2 The Linux Device and Driver Model
Each device driver is responsible for instantiating and registering an instance of the device_driver
structure (declared in include/linux/device.h in the kernel source tree) with the device model core.
The device_driver structure is declared as follows:
struct device_driver {
const char *name;
struct bus_type *bus;
bool suppress_bind_attrs;
See below the description of some of the main members of struct device_driver:
• The bus member is a pointer to the bus_type structure to which the device driver is
registered.
• The probe member is a callback function that is called for each device detected that is
supported by the driver. The driver should instantiate itself for each device and initialize
the device as well.
• The remove member is a callback function that is called to unbind the driver from the
device. This happens when the device is physically removed, when the driver is unloaded,
or when the system is shut down.
Devices
At the lowest level, every device in a Linux system is represented by an instance of struct device.
The device structure contains the information that the device model core needs to model the
system. Most subsystems, however, track additional information about the devices they host. As a
result, it is rare for devices to be represented by bare device structures; instead, that structure, like
the kobject structure, is usually embedded within a higher-level representation of the device. For
example, a struct device is included inside the definition of the platform_device structure. The struct
platform_device is declared as follows:
[ 45 ]
The Linux Device and Driver Model Chapter 2
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 platform_dma_mask;
u32 num_resources;
struct resource *resource;
You can see below a code snippet of the device structure, which is declared in include/linux/device.h
in the kernel source tree:
struct device {
struct kobject kobj;
struct device *parent;
struct device_private *p;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
void *driver_data; /* Driver data, set and get with
dev_set_drvdata/dev_get_drvdata */
[...]
[...]
[...]
};
[ 46 ]
Chapter 2 The Linux Device and Driver Model
• parent: The device's "parent" device, the device to which it is attached. In most cases, a
parent device is some sort of bus or host controller. If the parent is NULL, the device is a
top-level device, which is not usually what you want.
• type: The type of device. This identifies the device type and carries type-specific
information.
• bus: Type of bus, device is on.
• driver: Which driver has allocated this.
• driver_data: Private pointer for driver specific info that may be used by the device driver.
• release(): Callback to free the device after all references have gone away. This should be
set by the allocator of the device (i.e., the bus driver that discovered the device). This is
called from the embedded kobject release() method.
The Linux device model is illustrated in the following figure:
[ 47 ]
The Linux Device and Driver Model Chapter 2
Since the Linux device driver model was introduced in version 2.6, the sysfs represents all devices
and drivers as kernel objects. You can see the kernel's view of the system laid by looking under
/sys/, as shown here:
• /sys/bus/ -- Contains one subdirectory for each of the bus types in the kernel.
• /sys/devices/ -- Contains the list of devices.
• /sys/bus/<bus>/devices/ -- Devices on a given bus.
• /sys/bus/<bus>/drivers/ -- Drivers on a given bus.
• /sys/class/ --This subdirectory contains a single layer of further subdirectories for each
of the device classes that have been registered on the system (for example, terminals,
network devices, block devices, graphics devices, sound devices, and so on). Inside
each of these subdirectories are symbolic links for each of the devices in this class. These
symbolic links refer to entries in the /sys/devices/ directory.
• /sys/bus/<bus>/devices/<device>/driver/ -- Symlink to driver that manages the given device.
Let's focus now on two of the directories shown above:
1. The list of devices: /sys/devices/:
This directory contains a filesystem representation of the Device Tree. It maps directly
to the internal Device Tree, which is a hierarchy of the device structures. There are three
directories that are present on all systems:
• system: This contains devices at the heart of the system, including a collection of both
global and individual CPU attributes and clocks.
• virtual: This contains devices that are memory-based. You will find the memory
devices that appear as /dev/null, /dev/random and /dev/zero in virtual/mem. You will find
the loopback device, lo, in virtual/net.
• platform: This contains devices that are not connected via a conventional hardware
bus. This could be almost anything on an embedded device.
Devices will be added and removed dynamically as the machine runs, and between
different kernel versions, the layout of the devices within this tree will change. Do not rely
on the format of this tree because of this. If a program wishes to find different things in
the tree, use the /sys/class/ structure and rely on the symlinks there to point to the proper
location within the /sys/devices/ tree of the individual devices.
2. The device drivers grouped by classes: /sys/class/:
The /sys/class/ directory consist of a group of subdirectories describing individual classes
of devices in the kernel. The individual directories consist of either subdirectories or
symlinks to other directories.
[ 48 ]
Chapter 2 The Linux Device and Driver Model
For example, you will find I2C controller drivers under /sys/class/i2c-dev/. Each registered
I2C adapter gets a number, starting from 0. You can examine /sys/class/i2c-dev/ to see which
number corresponds to which adapter.
Some attribute files are writable and allow you to tune parameters in the driver at runtime.
The dev attribute is particularly interesting. If you look at its value, you will find the major
and minor numbers of the device.
[ 49 ]
The Linux Device and Driver Model Chapter 2
The kobj field of struct cdev is initialized by calling cdev_alloc() and named in register_chrdev().
For every kobject that is registered with the system, a directory is created for it in the sysfs. That
directory is created as a subdirectory of the kobject's parent, expressing internal object hierarchies
to user space. Every kobject needs to have an associated kobj_type structure. The Kobj_type structure
controls what happens when the kobject is created and deleted. The pointer to this structure can be
found in two different places:
1. Inside the kobject structure, there is a pointer Ktype to the kobj_type structure.
2. If, however, this kobject is a member of a kset, the kobj_type pointer is provided by that
kset instead. A kset is a collection of kobjects embedded within structures of the same
type. For example, a kset can be used by the kernel to track "all block devices" or "all PCI
device drivers".
The kobject_type structure is declared as follows:
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
The Kobj_type structure also contains attributes that can be exported to sysfs directories (directories
are created with kobject_add()) as files (files are created with sysfs_create_file()). All kobjects of
the same type have the same default set of files populating their sysfs directories. The kobj_type
structure contains a member, default_attrs, that is an array of attribute structures. These attributes
can be "read/write" via "show/store" methods by using the sysfs_ops structure that is pointed via
the sysfs_ops pointer variable (included in struct kobj_type). The sysfs_ops structure is declared as
follows:
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *, char *);
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
[ 50 ]
Chapter 2 The Linux Device and Driver Model
The default attributes provided by the ktype associated with a kobject, sometimes are not
sufficient. Subsystems are encouraged to define their own attribute structure and wrapper
functions for adding and removing attributes for a specific object type. For example, the kernel
device subsystem declares struct device_attribute like:
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
is equivalent to doing:
static struct device_attribute dev_attr_foo = {
.attr = {
.name = "foo",
.mode = S_IWUSR | S_IRUGO,
},
.show = show_foo,
.store = store_foo,
};
The device_create_file() function (defined in drivers/base/core.c in the kernel source tree) calls sysfs_
create_file(), which adds new attributes on top of the default set. The device_create_file() function is
defined as follows:
/*
* device_create_file - create sysfs attribute file for device.
* @dev: device.
[ 51 ]
The Linux Device and Driver Model Chapter 2
if (dev) {
WARN(((attr->attr.mode & S_IWUGO) && !attr->store),
"Attribute %s: write permission without 'store'\n",
attr->attr.name);
WARN(((attr->attr.mode & S_IRUGO) && !attr->show),
"Attribute %s: read permission without 'show'\n",
attr->attr.name);
error = sysfs_create_file(&dev->kobj, &attr->attr);
}
return error;
}
For example, you can create two structures of type device_attribute with respective names foo1 and
foo2:
static DEVICE_ATTR(foo1, S_IWUSR | S_IRUGO, show_foo1, store_foo1);
static DEVICE_ATTR(foo2, S_IWUSR | S_IRUGO, show_foo2, store_foo2);
You can add/remove a group of "sysfs attribute files" by using the next functions:
int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp);
void sysfs_remove_group(struct kobject * kobj, const struct attribute_group * grp);
You can add/remove a group of "sysfs attribute files, for example, to an I2C client device:
int sysfs_create_group(&client->dev.kobj, &dev_attr_group);
void sysfs_remove_group(&client->dev.kobj, &dev_attr_group);
[ 52 ]
Chapter 2 The Linux Device and Driver Model
[ 53 ]
The Linux Device and Driver Model Chapter 2
The DT is represented as a set of text files in the Linux kernel source tree. They are located under
arch/arm/boot/dts/ and can have two extensions:
• *.dtsi files are Device Tree source include files. They describe the hardware that is
common to several platforms which include these files on their *.dts files.
• *.dts files are Device Tree source files. They describe one specific platform.
Linux uses DT data for three major purposes:
1. Platform Identification: The kernel will use data in the DT to identify the specific machine.
In a perfect world, the specific platform shouldn't matter to the kernel because all platform
details would be described perfectly by the Device Tree in a consistent and reliable
manner. Hardware is not perfect though, and the kernel must identify the machine during
early boot so that it has the opportunity to run machine-specific fixups. In the majority
of cases, the machine identity is irrelevant, and the kernel will instead select setup code
based on the machine's core CPU or SoC. On ARM, for example, setup_arch() (defined in
arch/arm/kernel/setup.c in the kernel source tree) calls setup_machine_fdt() (defined in arch/
arm/kernel/devtree.c in the kernel source tree), which searches through the machine_desc
table and selects the machine_desc which best matches the Device Tree data. It determines
the best match by looking at the compatible property in the root Device Tree node and
comparing it with the dt_compat list of the machine_desc structure, which is declared in
arch/arm/include/asm/mach/arch.h in the kernel source tree.
In the Device Tree, the compatible property contains a sorted list of strings starting with the
exact name of the machine. For the Broadcom BCM2837 SoC, the bcm2837.dtsi file (located
in arch/arm/boot/dts in the kernel source tree) contains the following compatible property:
compatible = "brcm,bcm2837";
Again on ARM, for each machine_desc, the kernel looks to see if any of the dt_compat
list entries appears in the compatible property. If one does, then that machine_desc is a
candidate for driving the machine. For the Broadcom BCM2837 SoC, the bcm2835_compat[]
and DT_MACHINE_START declarations (located in arch/arm/mach-bcm/board_bcm2835.c in the
kernel source tree) are used to populate a machine_desc structure.
static const char * const bcm2835_compat[] = {
#ifdef CONFIG_ARCH_MULTI_V6
"brcm,bcm2835",
#endif
#ifdef CONFIG_ARCH_MULTI_V7
"brcm,bcm2836",
"brcm,bcm2837",
#endif
NULL
};
[ 54 ]
Chapter 2 The Linux Device and Driver Model
DT_MACHINE_START(BCM2835, "BCM2835")
.dt_compat = bcm2835_compat,
.smp = smp_ops(bcm2836_smp_ops),
MACHINE_END
if (!early_init_dt_scan(dt))
return NULL;
dt_root = of_get_flat_dt_root();
arc_set_early_base_baud(dt_root);
return mdesc;
}
/*
* of_flat_dt_match_machine - Iterate match tables to find matching machine.
* @default_match: A machine specific ptr to return in case of no match.
* @get_next_compat: callback function to return next compatible match table.
* Iterate through machine match tables to find the best match for the machine
* compatible string in the FDT.
*/
const void * __init of_flat_dt_match_machine(const void *default_match,
const void * (*get_next_compat)(const char * const**))
{
const void *data = NULL;
const void *best_data = default_match;
const char *const *compat;
[ 55 ]
The Linux Device and Driver Model Chapter 2
dt_root = of_get_flat_dt_root();
while ((data = get_next_compat(&compat))) {
score = of_flat_dt_match(dt_root, compat);
if (score > 0 && score < best_score) {
best_data = data;
best_score = score;
}
}
if (!best_data) {
const char *prop;
int size;
return best_data;
}
The chosen node above is included in the /arch/arm/boot/dts/bcm283x.dtsi file in the kernel
source tree. The stdout-path property specifies the device to be used for the boot console
output.
The setup_machine_fdt() function is also responsible for early scanning of the Device Tree.
During early boot, the setup_machine_fdt() function calls of_scan_flat_dt() several times
with different helper callbacks to parse the Device Tree data before paging is set up.
[ 56 ]
Chapter 2 The Linux Device and Driver Model
The of_scan_flat_dt() code scans through the Device Tree and uses the helpers to extract
information required during early boot. Typically, the early_init_dt_scan_chosen() helper
parses the chosen node, early_init_dt_scan_root() initializes the DT address space model,
and early_init_dt_scan_memory() determines the size and location of usable RAM.
/*
* setup_machine_fdt - Machine setup when an dtb was passed to the kernel
* @dt: virtual address pointer to dt blob
* If a dtb was passed to the kernel, then use it to choose the correct
* machine_desc and to setup the system.
*/
const struct machine_desc * __init setup_machine_fdt(void *dt)
{
const struct machine_desc *mdesc;
unsigned long dt_root;
if (!early_init_dt_scan(dt))
return NULL;
dt_root = of_get_flat_dt_root();
arc_set_early_base_baud(dt_root);
return mdesc;
}
bool __init early_init_dt_scan(void *params)
{
bool status;
status = early_init_dt_verify(params);
if (!status)
return false;
early_init_dt_scan_nodes();
return true;
}
void __init early_init_dt_scan_nodes(void)
{
int rc = 0;
[ 57 ]
The Linux Device and Driver Model Chapter 2
3. Device population: After the board has been identified, and after the early configuration
data has been parsed, then kernel initialization can proceed in the normal way. At some
point in this process, unflatten_device_tree() is called to convert the data into a more
efficient runtime representation. This is also when machine-specific setup hooks will
get called, like .init_early(), .init_irq() and .init_machine() hooks on ARM devices. As can be
guessed from the names, .init_early() is used for any machine-specific setup that needs to be
executed early in the boot process, and .init_irq() is used to set up interrupt handling.
The most interesting hook in the DT context is .init_machine(), which is primarily
responsible for populating the Linux device model with data about the platform. The list
of devices can be obtained by parsing the DT and allocating device structures dynamically.
The of_platform_populate() function, located in drivers/of/platform.c, walks through the
nodes in the Device Tree and creates platform devices from it. The second argument to
of_platform_populate() is an of_device_id table, and any node that matches an entry in that
table will also get its child nodes registered.
A Device Tree can be considered as a language with its own syntax and semantics. The Device
Tree data structure is a simple tree of nodes. The tree begins with a root node that doesn’t have a
name and it is marked with a slash (/) character. Each node may contain an arbitrary number of
properties and next level nodes. Nodes and properties have names. Properties are named arrays
of bytes, which may contain strings, numbers (big-endian), arbitrary sequences of bytes and any
combination thereof. By analogy to a filesystem, nodes are directories and properties are files.
This is a syntactically correct Device Tree file that shows the nodes and property types:
/* SoC.dtsi */
/ {
node1 {
empty-property;
cell-property = <100>;
sub-node1 {
cell-property = <1 2>;
byte-property = [0x01 0x23 0x34 0x56];
reference = <&primary>;
list-property = "one", "two";
};
sub-node2 {
string-property = "false";
};
};
primary: node2 {
mix-list-property = <4>, "Master", <5>;
};
[ 58 ]
Chapter 2 The Linux Device and Driver Model
node3 {
string-list-property = "first string", "second string";
};
};
The root node has 3 nodes, defined as node1, node2 and node3. The node2 has a label (primary). The
node1 has two properties and two next level nodes (sub-node1 and sub-node2). In the node1, empty-
property is handled as Boolean; if defined, it means true. In the node1, cell-property stores numeric
values as 32-bit big-endian numbers. A reference in sub-node1 expresses an association with node2
through the primary label.
The property of node2 includes data of differing representations concatenated using a comma. The
property of node3 includes a list of strings separated by commas.
This is another syntactically correct Device Tree file that includes the previous SoC.dtsi file:
/* board.dts */
/dts-v1/;
/include/ "SoC.dtsi"
/ {
node1 {
sub-node2 {
string-property = "true";
};
};
/delete-node/ node3;
};
The /dts-v1/ directive describes the used format of the Device Tree source. It’s a required header
and must be given only once.
The /include/ directive includes another file. Since every file defines a whole root node, every
definition is handled like a transparency that adds more definitions and details to the Device Tree.
Given that nodes are named, potentially with absolute paths, it is possible for the same node to
appear twice in a DT file (and its inclusions). When this happens, the nodes and properties are
combined, interleaving and overwriting properties as required (later values override earlier ones).
The board.dts file redefines /node1/sub-node2/string-property value. The previously defined "false"
will be replaced with "true" since the board.dts file will be processed after the SoC.dtsi file.
The /delete-node/ directive removes node3. A /delete-property/ directive could be used to delete a
property.
The Device Tree Compiler (dtc) compiles the DT source into a binary form (.dtb). It exhibits design
similarities to a C compiler: both can evaluate complicated numerical expressions, and both cannot
process alphanumeric definitions and macros. The source code of the Device Tree Compiler is
located in scripts/dtc/ in the kernel source tree. The Device Tree files are normally compiled in
arch/arm/boot/dts/ in the kernel source tree. A Makefile file in that directory is prepared to invoke
[ 59 ]
The Linux Device and Driver Model Chapter 2
a preprocessor and to search all needed include directories. It is easy to add new Device Tree files
to the Makefile. The dtb is automatically built when the kernel image is built using the "make"
command. However, to build just the dtb, the following command can be used from within the top
Linux directory:
~/linux_rpi3/linux$ make -j4 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
In the next chapters, you will learn about Device Tree special properties (for example, reg and
interrupts) and how to read their values from the Device Tree by using different kernel functions
(for example, platform_get_resource() and platform_get_irq()).
You can learn more about the usage of the Device Tree on Rasperry Pi at https://www.raspberrypi.
org/documentation/configuration/device-tree.md.
[ 60 ]
3
The Simplest Drivers
A key concept in the design of an embedded Linux system is the separation of user applications
from the underlying hardware. User space applications are not allowed to access peripheral
registers, storage media or even RAM memory directly. Instead, the hardware is accessed via
kernel drivers, and RAM memory is managed by the memory management unit (MMU), with
applications operating on virtual addresses.
This separation provides robustness. If it is assumed that the Linux kernel is operating correctly,
then allowing only the kernel to interact with underlying hardware keeps applications from
accidentally or maliciously misconfiguring hardware peripherals and placing them in unknown
states.
This separation also provides portability. If only the kernel drivers manage the hardware specific
code, only these drivers need to be modified in order to port a system from one hardware platform
to another. Applications access a set of driver APIs that is consistent across hardware platforms,
allowing applications to be moved from one platform to another with little or no modification to
the source code.
Device drivers can be kernel modules or statically built into the kernel image. The default kernel
builds most drivers into the kernel statically, so they are started automatically. A kernel module is
not necessarily a device driver; it is an extension of the kernel. The kernel modules are loaded into
virtual memory of the kernel. Building a device driver as a module makes the development easier,
since it can be loaded, tested and unloaded without rebooting the kernel. The kernel modules are
usually located in /lib/modules/<kernel_version>/ on the root filesystem.
Every Linux kernel module has an init() and an exit() function. The init() function is called once
when the driver is loaded, and the exit() function is called when the driver is removed. The init()
function lets the OS know what the driver is capable of and which of its function must be called
when a certain event takes place (for example, register a driver to the bus or register a char device).
The exit() function must free all the resources that were requested by the init() function.
Macros module_init() and module_exit() export the symbols for the init() and exit() functions such that
the kernel code that loads your module can identify these entry points.
[ 61 ]
The Simplest Drivers Chapter 3
There is a collection of macros used to identify various attributes of a module. These strings get
packaged into the module and can be accessed by various tools. The most important module
description macro is the MODULE_LICENSE macro. If this macro is not set to some sort of GPL
license tag, then the kernel will become tainted when you load your module. When the kernel
is tainted, it means that it is in a state that is not supported by the community. Most kernel
developers will ignore bug reports involving tainted kernels, and community members may ask
that you correct the tainting condition before they can proceed with diagnosing problems related
to the kernel. In addition, some debugging functionality and API calls may be disabled when the
kernel is tainted.
Licensing
The Linux kernel is licensed under the GNU General Public License version 2. This license gives
you the right to use, study, modify and share the software freely. However, when the software is
redistributed, modified or unmodified, the GPL requires that you redistribute the software under
the same license, with the source code. If modifications are made to the Linux kernel (for example
to adapt it to your hardware), it is a derivative work of the kernel and therefore must be released
under GPLv2. However, you're only required to do so at the time the device starts to be distributed
to your customers, not to the entire world.
The kernel modules provided in this book are released under the GPL license. For more
information on open source software licenses, please see http://opensource.org/licenses.
Where KERN_ERR is one of the eight different log levels defined in include/linux/kern_levels.h in
the kernel source tree and specifies the severity of the error message. The pr_* macros (defined in
include/linux/printk.h in the kernel source tree) are simple shorthand definitions for their respective
printk calls and should be used in newer drivers.
In the home folder of your host PC, you will create the linux_5.4_rpi3_drivers folder, where you are
going to store all the drivers and applications developed through this book.
~$ mkdir linux_5.4_rpi3_drivers
[ 62 ]
Chapter 3 The Simplest Drivers
Create the helloworld_rpi3.c and Makefile files using your favorite text editor, and save them in
the linux_5.4_rpi3_drivers folder. Write the Listing 3-1 code and the Listing 3-2 code to these files.
Secure Copy (SCP) will be added to the Makefile to transfer the modules from the host PC to the
Raspberry Pi filesystem via Ethernet:
scp *.ko [email protected]:
The same Makefile will be reused for many of the labs by simply adding the new <module name>.o
to the Makefile variable obj-m.
Compile the helloworld_rpi3.c driver, and deploy it to the Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
KERNEL_DIR ?= $(HOME)/linux_rpi3/linux
all:
make -C $(KERNEL_DIR) \
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- \
M=$(PWD) modules
[ 63 ]
The Simplest Drivers Chapter 3
clean:
make -C $(KERNEL_DIR) \
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- \
M=$(PWD) clean
deploy:
scp *.ko [email protected]:/home/pi
helloworld_rpi3.ko demonstration
Load the module:
root@raspberrypi:/home/pi# insmod helloworld_rpi3.ko
Hello world init
See the MODULE macros defined in your module:
root@raspberrypi:/home/pi# modinfo helloworld_rpi3.ko
filename: /home/pi/helloworld_rpi3.ko
description: This is a print out Hello World module
author: Alberto Liberal <[email protected]>
license: GPL
srcversion: F329256EE5AE6A9F62F65DF
depends:
name: helloworld_rpi3
vermagic: 5.4.83-v7+ SMP mod_unload modversions ARMv7 p2v8
An externally-built (“out-of-tree”) untainted module has been loaded:
root@raspberrypi:/home/pi# cat /sys/module/helloworld_rpi3/taint
O
Remove the module:
root@raspberrypi:/home/pi# rmmod helloworld_rpi3.ko
Hello world exit
Comment out the MODULE_LICENSE macro in the helloworld_rpi3.c file. Build, deploy and load the
module again. Reboot the Pi!. Work with your tainted module (PO):
root@raspberrypi:/home/pi# reboot
root@raspberrypi:/home/pi# insmod helloworld_rpi3.ko
helloworld_rpi3: module license 'unspecified' taints kernel.
Disabling lock debugging due to kernel taint
Hello world init
root@raspberrypi:/home/pi# cat /proc/sys/kernel/tainted
PO
root@raspberrypi:/home/pi# cat /proc/modules
helloworld_rpi3 16384 0 - Live 0x7f1b6000 (PO)
Find your module in the sysfs:
root@raspberrypi:/home/pi# find /sys -name "*helloworld*"
/sys/module/helloworld_rpi3
[ 64 ]
Chapter 3 The Simplest Drivers
root@raspberrypi:/home/pi# ls /sys/module/helloworld_rpi3/
coresize initsize notes sections taint
holders initstate refcnt srcversion uevent
Remove the module:
root@raspberrypi:/home/pi# rmmod helloworld_rpi3.ko
Hello world exit
[ 65 ]
The Simplest Drivers Chapter 3
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a module that accepts parameters");
KERNEL_DIR ?= $(HOME)/linux_rpi3/linux
all:
make -C $(KERNEL_DIR) \
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- \
M=$(PWD) modules
clean:
make -C $(KERNEL_DIR) \
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- \
M=$(PWD) clean
deploy:
scp *.ko [email protected]:/home/pi
[ 66 ]
Chapter 3 The Simplest Drivers
helloworld_rpi3_with_parameters.ko demonstration
Load the module:
root@raspberrypi:/home/pi# insmod helloworld_rpi3_with_parameters.ko
parameter num = 5
Remove the module:
root@raspberrypi:/home/pi# rmmod helloworld_rpi3_with_parameters.ko
Hello world with parameter exit
Load the module with a parameter:
root@raspberrypi:/home/pi# insmod helloworld_rpi3_with_parameters.ko num=10
parameter num = 10
root@raspberrypi:/home/pi# cat /sys/module/helloworld_rpi3_with_parameters/param
eters/num
10
Remove the module:
root@raspberrypi:/home/pi# rmmod helloworld_rpi3_with_parameters.ko
Hello world with parameter exit
[ 67 ]
4
Character Drivers
Typically, an operating system is designed to hide the underlying hardware details from the
user application. Applications do, however, require the ability to access data that is captured by
hardware peripherals, as well as the ability to drive peripherals with output. Since the peripheral
registers are accessible only by the Linux kernel, only the kernel is able to collect data streams as
they are captured by these peripherals.
Linux requires a mechanism to transfer data from kernel to user space. This transfer of data is
handled via device nodes, which are also known as virtual files. Device nodes exist within the
root filesystem, though they are not true files. When a user reads from a device node, the kernel
copies the data stream captured by the underlying driver into the application memory space.
When a user writes to a device node, the kernel copies the data stream provided by the application
into the data buffers of the driver, which are eventually output via the underlying hardware. These
virtual files can be "opened" and "read from" or "written to" by the user application using standard
system calls.
Each device has a unique driver that handles requests from user applications that are eventually
passed to the core. Linux supports three types of devices: character devices, block devices and
network devices. While the concept is the same, the difference in the drivers for each of these
devices is the manner in which the files are "opened" and "read from" or "written to". Character
devices are the most common devices, which are read and written directly without buffering,
for example, keyboards, monitors, printers and serial ports. Block devices can only be written to
and read from in multiples of the block size, typically 512 or 1024 bytes. They may be randomly
accessed, i.e., any block can be read or written no matter where it is on the device. A classic
example of a block device is a hard disk drive. Network devices are accessed via the BSD socket
interface and the networking subsystems.
Character devices are identified by a c in the first column of a listing, and block devices are
identified by a b. The access permissions, owner and group of the device are provided for each
device.
[ 69 ]
Character Drivers Chapter 4
From the point of view of an application, a character device is essentially a file. A process only
knows a /dev file path. The process opens the file by using the open() system call and performs
standard file operations like read() and write().
In order to achieve this, a character driver must implement the operations described in the file_
operations structure (declared in include/linux/fs.h in the kernel source tree) and register them. In
the file_operations structure shown below, you can see some of the most common operations for a
character driver:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
};
The Linux filesystem layer will ensure that these operations are called when a user space
application makes the corresponding system call (on the kernel side, the driver implements and
registers the callback operation).
[ 70 ]
Chapter 4 Character Drivers
The kernel driver will use the specific functions copy_from_user() and copy_to_user() to exchange
data with user space, as shown in the previous figure.
Both the read() and write() methods return a negative value if an error occurs. A return value
greater than or equal to 0, instead, tells the calling program how many bytes have been
successfully transferred. If some data is transferred correctly and then an error happens, the return
value must be the count of bytes successfully transferred, and the error does not get reported until
the next time the function is called. Implementing this convention requires, of course, that your
driver remember that the error has occurred so that it can return the error status in the future.
The return value for read() is interpreted by the calling application program:
1. If the value equals the count argument passed to the read system call, the requested
number of bytes has been transferred. This is the optimal case.
2. If the value is positive, but smaller than the count, then only part of the data has been
transferred. This may happen for a number of reasons, depending on the device. Most
often, the application program retries the read. For instance, if you read using the fread()
function, the library function reissues the system call until completion of the requested
data transfer. If the value is 0, end-of-file was reached.
3. A negative value means there was an error. The value specifies what the error was,
according to <linux/errno.h>. Typical values returned on error include -EINTR (interrupted
system call) or -EFAULT (bad address).
In Linux, every device is identified by two numbers: a major number and a minor number. These
numbers can be seen by invoking ls -l /dev on the host PC. Every device driver registers its major
number with the kernel and is completely responsible for managing its minor numbers. When
accessing a device file, the major number selects which device driver is being called to perform
the input/output operation. The major number is used by the kernel to identify the correct device
driver when the device is accessed. The role of the minor number is device dependent and is
handled internally within the driver. For instance, the Raspberry Pi has several hardware UART
ports. The same driver can be used to control all the UARTs, but each physical UART needs its
own device node, so the device nodes for these UARTs will all have the same major number, but
will have unique minor numbers.
[ 71 ]
Character Drivers Chapter 4
with the relevant major and minor device numbers for every possible device that might exist in the
world.
This is not the right approach to create device nodes nowadays, as you have to create a block or
character device file entry manually and associate it with the device, as shown in the Raspberry
Pi´s terminal command line below:
root@raspberrypi:/home/pi# mknod /dev/mydev c 202 108
Despite all this, you will develop your next driver using this static method, purely for educational
purposes, and you will see in the few next drivers a better way to create the device nodes by using
devtmpfs and the miscellaneous framework.
In this kernel module lab, you will interact with user space through an ioctl_test user application. You
will use open() and ioctl() system calls in your application, and you will write its corresponding driver´s
callback operations on the kernel side, providing a communication between user and kernel space.
In the first lab, you saw what a basic driver looks like. This driver didn’t do much except printing some
text during installation and removal. In the following lab, you will expand this driver to create a device
with a major and minor number. You will also create a user application to interact with the driver.
Finally, you will handle file operations in the driver to service requests from user space.
In the kernel, a character-type device is represented by struct cdev, a structure used to register it in the
system.
[ 72 ]
Chapter 4 Character Drivers
After assigning the identifiers, the character device will have to be initialized by using the cdev_
init() function and registered to the kernel by using the cdev_add() function. The cdev_init() and
cdev_add() functions will be called as many times as assigned device identifiers.
The following sequence registers and initializes a number (MY_MAX_MINORS) of devices:
#include <linux/fs.h>
#include <linux/cdev.h>
#define MY_MAJOR 42
#define MY_MAX_MINORS 5
struct my_device_data {
struct cdev cdev;
/* my data starts here */
[...]
};
[ 73 ]
Character Drivers Chapter 4
int init_module(void)
{
int i, err;
register_chrdev_region(MKDEV(MY_MAJOR, 0), MY_MAX_MINORS, "my_device_driver");
return 0;
}
The following code snippet deletes and unregisters the character devices:
void cleanup_module(void)
{
int i;
3. One of the first things your driver will need to do when setting up a char device is to
obtain one or more device identifiers (major and minor numbers) to work with. The
necessary function for this task is register_chrdev_region(), which is declared in include/linux/
fs.h in the kernel source tree. Add the following lines of code to the hello_init() function to
allocate the device numbers when the module is loaded. The MKDEV macro will combine
a major number and a minor number to a dev_t data type that is used to hold the first
device identifier.
[ 74 ]
Chapter 4 Character Drivers
4. Add the following line of code to the hello_exit() function to return the devices when the
module is removed:
unregister_chrdev_region(MKDEV(MY_MAJOR_NUM, 0), 1);
6. Implement each of the callback functions that are declared in the file_operations structure:
static int my_dev_open(struct inode *inode, struct file *file)
{
pr_info("my_dev_open() is called.\n");
return 0;
}
7. Add these file operation functionalities to your character device. The Linux kernel uses
struct cdev to represent character devices internally; therefore you will create a struct cdev
variable called my_dev and initialize it by using the cdev_init() function call, which takes
the variable my_dev and the structure my_dev_fops as parameters. Once the cdev structure
is setup, you will tell the kernel about it by using the cdev_add() function. You will call
cdev_init() and cdev_add() as many times as allocated character device identifiers (only once
in this driver).
[ 75 ]
Character Drivers Chapter 4
8. Add the following line of code to the hello_exit() function to delete the cdev structure:
cdev_del(&my_dev);
9. Once the kernel module has been dynamically loaded, the user needs to create a device
node to reference the driver. Linux provides the mknod utility for this purpose. The mknod
command has four parameters. The first parameter is the name of the device node that will
be created. The second parameter indicates whether the driver to which the device node
interfaces is a block driver or character driver. The last two parameters passed to mknod
are the major and minor numbers. Assigned major numbers are listed in the /proc/devices
file and can be viewed using the cat command. The created device node should be placed
in the /dev directory.
10. Create a new helloword_rpi3_char_driver.c file in the linux_5.4_rpi3_drivers folder, and write
the Listing 4-1 code on it. Add helloword_rpi3_char_driver.o to your Makefile obj-m variable.
11. In the linux_5.4_rpi3_drivers folder, you will create the apps folder, where you are going to
store most of the applications developed through this book.
~/linux_5.4_rpi3_drivers$ mkdir apps
12. In the apps folder, you will create an ioctl_test.c file, then write the Listing 4-3 code on it.
You will also create a Makefile (Listing 4-2) file in the apps folder to compile and deploy the
application.
13. Compile the helloworld_rpi3_char_driver.c driver and the ioctl_test.c application, and deploy
them to the Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
~/linux_5.4_rpi3_drivers/apps$ make
~/linux_5.4_rpi3_drivers/apps$ make deploy
[ 76 ]
Chapter 4 Character Drivers
static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
pr_info("my_dev_ioctl() is called. cmd = %d, arg = %ld\n", cmd, arg);
return 0;
}
return 0;
}
[ 77 ]
Character Drivers Chapter 4
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a module that interacts with the ioctl system call");
app : ioctl_test.c
$(CC) -o $@ $^
clean :
rm ioctl_test
deploy : ioctl_test
scp $^ [email protected]:/home/pi
int main(void)
{
/* First, you need run "mknod /dev/mydev c 202 0" to create /dev/mydev */
if (my_dev < 0) {
perror("Fail to open device file: /dev/mydev.");
} else {
ioctl(my_dev, 100, 110); /* cmd = 100, arg = 110. */
close(my_dev);
}
return 0;
}
[ 78 ]
Chapter 4 Character Drivers
helloworld_rpi3_char_driver.ko demonstration
Load the module:
root@raspberrypi:/home/pi# insmod helloworld_rpi3_char_driver.ko
Hello world init
See the allocated "my_char_device". The device mydev is not created under /dev yet:
root@raspberrypi:/home/pi# cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
[...]
202 my_char_device
[...]
root@raspberrypi:/home/pi# ls –l /dev/
Create mydev under /dev using mknod, and verify its creation:
root@raspberrypi:/home/pi# mknod /dev/mydev c 202 0
root@raspberrypi:/home/pi# ls -l /dev/mydev
crw-r--r-- 1 root root 202, 0 Apr 8 19:00 /dev/mydev
Run the application ioctl_test:
root@raspberrypi:/home/pi# ./ioctl_test
my_dev_open() is called.
my_dev_ioctl() is called. cmd = 100, arg = 110
my_dev_close() is called.
Remove the module:
root@raspberrypi:/home/pi# rmmod helloworld_rpi3_char_driver.ko
Hello world exit
[ 79 ]
Character Drivers Chapter 4
Device files are created by the kernel via the temporary devtmpfs filesystem. Any driver that wishes to
register a device node will use devtmpfs (via the core driver) to do it. When a devtmpfs instance is mounted
on /dev, the device node will initially be created with a fixed name, permissions and owner. These entries
can be both read from and written to. All device nodes are owned by root and have the default mode of
0600.
Shortly afterward, the kernel will send an uevent to udevd. Based on the rules specified in the files within the
/etc/udev/rules.d/, /lib/udev/rules.d/ and /run/udev/rules.d/ directories, udevd will create additional symlinks
to the device node, change its permissions, owner or group, or modify the internal udevd database entry
(name) for that object. The rules in these three directories are numbered, and all three directories are merged
together. If udevd can't find a rule for the device it is creating, it will leave the permissions and ownership at
whatever devtmpfs used initially.
The CONFIG_DEVTMPFS_MOUNT kernel configuration option makes the kernel mounts devtmpfs
automatically at boot time, except when booting on an initramfs.
A Linux driver creates the device nodes by using the following kernel APIs:
device_create() /* creates a device node in the /dev directory */
device_destroy() /* removes a device node in the /dev directory */
The main points that differ from your previous helloworld_rpi3_char_driver driver will now be
described:
1. Include the following header file to create the class and device files:
#include <linux/device.h> /* class_create(), device_create() */
[ 80 ]
Chapter 4 Character Drivers
2. Your driver will have a class name and a device name; hello_class is used as the class name
and mydev as the device name. This results in the creation of a device that appears on the
file system at /sys/class/hello_class/mydev. Add the following definitions for the device and
class names:
#define DEVICE_NAME "mydev"
#define CLASS_NAME "hello_class"
3. The hello_init() function is longer than the one written in the helloworld_rpi3_char_driver
driver. That is because it now automatically allocates a major number to the device by
using the function alloc_chrdev_region(), as well as registering the device class and creating
the device node.
static int __init hello_init(void)
{
dev_t dev_no;
int Major;
struct device* helloDevice;
/*
* Get the device identifiers using MKDEV. We are doing this
* for teaching purposes, as we only use one identifier in this
* driver and dev_no could be used as a parameter for cdev_add()
* and device_create() without needing to use the MKDEV macro
*/
/* Get the mayor number from the first device identifier */
Major = MAJOR(dev_no);
return 0;
}
4. Create a new helloword_ rpi3_class_driver.c file in the linux_5.4_rpi3_drivers folder, and add
helloword_ rpi3_class_driver.o to your Makefile obj-m variable, then build and deploy the
module to the Raspberry Pi:
[ 81 ]
Character Drivers Chapter 4
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
pr_info("my_dev_ioctl() is called. cmd = %d, arg = %ld\n", cmd, arg);
return 0;
}
[ 82 ]
Chapter 4 Character Drivers
return 0;
}
[ 83 ]
Character Drivers Chapter 4
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a module that interacts with the ioctl system call");
helloworld_rpi3_class_driver.ko demonstration
Reboot your Raspberry Pi to remove the mydev device that you created manually in the previous
lab:
root@raspberrypi:/home/pi# reboot
Load the module:
root@raspberrypi:/home/pi# insmod helloworld_rpi3_class_driver.ko
Hello world init
Allocated correctly with major number 238
device class registered correctly
The device is created correctly
Check that hello_class and mydev are created, and see the entries under mydev:
root@raspberrypi:/home/pi# ls /sys/class
bcm2708_vcio hello_class iscsi_session raw thermal
bcm2835-gpiomem hidraw iscsi_transport rc tty
bdi hwmon leds regulator uio
block i2c-adapter lirc rfkill vc
bluetooth i2c-dev mdio_bus rtc vchiq
bsg ieee80211 mem scsi_device vc-mem
devcoredump input misc scsi_disk vc-sm
dma iscsi_connection mmc_host scsi_host video4linux
dma_heap iscsi_endpoint net sound vtconsole
gpio iscsi_host power_supply spi_master watchdog
graphics iscsi_iface pwm spi_slave
root@raspberrypi:/home/pi# ls -l /dev/mydev
crw------- 1 root root 238, 0 Apr 8 18:56 /dev/mydev
root@raspberrypi:/home/pi# ls /sys/class/hello_class/mydev
dev power subsystem uevent
See the assigned mayor and minor numbers for the device mydev:
root@raspberrypi:/home/pi# cat /sys/class/hello_class/mydev/dev
238:0
Run the application ioctl_test:
root@raspberrypi:/home/pi# ./ioctl_test
my_dev_open() is called.
my_dev_ioctl() is called. cmd = 100, arg = 110
my_dev_close() is called.
Remove the module:
[ 84 ]
Chapter 4 Character Drivers
Where:
• minor is the minor number being registered.
• name is the name for this device, found in the /proc/misc file.
• fops is a pointer to the file_operations structure.
• parent is a pointer to a device structure that represents the hardware device exposed by this
driver.
The misc driver exports two functions, misc_register() and misc_deregister(), to register and
unregister their own minor number. These functions are declared in include/linux/miscdevice.h and
defined in drivers/char/misc.c in the kernel source tree:
int misc_register(struct miscdevice *misc);
int misc_deregister(struct miscdevice *misc);
[ 85 ]
Character Drivers Chapter 4
The misc_register() function registers a miscellaneous device with the kernel. If the minor number
is set to MISC_DYNAMIC_MINOR, a minor number is dynamically assigned and placed in the
minor field of the miscdevice structure. In other cases, the minor number requested is used.
The structure passed as an argument is linked into the kernel and may not be destroyed until it has
been unregistered. By default, an open() syscall to the device sets the file->private_data to point to
the structure. A zero is returned on success and a negative errno code for failure.
The typical code snippet for assigning a dynamic minor number is as follows:
static struct miscdevice my_dev;
int init_module(void)
{
my_dev.minor = MISC_DYNAMIC_MINOR;
my_dev.name = "my_device";
my_dev.fops = &my_fops;
misc_register(&my_dev);
pr_info("my: got minor %i\n", my_dev.minor);
return 0;
}
4. Create a new misc_ rpi3_driver.c file in the linux_5.4_rpi3_drivers folder, and add misc_ rpi3_
driver.o to your Makefile obj-m variable, then build and deploy the module to the Raspberry
Pi:
~/linux_5.4_rpi3_drivers$ make
[ 86 ]
Chapter 4 Character Drivers
static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
pr_info("my_dev_ioctl() is called. cmd = %d, arg = %ld\n", cmd, arg);
return 0;
}
if (ret_val != 0) {
pr_err("could not register the misc device mydev");
return ret_val;
}
[ 87 ]
Character Drivers Chapter 4
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is the helloworld_char_driver using misc framework");
misc_rpi3_driver.ko demonstration
Load the module:
root@raspberrypi:/home/pi# insmod misc_rpi3_driver.ko
Hello world init
mydev: got minor 60
Check that mydev is created under the misc class folder and under the /dev folder:
root@raspberrypi:/home/pi# ls /sys/class/misc
autofs cpu_dma_latency hw_random mydev vcsm-cma
cachefiles fuse loop-control rfkill watchdog
root@raspberrypi:/home/pi# ls -l /dev/mydev
crw------- 1 root root 10, 60 Apr 8 18:59 /dev/mydev
Run the application ioctl_test:
root@raspberrypi:/home/pi# ./ioctl_test
my_dev_open() is called.
my_dev_ioctl() is called. cmd = 100, arg = 110
my_dev_close() is called.
Remove the module:
root@raspberrypi:/home/pi# rmmod misc_rpi3_driver.ko
Hello world exit
[ 88 ]
5
Platform Drivers
So far, you have been building your driver as a loadable driver module which was loaded during
run time. The character driver is complete and has been tested thoroughly with a user space
application. In your next assignment, you will convert the character driver to a platform driver.
On embedded systems, devices are often not connected through a bus, allowing enumeration or
hotplugging for these devices.
However, you still want all of these devices to be part of the device model. Such devices, instead
of being dynamically detected, must be statically described described by using two different
methods:
1. By direct instantiation of platform_device structures, as done on a few old ARM
non-Device Tree based platforms. The definition is done in the board-specific or SoC
specific code.
2. In the Device Tree, a hardware description file used on some architectures. The device
driver matches with the physical devices described in the .dts file. After this matching, the
driver´s probe() function is called. An .of_match_table has to be included in the driver code
to allow this matching.
Amongst the non-discoverable devices, a huge family is directly part of a system-on-chip: UART
controllers, Ethernet controllers, SPI controllers, graphic or audio devices, etc. In the Linux
kernel, a special bus, called the platform bus, has been created to handle such devices. It supports
platform drivers that handle platform devices. It works like any other bus (USB, PCI), except that
the devices are enumerated statically instead of being discovered dynamically.
Each platform driver is responsible for instantiating and registering an instance of a platform_
driver structure within the device model core. Platform drivers follow the standard driver model
convention, where discovery/enumeration is handled outside the drivers, and drivers provide
probe() and remove() methods. Platform drivers support power management and shutdown
notifications using the standard conventions. The most important members of the platform_driver
structure are shown below:
struct platform_driver {
int (*probe)(struct platform_device *);
[ 89 ]
Platform Drivers Chapter 5
At a minimum, the probe() and remove() callbacks must be supplied. The probe() function is called
when the "bus driver" pairs the "device" to the "device driver". The probe() function is responsible
for initializing the device and registering it in the appropriate kernel framework:
1. It gets a pointer to a device structure as an argument (for example, struct pci_dev *, struct
usb_dev *, struct platform_device *, struct i2c_client *).
2. It initializes the device, maps I/O memory, allocates buffers, registers interrupt handlers,
etc.
3. It registers the device to specific framework(s).
The suspend() and resume() functions are used by devices that support low power management
features.
The platform driver responsible for the platform device should be registered to the platform core
by using the platform_driver_register() function. Register your platform driver in the module init()
function, and unregister it in the module exit() function, as shown in the following code snippet:
static int hello_init(void)
{
pr_info("demo_init enter\n");
platform_driver_register(&my_platform_driver);
pr_info("hello_init exit\n");
return 0;
}
module_init(hello_init);
module_exit(hello_exit);
You can also use the module_platform_driver() macro. This is a helper macro for drivers that don't
do anything special in the module init()/exit(). This eliminates a lot of boilerplate. Each module may
only use this macro once, and calling it replaces module_init() and module_exit().
[ 90 ]
Chapter 5 Platform Drivers
/*
* module_platform_driver() - Helper macro for drivers that don't do
* anything special in module init/exit. This eliminates a lot of
* boilerplate. Each module may only use this macro once, and
* calling it replaces module_init() and module_exit()
*/
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, platform_driver_unregister)
2. Declare a list of devices supported by the driver. Create an array of structures of type of_
device_id, and initialize their compatible fields with strings that will be used by the kernel to
bind your driver with the devices declared in the Device Tree that include the same strings
in their compatible properties. The platform bus driver will trigger the driver´s probe()
function if a match between device and driver occurs.
static const struct of_device_id my_of_ids[] = {
{ .compatible = "arrow,hellokeys"},
{},
}
[ 91 ]
Platform Drivers Chapter 5
MODULE_DEVICE_TABLE(of, my_of_ids);
3. Add a platform_driver structure that will be registered with the platform bus:
static struct platform_driver my_platform_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "hellokeys",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
4. After loading the kernel module, the function my_probe() will be called when a device
matching one of the supported device ids is discovered. The function my_remove() will be
called when the driver is unloaded. Therefore my_probe() does the role of the hello_init()
function, and my_remove() does the role of the hello_exit() function. So, it makes sense to
replace hello_init() with my_probe() and hello_exit() with my_remove():
static int __init my_probe(struct platform_device *pdev)
{
int ret_val;
pr_info("my_probe() function is called.\n");
ret_val = misc_register(&helloworld_miscdevice);
if (ret_val != 0) {
pr_err("could not register the misc device mydev");
return ret_val;
}
pr_info("mydev: got minor %i\n",helloworld_miscdevice.minor);
return 0;
}
6. Modify the Device Tree files (located in arch/arm/boot/dts/ in the kernel source tree),
including your DT device nodes. There must be a DT device node´s compatible property
identical to the compatible string stored in one of the driver´s of_device_id structures. Open
the bcm2710-rpi-3-b.dts DT file and add the hellokeys node in the soc node:
&soc {
virtgpio: virtgpio {
compatible = "brcm,bcm2835-virtgpio";
[ 92 ]
Chapter 5 Platform Drivers
gpio-controller;
#gpio-cells = <2>;
firmware = <&firmware>;
status = "okay";
};
hellokeys {
compatible = "arrow,hellokeys";
};
}
7. Create a new hellokeys_rpi3.c file in the linux_5.4_rpi3_drivers folder, and add hellokeys_rpi3.o
to your Makefile obj-m variable, then build and deploy the module to the Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
8. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
pr_info("my_dev_ioctl() is called. cmd = %d, arg = %ld\n", cmd, arg);
return 0;
}
[ 93 ]
Platform Drivers Chapter 5
.release = my_dev_close,
.unlocked_ioctl = my_dev_ioctl,
};
if (ret_val != 0) {
pr_err("could not register the misc device mydev");
return ret_val;
}
[ 94 ]
Chapter 5 Platform Drivers
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is the simplest platform driver");
hellokeys_rpi3.ko demonstration
See the Device Tree nodes under the root node:
root@raspberrypi:/home/pi# ls /proc/device-tree
'#address-cells' cpus model soc
aliases fixedregulator_3v3 name __symbols__
arm-pmu fixedregulator_5v0 __overrides__ system
axi interrupt-parent phy thermal-zones
chosen leds reserved-memory timer
clocks memory@0 serial-number
compatible memreserve '#size-cells'
root@raspberrypi:/home/pi# ls -l /sys/bus/platform/drivers/hellokeys/
total 0
--w------- 1 root root 4096 Apr 8 18:48 bind
lrwxrwxrwx 1 root root 0 Apr 8 18:48 module -> ../../../../module/hellokeys_rpi3
lrwxrwxrwx 1 root root 0 Apr 8 18:48 soc:hellokeys -> ../../../../devices/platform/soc/
soc:hellokeys
--w------- 1 root root 4096 Apr 8 18:43 uevent
--w------- 1 root root 4096 Apr 8 18:48 unbind
[ 95 ]
Platform Drivers Chapter 5
root@raspberrypi:/home/pi# ls -l /sys/module/hellokeys_rpi3/drivers/
total 0
lrwxrwxrwx 1 root root 0 Apr 8 18:49 platform:hellokeys -> ../../../bus/platform/drivers/hellokeys
root@raspberrypi:/home/pi# ls /sys/class/misc/
autofs cpu_dma_latency hw_random mydev vcsm-cma
cachefiles fuse loop-control rfkill watchdog
See the assigned mayor and minor numbers for the device mydev. The mayor number 10 is assigned by
the misc framework:
root@raspberrypi:/home/pi# ./ioctl_test
my_dev_open() is called.
my_dev_ioctl() is called. cmd = 100, arg = 110
my_dev_close() is called.
[ 96 ]
Chapter 5 Platform Drivers
[ 97 ]
Platform Drivers Chapter 5
[ 98 ]
Chapter 5 Platform Drivers
All the pins of the BCM2837 SoC are named in the pinctrl-bcm2835.c pinctrl driver, as shown in the
following code snippet. The pinctrl_pin_desc structure describes the pin name, pin index, etc.
/* pins are just named GPIO0..GPIO53 */
#define BCM2835_GPIO_PIN(a) PINCTRL_PIN(a, "gpio" #a)
static struct pinctrl_pin_desc bcm2835_gpio_pins[] = {
BCM2835_GPIO_PIN(0),
BCM2835_GPIO_PIN(1),
BCM2835_GPIO_PIN(2),
BCM2835_GPIO_PIN(3),
BCM2835_GPIO_PIN(4),
BCM2835_GPIO_PIN(5),
BCM2835_GPIO_PIN(6),
BCM2835_GPIO_PIN(7),
BCM2835_GPIO_PIN(8),
[...]
BCM2835_GPIO_PIN(45),
BCM2835_GPIO_PIN(46),
BCM2835_GPIO_PIN(47),
BCM2835_GPIO_PIN(48),
BCM2835_GPIO_PIN(49),
BCM2835_GPIO_PIN(50),
BCM2835_GPIO_PIN(51),
BCM2835_GPIO_PIN(52),
BCM2835_GPIO_PIN(53),
};
The pinctrl_dev structure will be used to instantiate a Pin Controller and register a descriptor to the
Pinctrl subsystem. This descriptor is represented by struct pinctrl_desc, which is used to describe the
information of a Pin Controller. The pinctrl_desc structure (declared in include/linux/pinctrl/pinctrl.h
in the kernel source tree) contains the pin definition, the pin configuration operation interface,
the pin muxing operation interface, and the pin group operation interface supported by the Pin
Controller of a SoC. The pinctrl_desc structure is declared as follows:
struct pinctrl_desc {
const char *name;
const struct pinctrl_pin_desc *pins;
unsigned int npins;
const struct pinctrl_ops *pctlops;
const struct pinmux_ops *pmxops;
const struct pinconf_ops *confops;
struct module *owner;
#ifdef CONFIG_GENERIC_PINCONF
unsigned int num_custom_params;
const struct pinconf_generic_params *custom_params;
const struct pin_config_item *custom_conf_items;
#endif
bool link_consumers;
};
[ 99 ]
Platform Drivers Chapter 5
Each physical pin is described through struct pin_desc. This structure is mainly used to record
the use count of the pin and describes the current configuration information of the pin (function
and group information the pin currently belongs to). The pin_desc structure is used by the
Pinctrl subsystem to determine whether a pin is used in multiple configurations with different
multiplexing conditions. The pin_desc structure is declared as follows:
/*
* struct pin_desc - pin descriptor for each physical pin in the arch
* @pctldev: corresponding pin control device
* @name: a name for the pin, e.g. the name of the pin/pad/finger on a
* datasheet or such
* @dynamic_name: if the name of this pin was dynamically allocated
* @drv_data: driver-defined per-pin data. pinctrl core does not touch this
* @mux_usecount: If zero, the pin is not claimed, and @owner should be NULL.
* If non-zero, this pin is claimed by @owner. This field is an integer
* rather than a boolean, since pinctrl_get() might process multiple
* mapping table entries that refer to, and hence claim, the same group
* or pin, and each of these will increment the @usecount.
* @mux_owner: The name of device that called pinctrl_get().
* @mux_setting: The most recent selected mux setting for this pin, if any.
* @gpio_owner: If pinctrl_gpio_request() was called for this pin, this is
* the name of the GPIO that "owns" this pin.
*/
struct pin_desc {
struct pinctrl_dev *pctldev;
const char *name;
bool dynamic_name;
void *drv_data;
/* These fields only added when supporting pinmux drivers */
#ifdef CONFIG_PINMUX
unsigned mux_usecount;
const char *mux_owner;
const struct pinctrl_setting_mux *mux_setting;
const char *gpio_owner;
#endif
};
[ 100 ]
Chapter 5 Platform Drivers
2. struct pinconf_ops: Pins can sometimes be configured in multiple ways by software, most
of which are related to their electrical characteristics when used as input/output. For
example, an output pin can be placed in a high-impedance state or "tri-state" (meaning
it is effectively disconnected). You can connect an input pin to VDD or GND by setting
a specific register value—pull-up/pull-down—so that there is a certain value on the pin
when there is no signal to drive the pin or when it is not connected. The pinconf_ops
structure is declared as follows:
/*
* struct pinconf_ops - pin config operations, to be implemented by
* pin configuration capable drivers.
* @is_generic: for pin controllers that want to use the generic interface,
* this flag tells the framework that it's generic.
* @pin_config_get: get the config of a certain pin, if the requested config
* is not available on this controller this should return -ENOTSUPP
* and if it is available but disabled it should return -EINVAL
* @pin_config_set: configure an individual pin
* @pin_config_group_get: get configurations for an entire pin group; should
* return -ENOTSUPP and -EINVAL using the same rules as pin_config_get.
* @pin_config_group_set: configure all pins in a group
* @pin_config_dbg_show: optional debugfs display hook that will provide
* per-device info for a certain pin in debugfs
* @pin_config_group_dbg_show: optional debugfs display hook that will provide
[ 101 ]
Platform Drivers Chapter 5
3. struct pinmux_ops: Pinmux, also known as padmux or ballmux is a way for chip
manufacturers to use a specific physical pin for multiple mutually exclusive functions.
The SoC will contain several I2C, SPI, SDIO/MMC, and other peripheral controllers which
can be routed to different pins through pin multiplexing settings. Because the number
of GPIOs is often insufficient, many of the unused pins of the peripheral controllers are
usually used as GPIO. The Pinctrl subsystem uses struct pinmux_ops to abstract pinmux
related operations. The pinmux_ops structure is declared as follows:
/*
* struct pinmux_ops - pinmux operations, to be implemented by pin controller
* drivers that support pinmuxing
* @request: called by the core to see if a certain pin can be made
* available for muxing. This is called by the core to acquire the pins
* before selecting any actual mux setting across a function. The driver
* is allowed to answer "no" by returning a negative error code
* @free: the reverse function of the request() callback, frees a pin after
* being requested
* @get_functions_count: returns number of selectable named functions available
* in this pinmux driver
[ 102 ]
Chapter 5 Platform Drivers
[ 103 ]
Platform Drivers Chapter 5
The Pinctrl driver needs to implement the callback operations which are specific to the Pin
Controller of the SoC. For example, the pinctrl-bcm2835.c pinctrl driver declares the following
callback operations:
static struct pinctrl_desc bcm2835_pinctrl_desc = {
.name = MODULE_NAME,
.pins = bcm2835_gpio_pins,
.npins = ARRAY_SIZE(bcm2835_gpio_pins),
.pctlops = &bcm2835_pctl_ops,
.pmxops = &bcm2835_pmx_ops,
.confops = &bcm2835_pinconf_ops,
.owner = THIS_MODULE,
};
static const struct pinctrl_ops bcm2835_pctl_ops = {
.get_groups_count = bcm2835_pctl_get_groups_count,
.get_group_name = bcm2835_pctl_get_group_name,
.get_group_pins = bcm2835_pctl_get_group_pins,
.pin_dbg_show = bcm2835_pctl_pin_dbg_show,
.dt_node_to_map = bcm2835_pctl_dt_node_to_map,
.dt_free_map = bcm2835_pctl_dt_free_map,
};
static const struct pinconf_ops bcm2835_pinconf_ops = {
.is_generic = true,
.pin_config_get = bcm2835_pinconf_get,
.pin_config_set = bcm2835_pinconf_set,
};
static const struct pinmux_ops bcm2835_pmx_ops = {
.free = bcm2835_pmx_free,
.get_functions_count = bcm2835_pmx_get_functions_count,
.get_function_name = bcm2835_pmx_get_function_name,
.get_function_groups = bcm2835_pmx_get_function_groups,
.set_mux = bcm2835_pmx_set,
.gpio_disable_free = bcm2835_pmx_gpio_disable_free,
.gpio_set_direction = bcm2835_pmx_gpio_set_direction,
};
Finally, struct pinctrl_desc is registered against the Pinctrl subsystem by calling the devm_pinctrl_
register() function (defined in drivers/pinctrl/core.c in the kernel source tree), which is called in the
probe() function of the pinctrl driver. In the following code snippet, extracted from the pinctrl-
bcm2835.c pinctrl driver, you can see the registration of the pinctrl_desc for the Rasperry Pi device.
pc->pctl_dev = devm_pinctrl_register(dev, &bcm2835_pinctrl_desc, pc);
if (IS_ERR(pc->pctl_dev)) {
gpiochip_remove(&pc->gpio_chip);
return PTR_ERR(pc->pctl_dev);
}
The devm_pinctrl_register() function calls pinctrl_register(), which is mainly divided into two
functions: pinctrl_init_controller() and pinctrl_enable(), as you can see in the following code snippet:
[ 104 ]
Chapter 5 Platform Drivers
error = pinctrl_enable(pctldev);
if (error)
return ERR_PTR(error);
return pctldev;
[ 105 ]
Platform Drivers Chapter 5
[ 106 ]
Chapter 5 Platform Drivers
.free = gpiochip_generic_free,
.direction_input = bcm2835_gpio_direction_input,
.direction_output = bcm2835_gpio_direction_output,
.get_direction = bcm2835_gpio_get_direction,
.get = bcm2835_gpio_get,
.set = bcm2835_gpio_set,
.set_config = gpiochip_generic_config,
.base = -1,
.ngpio = BCM2835_NUM_GPIOS,
.can_sleep = false,
};
The code implementing a gpio_chip structure should support multiple instances of the controller by
using the driver model. That code will configure each gpio_chip structure and issue gpiochip_add_
data(). In the following code snippet, extracted from the pinctrl-bcm2835.c pinctrl driver, you can see
the registration of the gpio_chip structure with the GPIOlib subsystem.
static int bcm2835_pinctrl_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct bcm2835_pinctrl *pc;
[...]
pc->gpio_chip = bcm2835_gpio_chip;
pc->gpio_chip.parent = dev;
pc->gpio_chip.of_node = np;
[...]
[...]
pc->gpio_range = bcm2835_pinctrl_gpio_range;
pc->gpio_range.base = pc->gpio_chip.base;
pc->gpio_range.gc = &pc->gpio_chip;
pinctrl_add_gpio_range(pc->pctl_dev, &pc->gpio_range);
return 0;
}
GPIO controllers can also provide interrupts, usually cascaded off a parent interrupt controller.
The IRQ portions of the GPIO block are implemented via an irq_chip structure (declared in include/
linux/irq.h in the kernel source tree). You will see a detailed description of the irq_chip structure in
Chapter 7.
[ 107 ]
Platform Drivers Chapter 5
In the following code snippet, extracted from the pinctrl-bcm2835.c pinctrl driver, you can
see the interrupt handler for the BCM2837 SoC:
static void bcm2835_gpio_irq_handler(struct irq_desc *desc)
{
struct gpio_chip *chip = irq_desc_get_handler_data(desc);
struct bcm2835_pinctrl *pc = gpiochip_get_data(chip);
struct irq_chip *host_chip = irq_desc_get_chip(desc);
int irq = irq_desc_get_irq(desc);
int group, i;
chained_irq_enter(host_chip, desc);
switch (group) {
case 0: /* IRQ0 covers GPIOs 0-27 */
bcm2835_gpio_irq_handle_bank(pc, 0, 0x0fffffff);
break;
case 1: /* IRQ1 covers GPIOs 28-45 */
bcm2835_gpio_irq_handle_bank(pc, 0, 0xf0000000);
bcm2835_gpio_irq_handle_bank(pc, 1, 0x00003fff);
break;
case 2: /* IRQ2 covers GPIOs 46-53 */
bcm2835_gpio_irq_handle_bank(pc, 1, 0x003fc000);
break;
}
chained_irq_exit(host_chip, desc);
}
[ 108 ]
Chapter 5 Platform Drivers
2. GENERIC CHAINED GPIO irqchips: These are the same as "CHAINED GPIO irqchips",
but chained IRQ handlers are not used. Instead, GPIO IRQs dispatching is performed by
the generic IRQ handler, which is configured using request_irq(). The GPIO irqchip will
then end up calling something like this sequence in its interrupt handler:
static irqreturn_t gpio_rcar_irq_handler(int irq, void *dev_id)
for each detected GPIO IRQ
generic_handle_irq(...);
3. NESTED THREADED GPIO irqchips: These are off-chip GPIO expanders and any other
GPIO irqchip residing on the other side of a sleeping bus. Of course, such drivers that need
slow bus traffic to read out the IRQ status and similar, traffic which may in turn incurs
other IRQs to happen, cannot be handled in a quick IRQ handler with IRQs disabled.
Instead, they need to spawn a thread and mask the parent IRQ line until the interrupt
is handled by the driver. The hallmark of this driver is to call something like this in its
interrupt handler:
static irqreturn_t foo_gpio_irq(int irq, void *data)
[...]
handle_nested_irq(irq);
In the LAB 7.4 of this book, you will implement a GPIO expander driver which includes a
nested threaded GPIO irqchip.
[ 109 ]
Platform Drivers Chapter 5
The devm_gpiod_get_index() variant of the devm_gpiod_get() function allows to access several GPIOs
defined inside a specific GPIO function. The devm_gpiod_get_index() function returns the GPIO
descriptor looking up the GPIO function (con_id) and its index (idx) from the Device Tree by using
the of_find_gpio() function:
struct gpio_desc *devm_gpiod_get_index(struct device *dev,
const char *con_id,
unsigned int idx,
enum gpiod_flags flags);
The flags parameter can specify a direction and initial value for the GPIO. These are some of its
most significant values:
• GPIOD_ASIS or 0 to not initialize the GPIO at all. The direction must be set later with one
of the dedicated functions.
• GPIOD_IN to initialize the GPIO as input.
• GPIOD_OUT_LOW to initialize the GPIO as output with a value of 0.
• GPIOD_OUT_HIGH to initialize the GPIO as output with a value of 1.
A GPIO descriptor can be disposed of by using the devm_gpiod_put() function.
Using GPIOs
Whenever you write a Linux driver that needs to control a GPIO, you must specify the GPIO
direction. This can be done using the parameter flags of a devm_gpiod_get*() function or calling later
a gpiod_direction_*() function if you have set the flags parameter to GPIOD_ASIS:
[ 110 ]
Chapter 5 Platform Drivers
The return value is zero for success, else a negative errno. For output GPIOs, the value provided
becomes the initial output value. This helps avoid signal glitching during system startup.
Most GPIO controllers can be accessed with memory read/write instructions. Those don't need to
sleep and can safely be done from inside hard (non-threaded) IRQ handlers.
You can use the following functions to access GPIOs from an atomic context:
int gpiod_get_value(const struct gpio_desc *desc);
void gpiod_set_value(struct gpio_desc *desc, int value);
As a driver should not have to care about the physical line level, all of the gpiod_set_value_xxx()
functions operate with the *logical* value. With this, they take the active-low property into
account. This means that they check whether the GPIO is configured to be active-low, and if so,
they manipulate the passed value before the physical line level is driven.
All the gpiod_set_value_xxx() functions interpret the parameter "value" as "active" ("1") or "inactive"
("0"). The physical line level will be driven accordingly.
As an example, if the active-low property for a dedicated GPIO is set and the gpiod_set_value_xxx()
passes "active" ("1"), the physical line level will be driven low.
To summarize:
Function (example) active-low property physical line
gpiod_set_value(desc, 0); default (active-high) low
gpiod_set_value(desc, 1); default (active-high) high
gpiod_set_value(desc, 0); active-low high
gpiod_set_value(desc, 1); active-low low
You will pass as a parameter the GPIO descriptor previously obtained using a devm_gpiod_get*()
function, then the gpiod_to_irq() function will return the Linux IRQ number corresponding to that
GPIO, or a negative errno code if the mapping can't be done. The gpiod_to_irq() function is not
allowed to sleep.
Non-error values returned from gpiod_to_irq() can be passed to the request_irq() or free_irq()
functions, which will request to obtain or release an interrupt. You will learn about these functions
in the Chapter 7.
[ 111 ]
Platform Drivers Chapter 5
Where &gpioa and &gpiob are the phandles to the specific gpio-controller nodes. Numbers 15, 16, 17
and 1 are the line offset for each gpio-controller, and GPIO_ACTIVE_HIGH is one of the flags used
for the GPIO.
The led-gpios property will make GPIOs 15, 16 and 17 of the gpioa controller available to the Linux
driver, and power-gpios will make GPIO 1 of the gpiob controller available to the driver:
struct gpio_desc *red, *green, *blue, *power;
red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);
green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH);
blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);
power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);
The second parameter of the gpiod_get*() functions, the con_id string, has to be identical to the
function prefix of the gpios suffixes used in the Device Tree. In the previous Device Tree example,
you will use the next con_id parameters: "led" and "power" to obtain each GPIO descriptor from
the foo_device. For the led-gpios property, in addition to the con_id parameter "led", you will also
need to set the index (idx) with values 0, 1 or 2 in the gpiod_get_index() function to get each GPIO
descriptor.
[ 112 ]
Chapter 5 Platform Drivers
APIs in user space. When a process executes a system call, the kernel will execute in process
context on behalf of the calling process. When the kernel responds to interrupts, the interrupt
handler runs asynchronously in interrupt context.
A driver for a device is the interface between an application and hardware. Accessing process
address space from the kernel cannot be done directly (by de-referencing a user-space pointer).
Direct access of a user space pointer can lead to incorrect behavior (depending on architecture,
a user-space pointer may not be valid or mapped to kernel space), a kernel oops (the user-mode
pointer can refer to a non-resident memory area) or security issues. Proper access to user space
data is done by calling the macros/functions below:
1. A single value:
get_user(type val, type *address);
The val kernel variable gets the value pointed by the address user space pointer.
put_user(type val, type *address);
The value pointed by the address user space pointer is set to the contents of the val kernel
variable.
2. A buffer:
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
copy_to_user() copies n bytes from the kernel space address pointed by from, to the user-
space address pointed by to.
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
copy_from_user() copies n bytes from the user space address pointed by from, to the kernel
space address pointed by to.
[ 113 ]
Platform Drivers Chapter 5
2. PIO
• Different address spaces for memory and I/O devices.
• Uses a special class of CPU instructions to access I/O devices.
The BCM2837 SoC uses the MMIO access, so this method will be described in more detail during
this section.
The Linux driver cannot access physical I/O addresses directly - MMU mapping is needed. For
accessing I/O memory, drivers need to have a virtual address that the processor can handle,
because I/O memory is not mapped by default in virtual memory.
You can obtain this I/O virtual address by using two different functions:
1. Map and remove mapping by using ioremap()/iounmap() functions. The ioremap() function
accepts the physical address and the size of the area. It returns a pointer to virtual memory
that can be dereferenced (or NULL if mapping is impossible).
void __iomem *ioremap(phys_addr_t offset, unsigned long size);
void iounmap(void *address);
2. Map and remove mapping attached to the driver device by using the devm_ioremap() and
devm_iounmap() managed functions (declared in include/linux/io.h and defined in lib/devres.c
in the kernel source tree), which simplify the driver code and error handling. Using
ioremap() in device drivers is now deprecated.
void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,
unsigned long size);
void devm_iounmap(struct device *dev, void __iomem *addr);
Each device (basic device structure) manages a linked list of resources via its included list_head
devres_head structure. Calling a managed resource allocator involves adding the resource to the
list. The resources are released in reverse order when the probe() function exits with an error
status or after the remove() function returns. The use of managed functions in the probe() function
removes the needed resource´s releases on error handling, replacing goto's and other resource´s
releases with just a return. It also removes resource´s releases in the remove() function.
Dereferencing the pointer returned by devm_ioremap() is not reliable. Cache and synchronization
issues may occur. The kernel provides functions to read and write to virtual addresses. To do
PCI-style, little-endian accesses, conversion is being done automatically using the functions below:
unsigned read[bwl](void *addr);
void write[bwl](unsigned val, void *addr);
[ 114 ]
Chapter 5 Platform Drivers
There are "generic" interfaces for doing new-style memory-mapped or PIO accesses. Architectures
may do their own arch-optimized versions. These just act as wrappers around the old-style IO
register access functions read[bwl]/write[bwl]/in[bwl]/out[bwl]:
unsigned int ioread8(void __iomem *addr);
unsigned int ioread16(void __iomem *addr);
unsigned int ioread32(void __iomem *addr);
void iowrite8(u8 value, void __iomem *addr);
void iowrite16(u16 value, void __iomem *addr);
void iowrite32(u32 value, void __iomem *addr);
The following figure represents the GPIO base address (0x3F200000) mapping for the BCM2837
SoC. In the driver of the LAB 5.3, you can see how this register address is mapped to a virtual
address by using the devm_ioremap() function.
[ 115 ]
Platform Drivers Chapter 5
LEDs using write() and read() driver´s operations. You will use the copy_to_user() and copy_from_
user() functions to exchange character arrays between the kernel and user spaces.
[ 116 ]
Chapter 5 Platform Drivers
To obtain the LEDs, you will use the Color click™ accessory board with mikroBUS™ form factor.
See the Color click™ accessory board at https://www.mikroe.com/color-click. You can download the
schematics from that link or from the GitHub repository of this book.
Connect the Raspberry Pi GPIO27 pin to the Color click™ RD pin, GPIO22 pin to GR and GPIO26
to BL. Connect 5V power and GND between both boards.
[ 117 ]
Platform Drivers Chapter 5
gpio: gpio@7e200000 {
compatible = "brcm,bcm2835-gpio";
reg = <0x7e200000 0xb4>;
/*
* The GPIO IP block is designed for 3 banks of GPIOs.
* Each bank has a GPIO interrupt for itself.
* There is an overall "any bank" interrupt.
* In order, these are GIC interrupts 17, 18, 19, 20.
* Since the BCM2835 only has 2 banks, the 2nd bank
* interrupt output appears to be mirrored onto the
* 3rd bank's interrupt signal.
* So, a bank0 interrupt shows up on 17, 20, and
* a bank1 interrupt shows up on 18, 19, 20!
*/
interrupts = <2 17>, <2 18>, <2 19>, <2 20>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
i2c0_gpio0: i2c0_gpio0 {
brcm,pins = <0 1>;
brcm,function = <BCM2835_FSEL_ALT0>;
};
i2c0_gpio28: i2c0_gpio28 {
brcm,pins = <28 29>;
brcm,function = <BCM2835_FSEL_ALT0>;
};
[...]
spi0_gpio7: spi0_gpio7 {
[ 118 ]
Chapter 5 Platform Drivers
uart1_gpio40: uart1_gpio40 {
brcm,pins = <40 41>;
brcm,function = <BCM2835_FSEL_ALT5>;
};
uart1_ctsrts_gpio42: uart1_ctsrts_gpio42 {
brcm,pins = <42 43>;
brcm,function = <BCM2835_FSEL_ALT5>;
};
};
The compatible property value of the gpio@7e200000 node matches with the compatible field value
included in the pinctrl-bcm2835.c pinctrl driver.
The pin controller device should contain the pin configuration nodes for each client device. The
contents of each of those pin configuration child nodes are defined entirely by the binding for
the individual pin controller device. There exists no common standard for this content. Each pin
configuration node lists the pin(s) to which it applies, one or more of the mux function to select
on those pin(s), and pull-up/down configuration. These are the required properties for the pin
controller of the BCM2837 SoC:
• brcm,pins: An array of cells. Each cell contains the ID of a pin. Valid IDs are the integer
GPIO IDs; 0==GPIO0, 1==GPIO1, ... 53==GPIO53.
• brcm,function: Integer, containing the function to mux to the pin(s):
0: GPIO in
1: GPIO out
2: alt5
3: alt4
4: alt0
5: alt1
6: alt2
7: alt3
• brcm,pull: Integer, representing the pull-down/up to apply to the pin(s):
0: none
1: down
2: up
[ 119 ]
Platform Drivers Chapter 5
Each of brcm,function and brcm,pull may contain either a single value which will be applied to all
pins in brcm,pins, or 1 value for each entry in brcm,pins.
For further info, go to the Device Tree pinctrl documentation binding folder, located at
Documentation/devicetree/bindings/pinctrl/, and examine the brcm,bcm2835-gpio.txt file.
For this lab, modify the bcm2710-rpi-3-b.dts Device Tree file by adding the led_pins pin
configuration node in the gpio node and the ledred, ledgreen and ledblue device nodes in the SoC
node:
/ {
model = "Raspberry Pi 3 Model B";
};
&gpio {
spi0_pins: spi0_pins {
brcm,pins = <9 10 11>;
brcm,function = <4>; /* alt0 */
};
spi0_cs_pins: spi0_cs_pins {
brcm,pins = <8 7>;
brcm,function = <1>; /* output */
};
i2c0_pins: i2c0 {
brcm,pins = <0 1>;
brcm,function = <4>;
};
[...]
led_pins: led_pins {
brcm,pins = <27 22 26>;
brcm,function = <1>; /* Output */
brcm,pull = <1 1 1>; /* Pull down */
};
};
&soc {
virtgpio: virtgpio {
compatible = "brcm,bcm2835-virtgpio";
gpio-controller;
#gpio-cells = <2>;
firmware = <&firmware>;
status = "okay";
};
hellokeys {
compatible = "arrow,hellokeys";
};
[ 120 ]
Chapter 5 Platform Drivers
ledred {
compatible = "arrow,RGBleds";
label = "ledred";
pinctrl-0 = <&led_pins>;
};
ledgreen {
compatible = "arrow,RGBleds";
label = "ledgreen";
};
ledblue {
compatible = "arrow,RGBleds";
label = "ledblue";
};
[...]
};
LAB 5.2 code description of the "RGB LED platform device" module
The main code sections of the driver will now be described:
1. Include the function headers:
#include <linux/module.h>
#include <linux/fs.h> /* struct file_operations */
#include <linux/platform_device.h> /* platform_driver_register(), platform_set_drvdata()
*/
#include <linux/io.h> /* devm_ioremap(), iowrite32() */
#include <linux/of.h> /* of_property_read_string() */
#include <linux/uaccess.h> /* copy_from_user(), copy_to_user() */
#include <linux/miscdevice.h> /* misc_register() */
2. Define the GPIO masks that will be used to configure the GPIO registers. See below the masks
for the BCM2837 processor:
#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26
[ 121 ]
Platform Drivers Chapter 5
/*
* To select GPIO pin 22, you will set to 0b111 the bits 8-6 (FSEL22) of the
* GPIO Alternate function select register 2. The GPIO pin 22 will control the green LED
*/
#define FSEL_22_MASK 0b111 << ((GPIO_22 % 10) * 3)
/*
* To select GPIO pin 26, you will set to 0b111 the bits 20-18 (FSEL26) of the
* GPIO Alternate function select register 2. The GPIO pin 26 will control the blue LED
*/
#define FSEL_26_MASK 0b111 << ((GPIO_26 % 10) * 3) /* blue since bit 18 (FSEL26) */
4. Declare the __iomen pointers that will hold the virtual addresses returned by the ioremap()
function:
static void __iomem *GPFSEL2_V;
static void __iomem *GPSET0_V;
static void __iomem *GPCLR0_V;
5. You will create an led_dev private structure to hold each device-specific information. In this
driver, you will handle multiple char devices, so a miscdevice structure will be created for each
device, then initialized and added to your device-specific data structure. The second field of the
data structure is an led_mask variable that will hold a red, green or blue mask depending on the
device. The last field of the private structure is a char array that will hold the command sent by
the user space application to turn the LED on/off.
struct led_dev
{
struct miscdevice led_misc_device; /* assign device for each led */
u32 led_mask; /* different mask if led is R,G or B */
[ 122 ]
Chapter 5 Platform Drivers
6. Now, in your probe() routine, declare an instance of the private structure, and allocate it for
each new probed device. The probe() function will be called three times (once per each DT node
(ledred, ledgreen and ledblue) that matches its compatible property value with the compatible field
value of the driver), allocating the corresponding devices.
struct led_dev *led_device;
led_device = devm_kzalloc(&pdev->dev, sizeof(struct led_dev), GFP_KERNEL);
7. In the led_init() routine, obtain the virtual addresses of the register addresses by using the
ioremap() function, and store them in the __iomem pointers:
GPFSEL2_V = ioremap(GPFSEL2, sizeof(u32));
GPSET0_V = ioremap(GPSET0, sizeof(u32));
GPCLR0_V = ioremap(GPCLR0, sizeof(u32));
8. Initialize each miscdevice structure within the probe() routine. As you have seen in the
Chapter 4, the miscellaneous framework provides a simple layer above character files
for devices that don’t have any other framework to connect to. Registering with the misc
subsystem simplifies the creation of a character file. The of_property_read_string() function
will find and read a string from the label property of each probed DT device node. The
third argument of the function is a pointer to a char pointer variable. The of_property_read_
string() function will store the "label" string in the address pointed by the led_name pointer
variable.
of_property_read_string(pdev->dev.of_node, "label", &led_device->led_name);
led_device->led_misc_device.minor = MISC_DYNAMIC_MINOR;
led_device->led_misc_device.name = led_device->led_name;
led_device->led_misc_device.fops = &led_fops;
9. When you are creating a character file, a file_operations structure is needed to define which
driver functions to call when a user opens, closes, reads and writes to the char file. This
structure will be stored in the miscdevice structure and passed to the misc subsystem when
you register a device to it. One thing to note is that when you use the misc subsystem, it
will automatically handle the "open" function for you. Inside the automatically created
"open" function, it will tie the miscdevice structure to the private data field of the file that’s
been opened. This is useful to get access to the parent structure of the miscdevice structure
by using the container_of() macro.
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.read = led_read,
.write = led_write,
};
[ 123 ]
Platform Drivers Chapter 5
10. In the probe() routine, register each device with the kernel by using the misc_register()
function. The platform_set_drvdata() function will attach each private structure to each
platform_device structure. This will allow you to access your private data structure in other
functions of the driver. You will recover the private structure in each remove() function call
(called three times) by using the platform_get_drvdata() function:
ret_val = misc_register(&led_device->led_misc_device);
platform_set_drvdata(pdev, led_device);
11. Create the led_write() function that gets called whenever a write operation occurs on
one of the character files. At the time you registered each misc device, you didn’t keep
any pointer to the led_dev structure. However, as the miscdevice structure is accessible
through file->private_data and is a member of the lev_dev structure, you can use a magic
macro to compute the address of the parent structure; the container_of() macro gets the
structure that miscdevice is stored inside of (which is your led_dev structure). The copy_
from_user() function will get the on/off command from user space, then you will write to
the corresponding register of the processor to switch on/off the LED using the iowrite32()
function:
static ssize_t led_write(struct file *file, const char __user *buff,
size_t count, loff_t *ppos)
{
const char *led_on = "on";
const char *led_off = "off";
struct led_dev *led_device;
led_device = container_of(file->private_data,
struct led_dev, led_misc_device);
led_device->led_value[count-1] = '\0';
if(!strcmp(led_device->led_value, led_on)) {
iowrite32(led_device->led_mask, GPSET0_V);
}
else if (!strcmp(led_device->led_value, led_off)) {
iowrite32(led_device->led_mask, GPCLR0_V);
}
else {
pr_info("Bad value\n");
return -EINVAL;
[ 124 ]
Chapter 5 Platform Drivers
return count;
}
12. Create the led_read() function that gets called whenever a read operation occurs on one
of the character device files. You will use the container_of() macro to recover the private
structure and the copy_to_user() function to return the led_value variable (on/off) to the user
application:
static ssize_t led_read(struct file *file, char __user *buff,
size_t count, loff_t *ppos)
{
struct led_dev *led_device;
if(*ppos == 0) {
if(copy_to_user(buff, &led_device->led_value,
sizeof(led_device->led_value))) {
pr_info("Failed to return led_value to user space\n");
return -EFAULT;
}
*ppos+=1;
return sizeof(led_device->led_value);
}
return 0;
}
13. Declare a list of devices supported by the driver. Create an array of structures of type
of_device_id where you initialize with strings the compatible fields that will be used
by the kernel to bind your driver with the compatible Device Tree devices. This will
automatically trigger your driver´s probe() function if the Device Tree contains a compatible
device entry (the probing happens three times in this driver).
static const struct of_device_id my_of_ids[] = {
{ .compatible = " arrow,RGBleds"},
{},
}
MODULE_DEVICE_TABLE(of, my_of_ids);
14. Add a platform_driver structure that will be registered to the platform bus:
static struct platform_driver led_platform_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "RGBleds",
.of_match_table = my_of_ids,
[ 125 ]
Platform Drivers Chapter 5
.owner = THIS_MODULE,
}
};
15. In the init() function, register your driver with the platform bus driver by using the
platform_driver_register() function:
platform_driver_register(&led_platform_driver);
16. Create a new ledRGB_rpi3_platform.c file in the linux_5.4_rpi3_drivers folder, and add ledRGB_
rpi3_platform.o to your Makefile obj-m variable, then build and deploy the module to the
Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
17. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
struct led_dev
{
struct miscdevice led_misc_device;
u32 led_mask; /* different mask if led is R,G or B */
const char *led_name;
char led_value[8];
};
#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26
[ 126 ]
Chapter 5 Platform Drivers
pr_info("led_write() is called.\n");
/*
* In the command line “echo” add a \n character.
* led_device->led_value = "on\n" or "off\n after copy_from_user()"
*/
if(copy_from_user(led_device->led_value, buff, count)) {
pr_info("Bad copied value\n");
return -EFAULT;
}
[ 127 ]
Platform Drivers Chapter 5
iowrite32(led_device->led_mask, GPSET0_V);
}
else if (!strcmp(led_device->led_value, led_off)) {
iowrite32(led_device->led_mask, GPCLR0_V);
}
else {
pr_info("Bad value\n");
return -EINVAL;
}
pr_info("led_write() is exit.\n");
return count;
}
static ssize_t led_read(struct file *file, char __user *buff, size_t count, loff_t *ppos)
{
struct led_dev *led_device;
pr_info("led_read() is called.\n");
if(*ppos == 0){
if(copy_to_user(buff, &led_device->led_value, sizeof(led_device->led_value))) {
pr_info("Failed to return led_value to user space\n");
return -EFAULT;
}
*ppos+=1;
return sizeof(led_device->led_value);
}
pr_info("led_read() is exit.\n");
return 0;
}
pr_info("leds_probe enter\n");
led_device = devm_kzalloc(&pdev->dev, sizeof(struct led_dev), GFP_KERNEL);
[ 128 ]
Chapter 5 Platform Drivers
led_device->led_misc_device.name = led_device->led_name;
led_device->led_misc_device.fops = &led_fops;
if (strcmp(led_device->led_name,"ledred") == 0) {
led_device->led_mask = GPIO_27_INDEX;
}
else if (strcmp(led_device->led_name,"ledgreen") == 0) {
led_device->led_mask = GPIO_22_INDEX;
}
else if (strcmp(led_device->led_name,"ledblue") == 0) {
led_device->led_mask = GPIO_26_INDEX;
}
else {
pr_info("Bad device tree value\n");
return -EINVAL;
}
/* Initialize the led status to off */
memcpy(led_device->led_value, led_val, sizeof(led_val));
ret_val = misc_register(&led_device->led_misc_device);
if (ret_val) return ret_val; /* misc_register returns 0 if success */
platform_set_drvdata(pdev, led_device);
pr_info("leds_probe exit\n");
return 0;
}
pr_info("leds_remove enter\n");
misc_deregister(&led_device->led_misc_device);
pr_info("leds_remove exit\n");
return 0;
}
[ 129 ]
Platform Drivers Chapter 5
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
ret_val = platform_driver_register(&led_platform_driver);
if (ret_val !=0)
{
pr_err("platform value returned %d\n", ret_val);
return ret_val;
}
/*
* Set FSEL27, FSEL26 and FSEL22 of GPFSEL2 register to 0,
* keeping the value of the rest of GPFSEL2 bits,
* then set to 1 the first bit of FSEL27, FSEL26 and FSEL22 to set
* the direction of GPIO27, GPIO26 and GPIO22 to output
*/
GPFSEL_write = (GPFSEL_read & ~GPIO_MASK_ALL_LEDS) | (GPIO_SET_FUNCTION_LEDS & GPIO_MASK_
ALL_LEDS);
pr_info("demo_init exit\n");
return 0;
}
iounmap(GPFSEL2_V);
iounmap(GPSET0_V);
iounmap(GPCLR0_V);
platform_driver_unregister(&led_platform_driver);
[ 130 ]
Chapter 5 Platform Drivers
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a platform driver that turns on/off three led devices");
ledRGB_rpi3_platform.ko demonstration
Load the module. The probe() function is called three times:
root@raspberrypi:/home/pi# insmod ledRGB_rpi3_platform.ko
demo_init enter
leds_probe enter
leds_probe exit
leds_probe enter
leds_probe exit
leds_probe enter
leds_probe exit
demo_init exit
See the led devices that have been created:
root@raspberrypi:/home/pi# ls /dev/led*
/dev/ledblue /dev/ledgreen /dev/ledred
Switch ON and OFF the LEDs and check their status:
[ 131 ]
Platform Drivers Chapter 5
[ 132 ]
Chapter 5 Platform Drivers
The first parameter of the previous function tells the function which device you are
interested in so that it can extract the info needed. The second parameter depends on what
kind of resource you are handling. If it is memory (or anything that can be mapped as
memory), then it's IORESOURCE_MEM. You can see all the macros in include/linux/ioport.h
in the kernel source tree. The last parameter determines which resource of that type is
desired, with zero indicating the first one. Thus, for example, a driver could find and map
its second MMIO region using the following lines of code:
struct resource *r;
r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
The return value r is a pointer to the resource variable. The resource_size() function will
return from the resource structure the memory size that will be mapped:
static inline resource_size_t resource_size(const struct resource *res)
{
return res->end - res->start + 1;
}
2. platform_get_irq() -- This function extracts the resource structure from the platform_device
structure, retrieving one of the interrupts properties declared in the Device Tree node. This
function will be explained in more detail in the Chapter 7.
[ 133 ]
Platform Drivers Chapter 5
driver in the more general sense of the word. The device model has specific objects called "drivers"
but a "class" is not one of those.
All the devices in a particular class tend to expose much the same interface, either to other devices
or in user space (via sysfs or otherwise). Exactly how uniform the included devices are, is really
up to the class though. Some aspects of the interfaces are optional, and not all devices implement
them. It is not unheard-of for some devices in the same class to be completely different from
others.
The LED class supports the blinking, flashing and brightness control features of physical LEDs.
This class requires an underlying device to be available (/sys/class/leds/<device>/). This underlying
device must be able to turn the LED on or off, may be able to set the brightness and might even
provide timer functionality to autonomously blink the LED with a given period and duty cycle.
Using the brightness file under each device subdirectory, the appropriate LED could be set to
different brightness levels, for example, not just turned on and off but also dimmed. The data type
used for passing the brightness level, enum led_brightness, defines only the levels LED_OFF,
LED_HALF and LED_FULL:
enum led_brightness {
LED_OFF = 0,
LED_HALF = 127,
LED_FULL = 255,
};
The LED class introduces the optional concept of LED trigger. A trigger is a kernel based source
of LED events. The timer trigger is an example that will periodically change the LED brightness
between LED_OFF and the current brightness setting. The "on" and "off" time can be specified via
/sys/class/leds/<device>/delay_{on,off} sysfs entry in milliseconds. You can change the brightness
value of an LED independently of the timer trigger. However, if you set the brightness value to
LED_OFF, it will also disable the timer trigger.
A driver registering an LED class device will first allocate and fill an led_classdev structure
(declared in include/linux/leds.h in the kernel source tree), then it will call devm_led_classdev_
register(), which registers a new LED class object. The struct led_classdev is declared as follows:
struct led_classdev {
const char *name;
enum led_brightness brightness;
enum led_brightness max_brightness;
[...]
/*
* Set LED brightness level. Use brightness_set_blocking for drivers
* that can sleep while setting brightness.
*/
[ 134 ]
Chapter 5 Platform Drivers
/*
* Activate hardware accelerated blink, delays are in milliseconds
* and if both are zero then a sensible default should be chosen.
* The call should adjust the timings in that case and if it can't
* match the values specified exactly.
* Deactivate blinking again when the brightness is set to LED_OFF
* via the brightness_set() callback.
*/
int (*blink_set)(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off);
[...]
#ifdef CONFIG_LEDS_TRIGGERS
/* Protects the trigger data below */
struct rw_semaphore trigger_lock;
/*
* devm_led_classdev_register - resource managed led_classdev_register()
* @parent: The device to register.
* @led_cdev: the led_classdev structure for this device.
*/
int devm_led_classdev_register(struct device *parent,
struct led_classdev *led_cdev)
{
struct led_classdev **dr;
int rc;
[ 135 ]
Platform Drivers Chapter 5
rc = led_classdev_register(parent, led_cdev);
if (rc) {
devres_free(dr);
return rc;
}
*dr = led_cdev;
devres_add(parent, dr);
return 0;
}
[ 136 ]
Chapter 5 Platform Drivers
virtgpio: virtgpio {
compatible = "brcm,bcm2835-virtgpio";
gpio-controller;
#gpio-cells = <2>;
firmware = <&firmware>;
status = "okay";
};
[...]
ledclassRGB {
compatible = "arrow,RGBclassleds";
reg = <0x7e200000 0xb4>;
pinctrl-names = "default";
pinctrl-0 = <&led_pins>;
red {
label = "red";
};
green {
label = "green";
};
blue {
label = "blue";
linux,default-trigger = "heartbeat";
};
};
};
2. Define the GPIO masks that will be used to configure the GPIO registers of the SoC. You
will take the base address stored in the DT reg property as a reference, then you will add
an offset to it to set each register address. See below the masks for the BCM2837 processor:
#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26
[ 137 ]
Platform Drivers Chapter 5
/*
* To select GPIO pin 27, you will set to 0b111 the bits 23-21 (FSEL7) of the
* GPIO Alternate function select register 2. The GPIO pin 27 will control the red LED
*/
#define FSEL_27_MASK 0b111 << ((GPIO_27 % 10) * 3)
/*
* To select GPIO pin 22, you will set to 0b111 the bits 8-6 (FSEL22) of the
* GPIO Alternate function select register 2. The GPIO pin 22 will control the green LED
*/
#define FSEL_22_MASK 0b111 << ((GPIO_22 % 10) * 3)
/*
* To select GPIO pin 26, you will set to 0b111 the bits 20-18 (FSEL26) of the
* GPIO Alternate function select register 2. The GPIO pin 26 will control the blue LED
*/
#define FSEL_26_MASK 0b111 << ((GPIO_26 % 10) * 3) /* blue since bit 18 (FSEL26) */
#define GPIO_SET_FUNCTION_LEDS (GPIO_27_FUNC | GPIO_22_FUNC | GPIO_26_FUNC)
#define GPIO_MASK_ALL_LEDS (FSEL_27_MASK | FSEL_22_MASK | FSEL_26_MASK)
#define GPIO_SET_ALL_LEDS (GPIO_27_INDEX | GPIO_22_INDEX | GPIO_26_INDEX)
3. You will create a private structure to hold the the specific data of the RGB LED device. In
this driver, the first field of the private structure is an led_mask variable that will hold a
red, green or blue mask depending of the LED device under control. The second field of
the private structure is an __iomem pointer that will hold the GPIO register base address.
The last field of the private structure is an led_classdev structure that will be initialized with
some specific device settings. You will allocate a private structure for each sub-node device
found.
struct led_dev
{
u32 led_mask; /* different mask if led is R,G or B */
void __iomem *base;
struct led_classdev cdev;
};
[ 138 ]
Chapter 5 Platform Drivers
4. See below a code snippet, extracted from the probe() routine, with the main lines of code
marked in bold:
• The platform_get_resource() function gets the I/O registers resource described by the DT
reg property.
• The dev_ioremap() function maps the area of register addresses to kernel virtual
addresses.
• The for_each_child_of_node() function walks for each sub-node of the main node,
allocating a private structure for each one by using the devm_kzalloc() function, then
initializes the led_classdev field of each private structure.
• The devm_led_classdev_register() function registers each LED class device to the LED
subsystem.
static int ledclass_probe(struct platform_device *pdev)
{
void __iomem *g_ioremap_addr;
struct device_node *child;
struct resource *r;
u32 GPFSEL_read, GPFSEL_write;
struct device *dev = &pdev->dev;
int ret = 0;
int count;
pr_info("platform_probe enter\n");
for_each_child_of_node(dev->of_node, child) {
cdev = &led_device->cdev;
led_device->base = g_ioremap_addr;
of_property_read_string(child, "label", &cdev->name);
if (strcmp(cdev->name,"red") == 0) {
led_device->led_mask = GPIO_27_INDEX;
}
else if (strcmp(cdev->name,"green") == 0) {
led_device->led_mask = GPIO_22_INDEX;
[ 139 ]
Platform Drivers Chapter 5
}
else if (strcmp(cdev->name,"blue") == 0) {
led_device->led_mask = GPIO_26_INDEX;
}
else {
pr_info("Bad device tree value\n");
return -EINVAL;
}
return 0;
}
5. Write the led_control() function. Every time you write to the brightness sysfs entry (/sys/
class/leds/<device>/brightness) under each device, the led_control() function is called. The
LED subsystem hides the complexity of creating a class, the devices under the class, and
the sysfs entries under each of the devices. Every time you write to the brightness sysfs
entry, the container_of() function recovers the private structure associated with each device,
then you can write to each register by using the iowrite32() function, which takes as a first
parameter the led_mask value (stored in the private structure) associated to each LED. The
following code snippet shows the led_control() function:
static void led_control(struct led_classdev *led_cdev, enum led_brightness b)
{
struct led_dev *led = container_of(led_cdev, struct led_dev, cdev);
if (b != LED_OFF) /* LED ON */
iowrite32(led->led_mask, led->base + GPSET0_offset);
else
iowrite32(led->led_mask, led->base + GPCLR0_offset); /* LED OFF */
}
[ 140 ]
Chapter 5 Platform Drivers
8. In the init() function, register your driver with the platform bus by using the platform_
driver_register() function:
static int led_init(void)
{
ret_val = platform_driver_register(&led_platform_driver);
return 0;
}
10. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26
[ 141 ]
Platform Drivers Chapter 5
#define FSEL_27_MASK 0b111 << ((GPIO_27 % 10) * 3) /* red since bit 21 (FSEL27) */
#define FSEL_22_MASK 0b111 << ((GPIO_22 % 10) * 3) /* green since bit 6 (FSEL22) */
#define FSEL_26_MASK 0b111 << ((GPIO_26 % 10) * 3) /* blue since bit 18 (FSEL26) */
struct led_dev
{
u32 led_mask; /* there are different masks if led is R,G or B */
void __iomem *base;
struct led_classdev cdev;
};
if (b != LED_OFF) /* LED ON */
iowrite32(led->led_mask, led->base + GPSET0_offset);
else
iowrite32(led->led_mask, led->base + GPCLR0_offset); /* LED OFF */
}
pr_info("platform_probe enter\n");
[ 142 ]
Chapter 5 Platform Drivers
if (!r) {
pr_err("IORESOURCE_MEM, 0 does not exist\n");
return -EINVAL;
}
pr_info("r->start = 0x%08lx\n", (long unsigned int)r->start);
pr_info("r->end = 0x%08lx\n", (long unsigned int)r->end);
count = of_get_child_count(dev->of_node);
if (!count)
return -EINVAL;
for_each_child_of_node(dev->of_node, child) {
cdev = &led_device->cdev;
led_device->base = g_ioremap_addr;
of_property_read_string(child, "label", &cdev->name);
if (strcmp(cdev->name,"red") == 0) {
led_device->led_mask = GPIO_27_INDEX;
//led_device->cdev.default_trigger = "heartbeat";
}
else if (strcmp(cdev->name,"green") == 0) {
led_device->led_mask = GPIO_22_INDEX;
}
else if (strcmp(cdev->name,"blue") == 0) {
led_device->led_mask = GPIO_26_INDEX;
[ 143 ]
Platform Drivers Chapter 5
}
else {
pr_info("Bad device tree value\n");
return -EINVAL;
}
return 0;
}
return 0;
}
ret_val = platform_driver_register(&led_platform_driver);
if (ret_val !=0)
[ 144 ]
Chapter 5 Platform Drivers
{
pr_err("platform value returned %d\n", ret_val);
return ret_val;
}
pr_info("demo_init exit\n");
return 0;
}
platform_driver_unregister(&led_platform_driver);
module_init(ledRGBclass_init);
module_exit(ledRGBclass_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a driver that turns on/off RGB leds using the LED subsystem");
ledRGB_rpi3_class_platform.ko demonstration
Load the module:
root@raspberrypi:/home/pi# insmod ledRGB_rpi3_class_platform.ko
demo_init enter
platform_probe enter
r->start = 0x3f200000
r->end = 0x3f2000b3
there are 3 nodes
leds_probe exit
demo_init exit
See the devices under the LED class:
root@raspberrypi:/home/pi# ls /sys/class/leds/
blue default-on green led0 led1 mmc0 red
Switch ON and OFF the LEDs, and blink the green LED:
root@raspberrypi:/home/pi# echo 1 > /sys/class/leds/red/brightness
root@raspberrypi:/home/pi# echo 1 > /sys/class/leds/blue/brightness
root@raspberrypi:/home/pi# echo 1 > /sys/class/leds/green/brightness
root@raspberrypi:/home/pi# cd /sys/class/leds/green
root@raspberrypi:/sys/class/leds/green# ls
brightness device max_brightness power subsystem trigger uevent
root@raspberrypi:/sys/class/leds/green# echo timer > trigger
[ 145 ]
Platform Drivers Chapter 5
[ 146 ]
Chapter 5 Platform Drivers
The main advantages and disadvantages of using user space and kernel space drivers are
summarized below:
1. User space driver advantages:
• Easy to debug, as debug tools are more readily available for application development.
• User space services such as floating point are available.
• Device access is very efficient, as there is no system call required.
• The application API on Linux is very stable.
• The driver can be written in any language, not just C.
2. User space driver disadvantages:
• No access to the kernel frameworks and services.
• Interrupt handling cannot be done in user space. It must be handled by a kernel
driver.
• There is no predefined API to allow applications to access the device driver.
3. Kernel space driver advantages:
• Run in kernel space in the highest privilege mode to allow access to interrupts and
hardware resources.
• There are a lot of kernel services such that kernel space drivers can be designed for
complex devices.
• The kernel provides an API to user space which allows multiple applications to access
a kernel space driver simultaneously.
4. Kernel space driver disadvantages:
• System call overhead to access drivers.
• Challenging to debug.
• Frequent kernel API changes. Kernel drivers built for one kernel version may not build
for another.
[ 147 ]
Platform Drivers Chapter 5
The following image shows how a user space driver might be designed. The application interfaces
to the user space part of the driver. The user space part handles the hardware, but uses its kernel
space part for startup, shutdown and receiving interrupts.
[ 148 ]
Chapter 5 Platform Drivers
[ 149 ]
Platform Drivers Chapter 5
The UIO platform device driver can be replaced by a user provided kernel driver. The kernel space
driver is a platform driver configured from the Device Tree that registers a UIO device inside the
probe() function. The Device Tree node can use whatever you want in the compatible property, as
it only has to match with the compatible string used in the kernel space driver, as with any other
platform device driver.
The UIO drivers must be enabled in the kernel. Configure the kernel with menuconfig. Navigate
from the main menu -> Device Drivers -> Userspace I/O drivers. Hit <spacebar> once to see a <*>
appear next to the new configuration. Hit <Exit> until you exit the menuconfig GUI and remember
to save the new configuration. In the Building the Linux kernel section of Chapter 1, you enabled the
UIO drivers in the kernel, built the new kernel image and loaded it onto your Raspberry Pi.
[ 150 ]
Chapter 5 Platform Drivers
[ 151 ]
Platform Drivers Chapter 5
generated interrupt but want to trigger the interrupt handler in some other way, set
irq to UIO_IRQ_CUSTOM. If you had no interrupt at all, you could set irq to
UIO_IRQ_NONE, though this rarely makes sense.
• unsigned long irq_flags: Required if you’ve set irq to a hardware interrupt number.
The flags given here will be used in the call to request_irq().
• int (*mmap)(struct uio_info *info, struct vm_area_struct *vma): Optional. If you need
a special mmap() function, you can set it here. If this pointer is not NULL, your mmap()
will be called instead of the built-in one.
• int (*open)(struct uio_info *info, struct inode *inode): Optional. You might want to
have your own open(), e.g. to enable interrupts only when your device is actually used.
• int (*release)(struct uio_info *info, struct inode *inode): Optional. If you define your
own open(), you will probably also want a custom release() function.
• int (*irqcontrol)(struct uio_info *info, s32 irq_on): Optional. If you need to be able
to enable or disable interrupts from user space by writing to /dev/uioX, you can
implement this function.
Usually, your device will have one or more memory regions that can be mapped to user
space. For each region, you have to set up a uio_mem structure in the mem[] array. Here’s a
description of the fields of the uio_mem structure:
• int memtype: Required if mapping is used. Set this to UIO_MEM_PHYS if you have
physical memory to be mapped. Use UIO_MEM_LOGICAL for logical memory (for
example, allocated with kmalloc()). There’s also UIO_MEM_VIRTUAL for virtual
memory.
• unsigned long size: Fill in the size of the memory block that addr points to. If the size
is zero, the mapping is considered unused. Note that you must initialize size with zero
for all unused mappings.
• void *internal_addr: If you have to access this memory region from within your
kernel module, you will want to map it internally by using something like ioremap().
Addresses returned by this function cannot be mapped to user space, so you must not
store it in addr. Use internal_addr instead to remember such an address.
2. The function uio_register_device() connects the driver to the UIO framework:
• Requires a uio_info structure as an input.
• It is typically called from the probe() function of a platform device driver.
• It creates the device file /dev/uio# (#starting from 0) and all the associated sysfs file
attributes.
• The function uio_unregister_device() disconnects the driver from the UIO framework,
deleting the device file /dev/uio#.
[ 152 ]
Chapter 5 Platform Drivers
[...]
UIO {
compatible = "arrow,UIO";
reg = <0x7e200000 0x1000>;
pinctrl-names = "default";
pinctrl-0 = <&led_pins>;
};
};
[ 153 ]
Platform Drivers Chapter 5
3. In the probe() function, the platform_get_resource() function returns the resource structure
filled with the values described by the DT reg property. The dev_ioremap() function maps
the area of register addresses to kernel virtual addresses:
struct resource *r;
void __iomem *g_ioremap_addr;
[ 154 ]
Chapter 5 Platform Drivers
}
};
9. Create a new led_rpi3_UIO_platform.c file in the linux_5.4_rpi3_drivers folder, and add led_
rpi3_UIO_platform.o to your Makefile obj-m variable, then build and deploy the module to
the Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
10. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
The main code sections of the UIO user space driver will now be described:
1. Include the function headers:
#include <sys/mman.h> /* mmap() */
2. Define the path to the sysfs parameter, from where you will obtain the size of memory that
is going to be mapped:
#define UIO_SIZE "/sys/class/uio/uio0/maps/map0/size"
[ 155 ]
Platform Drivers Chapter 5
[ 156 ]
Chapter 5 Platform Drivers
return 0;
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a UIO platform driver that turns the LED on/off \
without using system calls");
#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26
[ 157 ]
Platform Drivers Chapter 5
#define FSEL_27_MASK 0b111 << ((GPIO_27 % 10) * 3) /* red since bit 21 (FSEL27) */
#define FSEL_22_MASK 0b111 << ((GPIO_22 % 10) * 3) /* green since bit 6 (FSEL22) */
#define FSEL_26_MASK 0b111 << ((GPIO_26 % 10) * 3) /* blue since bit 18 (FSEL26) */
int main()
{
int ret, devuio_fd;
int mem_fd;
unsigned int uio_size;
void *temp;
int GPFSEL_read, GPFSEL_write;
void *demo_driver_map;
char sendstring[BUFFER_LENGHT];
char *led_on = "on";
char *led_off = "off";
char *Exit = "exit";
[ 158 ]
Chapter 5 Platform Drivers
/* Do the mapping */
demo_driver_map = mmap(0, uio_size, PROT_READ | PROT_WRITE, MAP_SHARED, devuio_fd, 0);
if(demo_driver_map == MAP_FAILED) {
perror("devuio mmap error");
close(devuio_fd);
exit(EXIT_FAILURE);
}
if(strncmp(led_on, sendstring, 3) == 0)
{
temp = demo_driver_map + GPSET0_offset;
*(int *)temp = GPIO_27_INDEX;
}
else if(strncmp(led_off, sendstring, 2) == 0)
{
temp = demo_driver_map + GPCLR0_offset;
*(int *)temp = GPIO_27_INDEX;
}
else if(strncmp(Exit, sendstring, 4) == 0)
printf("Exit application\n");
else {
printf("Bad value\n");
return -EINVAL;
}
[ 159 ]
Platform Drivers Chapter 5
close(devuio_fd);
printf("Application termined\n");
exit(EXIT_SUCCESS);
}
[ 160 ]
6
I2C Client Drivers
I2C is a protocol developed by Philips which original purpose was to link a CPU to other circuits
in television sets. It is a two-wire, bi-directional serial bus for slow speed digital data that links one
or more slave devices to a master (one or more bus controllers), providing a simple and efficient
method of data transmission. The I2C protocol is widely used with embedded systems.
SMBus (System Management Bus) is a derivative developed by Intel based on the I2C protocol.
The most common devices connected through SMBus are RAM modules configured using I2C
EEPROMs and hardware monitoring chips that monitor critical parameters on PC motherboards
and embedded systems. Because the SMBus is mostly a subset of the generalized I2C bus, you can
use its protocols on many I2C systems. However, there are systems that don't meet both SMBus
and I2C electrical constraints; and others which can't implement all the common SMBus protocol
semantics or messages.
If you write a driver for an I2C device, please try to use the SMBus commands if at all possible (if
the device uses only that subset of the I2C protocol). This makes it possible to use the device driver
on both SMBus adapters and I2C adapters (the SMBus command set is automatically translated
to I2C on I2C adapters, but plain I2C commands can not be handled at all on most pure SMBus
adapters). These are the functions used to establish a plain I2C communication:
int i2c_master_send(struct i2c_client *client, const char *buf, int count);
int i2c_master_recv(struct i2c_client *client, char *buf, int count);
The previous routines read and write some bytes from/to a client. The first parameter is a pointer
to struct i2c_client, which contains the I2C address of the client device. The second parameter is a
pointer to the buffer that will store the data read from the slave and the data that will be written
to the slave. The third parameter is the number of bytes to read/write (must be less than the length
of the buffer, also should be less than 64k since msg.len is u16.). Returned is the actual number of
bytes read/written.
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msg, int num);
The previous function sends a series of messages. Each message can be a read or write, and they
can be mixed in any way. The transactions are combined: no stop bit is sent between transactions.
[ 161 ]
I2C Client Drivers Chapter 6
The i2c_msg structure contains for each message, the client address, the number of bytes of the
message and the message data itself.
This is the generic function used to establish an SMBus communication:
s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
unsigned short flags, char read_write, u8 command,
int size, union i2c_smbus_data *data);
All the functions below are implemented in terms of it. Never use the function i2c_smbus_xfer()
directly.
s32 i2c_smbus_read_byte(struct i2c_client *client);
s32 i2c_smbus_write_byte(struct i2c_client *client, u8 value);
s32 i2c_smbus_read_byte_data(struct i2c_client *client, u8 command);
s32 i2c_smbus_write_byte_data(struct i2c_client *client, u8 command, u8 value);
s32 i2c_smbus_read_word_data(struct i2c_client *client, u8 command);
s32 i2c_smbus_write_word_data(struct i2c_client *client, u8 command, u16 value);
s32 i2c_smbus_read_block_data(struct i2c_client *client, u8 command, u8 *values);
s32 i2c_smbus_write_block_data(struct i2c_client *client, u8 command,
u8 length, const u8 *values);
s32 i2c_smbus_read_i2c_block_data(struct i2c_client *client, u8 command,
u8 length, u8 *values);
s32 i2c_smbus_write_i2c_block_data(struct i2c_client *client, u8 command,
u8 length, const u8 *values);
[ 162 ]
Chapter 6 I2C Client Drivers
In the probe() function, this adapter structure is initialized for each I2C controller that has
been probed:
adap = &i2c_dev->adapter;
i2c_set_adapdata(adap, i2c_dev);
adap->owner = THIS_MODULE;
adap->class = I2C_CLASS_DEPRECATED;
snprintf(adap->name, sizeof(adap->name), "bcm2835 (%s)",
of_node_full_name(pdev->dev.of_node));
adap->algo = &bcm2835_i2c_algo;
[ 163 ]
I2C Client Drivers Chapter 6
adap->dev.parent = &pdev->dev;
adap->dev.of_node = pdev->dev.of_node;
Finally, in the probe() function, each I2C controller is added to the I2C bus core by calling
the i2_add_numbered_adapter() function (defined in drivers/i2c/i2c-core.c):
i2c_add_adapter(adap);
3. The I2C device drivers are located throughout drivers/, depending on the type of device
(for example, drivers/input/ for input devices). The driver code is specific to the device
(for example, accelerometer, digital analog converter) and uses the I2C core API to send
and receive data to/from the I2C device. For example, if the I2C client driver calls the
i2c_smbus_write_byte_data() function (defined in drivers/i2c/i2c-core.c), you can see that this
function calls the i2c_smbus_xfer() function:
s32 i2c_smbus_read_word_data(const struct i2c_client *client, u8 command)
{
union i2c_smbus_data data;
int status;
If you check the code of the i2c_smbus_xfer() function, you can see that this function calls
the i2c_smbus_xfer_emulated() function, which in turn calls the master_xfer() method:
i2c_smbus_read_word_data() -> i2c_smbus_xfer() -> i2c_smbus_xfer_emulated() -> i2c_
transfer() -> __i2c_transfer() -> bcm2835_i2c_xfer()
For the BCM2837 SoC, the master_xfer variable points to the bcm2835_i2c_xfer() function,
which calls to the specific code that writes and reads the I2C controller registers.
See the I2C subsystem in the following figure. In the Linux device model, the of_platform_
populate() function will register the I2C controller devices to the platform bus core. For the
BCM2837 processor, the i2c-bcm2835.c controller driver registers itself to the platform bus
core. The I2C client drivers are registered by themselves to the I2C bus core.
[ 164 ]
Chapter 6 I2C Client Drivers
[ 165 ]
I2C Client Drivers Chapter 6
The i2c_add_driver() and i2c_del_driver() functions are used to register and unregister the driver.
They are included in the init() and exit() functions. If the driver doesn´t do anything else in these
functions, use the module_i2c_driver() macro instead.
static int __init i2c_init(void)
{
return i2c_add_driver(&ioaccel_driver);
}
module_init(i2c_init);
In your device driver, create an array of of_device_id structures where you specify .compatible
strings that should store the same value of the DT device node´s compatible property. The of_
device_id structure is declared in include/linux/mod_devicetable.h in the kernel source tree as follows:
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
};
The of_match_table field (included in the driver field) of the i2c_driver structure is a pointer to the
array of of_device_id structures that hold the compatible strings supported by the driver:
static const struct of_device_id ioaccel_dt_ids[] = {
{ .compatible = "fsl,mma8451", },
{ }
};
MODULE_DEVICE_TABLE(of, ioaccel_dt_ids);
The driver´s probe() function is called when the compatible field in one of the of_device_id entries
matches with the compatible property of a DT device node. The probe() function is responsible for
initializing the device with the configuration values obtained from the matched DT device node
and also to register the device to the appropriate kernel framework.
In your I2C device driver, you also have to define an array of i2c_device_id structures. This array is
pointed to by the id_table field of struct i2c_driver. The id_table is used for non-DT based probing of
I2C devices. If your driver only uses DT based probing, then you can use the probe_new() function
instead of the probe() one.
static const struct i2c_device_id mma8451_id[] = {
{ "mma8450", 0 },
{ "mma8451", 1 },
{ }
};
[ 166 ]
Chapter 6 I2C Client Drivers
MODULE_DEVICE_TABLE(i2c, mma8451_id);
The second argument of the probe() function is an element of this array related to your attached
device:
static ioaccel_probe(struct i2c_client *client, const struct i2c_device_id *id);
The binding will happen based on the i2c_device_id table or Device Tree compatible string. The I2C
core first tries to match the device by compatible string (OF style, which is Device Tree), and if it
fails, it then tries to match the device using the id_table.
i2c2: i2c@7e805000 {
compatible = "brcm,bcm2835-i2c";
reg = <0x7e805000 0x1000>;
interrupts = <2 21>;
clocks = <&clocks BCM2835_CLOCK_VPU>;
#address-cells = <1>;
#size-cells = <0>;
[ 167 ]
I2C Client Drivers Chapter 6
status = "disabled";
};
The Device Tree declaration for I2C devices is done as sub-nodes of the master controller at the
board/platform level (arch/arm/boot/dts/bcm2710-rpi-3-b.dts):
• The I2C controller device is enabled (status = "okay").
• The I2C bus frequency is defined by using the clock-frequency property.
• The I2C devices on the bus are described as children of the I2C controller node. The reg
property provides the I2C slave address on the bus.
• In the I2C device node, check that the compatible property matches with one of the driver´s
of_device_id compatible strings.
[...]
ioexp@39 {
compatible = "arrow,ioexp";
reg = <0x39>;
[ 168 ]
Chapter 6 I2C Client Drivers
};
}
See below the i2c1_pins pin configuration node, where the I2C controller pads are multiplexed as
I2C signals:
i2c1_pins: i2c1 {
brcm,pins = <2 3>; /* GPIO2 and GPIO3 pins */
brcm,function = <4>;
/* ALT0 mux function */
};
You can see that the GPIO2 and GPIO3 pins are set to the ALT0 function. See the meaning of
brcm,function = <4> in the brcm,bcm2835-gpio.txt document located in Documentation/devicetree/
bindings/pinctrl/ in the kernel source tree.
Open the file BCM2835 ARM Peripherals guide, and find the table included in the section 6.2
Alternative Function Assignments. You can see in the following screenshot that GPIO2 and GPIO3
pins must be programmed to ALT0 to be multiplexed as I2C signals.
LAB 6.1 code description of the "I2C I/O expander device" module
The main code sections of the driver will now be described:
1. Include the function headers:
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/uaccess.h>
2. You will create a private structure to store the device-specific information. In this driver,
the first field of the ioexp_dev private structure is an i2c_client structure used to handle the
I2C device. The second field of the private structure is a miscdevice structure. The misc
subsystem will automatically handle the open() function for you; inside the automatically
created open() function, it will tie your miscdevice structure to the ioexp_dev one, for the
file that’s been opened. In this way, in your write/read kernel callback functions, you
can recover the miscdevice structure, which will allow you to get access to the i2c_client
structure (included in the ioexp_dev structure). Once you get the i2c_client structure, you
[ 169 ]
I2C Client Drivers Chapter 6
can read/write each I2C specific device by using the SMBus functions. The last field of the
private structure is a char array that will store the name of the I2C device.
struct ioexp_dev {
struct i2c_client * client;
struct miscdevice ioexp_miscdevice;
char name[8]; /* ioexpXX */
};
3. Create a file_operations structure to define which driver functions are called when the
user reads and writes to the character devices. This structure will be passed to the misc
subsystem when you register a device to it.
static const struct file_operations ioexp_fops = {
.owner = THIS_MODULE,
.read = ioexp_read_file,
.write = ioexp_write_file,
};
4. In the probe() function, allocate the private structure by calling the devm_kzalloc() function.
Initialize each misc device and register it with the kernel by using the misc_register() function.
The i2c_set_clientdata() function attaches each allocated private structure to the i2c_client one,
which will allow you to access your private data structure in other functions of the driver. For
example, you will recover the private structure in each remove() function call (once per each
device attached to the bus) by using the i2c_get_clientdata() function.
static int ioexp_probe(struct i2c_client * client, const struct i2c_device_id * id)
{
static int counter = 0;
struct ioexp_dev * ioexp;
/*
* Initialize the misc device, ioexp is incremented
* after each probe call
*/
sprintf(ioexp->name, "ioexp%02d", counter++);
ioexp->ioexp_miscdevice.name = ioexp->name;
ioexp->ioexp_miscdevice.minor = MISC_DYNAMIC_MINOR;
ioexp->ioexp_miscdevice.fops = &ioexp_fops;
[ 170 ]
Chapter 6 I2C Client Drivers
return 0;
}
5. Create the ioexp_write_file() kernel callback function, which gets called whenever a user
space write operation occurs on one of the character devices. At the time you registered
each misc device, you didn’t keep any pointer to the private ioexp_dev structure. However,
as the miscdevice structure is accessible through file->private_data and is a member of the
ioexp_dev structure, you can use the container_of() macro to compute the address of your
private structure and recover the i2c_client structure from it. The copy_from_user() function
will get a char array from user space with values ranging from "0" to "255". This value will
be converted from a char string to an unsigned long value, then you will write it to the I2C
ioexp device by using the i2c_smbus_write_byte() SMBus function. You will also write an
ioexp_read_file() kernel callback function, which reads the ioexp device input and sends the
value to user space. See below a code snippet of the ioexp_write_file() function:
static ssize_t ioexp_write_file(struct file *file, const char __user *userbuf,
size_t count, loff_t *ppos)
{
int ret;
unsigned long val;
char buf[4];
struct ioexp_dev * ioexp;
return count;
}
[ 171 ]
I2C Client Drivers Chapter 6
10. Create a new io_rpi3_expander.c file in the linux_5.4_rpi3_drivers folder, and add io_rpi3_
expander.o to your Makefile obj-m variable, then build and deploy the module to the
Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
11. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
[ 172 ]
Chapter 6 I2C Client Drivers
/*
* Convert expval int value into a char string.
* For example, 255 int (4 bytes) = FF (2 bytes) + '\0' (1 byte) string.
*/
size = sprintf(buf, "%02x", expval); /* size is 2 */
/*
* Replace NULL by \n. It is not needed to have a char array
* ended with \0 character.
*/
buf[size] = '\n';
[ 173 ]
I2C Client Drivers Chapter 6
return 0;
}
dev_info(&ioexp->client->dev,
"ioexp_write_file entered on %s\n", ioexp->name);
dev_info(&ioexp->client->dev,
"we have written %zu characters\n", count);
if(copy_from_user(buf, userbuf, count)) {
dev_err(&ioexp->client->dev, "Bad copied value\n");
return -EFAULT;
}
dev_info(&ioexp->client->dev,
"ioexp_write_file exited on %s\n", ioexp->name);
return count;
}
[ 174 ]
Chapter 6 I2C Client Drivers
/* Initialize the misc device, ioexp is incremented after each probe call */
sprintf(ioexp->name, "ioexp%02d", counter++);
dev_info(&client->dev, "ioexp_probe is entered on %s\n", ioexp->name);
ioexp->ioexp_miscdevice.name = ioexp->name;
ioexp->ioexp_miscdevice.minor = MISC_DYNAMIC_MINOR;
ioexp->ioexp_miscdevice.fops = &ioexp_fops;
dev_info(&client->dev,
"ioexp_probe is exited on %s\n", ioexp->name);
return 0;
}
dev_info(&client->dev,
"ioexp_remove is entered on %s\n", ioexp->name);
dev_info(&client->dev,
"ioexp_remove is exited on %s\n", ioexp->name);
return 0;
}
[ 175 ]
I2C Client Drivers Chapter 6
module_i2c_driver(ioexp_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a driver that controls several I2C IO expanders");
io_rpi3_expander.ko demonstration
Scan I2C bus for devices:
root@raspberrypi:/home/pi# i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- 39 -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Load the module:
root@raspberrypi:/home/pi# insmod io_rpi3_expander.ko
io_rpi3_expander: loading out-of-tree module taints kernel.
ioexp 1-0039: ioexp_probe is entered on ioexp00
[ 176 ]
Chapter 6 I2C Client Drivers
[ 177 ]
I2C Client Drivers Chapter 6
When ENRGB/S is low, the RGB display will be off. If no other displays are programmed to be on,
the entire chip will be in shutdown.
If bit A2 is set to 1 (SUB display control), then ENRGB/S will have no effect on the RGB display.
Likewise, if bit A2 is set to 0 (RGB display control), then ENRGB/S will have no effect on the SUB
display.
If the ENRGB/S pin is not used, it should be connected to DVCC. It should not be grounded or left
floating.
In the page 9 of the LTC3206 datasheet you can see the bit assignments.
To test the driver, you can use the DC749A - Demo Board (http://www.analog.com/en/design-center/
evaluation-hardware-and-software/evaluation-boards-kits/dc749a.html). You will use the pin 6 of the DC749A
J1 connector to control the ENRGB/S pin, connecting it to a GPIO pin of the Raspberry Pi. Connect the
SDA signal of the Raspberry Pi to the pin 7 of the J1 connector and the SCL signal of the Raspberry
Pi to the pin 4 of the J1 connector. Connect 3.3V between the Raspberry Pi and the J20 DVCC pin. Do
not forget to connect GND between DC749A and the Raspberry Pi. If you do not want to enable the
ENRGB/S pin, connect it to DVCC.
To develop the driver, you will use the LED subsystem. Every LED Display device (R, G, B, SUB and
MAIN) will be registered to the LED subsystem by using the devm_led_classdev_register() function,
being created five devices (red, green, blue, sub and main) under the /sys/class/leds/ directory. These
five devices will be accessed within the kernel driver space throughout the same driver´s led_control()
function, which will be called every time you write to the brightness sysfs entry from user space. Two
additional sysfs entries will be created, allowing to switch from RGB to SUB device and vice versa.
These two sysfs functions set the control A2 bit, enabling the ENRGB/S pin to perform the switching.
[ 178 ]
Chapter 6 I2C Client Drivers
Open and modify the bcm2710-rpi-3-b.dts Device Tree file by adding the ltc3206@1b sub-node in
the i2c1 controller master node. The pinctrl-0 property of the ltc3206 node points to the cs_pins pin
configuration node, where the GPIO23 pin is multiplexed as a GPIO signal. The gpios property
will make the GPIO23 available to the driver so that you can set up the pin direction to output and
drive the physical line level from 0 to 1 to control the ENRGB/S pin. The reg property provides the
I2C address of the LTC3206 device. In the ltc3206 node, there are five sub-nodes representing the
different display devices. Each of the five nodes has a label property so that the driver can identify
and create devices with the label names:
&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins>;
clock-frequency = <100000>;
status = "okay";
[...]
ltc3206: ltc3206@1b {
compatible = "arrow,ltc3206";
reg = <0x1b>;
pinctrl-0 = <&cs_pins>;
gpios = <&gpio 23 GPIO_ACTIVE_LOW>;
led1r {
label = "red";
};
led1b {
label = "blue";
};
led1g {
label = "green";
};
ledmain {
label = "main";
};
ledsub {
label = "sub";
};
};
};
See below the cs_pins pin configuration node, where the GPIO23 pin is multiplexed as a GPIO
signal:
cs_pins: cs_pins {
brcm,pins = <23>;
[ 179 ]
I2C Client Drivers Chapter 6
};
For all of these functions, the first argument is a pointer to a fwnode_handle structure (declared
in include/linux/fwnode.h in the kernel source tree), which allows a device description object
(depending on what platform firmware interface is in use) to be obtained.
/* fwnode.h - Firmware device node object handle type definition. */
enum fwnode_type {
FWNODE_INVALID = 0,
FWNODE_OF,
FWNODE_ACPI,
FWNODE_ACPI_DATA,
FWNODE_PDATA,
FWNODE_IRQCHIP,
};
struct fwnode_handle {
enum fwnode_type type;
struct fwnode_handle *secondary;
};
[ 180 ]
Chapter 6 I2C Client Drivers
The function device_for_each_child_node() iterates over the children of the device description object
associated with a given device (for example, struct device *dev = &client->dev), and the function
device_get_child_node_count() returns the number of child nodes of a given device.
2. Define the masks that will be used to select the specific I2C commands:
#define CMD_RED_SHIFT 4
#define CMD_BLUE_SHIFT 4
#define CMD_GREEN_SHIFT 0
#define CMD_MAIN_SHIFT 4
#define CMD_SUB_SHIFT 0
#define EN_CS_SHIFT (1 << 2)
3. Create a private structure named led_device that will store specific data for each of the
five LED devices. The first field of the led_device structure is the brightness variable, that
will hold values ranging from "0" to "15". The second field is an led_classdev structure, that
will be filled for each led device within the probe() function. The last field is a pointer to
a private structure that will hold global data shared with all the LED devices. This global
structure will be analyzed in the next point.
struct led_device {
u8 brightness;
struct led_classdev cdev;
struct led_priv *private;
};
4. Create a private structure that will store global data accessible to all the LED devices. The
first field of the private structure is the num_leds variable, which will hold the number of
led devices declared in the Device Tree. The second field is an array of three commands
that will hold the command values sent to the LTC3206 device in each of the I2C
transactions. The display_cs variable is a pointer to a gpio_desc structure that will allow you
to control the ENRGB/S pin, and the last field is a pointer to an i2c_client structure that will
allow you to recover the I2C address of the LTC3206 device.
struct led_priv {
u32 num_leds;
u8 command[3];
[ 181 ]
I2C Client Drivers Chapter 6
5. These are the main points to set up the driver within the probe() function:
• Declare a pointer to a fwnode_handle structure and a pointer to the led_priv global
structure.
• Get the number of LED devices by calling the device_get_child_node_count() function.
• Allocate the global structure by calling devm_kzalloc(), and store the pointer to your
client device on it (private->client = client). The i2c_set_clientdata() function attaches your
private structure to the i2c_client one.
• Get the gpio descriptor, and store it in the global private structure (private->display_cs =
devm_gpiod_get(dev, NULL, GPIOD_ASIS)). Set the gpio pin direction to output and the pin
physical level to low (gpiod_direction_output(private->display_cs, 1)). In one of the gpios
property fields of the Device Tree, is declared GPIO_ACTIVE_LOW, meaning that gpiod_
set_value(desc, 1) will set the physical line to low and gpiod_set_value(desc, 0) to high.
• The device_for_each_child_of_node() function walks through each LED child node,
allocating an led_device private structure for each one by using the devm_kzalloc()
function and initializing the led_classdev structure included in each private structure.
The fwnode_property_read_string() function reads each LED node´s label property and
stores it in the cdev->name field of each led_device structure.
• The devm_led_classdev_register() function registers each LED class device to the LED
subsystem.
• Finally, add a group of "sysfs attribute files" to control the ENRGB/S pin by using the
function sysfs_create_group().
static int __init ltc3206_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct fwnode_handle *child;
struct device *dev = &client->dev;
struct led_priv *private;
device_get_child_node_count(dev);
[ 182 ]
Chapter 6 I2C Client Drivers
if (strcmp(cdev->name,"main") == 0) {
led_device->cdev.brightness_set_blocking = led_control;
devm_led_classdev_register(dev, &led_device->cdev);
}
else if (strcmp(cdev->name,"sub") == 0) {
led_device->cdev.brightness_set_blocking = led_control;
devm_led_classdev_register(dev, &led_device->cdev);
}
else if (strcmp(cdev->name,"red") == 0) {
led_device->cdev.brightness_set_blocking = led_control;
ret = devm_led_classdev_register(dev, &led_device->cdev);
}
else if (strcmp(cdev->name,"green") == 0) {
led_device->cdev.brightness_set_blocking = led_control;
ret = devm_led_classdev_register(dev, &led_device->cdev);
}
else if (strcmp(cdev->name,"blue") == 0) {
led_device->cdev.brightness_set_blocking = led_control;
ret = devm_led_classdev_register(dev, &led_device->cdev);
}
else {
dev_err(dev, "Bad device tree value\n");
return -EINVAL;
}
private->num_leds++;
}
dev_info(dev, "i am out of the device tree\n");
dev_info(dev, "my_probe() function is exited.\n");
return 0;
}
6. Write the LED brightness led_control() function. Every time your user space application
writes to the brightness sysfs entry (/sys/class/leds/<device>/brightness) under each LED
device, the driver´s led_control() function is called. The LED subsystem hides the
complexity of creating a class, the devices under the class and the sysfs entries under
each of the devices. The led_device structure associated with each device is recovered
by using the container_of() function. Depending of the cdev->name value (included in the
[ 183 ]
I2C Client Drivers Chapter 6
led_device structure), different masks are applied to the char array command values, then
the updated values are stored in the led_priv global structure. Finally, you will send the
updated command values to the LTC3206 device by using the ltc3206_led_write() function,
which calls to the plain i2c_master_send() function.
7. In the probe() function, you added a group of "sysfs attribute files" to control the ENRGB/S
pin by writing the line of code sysfs_create_group(&client->dev.kobj, &display_cs_group). Now,
you will create two structures of type device_attribute with the respective names 'rgb' and
'sub', and you will organize these two attributes into a group:
static DEVICE_ATTR(rgb, S_IWUSR, NULL, rgb_select);
static DEVICE_ATTR(sub, S_IWUSR, NULL, sub_select);
8. Write the rgb_select() and sub_select() functions that will be called each time the user
application writes "on" or "off" to the rgb" and "sub" sysfs entries. Inside these functions,
you will recover the i2c_client structure by using the to_i2c_client() function, then the i2c_
get_clientdata() function will recover the led_priv global structure. The i2c_get_clientdata()
function takes the previously recovered i2c_client structure as a parameter. Once you have
retrieved the global structure, you can update the bit A2 of the command[0] by using the
mask EN_CS_SHIFT, then you will send the new command values to the LTC3206 device
by using the ltc3206_led_write() function. Depending of the selected "on" or "off" value, the
GPIO physical line will be set from "low to high" or from "high to low" by using the gpiod_
set_value() function, which takes as a parameter the gpio descriptor stored in your led_priv
global structure.
9. Declare a list of devices supported by the driver:
static const struct of_device_id my_of_ids[] = {
{ .compatible = "arrow, ltc3206", },
{ }
};
MODULE_DEVICE_TABLE(of, my_of_ids);
[ 184 ]
Chapter 6 I2C Client Drivers
{ }
};
MODULE_DEVICE_TABLE(i2c, ltc3206_id);
11. Add an i2c_driver structure that will be registered to the I2C bus:
static struct i2c_driver ltc3206_driver = {
.probe = ltc3206_probe,
.remove = ltc3206_remove,
.id_table = ltc3206_id,
.driver = {
.name = "ltc3206",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
13. Create a new ltc3206_rpi3_led_class.c file in the linux_5.4_rpi3_drivers folder, and add
ltc3206_rpi3_led_class.o to your Makefile obj-m variable, then build and deploy the module
to the Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
14. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
[ 185 ]
I2C Client Drivers Chapter 6
/*
* Store the global parameters shared for the 5 led devices.
* The parameters are updated after each led_control() call
*/
struct led_priv {
u32 num_leds;
u8 command[3];
struct gpio_desc *display_cs;
struct i2c_client *client;
};
buffer = buf;
[ 186 ]
Chapter 6 I2C Client Drivers
client = to_i2c_client(dev);
private = i2c_get_clientdata(client);
return count;
}
static DEVICE_ATTR(sub, S_IWUSR, NULL, sub_select);
*(buffer+(count-1)) = '\0';
ltc3206_led_write(private->client, private->command);
if(!strcmp(buffer, "on")) {
gpiod_set_value(private->display_cs, 1); /* low */
usleep_range(100, 200);
gpiod_set_value(private->display_cs, 0); /* high */
}
else if (!strcmp(buffer, "off")) {
gpiod_set_value(private->display_cs, 0); /* high */
usleep_range(100, 200);
gpiod_set_value(private->display_cs, 1); /* low */
}
else {
dev_err(&client->dev, "Bad led value.\n");
[ 187 ]
I2C Client Drivers Chapter 6
return -EINVAL;
}
return count;
}
static DEVICE_ATTR(rgb, S_IWUSR, NULL, rgb_select);
/*
* This is the function that is called
* when you write the brightness file under each device.
* The command parameters are stored in the led_priv structure
* that is pointed inside each led_device structure
*/
static int led_control(struct led_classdev *led_cdev, enum led_brightness value)
{
struct led_classdev *cdev;
struct led_device *led;
led = container_of(led_cdev, struct led_device, cdev);
cdev = &led->cdev;
led->brightness = value;
if (strcmp(cdev->name,"red") == 0) {
led->private->command[0] &= 0x0F; /* clear the upper nibble */
led->private->command[0] |= ((led->brightness << CMD_RED_SHIFT) & 0xF0);
}
else if (strcmp(cdev->name,"blue") == 0) {
led->private->command[1] &= 0x0F; /* clear the upper nibble */
led->private->command[1] |= ((led->brightness << CMD_BLUE_SHIFT) & 0xF0);
}
else if (strcmp(cdev->name,"green") == 0) {
led->private->command[1] &= 0xF0; /* clear the lower nibble */
led->private->command[1] |= ((led->brightness << CMD_GREEN_SHIFT) & 0x0F);
}
else if (strcmp(cdev->name,"main") == 0) {
led->private->command[2] &= 0x0F; /* clear the upper nibble */
led->private->command[2] |= ((led->brightness << CMD_MAIN_SHIFT) & 0xF0);
}
else if (strcmp(cdev->name,"sub") == 0) {
[ 188 ]
Chapter 6 I2C Client Drivers
static int __init ltc3206_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int count, ret;
u8 value[3];
struct fwnode_handle *child;
struct device *dev = &client->dev;
struct led_priv *private;
/*
* Set blue LED to maximum value for I2C testing.
* ENRGB must be set to VCC to do the testing
*/
value[0] = 0x00;
value[1] = 0xF0;
value[2] = 0x00;
count = device_get_child_node_count(dev);
if (!count)
return -ENODEV;
private->client = client;
i2c_set_clientdata(client, private);
gpiod_direction_output(private->display_cs, 1);
[ 189 ]
I2C Client Drivers Chapter 6
cdev = &led_device->cdev;
led_device->private = private;
fwnode_property_read_string(child, "label", &cdev->name);
if (strcmp(cdev->name,"main") == 0) {
led_device->cdev.brightness_set_blocking = led_control;
ret = devm_led_classdev_register(dev, &led_device->cdev);
if (ret)
goto err;
dev_info(cdev->dev, "the subsystem is %s and num is %d\n",
cdev->name, private->num_leds);
}
else if (strcmp(cdev->name,"sub") == 0) {
led_device->cdev.brightness_set_blocking = led_control;
ret = devm_led_classdev_register(dev, &led_device->cdev);
if (ret)
goto err;
dev_info(cdev->dev, "the subsystem is %s and num is %d\n",
cdev->name, private->num_leds);
}
else if (strcmp(cdev->name,"red") == 0) {
led_device->cdev.brightness_set_blocking = led_control;
ret = devm_led_classdev_register(dev, &led_device->cdev);
if (ret)
goto err;
dev_info(cdev->dev, "the subsystem is %s and num is %d\n",
cdev->name, private->num_leds);
}
else if (strcmp(cdev->name,"green") == 0) {
led_device->cdev.brightness_set_blocking = led_control;
ret = devm_led_classdev_register(dev, &led_device->cdev);
if (ret)
goto err;
dev_info(cdev->dev, "the subsystem is %s and num is %d\n",
cdev->name, private->num_leds);
}
else if (strcmp(cdev->name,"blue") == 0) {
[ 190 ]
Chapter 6 I2C Client Drivers
led_device->cdev.brightness_set_blocking = led_control;
ret = devm_led_classdev_register(dev, &led_device->cdev);
if (ret)
goto err;
dev_info(cdev->dev, "the subsystem is %s and num is %d\n",
cdev->name, private->num_leds);
}
else {
dev_err(dev, "Bad device tree value\n");
return -EINVAL;
}
private->num_leds++;
}
dev_info(dev, "i am out of the device tree\n");
dev_info(dev, "my_probe() function is exited.\n");
return 0;
err:
fwnode_handle_put(child);
sysfs_remove_group(&client->dev.kobj, &display_cs_group);
return ret;
}
return 0;
}
[ 191 ]
I2C Client Drivers Chapter 6
}
};
module_i2c_driver(ltc3206_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a driver that controls the ltc3206 I2C multidisplay device");
ltc3206_rpi3_led_class.ko demonstration
Scan I2C bus for devices:
root@raspberrypi:/home/pi# i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- 1b -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
In the LTC3206 board, connect the ENRGB/S pin to DVCC.
Load the module. The probe() function is called, and the blue LED is ON:
root@raspberrypi:/home/pi# insmod ltc3206_rpi3_led_class.ko
ltc3206 1-001b: platform_probe enter
ltc3206 1-001b: led BLUE is ON
ltc3206 1-001b: there are 5 nodes
leds red: the subsystem is red and num is 0
leds blue: the subsystem is blue and num is 1
leds green: the subsystem is green and num is 2
leds main: the subsystem is main and num is 3
leds sub: the subsystem is sub and num is 4
ltc3206 1-001b: i am out of the device tree
ltc3206 1-001b: my_probe() function is exited.
Check all the devices under the leds class:
root@raspberrypi:/home/pi# ls –l /sys/class/leds
total 0
lrwxrwxrwx 1 root root 0 Apr 8 18:43 blue -> ../../devices/platform/soc/3f80400
0.i2c/i2c-1/1-001b/leds/blue
lrwxrwxrwx 1 root root 0 Apr 8 18:43 default-on -> ../../devices/virtual/leds/d
efault-on
lrwxrwxrwx 1 root root 0 Apr 8 18:43 green -> ../../devices/platform/soc/3f8040
00.i2c/i2c-1/1-001b/leds/green
lrwxrwxrwx 1 root root 0 Apr 8 18:43 led0 -> ../../devices/platform/leds/leds/l
ed0
lrwxrwxrwx 1 root root 0 Apr 8 18:43 led1 -> ../../devices/platform/leds/leds/l
ed1
lrwxrwxrwx 1 root root 0 Apr 8 18:43 main -> ../../devices/platform/soc/3f80400
[ 192 ]
Chapter 6 I2C Client Drivers
0.i2c/i2c-1/1-001b/leds/main
lrwxrwxrwx 1 root root 0 Apr 8 18:43 mmc0 -> ../../devices/virtual/leds/mmc0
lrwxrwxrwx 1 root root 0 Apr 8 18:43 red -> ../../devices/platform/soc/3f804000
.i2c/i2c-1/1-001b/leds/red
lrwxrwxrwx 1 root root 0 Apr 8 18:43 sub -> ../../devices/platform/soc/3f804000
.i2c/i2c-1/1-001b/leds/sub
Change the brightness of the RGB, MAIN and SUB displays of the LTC3206 device. The maximum
brightness value is 15 and the minimum is 0:
root@raspberrypi:/home/pi# echo 10 > /sys/class/leds/red/brightness
root@raspberrypi:/home/pi# echo 15 > /sys/class/leds/red/brightness
root@raspberrypi:/home/pi# echo 0 > /sys/class/leds/red/brightness
root@raspberrypi:/home/pi# echo 10 > /sys/class/leds/blue/brightness
root@raspberrypi:/home/pi# echo 15 > /sys/class/leds/blue/brightness
root@raspberrypi:/home/pi# echo 0 > /sys/class/leds/blue/brightness
root@raspberrypi:/home/pi# echo 10 > /sys/class/leds/green/brightness
root@raspberrypi:/home/pi# echo 15 > /sys/class/leds/green/brightness
root@raspberrypi:/home/pi# echo 0 > /sys/class/leds/green/brightness
root@raspberrypi:/home/pi# echo 10 > /sys/class/leds/main/brightness
root@raspberrypi:/home/pi# echo 15 > /sys/class/leds/main/brightness
root@raspberrypi:/home/pi# echo 0 > /sys/class/leds/main/brightness
root@raspberrypi:/home/pi# echo 10 > /sys/class/leds/sub/brightness
root@raspberrypi:/home/pi# echo 15 > /sys/class/leds/sub/brightness
root@raspberrypi:/home/pi# echo 0 > /sys/class/leds/sub/brightness
Mix RED, GREEN, and BLUE colors for the RGB display:
root@raspberrypi:/home/pi# echo 15 > /sys/class/leds/red/brightness
root@raspberrypi:/home/pi# echo 15 > /sys/class/leds/blue/brightness
root@raspberrypi:/home/pi# echo 15 > /sys/class/leds/green/brightness
Remove the module:
root@raspberrypi:/home/pi# rmmod ltc3206_rpi3_led_class.ko
Switch off the power supply and connect the ENRGB/S pin of the LTC3206 board to the GPIO23 pin
of the Raspberry Pi board. Switch on the power supply to boot the Raspberry Pi.
Load the module:
root@raspberrypi:/home/pi# insmod ltc3206_rpi3_led_class.ko
Switch on the SUB display with brightness = 10. The SUB display is ON:
root@raspberrypi:/home/pi# echo 10 > /sys/class/leds/sub/brightness
Switch on the red LED with brightness = 10. The red LED is OFF:
[ 193 ]
I2C Client Drivers Chapter 6
[ 194 ]
7
Handling Interrupts in Device
Drivers
An IRQ is an interrupt request which can come from different sources, such as a GPIO, EXTI, or
on-chip peripherals. Different devices can use the same interrupt line, thus sharing an IRQ.
In Linux, the IRQ number is an enumeration of the different interrupt sources on a machine.
Typically, what is enumerated is the number of input pins on all of the interrupt controllers in the
system. The IRQ number is a virtual interrupt ID and hardware independent.
The Linux kernel uses a single large number space where each separate IRQ source is assigned a
different number. This is simple when there is only one interrupt controller, but in systems with
multiple interrupt controllers, the kernel must ensure that each one gets assigned non-overlapping
allocations of Linux IRQ numbers.
In modern SoCs, the number of interrupt controllers registered as irqchips is growing. Whereas
in the past, IRQ numbers could be chosen so that they matched the hardware IRQ line into the root
interrupt controller, nowadays this number is just a number. For this reason, we need a mechanism
to separate controller-local interrupt numbers, called hardware irq's (hwirq), from Linux IRQ
numbers.
An interrupt controller driver, dependent of a specific architecture, registers an irq_chip structure
to the kernel. This structure contains a group of pointers to the functions that are going to manage
the IRQs of the interrupt controller. The struct irq_chip is declared as follows:
/*
* struct irq_chip - hardware interrupt chip descriptor
* @parent_device: pointer to parent device for irqchip
* @name: name for /proc/interrupts
* @irq_startup: start up the interrupt (defaults to ->enable if NULL)
* @irq_shutdown: shut down the interrupt (defaults to ->disable if NULL)
* @irq_enable: enable the interrupt (defaults to chip->unmask if NULL)
* @irq_disable: disable the interrupt
* @irq_ack: start of a new interrupt
* @irq_mask: mask an interrupt source
* @irq_mask_ack: ack and mask an interrupt source
[ 195 ]
Handling Interrupts in Device Drivers Chapter 7
[...]
*/
struct irq_chip {
struct device *parent_device;
const char *name;
unsigned int (*irq_startup)(struct irq_data *data);
void (*irq_shutdown)(struct irq_data *data);
void (*irq_enable)(struct irq_data *data);
void (*irq_disable)(struct irq_data *data);
[...]
};
The irq_chip structure contains all the direct chip relevant functions which can be utilized by the
IRQ flow implementations. These primitives mean exactly what their name says: ack means ACK,
mask means masking of an IRQ line, etc. It is up to the flow handler(s) to use these basic units of
low-level functionality.
A Linux IRQ number is always tied to an irq_desc structure, which is the structure that represents
an IRQ. A list of IRQ descriptors are maintained in an array (indexed by the IRQ number) called
the IRQ descriptor table. The handle_irq element of struct irq_desc is a function pointer of type irq_
flow_handler_t which refers to a high-level function that deals with flow management on the line
(typedef void (*irq_flow_handler_t)(struct irq_desc *desc);). Whenever an interrupt triggers, the low
level arch code calls into the generic interrupt code by calling irq_desc->handle_irq. This high level
[ 196 ]
Chapter 7 Handling Interrupts in Device Drivers
IRQ handling function only uses irq_desc->irq_data->chip primitives referenced by the assigned chip
descriptor structure. The struct irq_desc is declared as follows:
struct irq_desc {
struct irq_common_data irq_common_data;
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
irq_preflow_handler_t preflow_handler;
#endif
struct irqaction *action; /* IRQ action list */
unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it;
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned long last_unhandled; /* Aging timer unhandled count */
unsigned int irqs_unhandled;
atomic_t threads_handled;
int threads_handled_last;
raw_spinlock_t lock;
struct cpumask *percpu_enabled;
const struct cpumask *percpu_affinity;
#ifdef CONFIG_SMP
const struct cpumask *affinity_hint;
struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask;
[...]
Inside each irq_desc structure, there is an instance of irq_data (in bold in irq_desc above). The
irq_desc structure contains low-level information that is relevant for interrupt management, such
as Linux IRQ number, hwirq number, interrupt translation domain (irq_domain) and a pointer to
interrupt controller operations (irq_chip) among other important fields.
/*
* struct irq_data - per irq chip data passed down to chip functions
* @mask: precomputed bitmask for accessing the chip registers
* @irq: interrupt number
* @hwirq: hardware interrupt number, local to the interrupt domain
* @common: point to data shared by all irqchips
* @chip: low level interrupt hardware access
* @domain: Interrupt translation domain; responsible for mapping
* between hwirq number and linux irq number.
* @parent_data: pointer to parent struct irq_data to support hierarchy
* irq_domain
* @chip_data: platform-specific per-chip private data for the chip
* methods, to allow shared chip implementations
[ 197 ]
Handling Interrupts in Device Drivers Chapter 7
*/
struct irq_data {
u32 mask;
unsigned int irq; /* linux IRQ number */
unsigned long hwirq; /* hwirq number */
struct irq_common_data *common;
struct irq_chip *chip; /* low level int controller hw access */
struct irq_domain *domain;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_data *parent_data;
#endif
void *chip_data;
};
/* Optional data */
struct fwnode_handle *fwnode;
enum irq_domain_bus_token bus_token;
struct irq_domain_chip_generic *gc;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_domain *parent;
#endif
/* reverse map data. The linear map gets appended to the irq_domain */
irq_hw_number_t hwirq_max;
unsigned int revmap_direct_max_irq;
unsigned int revmap_size;
struct radix_tree_root revmap_tree;
unsigned int linear_revmap[];
};
[ 198 ]
Chapter 7 Handling Interrupts in Device Drivers
An interrupt controller driver allocates and registers an irq_domain by calling one of the
irq_domain_add_*() functions. The function will return a pointer to the irq_domain structure on
success. The driver must provide to the chosen allocator function an irq_domain_ops structure as an
argument. There are several mechanisms available for reverse mapping from hwirq to Linux IRQ,
and each mechanism uses a different allocation function. The majority of drivers should use the
linear map through the irq_domain_add_linear() function:
/*
* irq_domain_add_linear() - Allocate and register a linear revmap irq_domain.
* @of_node: pointer to interrupt controller's device tree node.
* @size: Number of interrupts in the domain,eg.,the number of GPIO inputs
* @ops: map/unmap domain callbacks
* @host_data: Controller private data pointer
*/
struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
unsigned int size,
const struct irq_domain_ops *ops,
void *host_data)
{
return __irq_domain_add(of_node_to_fwnode(of_node), size,
size, 0, ops, host_data);
}
In most cases, the irq_domain will begin empty without any mappings between the hwirq and IRQ
numbers. The irq_domain structure is filled with the IRQ mapping by calling irq_create_mapping(),
which accepts the irq_domain structure and a hwirq number as arguments and returns the Linux
IRQ number:
unsigned int irq_create_mapping(struct irq_domain *domain,
irq_hw_number_t hwirq)
{
struct device_node *of_node;
int virq;
[...]
of_node = irq_domain_get_of_node(domain);
[ 199 ]
Handling Interrupts in Device Drivers Chapter 7
return virq;
}
When writing drivers for GPIO controllers that are also interrupt controllers, irq_create_mapping()
can be called from within gpio_chip.to_irq() callback function. This callback function is called
whenever a driver calls the gpiod_to_irq() function to get the Linux IRQ number associated with the
GPIO pin of a GPIO interrupt controller.
If a mapping for the hwirq doesn't already exist, then irq_create_mapping() will allocate a new
irq_desc structure, associate it with the hwirq and call the irq_domain_ops.map() callback (by means
of the irq_domain_associate() function) so that the driver can perform any required hardware setup.
In the .map() function is created a mapping between a Linux IRQ number and a hwirq number.
The mapping is done inside .map() by calling the irq_set_chip_and_handler() function. The third
parameter (handle) of irq_set_chip_and_handler() determines the wrapper function that will call
the real handler (registered by using request_irq() or request_threaded_irq()) for the "GPIO device"
driver that caused the interrupt.
The GPIO irqchips usually fall into one of two categories:
1. CHAINED GPIO irqchips: These are usually the type that is embedded on an SoC. This
means that there is a fast IRQ handler for the GPIOs that gets called in a chain from the
parent IRQ handler, most typically the system interrupt controller. The GPIO irqchip will
register to its parent IRQ handler by calling irq_set_chained_handler_and_data(), then the
GPIO irqchip handler (which is passed as a parameter to the irq_set_chained_handler_and_
data() function, along with the parent IRQ of the GPIO irqchip) will be called immediately
from its parent IRQ handler while holding the IRQs disabled. The GPIO irqchip will then
end up calling something like this sequence in its interrupt handler:
static void gpio_irq_handler()
chained_irq_enter(...);
generic_handle_irq(...);
chained_irq_exit(...);
In the code snippet below, extracted from drivers/pinctrl/bcm/pinctrl-bcm2835.c, you can see
how the interrupt chip functionality is integrated in the GPIO chip, then the GPIO chip is
registered with the kernel using the gpiochip_add_data() function.
[ 200 ]
Chapter 7 Handling Interrupts in Device Drivers
The gpiochip_add_data() function will call gpiochip_add_irqchip(), which in turn calls irq_set_
chained_handler_and_data(), which sets a highlevel chained flow handler and its data for a
given IRQ. See the code snippet below, extracted from the gpiochip_add_irqchip() function:
if (gpiochip->irq.parent_handler) {
void *data = gpiochip->irq.parent_handler_data ?: gpiochip;
[ 201 ]
Handling Interrupts in Device Drivers Chapter 7
chained_irq_enter(host_chip, desc);
switch (group) {
case 0: /* IRQ0 covers GPIOs 0-27 */
bcm2835_gpio_irq_handle_bank(pc, 0, 0x0fffffff);
break;
case 1: /* IRQ1 covers GPIOs 28-45 */
bcm2835_gpio_irq_handle_bank(pc, 0, 0xf0000000);
bcm2835_gpio_irq_handle_bank(pc, 1, 0x00003fff);
break;
case 2: /* IRQ2 covers GPIOs 46-53 */
bcm2835_gpio_irq_handle_bank(pc, 1, 0x003fc000);
break;
}
chained_irq_exit(host_chip, desc);
}
static void bcm2835_gpio_irq_handle_bank(struct bcm2835_pinctrl *pc,
unsigned int bank, u32 mask)
{
unsigned long events;
unsigned offset;
unsigned gpio;
[ 202 ]
Chapter 7 Handling Interrupts in Device Drivers
2. NESTED THREADED GPIO irqchips: These are off-chip GPIO expanders and any other
GPIO irqchip residing on the other side of a sleeping bus. Of course, such drivers that
need slow bus traffic to read out the IRQ status and similar, traffic which may in turn
incur other IRQs to happen, cannot be handled in a quick IRQ handler with IRQs disabled.
Instead, they need to spawn a thread and mask the parent IRQ line until the interrupt is
handled by the driver.
To help out in handling the setup and management of GPIO irqchips and the associated
irqdomain and resource allocation callbacks, the gpiolib has some helpers that can be
enabled by selecting the GPIOLIB_IRQCHIP kconfig symbol. These are gpiochip_irqchip_
add() and gpiochip_set_chained_irqchip().
The gpiochip_irqchip_add_nested() function adds a nested irqchip to a gpiochip. This
function takes as a parameter the handle_simple_irq flow handler, which handles simple
interrupts sent from a demultiplexing interrupt handler or coming from hardware where
no interrupt hardware control is necessary. The interrupt handler for the GPIO child
driver (which requests a GPIO interrupt by using the devm_request_threaded_irq() function)
will be called inside of a new thread created by the handle_nested_irq() function, which is
called inside the interrupt handler of the GPIO irqchip driver.
In the LAB 7.4, you will develop a driver for an off-chip GPIO expander with interrupt
capabilities that uses the gpiochip_irqchip_add_nested() function. This way of adding a
nested cascaded irqchip to a gpiochip is still available in kernel 5.4, but the preferred way
to set up the helpers today is to fill in the gpio_irq_chip structure that is inside struct gpio_
chip before adding gpio_chip to the kernel. In the LAB 7.5, you will develop a driver for the
same GPIO expander of the LAB 7.4, but you will use this new method.
The following is a typical example of a nested threaded GPIO irqchip that uses the
gpio_irq_chip method:
/* Typical state container with dynamic irqchip */
struct my_gpio {
struct gpio_chip gc;
struct irq_chip irq;
};
[ 203 ]
Handling Interrupts in Device Drivers Chapter 7
[ 204 ]
Chapter 7 Handling Interrupts in Device Drivers
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
[...]
};
See below the ADXL345 node for the accelerometer driver that will be developed in the LAB 10.3.
The interrupt-parent property contains a phandle to the gpio controller node above. The interrupts
property contains two interrupt specifiers, as it was indicated with the interrupt-cells property of its
parent gpio node.
Accel: ADXL345@0 {
compatible = "arrow,adxl345";
spi-max-frequency = <5000000>;
spi-cpol;
spi-cpha;
reg = <0>;
pinctrl-0 = <&accel_int_pin>;
int-gpios = <&gpio 23 0>;
interrupts = <23 1>;
interrupt-parent = <&gpio>;
};
[ 205 ]
Handling Interrupts in Device Drivers Chapter 7
To allocate an interrupt line, you will call devm_request_irq(). This function allocates interrupt
resources and enables the interrupt line and IRQ handling. When calling this function, you must
specify as parameters: a pointer (dev) to the device structure, the Linux IRQ number (irq), a handler
that will be called when the interrupt is generated, flags that will instruct the kernel about the
desired behaviour (irqflags), the name of the device using this interrupt (devname), and a pointer
(dev_id) that can be configured at any value. Usually, dev_id will be a pointer to the device driver’s
private data. The value that devm_request_irq() returns is 0 if the entry was successful or a negative
error code indicating the reason for the failure. A typical value is -EBUSY, which means that the
interrupt was already requested by another device driver.
The main task of an interrupt handler is to provide feedback to the processor about the interrupt
reception and to read or write data according to the meaning of the interrupt being serviced. The
hardware will replay the interrupt (interrupt flood) or won’t generate other interrupt until you
acknowledge it. The method of acknowledging an interrupt can vary from reading an interrupt
controller register, reading the content of a register, or clearing an "interrupt-pending" bit. Some
processors have an interrupt acknowledge signal that takes care of this automatically in hardware.
The handler function is executed in interrupt context, which means that you can’t call blocking
APIs such as mutex_lock() or msleep(). You must also avoid doing a lot of work in the interrupt
handler and instead use deferred work if needed. The function prototype is shown below:
irqreturn_t (*handler)(int irq_no, void *dev_id);
The interrupt handler function receives as parameters the Linux IRQ number of the interrupt
(irq_no) and the pointer to the private data (dev_id) which was sent to request_irq() when the
interrupt was requested. The interrupt handling routine must return a value with a type of
[ 206 ]
Chapter 7 Handling Interrupts in Device Drivers
typedef irqreturn_t. There are three valid values: IRQ_NONE, IRQ_HANDLED and IRQ_WAKE_
THREAD. The device driver must return IRQ_NONE if it notices that the interrupt has not
been generated by the device it is in charge. Otherwise, the device driver must return IRQ_
HANDLED if the interrupt can be handled directly from the interrupt context or IRQ_WAKE_
THREAD to schedule the running of the process context processing function.
/*
* enum irqreturn
* @IRQ_NONE interrupt was not from this device or was not handled
* @IRQ_HANDLED interrupt was handled by this device
* @IRQ_WAKE_THREAD handler requests to wake the handler thread
*/
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
Your driver should support interrupt sharing whenever this is possible. It is possible if and only
if your driver can detect whether your hardware has triggered the interrupt or not. The argument
dev_id is a sort of client data; this argument is passed to the devm_request_irq() function, and the
same pointer is passed back as an argument to the handler when the interrupt happens. You
usually pass a pointer to your private device data structure in dev_id, so you don’t need any extra
code in the interrupt handler to find out which device is in charge of the current interrupt event.
If the handler found that its device did, it should return IRQ_HANDLED. If the driver detects that
it was not your hardware that caused the interrupt, it will do nothing and returns IRQ_NONE,
allowing the kernel to call the next interrupt handler.
[ 207 ]
Handling Interrupts in Device Drivers Chapter 7
[...]
key_pin: key_pin {
brcm,pins = <23>;
brcm,function = <0>; /* Input */
brcm,pull = <1>; /* Pull down */
};
};
&soc {
virtgpio: virtgpio {
compatible = "brcm,bcm2835-virtgpio";
gpio-controller;
#gpio-cells = <2>;
firmware = <&firmware>;
status = "okay";
};
[...]
int_key {
compatible = "arrow,intkey";
pinctrl-names = "default";
pinctrl-0 = <&key_pin>;
gpios = <&gpio 23 0>;
interrupts = <23 1>;
interrupt-parent = <&gpio>;
};
};
[ 208 ]
Chapter 7 Handling Interrupts in Device Drivers
2. For teaching purposes, in the probe() function, you are going to obtain the Linux IRQ
number in two different ways. The first method obtains the GPIO descriptor from the gpios
property of the int_key DT node by using the devm_gpiod_get() function, then the Linux IRQ
number corresponding to the given GPIO is returned by using the function gpiod_to_irq(),
which takes the GPIO descriptor as a parameter. The second method uses the platform_get_
irq() function, which gets the hwirq number from the interrupts property of the int_key DT
node, then returns the Linux IRQ number.
In the probe() function, you will call devm_request_irq() to allocate the interrupt line. When
calling this function, you must specify as parameters: a pointer to the device structure, the
Linux IRQ number (irq), a handler (hello_keys_isr) that will be called when the interrupt
is generated, a flag (IRQF_TRIGGER_FALLING) that will instruct the kernel about the desired
interrupt behaviour, the name (HELLO_KEYS_NAME) of the device using this interrupt, and a
pointer that can be configured at any value. In this driver, dev_id will point to your device
structure.
static int __init my_probe(struct platform_device *pdev)
{
int ret_val, irq;
struct gpio_desc *gpio;
struct device *dev = &pdev->dev;
/* First method to get the virtual linux IRQ number */
gpio = devm_gpiod_get(dev, NULL, GPIOD_IN);
irq = gpiod_to_irq(gpio);
/* Second method to get the virtual Linux IRQ number */
irq = platform_get_irq(pdev, 0);
return 0;
}
[ 209 ]
Handling Interrupts in Device Drivers Chapter 7
3. Write the interrupt handler. In this driver, an interrupt will be generated and handled (a
message will be printed out to the console ) each time you press a button. In the handler,
you will recover the device structure, which is used as a parameter in the dev_info()
function.
static irqreturn_t hello_keys_isr(int irq, void *data)
{
struct device *dev = data;
dev_info(dev, "interrupt received. key: %s\n", HELLO_KEYS_NAME);
return IRQ_HANDLED;
}
7. Create a new int_rpi3_key.c file in the linux_5.4_rpi3_drivers folder, and add int_rpi3_key.o to
your Makefile obj-m variable, then build and deploy the module to the Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
8. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
[ 210 ]
Chapter 7 Handling Interrupts in Device Drivers
/* Interrupt handler */
static irqreturn_t hello_keys_isr(int irq, void *data)
{
struct device *dev = data;
dev_info(dev, "interrupt received. key: %s\n", HELLO_KEYS_NAME);
return IRQ_HANDLED;
}
[ 211 ]
Handling Interrupts in Device Drivers Chapter 7
if (ret_val) {
dev_err(dev, "Failed to request interrupt %d, error %d\n", irq, ret_val);
return ret_val;
}
ret_val = misc_register(&helloworld_miscdevice);
if (ret_val != 0)
{
dev_err(dev, "could not register the misc device mydev\n");
return ret_val;
}
return 0;
}
return 0;
}
module_platform_driver(my_platform_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a button INT platform driver");
[ 212 ]
Chapter 7 Handling Interrupts in Device Drivers
int_rpi3_key.ko demonstration
Load the module:
root@raspberrypi:/home/pi# insmod int_rpi3_key.ko
int_rpi3_key: loading out-of-tree module taints kernel.
intkey soc:int_key: my_probe() function is called.
intkey soc:int_key: The IRQ number is: 166
intkey soc:int_key: IRQ_using_platform_get_irq: 166
intkey soc:int_key: mydev: got minor 60
intkey soc:int_key: my_probe() function is exited.
Check Raspberry Pi interrupts. See the linux IRQ number (166) with hwirq number (23) for the
pinctrl-bcm2835 controller:
root@raspberrypi:/home/pi# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
17: 201 0 0 0 ARMCTRL-level 1 Edge 3f
00b880.mailbox
18: 718 0 0 0 ARMCTRL-level 2 Edge VC
HIQ doorbell
40: 0 0 0 0 ARMCTRL-level 48 Edge bc
[...]
162: 1749 2514 1884 1764 bcm2836-timer 1 Edge ar
ch_timer
165: 0 0 0 0 bcm2836-pmu 9 Edge arm-
pmu
166: 0 0 0 0 pinctrl-bcm2835 23 Edge
PB_KEY
FIQ: usb_fiq
IPI0: 0 0 0 0 CPU wakeup interrupts
IPI1: 0 0 0 0 Timer broadcast interrupts
IPI2: 2987 6766 4092 3772 Rescheduling interrupts
IPI3: 448 662 1605 968 Function call interrupts
IPI4: 0 0 0 0 CPU stop interrupts
IPI5: 93 110 22 42 IRQ work interrupts
IPI6: 0 0 0 0 completion interrupts
Err: 0
Press the button of the Button R click board to generate interrupts:
root@raspberrypi:/home/pi#
intkey soc:int_key: interrupt received. key: PB_KEY
intkey soc:int_key: interrupt received. key: PB_KEY
intkey soc:int_key: interrupt received. key: PB_KEY
Remove the module:
root@raspberrypi:/home/pi# rmmod int_rpi3_key.ko
intkey int_key: my_remove() function is called.
intkey int_key: my_remove() function is exited
[ 213 ]
Handling Interrupts in Device Drivers Chapter 7
Deferred work
The Linux kernel performs operations in two contexts:
1. Process context: Process context is the mode of operation the kernel is in while it is executing
on behalf of a user process, for example, executing a system call kernel service routine. Also,
the deferred work scheduled by workqueues and threaded interrupts is said to be executed
in process context; these kernel threads run in kernel space process context but do not
represent any user process. The code executing in process context is able to block.
2. Interrupt context: On request from a hardware interrupt controller (asynchronously). This
special context is also called "atomic context" because code executing in this context is
unable to block. On the other hand, interrupts are not schedulable. They occur and execute
the interrupt handler spawning its own context. Softirqs, tasklets and timers are running in
interrupt context, which means that they cannot call blocking functions.
Deferred work allows one to schedule code to be executed to a later point. This scheduled code can
run either in process context using worqueues or threaded interrupts, both methods using kernel
threads, or in interrupt context using softirqs, tasklets and timers. Work queues and bottom-half of
threaded irqs are implemented on top of kernel threads that are able to block, and tasklets and timers
are implemented on top of softirqs that cannot call block functions.
[ 214 ]
Chapter 7 Handling Interrupts in Device Drivers
Deferred work is used to complement the interrupt handler functionality, since interrupts have
important requirements and limitations:
• The execution time of the interrupt handler must be as small as possible.
• In interrupt context you can not use blocking calls.
When using the deferred work you can perform the minimum timing-sensitive required work in the
interrupt handler and schedule an asynchronous action from the interrupt handler to run at a later time
when the interrupts are enabled. This deferred work used in interrupts is also known as bottom-half,
since its purpose is to execute the rest of the action from an interrupt handler (top-half). The top-half does
what needs to be done immediately, the time critical stuff. Basically, the top-half itself is the interrupt
handler. The top-half should complete as quickly as possible since all interrupts are disabled and
schedules a bottom half to handle the hard processing. The bottom-half does the rest of the processing
that has been deferred - the time-dependent, less critical actions. It is signaled by the ISR. A bottom-half
is used to process data, letting the top-half to deal with new incoming interrupts. Interrupts are enabled
when a bottom-half runs. Interrupts can be disabled if necessary, but generally this should be avoided
as this goes against the basic purpose of having a bottom-half - processing data while listening for new
interrupts. Interrupt bottom halves are implemented in Linux as softirqs and tasklets in interrupt context,
or via threaded irqs in process context.
Softirqs
Softirqs run in interrupt context and are indicated to execute the most timing-critical and
important bottom-half processing work that does not need to sleep. They are executed once all
interrupt handlers have completed and can be preempted by any top half interrupt. Softirqs can
not be used by device drivers, as they are reserved for various kernel subsystems; there is a fixed
number of softirqs defined at compile time. For the current kernel version, the following types are
defined:
enum {
HI_SOFTIRQ = 0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ,
NR_SOFTIRQS
};
[ 215 ]
Handling Interrupts in Device Drivers Chapter 7
[ 216 ]
Chapter 7 Handling Interrupts in Device Drivers
Tasklets
A tasklet is a bottom-half mechanism running in interrupt context built on top of softirqs. The
main difference between sofirqs and tasklets are that tasklets can be allocated dynamically and
thus they can be used by device drivers. A tasklet is represented by a tasklet structure and as many
other kernel structures it needs to be initialized before being used.
Tasklets are executed within the HI and TASKLET softirqs. They are executed with all interrupts
enabled, but a given tasklet is guaranteed to execute on a single CPU at a time. A pre-initialized
tasklet can be defined as follows:
void handler(unsigned long data);
DECLARE_TASKLET(tasklet, handler, data);
DECLARE_TASKLET_DISABLED(tasklet, handler, data);
If you want to initialize the tasklet manually use the tasklet_init() function. A tasklet is simply
implemented as a function. Tasklets can easily be used by individual device drivers, as opposed to
softirqs.
void handler(unsigned long data);
struct tasklet_struct tasklet;
tasklet_init(&tasklet, handler, data);
The interrupt handler can schedule the tasklet execution using the following functions:
void tasklet_schedule(struct tasklet_struct *tasklet);
void tasklet_hi_schedule(struct tasklet_struct *tasklet);
Timers
Timers are a type of deferred work running in interrupt context and built on top of softirqs, very
often used in Linux drivers. They are defined by the timer_list structure. To be used, a timer must
first be initialized by calling setup_timer():
void setup_timer(struct timer_list * timer,
void (*function)(unsigned long),
unsigned long data);
The previous function initializes the internal fields of the timer_list structure and associates a
function as the timer handler.
Scheduling a timer is done with mod_timer():
int mod_timer(struct timer_list *timer, unsigned long expires);
[ 217 ]
Handling Interrupts in Device Drivers Chapter 7
Where expires parameter is the time (in the future) to run the handler function. The function can be
used to schedule or reschedule a timer inside the handler function.
The time unit for the timers is jiffie. The absolute value of a jiffie is dependent on the platform, and
it can be found by using the HZ macro, which defines the number of jiffies for 1 second. To convert
between jiffies (jiffies_value) and seconds (seconds_value), the following formulas are used:
jiffies_value = seconds_value * HZ;
seconds_value = jiffies_value / HZ;
The kernel maintains a counter that contains the number of jiffies since the last boot, which can be
accessed via the jiffies global variable or macro. You can use it to calculate a time in the future for
timers.
#include <linux/jiffies.h>
unsigned long current_jiffies, next_jiffies;
unsigned long seconds = 1;
current_jiffies = jiffies;
next_jiffies = jiffies + seconds * HZ;
To stop a timer, use del_timer() and del_timer_sync(). A frequent mistake in using timers is that
you forget to turn off timers. For example, before removing a module, you must stop the timers
because if a timer expires after the module is removed, the handler function will no longer be
loaded into the kernel, and a kernel oops will be generated.
You can see below the code of a driver that blinks an LED every second by using a timer deferred
work. You can change the blinking period from user space by using the period sysfs entry. You can
test the driver using a Raspberry Pi 3 board and the Color click™ accessory board with the HW
configuration of the LAB 5.2.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/timer.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
struct GpioRegisters
{
uint32_t GPFSEL[6];
uint32_t Reserved1;
uint32_t GPSET[2];
uint32_t Reserved2;
uint32_t GPCLR[2];
[ 218 ]
Chapter 7 Handling Interrupts in Device Drivers
};
s_BlinkPeriod = period_value;
return count;
}
[ 219 ]
Handling Interrupts in Device Drivers Chapter 7
ret_val = misc_register(&led_miscdevice);
if (ret_val != 0)
{
dev_err(dev, "could not register the misc device mydev");
return ret_val;
}
dev_info(dev, "mydev: got minor %i\n",led_miscdevice.minor);
[ 220 ]
Chapter 7 Handling Interrupts in Device Drivers
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "ledred",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
module_platform_driver(my_platform_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a blinking led driver");
Threaded interrupts
You can find situations where the device driver handling the interrupts can’t read the registers of
the device in a non-blocking mode (for example, a sensor connected to an I2C or SPI bus whose
driver does not guarantee that bus read/write operations are non-blocking). In this situation,
you must plan a work-in-process action (work queue, kernel thread) to access the registers of the
device. Because such a situation is relatively common, the kernel provides the request_threaded_
irq() function to write interrupt handling routines running in two phases: a process phase and
an interrupt context phase. As with the function request_irq(), it is recommended to use the
managed API devm_request_threaded_irq(). Within the parameters of the function, handler is the
function running in interrupt context and implements the critical operations, while the thread_fn
handler runs in process context and implements the rest of the operations. The devm_request_
threaded_irq() function is defined as follows:
/*
* devm_request_threaded_irq - allocate an interrupt line for a managed device
* @dev: device to request interrupt for
* @irq: Interrupt line to allocate
* @handler: Function to be called when the IRQ occurs
* @thread_fn: function to be called in a threaded interrupt context. NULL
* for devices which handle everything in @handler
* @irqflags: Interrupt type flags
* @devname: An ascii name for the claiming device
* @dev_id: A cookie passed back to the handler function
* Except for the extra @dev argument, this function takes the
* same arguments and performs the same function as
* request_threaded_irq().IRQs requested with this function will be
* automatically freed on driver detach.
* If an IRQ allocated with this function needs to be freed
* separately, devm_free_irq() must be used.
*/
int devm_request_threaded_irq(struct device *dev, unsigned int irq,
irq_handler_t handler, irq_handler_t thread_fn,
unsigned long irqflags, const char *devname, void *dev_id)
[ 221 ]
Handling Interrupts in Device Drivers Chapter 7
{
struct irq_devres *dr;
int rc;
If the handler parameter is set to NULL, the default primary handler will be executed. This primary
handler only returns IRQ_WAKE_THREAD to wake up the associated kernel thread which will
execute the thread_fn handler. The thread_fn handler will run with all the interrupts enabled.
The flag parameter can be zero or a bit mask using one or combining some of the flags defined in
include/linux/interrupt.h in the kernel source tree. These are some of the most important flags:
• IRQF_DISABLED: When this flag is set, all the interrupts are disabled while the interrupt
handler is being executed. Another processor core cannot handle a new interrupt that occurs
while the current interrupt handler is running. When this flag is disabled, the interrupt
handler runs with all the interrupts except their own enabled, so another processor core can
handle a new different interrupt while the current interrupt handler is being serviced.
• IRQF_SHARED: When this flag is set, the interrupt line can be shared among several interrupt
handlers. All of these interrupt handlers will execute until the interrupt responsible is found.
When this flag is disabled, only one handler can exist per interrupt line. The request for
interrupt will fail if there is already a handler for the requested interrupt.
• IRQF_ONESHOT: When this flag is set, the interrupt line will be reactivated after the thread_fn
handler (process context) is being executed. When this flag is disabled, the interrupt line will
be reactivated after running the handler routine (interrupt context). It is not convenient to use
IRQF_ONESHOT with IRQF_SHARED.
[ 222 ]
Chapter 7 Handling Interrupts in Device Drivers
Workqueues
Workqueues are another way of deferring work. The base unit with which they work is called
work and is queued in a workqueue. Workqueues defer work into a kernel thread that runs in
process context and can sleep unlike softirqs and tasklets. The other alternative to workqueues is
kernel threads, although using of workqueues is more convenient and easy to use. A kernel thread
called worker will be responsible for handling the work queued in the workqueue, dequeuing the
work items from the workqueue and executing the functions associated with those items.
A work item is a simple data structure that holds a pointer to the function that is to be executed
asynchronously. Whenever a user (e.g., a driver or subsystem) wants a function to be executed
asynchronously by means of workqueues, it has to set up a work item pointing to that function
and queue that work item on a workqueue. Special purpose threads, called worker threads,
execute the functions after dequeuing the items, one after the other. If no work is queued, the
worker threads become idle.
Worker threads are controlled by worker-pools, which take care of the level of concurrency (the
simultaneously running worker threads) and the process management.
Subsystems and drivers can create and queue work items through special workqueue API
functions as they see fit. They can influence some aspects of the way the work items are executed
by setting flags on the workqueue they are putting the work item on. These flags include things
like CPU locality, concurrency limits, priority and more. To get a detailed overview, refer to the
API description of the alloc_workqueue() function.
The workqueue API offers two types of function interfaces: first, a set of interface routines
to instantiate and queue work items onto a global workqueue, which is shared by all kernel
subsystems and services, and second, a set of interface routines to set up a new workqueue
and queue work items onto it. You will begin to explore workqueue interfaces with macros and
functions related to the global shared workqueue.
There are two types of structures associated to the worker thread:
• work_struct -- It schedules a task to run at a later time.
• delayed_work -- It schedules a task to run after at least a given time interval.
A delayed work uses a timer to run after the specified time interval. The calls with this type of
work are similar to those for work_struct, but has _delayed in the function names. Before using
them, a work item must be initialized. There are two types of macros that can be used, one that
declares and initializes the work item at the same time, and one that only initializes the work item
(and the declaration must be done separately):
#include <linux/workqueue.h>
[ 223 ]
Handling Interrupts in Device Drivers Chapter 7
Once declared and initialized, a work instance can be scheduled in the workqueue through schedule_
work() or schedule_delayed_work(). This function enqueues the given work item on the local CPU
workqueue, but does not guarantee its execution of it. It returns true if the given work is successfully
enqueued, or false if the given work is already found in the workqueue. Once queued, the function
associated with the work item is executed on any of the available CPUs by the relevant kworker
thread:
schedule_work(struct work_struct *work);
schedule_delayed_work(struct delayed_work *work, unsigned long delay);
You can wait for a workqueue to complete running all of its work items by calling flush_scheduled_work().
Finally, the following functions can be used to schedule work items on a particular CPU (schedule_
delayed_work_on()) or on all CPUs (schedule_on_each_cpu()):
int schedule_delayed_work_on(int cpu, struct delayed_work *work, unsigned long delay);
int schedule_on_each_cpu(void(*function)(struct work_struct *));
[ 224 ]
Chapter 7 Handling Interrupts in Device Drivers
struct my_device_data {
struct work_struct my_work;
[...]
};
The handler will run in the context of a kernel thread called events/x, where x is the processor core
number. The kernel will initialize a kernel thread (or a pool of workers) for each processor core
present in the system. The above functions use a predefined workqueue (called events), and they
run in the context of the events/x thread, as noted above. Although this is sufficient in most cases,
it is a shared resource, and large delays in work item handlers can cause delays for other queue
users. For this reason, there are functions for creating additional queues.
A workqueue is represented by the workqueue_struct structure. A new workqueue can be created
with the following functions:
struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name);
The create_workqueue() function uses one thread for each processor in the system, and create_
singlethread_workqueue() uses a single thread.
To add a task in the new queue, use queue_work() or queue_delayed_work():
int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue,
struct delayed_work *work, unsigned long delay);
The following code snippet declares and initializes an additional workqueue, then declares and
initializes a work item and adds it to the queue:
void my_work_handler(struct work_struct *work);
struct work_struct my_work;
struct workqueue_struct *my_workqueue;
[ 225 ]
Handling Interrupts in Device Drivers Chapter 7
my_workqueue = create_singlethread_workqueue("my_workqueue");
INIT_WORK(&my_work, my_work_handler);
queue_work(my_workqueue, &my_work);
Note: Part of the text of the "Deferred Work" section has been extracted from the Linux kernel
Documentation. You can find further information in the following links:
https://linux-kernel-labs.github.io/refs/heads/master/labs/deferred_work.html
https://linux-kernel-labs.github.io/refs/heads/master/lectures/interrupts.html
https://www.kernel.org/doc/html/latest/core-api/workqueue.html
[ 226 ]
Chapter 7 Handling Interrupts in Device Drivers
[ 227 ]
Handling Interrupts in Device Drivers Chapter 7
or dynamically:
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);
The wait_event(wq, condition) macro (with a few variants ) will put the process to sleep (TASK_
UNINTERRUPTIBLE) until the condition evaluates to true. The condition is checked each time the
wq waitqueue is woken up. These are the different variants of the wait_event macro:
wait_event(queue, condition);
wait_event_interruptible(queue, condition);
wait_event_timeout(queue, condition, timeout);
wait_event_interruptible_timeout(queue, condition, timeout);
[ 228 ]
Chapter 7 Handling Interrupts in Device Drivers
All the previous functions share the queue parameter. The condition parameter will be evaluated
by the macro before and after sleeping; until the condition evaluates to a true value, the process
continues to sleep. If you use wait_event, your process is put into an uninterruptible sleep. The
preferred alternative is wait_event_interruptible, which can be interrupted by signals. The timeout
versions (wait_event_timeout and wait_event_interruptible_timeout) wait for a limited time; after that
time period (expressed in jiffies) expires, the macros return with a value of zero regardless of how
the condition evaluates.
Another different process, or an interrupt handler will wake up the asleep process. The wake_up()
function awakens processes that have gone to sleep using the same condition variable. This function
does not block and may be called from interrupt handlers. You can see below two of its forms:
void wake_up(wait_queue_head_t *queue); /* wake_up wakes up all processes waiting on the given
queue */
void wake_up_interruptible(wait_queue_head_t *queue); /* restricts itself to processes
performing an interruptible sleep */
[ 229 ]
Handling Interrupts in Device Drivers Chapter 7
You will keep the same HW configuration used in the previous LAB 7.1.
[ 230 ]
Chapter 7 Handling Interrupts in Device Drivers
Open and modify the bcm2710-rpi-3-b.dts Device Tree file by adding the following code in bold:
&gpio {
spi0_pins: spi0_pins {
brcm,pins = <9 10 11>;
brcm,function = <4>; /* alt0 */
};
[...]
key_pin: key_pin {
brcm,pins = <23>;
brcm,function = <0>; /* Input */
brcm,pull = <1>; /* Pull down */
};
};
&soc {
virtgpio: virtgpio {
compatible = "brcm,bcm2835-virtgpio";
gpio-controller;
#gpio-cells = <2>;
firmware = <&firmware>;
status = "okay";
};
[...]
int_key_wait {
compatible = "arrow,intkeywait";
pinctrl-names = "default";
pinctrl-0 = <&key_pin>;
gpios = <&gpio 23 0>;
interrupts = <23 IRQ_TYPE_EDGE_BOTH>;
interrupt-parent = <&gpio>;
};
};
[ 231 ]
Handling Interrupts in Device Drivers Chapter 7
2. Create a private structure that will store the button device-specific information. The
second field of the private structure is a pointer to the gpio_desc structure associated with
your button. In this driver, you will handle a char device, so a miscdevice structure will be
created, initialized and added to your device-specific data structure in the third field. The
fourth field of the private structure is a structure of type wait_queue_head_t that will be
initialized dynamically within the probe() function. The last field will store the Linux IRQ
number.
struct key_priv {
struct device *dev;
struct gpio_desc *gpio;
struct miscdevice int_miscdevice;
wait_queue_head_t wq_data_available;
int irq;
};
3. In the probe() function, a wait queue head is initialized with the line of code init_waitqueue_
head(&priv->wq_data_available). You will recover the DT interrupt number by using
the same two methods of the LAB 7.1. In the probe() function, you will also call devm_
request_irq() to allocate the interrupt line. When calling this function, you must specify as
parameters: a pointer to the device structure, the interrupt number, a handler (hello_keys_
isr) that will be called when the interrupt is generated, a flag (IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING) that will instruct the kernel about the desired interrupt
behaviour, the name (HELLO_KEYS_NAME) of the device which uses this interrupt, and
a pointer to your private structure.
static int __init my_probe(struct platform_device *pdev)
{
struct key_priv *priv;
struct device *dev = &pdev->dev;
[ 232 ]
Chapter 7 Handling Interrupts in Device Drivers
platform_set_drvdata(pdev, priv);
init_waitqueue_head(&priv->wq_data_available);
ret_val = misc_register(&priv->int_miscdevice);
return 0;
}
4. Write now the interrupt handler. In this driver, an interrupt will be generated and handled
each time you press and release a button. In the handler, you will recover the private
structure from the data argument. Once you have retrieved the private structure, you can
read the GPIO input value by using the gpiod_get_value() function to determine if you have
pressed or released the button. After reading the input, you will wake up the process by
using the wake_up_interruptible() function, which takes as its argument the wait queue head
declared in your private structure.
static irqreturn_t hello_keys_isr(int irq, void *data)
{
int val;
struct key_priv *priv = data;
dev_info(priv->dev, "interrupt received. key: %s\n", HELLO_KEYS_NAME);
val = gpiod_get_value(priv->gpio);
dev_info(priv->dev, "Button state: 0x%08X\n", val);
if (val == 1)
hello_keys_buf[buf_wr++] = 'P';
else
hello_keys_buf[buf_wr++] = 'R';
[ 233 ]
Handling Interrupts in Device Drivers Chapter 7
return IRQ_HANDLED;
}
5. Create the my_dev_read() kernel function, which gets called whenever a user space read
operation occurs on the character device file. You will recover the private structure by
using the container_of() macro. The wait_event_interruptible() function puts the user process
to sleep into the wait queue, waiting for a specific event. In this function, you will set as
a parameter the condition to be evaluated to wake up the process. When the process is
woken up, the 'P' or 'R' character (that was stored within the ISR) is sent to user space by
using the copy_to_user() function.
static int my_dev_read(struct file *file, char __user *buff,
size_t count, loff_t *off)
{
int ret_val;
char ch[2];
struct key_priv *priv;
container_of(file->private_data,
struct key_priv, int_miscdevice);
/*
* Sleep the process.
* The condition is checked each time the waitqueue is woken up
*/
wait_event_interruptible(priv->wq_data_available, buf_wr != buf_rd);
/* Send values to user application */
ch[0] = hello_keys_buf[buf_rd];
ch[1] = '\n';
copy_to_user(buff, &ch, 2);
buf_rd++;
if(buf_rd >= MAX_KEY_STATES)
buf_rd = 0;
*off+=1;
return 2;
}
[ 234 ]
Chapter 7 Handling Interrupts in Device Drivers
.owner = THIS_MODULE,
}
};
9. Create a new int_rpi3_key_wait.c file in the linux_5.4_rpi3_drivers folder, and add int_
rpi3_key_wait.o to your Makefile obj-m variable, then build and deploy the module to the
Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
10. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
struct key_priv {
struct device *dev;
struct gpio_desc *gpio;
struct miscdevice int_miscdevice;
wait_queue_head_t wq_data_available;
int irq;
};
[ 235 ]
Handling Interrupts in Device Drivers Chapter 7
val = gpiod_get_value(priv->gpio);
dev_info(priv->dev, "Button state: 0x%08X\n", val);
if (val == 1)
hello_keys_buf[buf_wr++] = 'P';
else
hello_keys_buf[buf_wr++] = 'R';
return IRQ_HANDLED;
}
priv = container_of(file->private_data,
struct key_priv, int_miscdevice);
/*
* Sleep the process.
* The condition is checked each time the waitqueue is woken up
*/
ret_val = wait_event_interruptible(priv->wq_data_available, buf_wr != buf_rd);
if(ret_val)
return ret_val;
/* Send values to user application */
ch[0] = hello_keys_buf[buf_rd];
ch[1] = '\n';
if(copy_to_user(buff, &ch, 2)) {
return -EFAULT;
}
buf_rd++;
if(buf_rd >= MAX_KEY_STATES)
buf_rd = 0;
*off+=1;
return 2;
}
[ 236 ]
Chapter 7 Handling Interrupts in Device Drivers
platform_set_drvdata(pdev, priv);
/* Init the wait queue head */
init_waitqueue_head(&priv->wq_data_available);
/* Get the virtual interrupt number from Device Tree using 2 methods */
priv->gpio = devm_gpiod_get(dev, NULL, GPIOD_IN);
if (IS_ERR(priv->gpio)) {
dev_err(dev, "gpio get failed\n");
return PTR_ERR(priv->gpio);
}
priv->irq = gpiod_to_irq(priv->gpio);
if (priv->irq < 0)
return priv->irq;
dev_info(dev, "The IRQ number is: %d\n", priv->irq);
priv->irq = platform_get_irq(pdev, 0);
if (priv->irq < 0) {
dev_err(dev, "irq is not available\n");
return priv->irq;
}
dev_info(dev, "IRQ_using_platform_get_irq: %d\n", priv->irq);
priv->int_miscdevice.name = "mydev";
priv->int_miscdevice.minor = MISC_DYNAMIC_MINOR;
priv->int_miscdevice.fops = &my_dev_fops;
ret_val = misc_register(&priv->int_miscdevice);
if (ret_val != 0)
[ 237 ]
Handling Interrupts in Device Drivers Chapter 7
{
dev_err(dev, "could not register the misc device mydev\n");
return ret_val;
}
return 0;
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a platform driver that sends to user space \
the number of times you press the switch using INTs");
int_rpi3_key_wait.ko demonstration
Load the module:
[ 238 ]
Chapter 7 Handling Interrupts in Device Drivers
Sleep the process. Press and release the button of the Button R click board several times:
Check all the times you pressed and released the button:
Kernel threads
Kernel threads have emerged from the need to run kernel code in process context. Kernel threads
are the basis of the workqueue mechanism. Essentially, a kernel thread is a thread that only runs in
kernel mode and has no user address space or other user attributes.
To create a kernel thread, use kthread_create():
#include <linux/kthread.h>
structure task_struct *kthread_create(int (*threadfn)(void *data),
void *data, const char namefmt[], ...);
[ 239 ]
Handling Interrupts in Device Drivers Chapter 7
Alternatively, you can use kthread_run() to create and run a kernel thread:
struct task_struct *kthread_run(int (*threadfn)(void *data),
void *data, const char namefmt[], ...);
To stop a thread, use the kthread_stop() function. This function works by sending a signal to the
thread. As a result, the thread function will not be interrupted in the middle of some important
task. But, if the thread function never returns and does not check for signals, it will never actually
stop.
[ 240 ]
Chapter 7 Handling Interrupts in Device Drivers
&gpio {
spi0_pins: spi0_pins {
brcm,pins = <9 10 11>;
brcm,function = <4>; /* alt0 */
};
[...]
key_pins: key_pins {
brcm,pins = <23 24>;
brcm,function = <0>; /* Input */
brcm,pull = <1 1>; /* Pull down */
};
led_pins: led_pins {
brcm,pins = <27 22 26>;
brcm,function = <1>; /* Output */
brcm,pull = <1 1 1>; /* Pull down */
};
};
&soc {
virtgpio: virtgpio {
compatible = "brcm,bcm2835-virtgpio";
gpio-controller;
#gpio-cells = <2>;
firmware = <&firmware>;
[ 241 ]
Handling Interrupts in Device Drivers Chapter 7
status = "okay";
};
[...]
ledpwm {
compatible = "arrow,ledpwm";
pinctrl-names = "default";
pinctrl-0 = <&key_pins &led_pins>;
bp1 {
label = "MIKROBUS_KEY_1";
gpios = <&gpio 23 GPIO_ACTIVE_LOW>;
trigger = "falling";
};
bp2 {
label = "MIKROBUS_KEY_2";
gpios = <&gpio 24 GPIO_ACTIVE_LOW>;
trigger = "falling";
};
ledred {
label = "led";
colour = "red";
gpios = <&gpio 27 GPIO_ACTIVE_LOW>;
};
ledgreen {
label = "led";
colour = "green";
gpios = <&gpio 22 GPIO_ACTIVE_LOW>;
};
ledblue {
label = "led";
colour = "blue";
gpios = <&gpio 26 GPIO_ACTIVE_LOW>;
};
};
[...]
};
[ 242 ]
Chapter 7 Handling Interrupts in Device Drivers
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/property.h>
#include <linux/kthread.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
2. Create a private structure that will store the specific data for each of the three led devices.
The first field holds the name of each device. The ledd field holds the gpio descriptor of
each pin connected to one of the three LEDs. The last field is a pointer to a keyled_priv
structure that will hold the global data used for all the led devices. The keyled_priv
structure will be analyzed in the next point.
struct led_device {
char name[LED_NAME_LEN];
struct gpio_desc *ledd;
struct device *dev;
struct keyled_priv *private;
};
3. Create a keyled_priv structure that will store global data accessible to all the LED devices.
The first field of the private structure is the num_leds variable, which will store the
number of LED devices declared in the DT. The led_flag field will tell you if there is
any LED ON, to switch first all the LEDs OFF, before switching a new LED ON. The
task_flag field will inform you if there is a kthread running. The period field holds the
blinking period. The period_lock is a spinlock that will protect the access to the shared
period variable between the user and interrupt context tasks. The task pointer variable
will point to the task_struct structure returned by the kthread_run() function. The
led_class field will point to the class structure returned by the class_create() function;
this class structure will be used in calls to device_create(). The dev field holds your
platform device. The led_devt field holds the first device identifier returned by the
alloc_chrdev_region() function. The last field is an array of pointers pointing to each of
the led_device private structures.
struct keyled_priv {
u32 num_leds;
u8 led_flag;
u8 task_flag;
u32 period;
spinlock_t period_lock;
struct task_struct *task;
struct class *led_class;
struct device *dev;
dev_t led_devt;
struct led_device *leds[];
};
[ 243 ]
Handling Interrupts in Device Drivers Chapter 7
4. These are the main points to set up the driver within the probe() function:
• Declare a pointer to a fwnode_handle structure (struct fwnode_handle *child) and a
pointer to the global private structure (struct keyled_priv *priv).
• Get the number of LEDs and interrupt devices by using the device_get_child_node_
count() function. You should get five devices returned.
• Allocate the private structures by calling devm_kzalloc(). You will allocate space for the
global structure and three pointers to led_device structures (see the sizeof_keyled_priv()
function).
• Allocate three device numbers with alloc_chrdev_region(), and create the keyled class
with class_create().
• Initialize a spinlock by using the spin_lock_init() function. The spinlock will be used
to protect the access to the period variable. You will use spin_lock_irqsave() in user
context and spin_lock() inside the ISR when using SMP architectures (for example, the
BCM2837 SoC). For uniprocessor architectures, it is not needed to call spin_lock() inside
the ISR, as the ISR cannot be executed in a different core to the one that has acquired
the spinlock in user context.
• The device_for_each_child_of_node() function walks for each child node, creating a
sysfs device entry (under /sys/class/keyled/) for each found LED device by calling the
led_device_register() function. You will get the GPIO descriptor of each GPIO declared
inside each led node by using the devm_fwnode_get_gpiod_from_child() function, then
the direction of the GPIO is set to output by calling gpiod_direction_output(). The GPIO
descriptor of each GPIO declared inside each bp node is obtained by using the devm_
fwnode_get_gpiod_from_child() function, then the direction of the GPIO is set to input by
calling gpiod_direction_input(). The Linux IRQ numbers are obtained by using gpiod_to_
irq(), and both interrupts are allocated by using devm_request_irq().
static int __init my_probe(struct platform_device *pdev)
{
int count, ret, i;
unsigned int major;
struct fwnode_handle *child;
struct device *dev = &pdev->dev;
struct keyled_priv *priv;
count = device_get_child_node_count(dev);
[ 244 ]
Chapter 7 Handling Interrupts in Device Drivers
device_for_each_child_node(dev, child) {
int irq, flags;
struct gpio_desc *keyd;
const char *label_name, *colour_name, *trigger;
struct led_device *new_led;
if (strcmp(label_name,"led") == 0) {
/*
* Point to each led struct
* inside the global struct array of pointers
*/
priv->leds[priv->num_leds] = new_led;
priv->num_leds++;
[ 245 ]
Handling Interrupts in Device Drivers Chapter 7
irq = gpiod_to_irq(keyd);
/* Reset period to 10 */
priv->period = 10;
platform_set_drvdata(pdev, priv);
return 0;
}
[ 246 ]
Chapter 7 Handling Interrupts in Device Drivers
5. In the probe() function, you will set a group of "sysfs attribute files" (to control each LED)
with the line of code priv->led_class->dev_groups = led_groups. You have to declare outside of
the probe() function the following structures:
static struct attribute *led_attrs[] = {
&dev_attr_set_led.attr,
&dev_attr_blink_on_led.attr,
&dev_attr_blink_off_led.attr,
&dev_attr_set_period.attr,
NULL,
};
6. Write the sysfs functions which are called every time you write from user space (/sys/class/
Keyled/<led_device>/<attribute>) to one of the next attributes (set_led, blink_on, blink_off and
set_period). See below a brief description of what each function does:
• The set_led_store() function will receive two parameters ("on" and "off") from user space.
Each led_device structure is recovered by using the dev_get_drvdata() function. In the led_
device_register() function, called within the probe() function, was previously done the setting
between each led device and its led_device structure by using the dev_set_drvdata() function.
If there is a kthread running, then it is stopped. If the parameter received is "on", you will
switch ON the specific LED by previously switching OFF all the LEDs. You will use gpiod_
set_value() to perform this task. If the parameter received is "off," you will switch OFF the
specific LED. The led_flag variable will be always set since the moment you switch ON the
first LED, although all the LEDs are switched OFF later (I leave you as a task to modify
the operation of this variable so that it is only set when any of the LEDs is ON during the
execution of the driver).
• The blink_on_led_store() function will receive an "on" parameter from user space. First of all,
all the LEDs will be switched OFF, then if there is no any kthread running, it will be started
a new one that will blink one of the LEDs with a specific period. If there is already a kthread
running, the function will be exited.
• The blink_off_led_store() function will receive an "off" parameter from user space. If there is a
kthread running (blinking any of the LEDs), it will be stopped.
• The set_period_store() function will set a new blinking period.
[ 247 ]
Handling Interrupts in Device Drivers Chapter 7
7. Write the two interrupt handlers. In this driver, an interrupt will be generated and
handled each time you press one of the two buttons. In the handler, you will recover the
global private structure from the ISR data argument. In one of the ISRs, the period variable
will be increased by ten and in the other ISR decreased by the same value. The new value
will be stored in the period variable of the global private structure. See below the ISR that
increases the period:
static irqreturn_t KEY_ISR1(int irq, void *data)
{
struct keyled_priv *priv = data;
priv->period = priv->period + 10;
if ((priv->period < 10) || (priv->period > 10000))
priv->period = 10;
return IRQ_HANDLED;
}
8. Write the thread function. Inside this function, you will recover the led_device structure
that was set as a parameter in the kthread_run() function. The function kthread_should_
stop() returns non-zero value if there is a stop request submitted by the kthread_stop()
function. Until the call is exited, it will blink the specific LED by using the gpiod_set_value()
and msleep() functions.
static int led_flash(void *data) {
u32 value = 0;
struct led_device *led_dev = data;
while(!kthread_should_stop()) {
u32 period = led_dev->private->period;
value = !value;
gpiod_set_value(led_dev->ledd, value);
msleep(period/2);
}
gpiod_set_value(led_dev->ledd, 1); /* switch off the led */
dev_info(led_dev->dev, "Task completed\n");
return 0;
};
10. Add a platform_driver structure that will be registered to the platform bus:
static struct platform_driver my_platform_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "ledpwm",
[ 248 ]
Chapter 7 Handling Interrupts in Device Drivers
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
12. Create a new keyled_rpi3_class.c file in the linux_5.4_rpi3_drivers folder, and add keyled_rpi3_
class.o to your Makefile obj-m variable, then build and deploy the module to the Raspberry
Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
13. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
#define LED_NAME_LEN 32
#define INT_NUMBER 2
static const char *HELLO_KEYS_NAME1 = "MIKROBUS_KEY1";
static const char *HELLO_KEYS_NAME2 = "MIKROBUS_KEY2";
[ 249 ]
Handling Interrupts in Device Drivers Chapter 7
u32 num_leds;
u8 led_flag;
u8 task_flag;
u32 period;
spinlock_t period_lock;
struct task_struct *task; /* kthread task_struct */
struct class *led_class; /* the keyled class */
struct device *dev;
dev_t led_devt; /* first device identifier */
struct led_device *leds[]; /* pointers to each led private struct */
};
/* kthread function */
static int led_flash(void *data){
unsigned long flags;
u32 value = 0;
struct led_device *led_dev = data;
dev_info(led_dev->dev, "Task started\n");
dev_info(led_dev->dev, "I am inside the kthread\n");
while(!kthread_should_stop()) {
spin_lock_irqsave(&led_dev->private->period_lock, flags);
u32 period = led_dev->private->period;
spin_unlock_irqrestore(&led_dev->private->period_lock, flags);
value = !value;
gpiod_set_value(led_dev->ledd, value);
msleep(period/2);
}
gpiod_set_value(led_dev->ledd, 1); /* switch off the led */
dev_info(led_dev->dev, "Task completed\n");
return 0;
};
/*
* Sysfs methods
*/
if (led->private->task_flag == 1) {
kthread_stop(led->private->task);
led->private->task_flag = 0;
}
[ 250 ]
Chapter 7 Handling Interrupts in Device Drivers
if(!strcmp(buffer, "on")) {
if (led->private->led_flag == 1) {
for (i = 0; i < led->private->num_leds; i++) {
led_count = led->private->leds[i];
gpiod_set_value(led_count->ledd, 1);
}
gpiod_set_value(led->ledd, 0);
}
else {
gpiod_set_value(led->ledd, 0);
led->private->led_flag = 1;
}
}
else if (!strcmp(buffer, "off")) {
gpiod_set_value(led->ledd, 1);
}
else {
dev_info(led->dev, "Bad led value.\n");
return -EINVAL;
}
return count;
}
static DEVICE_ATTR_WO(set_led);
if (led->private->led_flag == 1) {
for (i = 0; i < led->private->num_leds; i++) {
led_count = led->private->leds[i];
gpiod_set_value(led_count->ledd, 1);
}
}
if(!strcmp(buffer, "on")) {
if (led->private->task_flag == 0)
{
led->private->task = kthread_run(led_flash, led, "Led_flash_tread");
if(IS_ERR(led->private->task)) {
dev_info(led->dev, "Failed to create the task\n");
return PTR_ERR(led->private->task);
}
}
[ 251 ]
Handling Interrupts in Device Drivers Chapter 7
else
return -EBUSY;
}
else {
dev_info(led->dev, "Bad led value.\n");
return -EINVAL;
}
led->private->task_flag = 1;
if(!strcmp(buffer, "off")) {
if (led->private->task_flag == 1) {
kthread_stop(led->private->task);
for (i = 0; i < led->private->num_leds; i++) {
led_count = led->private->leds[i];
gpiod_set_value(led_count->ledd, 1);
}
}
else
return 0;
}
else {
dev_info(led->dev, "Bad led value.\n");
return -EINVAL;
}
led->private->task_flag = 0;
return count;
}
static DEVICE_ATTR_WO(blink_off_led);
[ 252 ]
Chapter 7 Handling Interrupts in Device Drivers
spin_lock_irqsave(&led->private->period_lock, flags);
led->private->period = period;
spin_unlock_irqrestore(&led->private->period_lock, flags);
/*
* Allocate space for the global private struct
* and the three LED private structs
*/
static inline int sizeof_keyled_priv(int num_leds)
{
return sizeof(struct keyled_priv) + (sizeof(struct led_device*) * num_leds);
}
spin_lock(&priv->period_lock);
[ 253 ]
Handling Interrupts in Device Drivers Chapter 7
spin_lock(&priv->period_lock);
priv->period = priv->period - 10;
if ((priv->period < 10) || (priv->period > 10000))
priv->period = 10;
spin_unlock(&priv->period_lock);
[ 254 ]
Chapter 7 Handling Interrupts in Device Drivers
dev_set_drvdata(led->dev, led);
return led;
}
count = device_get_child_node_count(dev);
if (!count)
return -ENODEV;
spin_lock_init(&priv->period_lock);
[ 255 ]
Handling Interrupts in Device Drivers Chapter 7
fwnode_handle_put(child);
ret = PTR_ERR(new_led);
[ 256 ]
Chapter 7 Handling Interrupts in Device Drivers
irq = gpiod_to_irq(keyd);
if (irq < 0)
return irq;
irq = gpiod_to_irq(keyd);
if (irq < 0)
return irq;
[ 257 ]
Handling Interrupts in Device Drivers Chapter 7
}
dev_info(dev, "i am out of the device tree\n");
/* Reset period to 10 */
priv->period = 10;
platform_set_drvdata(pdev, priv);
return 0;
error:
/* Unregister everything in case of errors */
for (i = 0; i < priv->num_leds; i++) {
device_destroy(priv->led_class, MKDEV(MAJOR(priv->led_devt), i));
}
class_destroy(priv->led_class);
unregister_chrdev_region(priv->led_devt, priv->num_leds);
return ret;
}
if (priv->task_flag == 1) {
kthread_stop(priv->task);
priv->task_flag = 0;
}
if (priv->led_flag == 1) {
for (i = 0; i < priv->num_leds; i++) {
led_count = priv->leds[i];
gpiod_set_value(led_count->ledd, 1);
}
}
[ 258 ]
Chapter 7 Handling Interrupts in Device Drivers
{ .compatible = "arrow,ledpwm"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a platform keyled_class driver that decreases \
and increases the LED flashing period");
keyled_rpi3_class.ko demonstration
Load the module:
root@raspberrypi:/home/pi# insmod keyled_rpi3_class.ko
ledpwm soc:ledpwm: my_probe() function is called.
ledpwm soc:ledpwm: there are 5 nodes
ledpwm soc:ledpwm: the major number is 238
ledpwm soc:ledpwm: IRQ number: 166
ledpwm soc:ledpwm: IRQ number: 167
keyled red: the major number is 238
keyled red: the minor number is 0
keyled red: led red added
keyled green: the major number is 238
keyled green: the minor number is 1
keyled green: led green added
keyled blue: the major number is 238
keyled blue: the minor number is 2
keyled blue: led blue added
ledpwm soc:ledpwm: i am out of the device tree
ledpwm soc:ledpwm: the led period is 10
ledpwm soc:ledpwm: my_probe() function is exited.
Check the interrupts of the Raspberry Pi. See the linux IRQ numbers (166 and 167) with their
hwirq numbers (23 and 24) for the pinctrl-bcm2835 controller:
root@raspberrypi:/home/pi# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
17: 225 0 0 0 ARMCTRL-level 1 Edge 3f
00b880.mailbox
18: 715 0 0 0 ARMCTRL-level 2 Edge VC
HIQ doorbell
[ 259 ]
Handling Interrupts in Device Drivers Chapter 7
[ 260 ]
Chapter 7 Handling Interrupts in Device Drivers
[ 261 ]
Handling Interrupts in Device Drivers Chapter 7
[ 262 ]
Chapter 7 Handling Interrupts in Device Drivers
Connect the Raspberry Pi´s I2C pins to the I2C ones of the CY8C9520A device (obtained from the
EXPAND 6 Click mikroBUS™ socket):
• Connect Raspberry Pi SCL to CY8C9520A SCL (Pin 12 of Mikrobus).
• Connect Raspberry Pi SDA to CY8C9520A SDA (Pin 11 of Mikrobus).
• Connect Raspberry Pi GPIO23 to CY8C9520A INT (Pin 15 of Mikrobus).
Connect the next power pins between the two boards:
• Connect Raspberry Pi 3.3V to CY8C9520A 3.3V (Pin 7 of Mikrobus).
• Connect Raspberry Pi GND to CY8C9520A GND (Pin 8 of Mikrobus).
[ 263 ]
Handling Interrupts in Device Drivers Chapter 7
[...]
cy8c9520a: cy8c9520a@20 {
compatible = "cy8c9520a";
[ 264 ]
Chapter 7 Handling Interrupts in Device Drivers
reg = <0x20>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-controller;
#gpio-cells = <2>;
interrupts = <23 1>;
interrupt-parent = <&gpio>;
};
};
4. Add "cy8c9520a" to the list of devices supported by the driver. The compatible variable
matches with the compatible property of the cy8c9520a DT node:
static const struct of_device_id my_of_ids[] = {
{ .compatible = "cy8c9520a"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);
[ 265 ]
Handling Interrupts in Device Drivers Chapter 7
2. Initialize the gpio_chip structure with the different callbacks that will control the gpio
lines of the GPIO controller, and register the gpiochip with the kernel by using the
devm_gpiochip_add_data() function. In the Listing 7-4, you can check the source code of
these callback functions. Comments have been added before the main lines of the code to
understand the meaning of the same:
static int cy8c9520a_gpio_init(struct cy8c9520a *cygpio)
{
struct gpio_chip *gpiochip = &cygpio->gpio_chip;
int err;
gpiochip->label = cygpio->client->name;
gpiochip->base = -1;
gpiochip->ngpio = NGPIO;
gpiochip->parent = &cygpio->client->dev;
gpiochip->of_node = gpiochip->parent->of_node;
gpiochip->can_sleep = true;
[ 266 ]
Chapter 7 Handling Interrupts in Device Drivers
gpiochip->direction_input = cy8c9520a_gpio_direction_input;
gpiochip->direction_output = cy8c9520a_gpio_direction_output;
gpiochip->get = cy8c9520a_gpio_get;
gpiochip->set = cy8c9520a_gpio_set;
gpiochip->owner = THIS_MODULE;
/* register a gpio_chip */
err = devm_gpiochip_add_data(gpiochip->parent, gpiochip, cygpio);
if (err)
return err;
return 0;
}
3. Initialize the irq_chip structure with the different callbacks that will handle the GPIO
interrupts flow. In the Listing 7-4, you can check the source code of these callback functions.
Comments have been added before the main lines of the code to understand the meaning
of the same:
static struct irq_chip cy8c9520a_irq_chip = {
.name = "cy8c9520a-irq",
.irq_mask = cy8c9520a_irq_mask,
.irq_unmask = cy8c9520a_irq_unmask,
.irq_bus_lock = cy8c9520a_irq_bus_lock,
.irq_bus_sync_unlock = cy8c9520a_irq_bus_sync_unlock,
.irq_set_type = cy8c9520a_irq_set_type,
};
[ 267 ]
Handling Interrupts in Device Drivers Chapter 7
int ret, i;
mutex_init(&cygpio->irq_lock);
/*
* Clear interrupt state registers by reading the three registers
* Interrupt Status Port0, Interrupt Status Port1,
* Interrupt Status Port2,
* and store the values in a dummy array
*/
i2c_smbus_read_i2c_block_data(client, REG_INTR_STAT_PORT0, NPORTS, dummy);
/*
* Initialize Interrupt Mask Port Register (19h) for each port
* Disable the activation of the INT lines. Each 1 in this
* register, masks (disables) the INT for the corresponding GPIO
*/
memset(cygpio->irq_mask_cache, 0xff, sizeof(cygpio->irq_mask_cache));
memset(cygpio->irq_mask, 0xff, sizeof(cygpio->irq_mask));
/*
* Request interrupt on a GPIO pin of the external processor
* this processor pin is connected to the INT pin of the cy8c9520a
*/
devm_request_threaded_irq(&client->dev, client->irq, NULL,
cy8c9520a_irq_handler,
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
dev_name(&client->dev), cygpio);
/*
* set up a nested irq handler
* you can now request interrupts from GPIO child drivers nested
* to the cy8c9520a driver
*/
gpiochip_set_nested_irqchip(chip, &cy8c9520a_irq_chip, client->irq);
return 0;
err:
mutex_destroy(&cygpio->irq_lock);
return ret;
}
[ 268 ]
Chapter 7 Handling Interrupts in Device Drivers
5. Write the interrupt handler. Inside this handler, the pending GPIO interrupts are checked
by reading the pending variable value, then the position of the first bit set in the variable is
returned; the _ffs() function is used to perform this task. For each pending interrupt that
is found, there is a call to the handle_nested_irq() wrapper function, which in turn calls the
interrupt handler of the GPIO child driver that requested a GPIO interrupt by using the
devm_request_threaded_irq() function. The parameter of the handle_nested_irq() function
is the Linux IRQ number previously returned by using the irq_find_mapping() function,
which receives the hwirq of the input pin as a parameter (gpio_irq variable). The pending
interrupt is cleared by doing pending &= ~BIT(gpio), and the same process is repeated until
all the pending interrupts are being managed.
static irqreturn_t cy8c9520a_irq_handler(int irq, void *devid)
{
struct cy8c9520a *cygpio = devid;
u8 stat[NPORTS], pending;
unsigned port, gpio, gpio_irq;
int ret;
/*
* store in stat and clear (to enable ints)
* the three interrupt status registers by reading them
*/
i2c_smbus_read_i2c_block_data(cygpio->client, REG_INTR_STAT_PORT0, NPORTS, stat);
ret = IRQ_NONE;
/*
* In every port, check the GPIOs that have their INT unmasked
* and whose bits have been enabled in their REG_INTR_STAT_PORT
* register due to an interrupt in the GPIO, then store the new
* value in the pending register
*/
pending = stat[port] & (~cygpio->irq_mask[port]);
mutex_unlock(&cygpio->irq_lock);
while (pending) {
ret = IRQ_HANDLED;
/* get the first gpio that has got an INT */
gpio = __ffs(pending);
[ 269 ]
Handling Interrupts in Device Drivers Chapter 7
handle_nested_irq(irq_find_mapping(cygpio->gpio_chip.irq.domain,
gpio_irq));
}
}
return ret;
}
8. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
[ 270 ]
Chapter 7 Handling Interrupts in Device Drivers
/* cy8c9520a settings */
#define NGPIO 20
#define DEVID_CY8C9520A 0x20
#define NPORTS 3
/* Register offset */
#define REG_INPUT_PORT0 0x00
#define REG_OUTPUT_PORT0 0x08
#define REG_INTR_STAT_PORT0 0x10
#define REG_PORT_SELECT 0x18
#define REG_SELECT_PWM 0x1a
#define REG_INTR_MASK 0x19
#define REG_PIN_DIR 0x1c
#define REG_DRIVE_PULLUP 0x1d
#define REG_DRIVE_PULLDOWN 0x1e
#define REG_DEVID_STAT 0x2e
[ 271 ]
Handling Interrupts in Device Drivers Chapter 7
/*
* struct gpio_chip get callback function.
* It gets the input value of the GPIO line (0=low, 1=high)
* accessing to the REG_INPUT_PORT register
*/
static int cy8c9520a_gpio_get(struct gpio_chip *chip, unsigned int gpio)
{
int ret;
u8 port, in_reg;
mutex_lock(&cygpio->lock);
mutex_unlock(&cygpio->lock);
/*
* Check the status of the GPIO in its input port register
* and return it. If expression is not 0 returns 1
*/
return !!(ret & BIT(cypress_get_offs(gpio, port)));
}
[ 272 ]
Chapter 7 Handling Interrupts in Device Drivers
/*
* struct gpio_chip set callback function.
* It sets the output value of the GPIO line in
* GPIO ACTIVE_HIGH mode (0=low, 1=high)
* writing to the REG_OUTPUT_PORT register
*/
static void cy8c9520a_gpio_set(struct gpio_chip *chip, unsigned int gpio, int val)
{
int ret;
u8 port, out_reg;
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
mutex_lock(&cygpio->lock);
/*
* If val is 1, gpio output level is high.
* If val is 0, gpio output level is low.
* The output registers were previously cached in cy8c9520a_setup()
*/
if (val) {
cygpio->outreg_cache[port] |= BIT(cypress_get_offs(gpio, port));
} else {
cygpio->outreg_cache[port] &= ~BIT(cypress_get_offs(gpio, port));
}
mutex_unlock(&cygpio->lock);
}
/*
* struct gpio_chip direction_output callback function.
* It configures the GPIO as an output writing to
* the REG_PIN_DIR register of the selected port
*/
static int cy8c9520a_gpio_direction_output(struct gpio_chip *chip, unsigned int gpio, int val)
{
int ret;
u8 pins, port;
[ 273 ]
Handling Interrupts in Device Drivers Chapter 7
mutex_lock(&cygpio->lock);
/* Add the direction of the new pin. Set 1 for input and set 0 for output */
pins &= ~BIT(cypress_get_offs(gpio, port));
err:
mutex_unlock(&cygpio->lock);
cy8c9520a_gpio_set(chip, gpio, val);
return ret;
}
/*
* struct gpio_chip direction_input callback function.
* It configures the GPIO as an input writing to
* the REG_PIN_DIR register of the selected port
*/
static int cy8c9520a_gpio_direction_input(struct gpio_chip *chip, unsigned int gpio)
{
int ret;
u8 pins, port;
mutex_lock(&cygpio->lock);
[ 274 ]
Chapter 7 Handling Interrupts in Device Drivers
/*
* Add the direction of the new pin.
* Set 1 for input (out == 0) and set 0 for ouput (out == 1)
*/
pins |= BIT(cypress_get_offs(gpio, port));
err:
mutex_unlock(&cygpio->lock);
return ret;
}
/*
* Function to sync and unlock slow bus (i2c) chips.
* REG_INTR_MASK register is accessed via I2C.
* Write 0 to the interrupt mask register line to
* activate the interrupt on the GPIO
*/
static void cy8c9520a_irq_bus_sync_unlock(struct irq_data *d)
{
struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
int ret, i;
unsigned int gpio;
[ 275 ]
Handling Interrupts in Device Drivers Chapter 7
u8 port;
dev_info(chip->parent, "cy8c9520a_irq_bus_sync_unlock is called\n");
gpio = d->hwirq;
port = cypress_get_port(gpio);
err:
mutex_unlock(&cygpio->irq_lock);
}
/*
* Mask (disable) the GPIO interrupt.
* In the initial setup all the INT lines are masked
*/
static void cy8c9520a_irq_mask(struct irq_data *d)
{
u8 port;
struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
unsigned gpio = d->hwirq;
port = cypress_get_port(gpio);
dev_info(chip->parent, "cy8c9520a_irq_mask is called\n");
[ 276 ]
Chapter 7 Handling Interrupts in Device Drivers
/*
* Unmask (enable) the GPIO interrupt.
* In the initial setup all the INT lines are masked
*/
static void cy8c9520a_irq_unmask(struct irq_data *d)
{
u8 port;
struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
unsigned gpio = d->hwirq;
port = cypress_get_port(gpio);
dev_info(chip->parent, "cy8c9520a_irq_unmask is called\n");
err:
return ret;
}
/*
* Interrupt handler for the cy8c9520a. It is called when
* there is a rising or falling edge in the unmasked GPIO
*/
static irqreturn_t cy8c9520a_irq_handler(int irq, void *devid)
{
struct cy8c9520a *cygpio = devid;
[ 277 ]
Handling Interrupts in Device Drivers Chapter 7
u8 stat[NPORTS], pending;
unsigned port, gpio, gpio_irq;
int ret;
/*
* Store in stat and clear (to enable INTs)
* the three interrupt status registers by reading them
*/
ret = i2c_smbus_read_i2c_block_data(cygpio->client, REG_INTR_STAT_PORT0, NPORTS, stat);
if (ret < 0) {
memset(stat, 0, sizeof(stat));
}
ret = IRQ_NONE;
/*
* In every port, check the GPIOs that have their INT unmasked
* and whose bits have been enabled in their REG_INTR_STAT_PORT
* register due to an interrupt in the GPIO, then store the new
* value in the pending register
*/
pending = stat[port] & (~cygpio->irq_mask[port]);
mutex_unlock(&cygpio->irq_lock);
}
}
return ret;
}
[ 278 ]
Chapter 7 Handling Interrupts in Device Drivers
int ret, i;
struct i2c_client *client = cygpio->client;
/* Disable PWM, set all GPIOs as input. */
for (i = 0; i < NPORTS; i ++) {
ret = i2c_smbus_write_byte_data(client, REG_PORT_SELECT, i);
if (ret < 0) {
dev_err(&client->dev, "can't select port %u\n", i);
goto end;
}
/* Cache the output registers (Output Port 0, Output Port 1, Output Port 2) */
ret = i2c_smbus_read_i2c_block_data(client, REG_OUTPUT_PORT0,
sizeof(cygpio->outreg_cache),
cygpio->outreg_cache);
if (ret < 0) {
dev_err(&client->dev, "can't cache output registers\n");
goto end;
}
end:
return ret;
}
mutex_init(&cygpio->irq_lock);
dev_info(&client->dev, "the cy8c9520a_irq_setup function is entered\n");
/*
* Clear interrupt state registers by reading the three registers:
* Interrupt Status Port0, Interrupt Status Port1, Interrupt Status Port2,
* and store the values in a dummy array
[ 279 ]
Handling Interrupts in Device Drivers Chapter 7
*/
ret = i2c_smbus_read_i2c_block_data(client, REG_INTR_STAT_PORT0, NPORTS, dummy);
if (ret < 0) {
dev_err(&client->dev, "couldn't clear int status\n");
goto err;
}
/*
* Initialize Interrupt Mask Port Register (19h) for each port.
* Disable the activation of the INT lines. Each 1 in this
* register, masks (disables) the INT from the corresponding GPIO
*/
memset(cygpio->irq_mask_cache, 0xff, sizeof(cygpio->irq_mask_cache));
memset(cygpio->irq_mask, 0xff, sizeof(cygpio->irq_mask));
/*
* Request interrupt on a GPIO pin of the external processor.
* This processor pin is connected to the INT pin of the cy8c9520a
*/
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
cy8c9520a_irq_handler,
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
dev_name(&client->dev), cygpio);
if (ret) {
[ 280 ]
Chapter 7 Handling Interrupts in Device Drivers
/*
* Set up a nested irq handler for a gpio_chip from a parent IRQ.
* You can now request interrupts from GPIO child drivers nested
* to the cy8c9520a driver
*/
gpiochip_set_nested_irqchip(chip, &cy8c9520a_irq_chip, client->irq);
return 0;
err:
mutex_destroy(&cygpio->irq_lock);
return ret;
}
/*
* Initialize the cy8c9520a gpio controller (struct gpio_chip)
* and register it to the kernel
*/
static int cy8c9520a_gpio_init(struct cy8c9520a *cygpio)
{
struct gpio_chip *gpiochip = &cygpio->gpio_chip;
int err;
gpiochip->label = cygpio->client->name;
gpiochip->base = -1;
gpiochip->ngpio = NGPIO;
gpiochip->parent = &cygpio->client->dev;
gpiochip->of_node = gpiochip->parent->of_node;
gpiochip->can_sleep = true;
gpiochip->direction_input = cy8c9520a_gpio_direction_input;
gpiochip->direction_output = cy8c9520a_gpio_direction_output;
gpiochip->get = cy8c9520a_gpio_get;
gpiochip->set = cy8c9520a_gpio_set;
gpiochip->owner = THIS_MODULE;
/* Register a gpio_chip */
err = devm_gpiochip_add_data(gpiochip->parent, gpiochip, cygpio);
if (err)
return err;
return 0;
}
[ 281 ]
Handling Interrupts in Device Drivers Chapter 7
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(&client->dev, "SMBUS Byte/Block unsupported\n");
return -EIO;
}
cygpio->client = client;
mutex_init(&cygpio->lock);
/* Whoami */
dev_id = i2c_smbus_read_byte_data(client, REG_DEVID_STAT);
if (dev_id < 0) {
dev_err(&client->dev, "can't read device ID\n");
ret = dev_id;
goto err;
}
dev_info(&client->dev, "dev_id=0x%x\n", dev_id & 0xff);
/* Initial setup for the cy8c9520a */
ret = cy8c9520a_setup(cygpio);
if (ret < 0) {
goto err;
}
return 0;
err:
mutex_destroy(&cygpio->lock);
return ret;
}
[ 282 ]
Chapter 7 Handling Interrupts in Device Drivers
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a driver that controls the \
cy8c9520a I2C GPIO expander");
[ 283 ]
Handling Interrupts in Device Drivers Chapter 7
Open the bcm2710-rpi-3-b.dts DT file, and add the int_gpio node below the int_key one. Check the
differences between both Device Tree nodes. In our new driver, the interrupt-parent is the cy8c9520a
node of our CY8C9520A gpio controller driver, and the GPIO interrupt line included in the
interrupts property has the number 0, which matches with the first input line of the CY8C9520A P0
controller.
int_key {
compatible = "arrow,intkey";
pinctrl-names = "default";
pinctrl-0 = <&key_pin>;
gpios = <&gpio 23 0>;
interrupts = <23 1>;
interrupt-parent = <&gpio>;
};
int_gpio {
compatible = "arrow,int_gpio_expand";
pinctrl-names = "default";
interrupt-parent = <&cy8c9520a>;
interrupts = <0 IRQ_TYPE_EDGE_BOTH>;
};
Create a new int_rpi3_gpio.c file and a Makefile file in the linux_5.4_gpio_int_driver folder, and add
int_rpi3_gpio.o to your Makefile obj-m variable, then build and deploy the module to the Raspberry
Pi:
~/linux_5.4_rpi3_drivers/linux_5.4_CY8C9520A_driver/linux_5.4_gpio_int_driver$ make
~/linux_5.4_rpi3_drivers/linux_5.4_CY8C9520A_driver/linux_5.4_gpio_int_driver$ make deploy
Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
[ 284 ]
Chapter 7 Handling Interrupts in Device Drivers
/* Interrupt handler */
static irqreturn_t P0_line0_isr(int irq, void *data)
{
struct device *dev = data;
dev_info(dev, "interrupt received. key: %s\n", INT_NAME);
return IRQ_HANDLED;
}
[ 285 ]
Handling Interrupts in Device Drivers Chapter 7
return 0;
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a GPIO INT platform driver");
Create a new gpio_int.c file in the app folder, and send the application to the Raspberry Pi:
~/linux_5.4_rpi3_drivers/linux_5.4_CY8C9520A_driver/app$ scp gpio_int.c [email protected]:/home/pi
[ 286 ]
Chapter 7 Handling Interrupts in Device Drivers
/* Open gpio */
fd = open(DEV_GPIO, O_RDWR);
if (fd < 0) {
printf("ERROR: open %s ret=%d\n", DEV_GPIO, fd);
return -1;
}
[ 287 ]
Handling Interrupts in Device Drivers Chapter 7
flags |= O_NONBLOCK;
ret = fcntl(fd_in, F_SETFL, flags);
if (ret) {
printf("ERROR: fcntl set nonblock read\n");
}
for (;;) {
fdset.fd = fd_in;
fdset.events = POLLIN;
fdset.revents = 0;
/* close gpio */
close(fd);
return 0;
}
[ 288 ]
Chapter 7 Handling Interrupts in Device Drivers
[ 289 ]
Handling Interrupts in Device Drivers Chapter 7
Connect pin 0 to pin 1 on the P0 port of the I/O Expander board. The gpio lines of the
gpiochip3 are configured with internal pull-up to Vcc. See below the I/O Expander Pins of the
CY8C9520A board:
[ 290 ]
Chapter 7 Handling Interrupts in Device Drivers
[ 291 ]
Handling Interrupts in Device Drivers Chapter 7
[ 292 ]
Chapter 7 Handling Interrupts in Device Drivers
Launch the gpio_int application. Connect pin 0 of P0 to GND, then remove it from GND. Two
interrupts are fired:
root@raspberrypi:/home/pi# ./gpio_int
cy8c9520a 1-0020: cy8c9520a_gpio_direction input is called
cy8c9520a 1-0020: cy8c9520a_irq_bus_lock is called
cy8c9520a 1-0020: cy8c9520a_irq_set_type is called
cy8c9520a 1-0020: cy8c9520a_irq_unmask is called
cy8c9520a 1-0020: cy8c9520a_irq_bus_sync_unlock is called
the interrupt ISR has been entered
cy8c9520a 1-0020: cy8c9520a_gpio_get function is called
cy8c9520a 1-0020: the in_reg address is 0
cy8c9520a 1-0020: cy8c9520a_gpio_get function with 254 value is returned
irq received.
id: 2, timestamp: 1606046401377764044
the interrupt ISR has been entered
cy8c9520a 1-0020: cy8c9520a_gpio_get function is called
cy8c9520a 1-0020: the in_reg address is 0
cy8c9520a 1-0020: cy8c9520a_gpio_get function with 255 value is returned
irq received.
id: 1, timestamp: 1606046404416847616
Exit the application with ^C:
root@raspberrypi:/home/pi#
Remove the CY8C9520A module:
root@raspberrypi:/home/pi# rmmod CY8C9520A_rpi3.ko
cy8c9520a 1-0020: cy8c9520a_remove() function is called
[ 293 ]
Handling Interrupts in Device Drivers Chapter 7
2. You will add the next lines of code in bold to fill the gpio_irq_chip structure that is inside
struct gpio_chip:
/* Interrupt setup for the cy8c9520a */
static int cy8c9520a_irq_setup(struct cy8c9520a *cygpio)
{
struct gpio_irq_chip *girq;
struct i2c_client *client = cygpio->client;
[...]
/*
* Request interrupt on a GPIO pin of the processor
* this processor GPIO is connected to the INT pin of cy8c9520a
*/
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
cy8c9520a_irq_handler,
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
dev_name(&client->dev), cygpio);
if (ret) {
dev_err(&client->dev, "failed to request irq %d\n", cygpio->irq);
return ret;
}
/*
* set up a nested irq handler for a gpio_chip from a parent IRQ
* you can now request interrupts from GPIO child drivers nested
* to the cy8c9520a driver
*/
[ 294 ]
Chapter 7 Handling Interrupts in Device Drivers
girq = &cygpio->gpio_chip.irq;
girq->chip = &cy8c9520a_irq_chip;
girq->parent_handler = NULL;
girq->num_parents = 0;
girq->parents = NULL;
girq->default_type = IRQ_TYPE_NONE;
girq->handler = handle_simple_irq;
girq->threaded = true;
[...]
}
3. Finally, you will register the gpio_chip with the kernel by using the devm_gpiochip_add_
data() function:
devm_gpiochip_add_data(&client->dev, &cygpio->gpio_chip, cygpio);
[ 295 ]
Handling Interrupts in Device Drivers Chapter 7
A pin controller is registered to the Pinctrl subsystem by filling in a pinctrl_desc structure and
calling the devm_pinctrl_register() function, which takes the pinctrl_desc structure as an argument.
See below the setup of the pintrl_desc structure, done in our driver´s probe() function.
cygpio->pinctrl_desc.name = "cy8c9520a-pinctrl";
cygpio->pinctrl_desc.pctlops = &cygpio_pinctrl_ops;
cygpio->pinctrl_desc.confops = &cygpio_pinconf_ops;
cygpio->pinctrl_desc.npins = cygpio->gpio_chip.ngpio;
cygpio->pinctrl_desc.pins = cy8c9520a_pins;
cygpio->pinctrl_desc.owner = THIS_MODULE;
The pctlops variable points to the custom cygpio_pinctrl_ops structure, which contains pointers to
several callback functions. The pinconf_generic_dt_node_to_map_pin function parses our Device
Tree "pin configuration nodes" and creates mapping table entries for them. You will not implement
the rest of the callback functions inside the pinctrl_ops structure.
static const struct pinctrl_ops cygpio_pinctrl_ops = {
.get_groups_count = cygpio_pinctrl_get_groups_count,
.get_group_name = cygpio_pinctrl_get_group_name,
.get_group_pins = cygpio_pinctrl_get_group_pins,
#ifdef CONFIG_OF
.dt_node_to_map = pinconf_generic_dt_node_to_map_pin,
.dt_free_map = pinconf_generic_dt_free_map,
#endif
};
[ 296 ]
Chapter 7 Handling Interrupts in Device Drivers
The confops variable points to the custom cygpio_pinconf_ops structure, which contains pointers to
callback functions that perform pin config operations. You will only implement the cygpio_pinconf_
set callback function, which sets the drive modes for all the gpios configured in our Device Tree
pin configuration nodes.
static const struct pinconf_ops cygpio_pinconf_ops = {
.pin_config_set = cygpio_pinconf_set,
.is_generic = true,
};
mutex_lock(&cygpio->lock);
switch (param) {
case PIN_CONFIG_BIAS_PULL_UP:
offs = 0x0;
break;
case PIN_CONFIG_BIAS_PULL_DOWN:
offs = 0x01;
break;
case PIN_CONFIG_DRIVE_STRENGTH:
offs = 0x04;
break;
case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
offs = 0x06;
break;
default:
dev_err(&client->dev, "Invalid config param %04x\n", param);
return -ENOTSUPP;
}
/* Write to the REG_DRIVE registers of the CY8C9520A device */
i2c_smbus_write_byte_data(client, REG_PORT_SELECT, port);
[ 297 ]
Handling Interrupts in Device Drivers Chapter 7
mutex_unlock(&cygpio->lock);
return ret;
}
You will add the following lines in bold to the Device Tree configuration of our cy8c9520a device:
cy8c9520a: cy8c9520a@20 {
compatible = "cy8c9520a";
reg = <0x20>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-controller;
#gpio-cells = <2>;
interrupts = <23 1>;
interrupt-parent = <&gpio>;
pinctrl-names = "default";
pinctrl-0 = <&accel_int_pin &cy8c9520apullups &cy8c9520apulldowns
&cy8c9520adrivestrength>;
cy8c9520apullups: pinmux1 {
pins = "gpio0", "gpio1";
bias-pull-up;
};
cy8c9520apulldowns: pinmux2 {
pins = "gpio2";
bias-pull-down;
};
/* pwm channel */
cy8c9520adrivestrength: pinmux3 {
pins = "gpio3";
drive-strength;
};
};
Inside the gpio node, you will create the accel_int_pin pin configuration node, where the GPIO23
pin is multiplexed as a GPIO signal:
accel_int_pin: accel_int_pin {
brcm,pins = <23>;
brcm,function = <0>; /* Input */
brcm,pull = <0>; /* none */
[ 298 ]
Chapter 7 Handling Interrupts in Device Drivers
};
The pinctrl-x properties link to a pin configuration node for a given state of the device. The pinctrl-
names property associates a name to each state. In our driver, we will use only one state, and the
name default is used for the pinctrl-names property, and it is selected by our device driver without
having to make a pinctrl function call.
In our DT device node, the pinctrl-0 property lists several phandles, each of which points to a pin
configuration node. These referenced pin configuration nodes must be child nodes of the pin
controller that they configure. The first pin configuration node applies the pull-up configuration to
the gpi0 and gpio1 pins (GPort 0, pins 0 and 1). The second pin configuration node applies the pull-
down configuration to the gpio2 pin (GPort 0, pin 2), and finally, the last pin configuration node
applies the strong drive configuration to the gpio3 pin (GPort 0, pin 3). These pin configurations
will be written to the CY8C9520A registers by calling the cygpio_pinconf_set() callback function
previously described.
The npwm variable sets the number of PWM channels. The CY8C9520A device has four PWM
channels. The ops variable points to the cy8c9520a_pwm_ops structure, which includes pointers to
the PWM chip-specific callback functions that configure, enable and disable the PWM channels of
the CY8C9520A device.
/* Declare the PWM callback functions */
static const struct pwm_ops cy8c9520a_pwm_ops = {
.request = cy8c9520a_pwm_request,
[ 299 ]
Handling Interrupts in Device Drivers Chapter 7
.config = cy8c9520a_pwm_config,
.enable = cy8c9520a_pwm_enable,
.disable = cy8c9520a_pwm_disable,
};
The cy8c9520a_pwm_config callback function will set up the period and the duty cycle for each
PWM channel of the device. The cy8c9520a_pwm_enable and cy8c9520a_pwm_disable functions
will enable/disable each PWM channel of the device. In the listing code of the driver, you can see
the full code for these callback functions. These functions will be called from user space by using
the sysfs method or from kernel space (API) by using a PWM kernel driver. You will use the syfs
method during the demonstration section of the driver.
Finally, you will add the following lines in bold to the Device Tree configuration of our cy8c9520a
device:
cy8c9520a: cy8c9520a@20 {
compatible = "cy8c9520a";
reg = <0x20>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-controller;
#gpio-cells = <2>;
interrupts = <23 1>;
interrupt-parent = <&gpio>;
#pwm-cells = <2>;
pwm0 = <20>; // pwm not supported
pwm1 = <3>;
pwm2 = <20>; // pwm not supported
pwm3 = <2>;
pinctrl-names = "default";
pinctrl-0 = <&accel_int_pin &cy8c9520apullups &cy8c9520apulldowns
&cy8c9520adrivestrength>;
cy8c9520apullups: pinmux1 {
pins = "gpio0", "gpio1";
bias-pull-up;
};
cy8c9520apulldowns: pinmux2 {
pins = "gpio2";
bias-pull-down;
};
/* pwm channel */
cy8c9520adrivestrength: pinmux3 {
pins = "gpio3";
drive-strength;
};
};
[ 300 ]
Chapter 7 Handling Interrupts in Device Drivers
The pwmX property selects the pin of the CY8C9520A device that will be configured as a PWM
channel. You will select a pin for every PWM channel (PWM0 to PWM3) of the device. In the
following image, extracted from the data-sheet of the CY8C9520A device, you can see which PWM
channel is associated with each port pin of the device. In the Device Tree, you will set the pwm1
channel to the Bit 3 (gpio3) of the GPort0 and the pwm3 channel to the bit 2 (gpio2) of the GPort0.
If a PWM channel is not used, you will set its pwmX property to a value of 20. This configuration is
just an example, you can of course add your own configuration.
You will recover the values of the pwmX properties by using the device_property_read_u32()
function, as shown in the following code snippet:
/* parse the DT to get the pwm-pin mapping */
for (i = 0; i < NPWM; i++) {
ret = device_property_read_u32(&client->dev, name[i], &tmp);
if (!ret)
cygpio->pwm_number[i] = tmp;
else
goto err;
};
[ 301 ]
Handling Interrupts in Device Drivers Chapter 7
Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
root@raspberrypi:/home/pi# reboot
/* cy8c9520a settings */
#define NGPIO 20
#define DEVID_CY8C9520A 0x20
#define NPORTS 3
#define NPWM 4
#define PWM_MAX_PERIOD 0xff
#define PWM_BASE_ID 0
#define PWM_CLK 0x00 /* see resulting PWM_TCLK_NS */
#define PWM_TCLK_NS 31250 /* 32kHz */
#define PWM_UNUSED 20
/* Register offset */
#define REG_INPUT_PORT0 0x00
#define REG_OUTPUT_PORT0 0x08
#define REG_INTR_STAT_PORT0 0x10
#define REG_PORT_SELECT 0x18
#define REG_INTR_MASK 0x19
#define REG_PIN_DIR 0x1c
#define REG_DRIVE_PULLUP 0x1d
#define REG_DRIVE_PULLDOWN 0x1e
[ 302 ]
Chapter 7 Handling Interrupts in Device Drivers
/* Register PWM */
#define REG_SELECT_PWM 0x1a
#define REG_PWM_SELECT 0x28
#define REG_PWM_CLK 0x29
#define REG_PWM_PERIOD 0x2a
#define REG_PWM_PULSE_W 0x2b
[ 303 ]
Handling Interrupts in Device Drivers Chapter 7
PINCTRL_PIN(14, "gpio14"),
PINCTRL_PIN(15, "gpio15"),
PINCTRL_PIN(16, "gpio16"),
PINCTRL_PIN(17, "gpio17"),
PINCTRL_PIN(18, "gpio18"),
PINCTRL_PIN(19, "gpio19"),
};
/*
* Global pin control operations implemented by
* pin controller drivers.
* pinconf_generic_dt_node_to_map_pin function
* will parse a device tree "pin configuration node", and create
* mapping table entries for it
*/
static const struct pinctrl_ops cygpio_pinctrl_ops = {
.get_groups_count = cygpio_pinctrl_get_groups_count,
.get_group_name = cygpio_pinctrl_get_group_name,
[ 304 ]
Chapter 7 Handling Interrupts in Device Drivers
.get_group_pins = cygpio_pinctrl_get_group_pins,
#ifdef CONFIG_OF
.dt_node_to_map = pinconf_generic_dt_node_to_map_pin,
.dt_free_map = pinconf_generic_dt_free_map,
#endif
};
mutex_lock(&cygpio->lock);
switch (param) {
case PIN_CONFIG_BIAS_PULL_UP:
offs = 0x0;
dev_info(&client->dev,
"The pin %d drive mode is PIN_CONFIG_BIAS_PULL_UP\n", pin);
break;
case PIN_CONFIG_BIAS_PULL_DOWN:
offs = 0x01;
dev_info(&client->dev,
"The pin %d drive mode is PIN_CONFIG_BIAS_PULL_DOWN\n", pin);
break;
case PIN_CONFIG_DRIVE_STRENGTH:
offs = 0x04;
dev_info(&client->dev,
"The pin %d drive mode is PIN_CONFIG_DRIVE_STRENGTH\n", pin);
break;
case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
offs = 0x06;
dev_info(&client->dev,
"The pin %d drive mode is PIN_CONFIG_BIAS_HIGH_IMPEDANCE\n", pin);
break;
default:
dev_err(&client->dev, "Invalid config param %04x\n", param);
return -ENOTSUPP;
[ 305 ]
Handling Interrupts in Device Drivers Chapter 7
end:
mutex_unlock(&cygpio->lock);
return ret;
}
/*
* pin config operations implemented by
* pin configuration capable drivers
* pin_config_set: configure an individual pin
*/
static const struct pinconf_ops cygpio_pinconf_ops = {
.pin_config_set = cygpio_pinconf_set,
.is_generic = true,
};
/*
* struct gpio_chip get callback function.
* It gets the input value of the GPIO line (0=low, 1=high)
* accessing to the REG_INPUT_PORT register
*/
static int cy8c9520a_gpio_get(struct gpio_chip *chip, unsigned int gpio)
{
int ret;
u8 port, in_reg;
/* Get the input port address address (in_reg) for the GPIO */
[ 306 ]
Chapter 7 Handling Interrupts in Device Drivers
port = cypress_get_port(gpio);
in_reg = REG_INPUT_PORT0 + port;
mutex_lock(&cygpio->lock);
mutex_unlock(&cygpio->lock);
/*
* Check the status of the GPIO in its input port register
* and return it. If expression is not 0 returns 1
*/
return !!(ret & BIT(cypress_get_offs(gpio, port)));
}
/*
* struct gpio_chip set callback function.
* It sets the output value of the GPIO line in
* GPIO ACTIVE_HIGH mode (0=low, 1=high)
* writing to the REG_OUTPUT_PORT register
*/
static void cy8c9520a_gpio_set(struct gpio_chip *chip, unsigned int gpio, int val)
{
int ret;
u8 port, out_reg;
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
/* Get the output port address address (out_reg) for the GPIO */
port = cypress_get_port(gpio);
out_reg = REG_OUTPUT_PORT0 + port;
mutex_lock(&cygpio->lock);
/*
* If val is 1, gpio output level is high.
* If val is 0, gpio output level is low.
* The output registers were previously cached in cy8c9520a_setup()
*/
if (val) {
cygpio->outreg_cache[port] |= BIT(cypress_get_offs(gpio, port));
} else {
cygpio->outreg_cache[port] &= ~BIT(cypress_get_offs(gpio, port));
}
[ 307 ]
Handling Interrupts in Device Drivers Chapter 7
mutex_unlock(&cygpio->lock);
}
/*
* struct gpio_chip direction_output callback function.
* It configures the GPIO as an output writing to
* the REG_PIN_DIR register of the selected port
*/
static int cy8c9520a_gpio_direction_output(struct gpio_chip *chip, unsigned int gpio, int val)
{
int ret;
u8 pins, port;
mutex_lock(&cygpio->lock);
/* Add the direction of the new pin. Set 1 if input and set 0 is output */
pins &= ~BIT(cypress_get_offs(gpio, port));
err:
mutex_unlock(&cygpio->lock);
cy8c9520a_gpio_set(chip, gpio, val);
[ 308 ]
Chapter 7 Handling Interrupts in Device Drivers
return ret;
}
/*
* struct gpio_chip direction_input callback function.
* It configures the GPIO as an input writing to
* the REG_PIN_DIR register of the selected port
*/
static int cy8c9520a_gpio_direction_input(struct gpio_chip *chip, unsigned int gpio)
{
int ret;
u8 pins, port;
mutex_lock(&cygpio->lock);
/*
* Add the direction of the new pin.
* Set 1 if input (out == 0) and set 0 is ouput (out == 1)
*/
pins |= BIT(cypress_get_offs(gpio, port));
err:
mutex_unlock(&cygpio->lock);
return ret;
}
[ 309 ]
Handling Interrupts in Device Drivers Chapter 7
/*
* Function to sync and unlock slow bus (i2c) chips.
* REG_INTR_MASK register is accessed via I2C
* Write 0 to the interrupt mask register line to
* activate the interrupt on the GPIO
*/
static void cy8c9520a_irq_bus_sync_unlock(struct irq_data *d)
{
struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
int ret, i;
unsigned int gpio;
u8 port;
dev_info(chip->parent, "cy8c9520a_irq_bus_sync_unlock is called\n");
gpio = d->hwirq;
port = cypress_get_port(gpio);
[ 310 ]
Chapter 7 Handling Interrupts in Device Drivers
}
}
err:
mutex_unlock(&cygpio->irq_lock);
}
/*
* Mask (disable) the GPIO interrupt.
* In the initial setup all the INT lines are masked
*/
static void cy8c9520a_irq_mask(struct irq_data *d)
{
u8 port;
struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
unsigned gpio = d->hwirq;
port = cypress_get_port(gpio);
dev_info(chip->parent, "cy8c9520a_irq_mask is called\n");
/*
* Unmask (enable) the GPIO interrupt.
* In the initial setup all the INT lines are masked
*/
static void cy8c9520a_irq_unmask(struct irq_data *d)
{
u8 port;
struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
unsigned gpio = d->hwirq;
port = cypress_get_port(gpio);
dev_info(chip->parent, "cy8c9520a_irq_unmask is called\n");
[ 311 ]
Handling Interrupts in Device Drivers Chapter 7
err:
return ret;
}
/*
* Interrupt handler for the cy8c9520a. It is called when
* there is a rising or falling edge in the unmasked GPIO
*/
static irqreturn_t cy8c9520a_irq_handler(int irq, void *devid)
{
struct cy8c9520a *cygpio = devid;
u8 stat[NPORTS], pending;
unsigned port, gpio, gpio_irq;
int ret;
/*
* Store in stat and clear (to enable ints)
* the three interrupt status registers by reading them
*/
ret = i2c_smbus_read_i2c_block_data(cygpio->client, REG_INTR_STAT_PORT0, NPORTS, stat);
if (ret < 0) {
memset(stat, 0, sizeof(stat));
}
ret = IRQ_NONE;
/*
* In every port, check the GPIOs that have their INTs unmasked
* and whose bits have been enabled in their REG_INTR_STAT_PORT
* register due to an interrupt in the GPIO, then store the new
* value in the pending register
*/
pending = stat[port] & (~cygpio->irq_mask[port]);
mutex_unlock(&cygpio->irq_lock);
[ 312 ]
Chapter 7 Handling Interrupts in Device Drivers
return ret;
}
/*
* Select the period and the duty cycle of the PWM signal (in nanoseconds):
* echo 100000 > pwm1/period
* echo 50000 > pwm1/duty_cycle
*/
static int cy8c9520a_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
int ret;
int period = 0, duty = 0;
/*
* Check period's upper bound. Note the duty cycle is already sanity
* checked by the PWM framework.
*/
if (period > PWM_MAX_PERIOD) {
dev_err(&client->dev, "period must be within [0-%d]ns\n",
PWM_MAX_PERIOD * PWM_TCLK_NS);
return -EINVAL;
}
mutex_lock(&cygpio->lock);
/*
* Select the pwm number (from 0 to 3)
[ 313 ]
Handling Interrupts in Device Drivers Chapter 7
* to set the period and the duty for the enabled pwm pins
*/
ret = i2c_smbus_write_byte_data(client, REG_PWM_SELECT, (u8)pwm->pwm);
if (ret < 0) {
dev_err(&client->dev, "can't write to REG_PWM_SELECT\n");
goto end;
}
end:
mutex_unlock(&cygpio->lock);
return ret;
}
/*
* Enable the PWM signal:
* echo 1 > pwm1/enable
*/
static int cy8c9520a_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
int ret, gpio, port, pin;
u8 out_reg, val;
/*
* Get the pin configured as pwm in the Device Tree
* for this pwm port (pwm_device)
*/
gpio = cygpio->pwm_number[pwm->pwm];
port = cypress_get_port(gpio);
pin = cypress_get_offs(gpio, port);
out_reg = REG_OUTPUT_PORT0 + port;
/*
[ 314 ]
Chapter 7 Handling Interrupts in Device Drivers
mutex_lock(&cygpio->lock);
end:
mutex_unlock(&cygpio->lock);
return ret;
}
/*
* Disable the PWM signal:
* echo 0 > pwm1/enable
*/
static void cy8c9520a_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
int ret, gpio, port, pin;
u8 val;
gpio = cygpio->pwm_number[pwm->pwm];
if (PWM_UNUSED == gpio) {
dev_err(&client->dev, "pwm%d is unused\n", pwm->pwm);
return;
}
[ 315 ]
Handling Interrupts in Device Drivers Chapter 7
port = cypress_get_port(gpio);
pin = cypress_get_offs(gpio, port);
mutex_lock(&cygpio->lock);
/* Disable PWM */
val = i2c_smbus_read_byte_data(client, REG_SELECT_PWM);
if (val < 0) {
dev_err(&client->dev, "can't read REG_SELECT_PWM\n");
goto end;
}
val &= ~BIT((u8)pin);
ret = i2c_smbus_write_byte_data(client, REG_SELECT_PWM, val);
if (ret < 0) {
dev_err(&client->dev, "can't write to SELECT_PWM\n");
}
end:
mutex_unlock(&cygpio->lock);
return;
}
/*
* Request the PWM device:
* echo 0 > export
*/
static int cy8c9520a_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
int gpio = 0;
struct cy8c9520a *cygpio = container_of(chip, struct cy8c9520a, pwm_chip);
struct i2c_client *client = cygpio->client;
gpio = cygpio->pwm_number[pwm->pwm];
if (PWM_UNUSED == gpio) {
dev_err(&client->dev, "pwm%d unavailable\n", pwm->pwm);
return -EINVAL;
}
return 0;
}
[ 316 ]
Chapter 7 Handling Interrupts in Device Drivers
.disable = cy8c9520a_pwm_disable,
};
/* Cache the output registers (Output Port 0, Output Port 1, Output Port 2) */
ret = i2c_smbus_read_i2c_block_data(client, REG_OUTPUT_PORT0,
sizeof(cygpio->outreg_cache),
cygpio->outreg_cache);
if (ret < 0) {
dev_err(&client->dev, "can't cache output registers\n");
goto end;
}
[ 317 ]
Handling Interrupts in Device Drivers Chapter 7
end:
return ret;
}
mutex_init(&cygpio->irq_lock);
/*
* Clear interrupt state registers by reading the three registers:
* Interrupt Status Port0, Interrupt Status Port1, Interrupt Status Port2,
* and store the values in a dummy array
*/
ret = i2c_smbus_read_i2c_block_data(client, REG_INTR_STAT_PORT0, NPORTS, dummy);
if (ret < 0) {
dev_err(&client->dev, "couldn't clear int status\n");
goto err;
}
/*
* Initialize Interrupt Mask Port Register (19h) for each port.
* Disable the activation of the INT lines. Each 1 in this
* register masks (disables) the INT for the corresponding GPIO
*/
memset(cygpio->irq_mask_cache, 0xff, sizeof(cygpio->irq_mask_cache));
memset(cygpio->irq_mask, 0xff, sizeof(cygpio->irq_mask));
[ 318 ]
Chapter 7 Handling Interrupts in Device Drivers
/*
* Request interrupt on a GPIO pin of the external processor.
* This processor pin is connected to the INT pin of the cy8c9520a
*/
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
cy8c9520a_irq_handler,
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
dev_name(&client->dev), cygpio);
if (ret) {
dev_err(&client->dev, "failed to request irq %d\n", cygpio->irq);
return ret;
}
/*
* Set up a nested irq handler for a gpio_chip from a parent IRQ.
* You can now request interrupts from GPIO child drivers nested
* to the cy8c9520a driver
*/
girq = &cygpio->gpio_chip.irq;
girq->chip = &cy8c9520a_irq_chip;
girq->parent_handler = NULL;
girq->num_parents = 0;
girq->parents = NULL;
girq->default_type = IRQ_TYPE_NONE;
girq->handler = handle_simple_irq;
girq->threaded = true;
return 0;
err:
mutex_destroy(&cygpio->irq_lock);
return ret;
}
/*
* Initialize the cy8c9520a gpio controller (struct gpio_chip)
* and register it to the kernel
*/
static void cy8c9520a_gpio_init(struct cy8c9520a *cygpio)
{
struct gpio_chip *gpiochip = &cygpio->gpio_chip;
gpiochip->label = cygpio->client->name;
gpiochip->base = -1;
gpiochip->ngpio = NGPIO;
gpiochip->parent = &cygpio->client->dev;
gpiochip->of_node = gpiochip->parent->of_node;
gpiochip->can_sleep = true;
gpiochip->direction_input = cy8c9520a_gpio_direction_input;
gpiochip->direction_output = cy8c9520a_gpio_direction_output;
gpiochip->get = cy8c9520a_gpio_get;
[ 319 ]
Handling Interrupts in Device Drivers Chapter 7
gpiochip->set = cy8c9520a_gpio_set;
gpiochip->owner = THIS_MODULE;
}
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(&client->dev, "SMBUS Byte/Block unsupported\n");
return -EIO;
}
cygpio->client = client;
mutex_init(&cygpio->lock);
/* Whoami */
dev_id = i2c_smbus_read_byte_data(client, REG_DEVID_STAT);
if (dev_id < 0) {
dev_err(&client->dev, "can't read device ID\n");
ret = dev_id;
goto err;
}
dev_info(&client->dev, "dev_id=0x%x\n", dev_id & 0xff);
[ 320 ]
Chapter 7 Handling Interrupts in Device Drivers
ret = pwmchip_add(&cygpio->pwm_chip);
if (ret) {
dev_err(&client->dev, "pwmchip_add failed %d\n", ret);
goto err;
}
[ 321 ]
Handling Interrupts in Device Drivers Chapter 7
err:
mutex_destroy(&cygpio->lock);
return ret;
}
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a driver that controls the cy8c9520a I2C GPIO expander");
[ 322 ]
Chapter 7 Handling Interrupts in Device Drivers
[ 323 ]
Handling Interrupts in Device Drivers Chapter 7
pwmchip0
root@raspberrypi:/sys/class/pwm# cd pwmchip0/
npwm is the number of PWM channels this controller supports (read-only):
root@raspberrypi:/sys/class/pwm/pwmchip0# ls
device export npwm power subsystem uevent unexport
Exports a PWM channel (pwm1) with sysfs (write-only). The PWM channels are numbered using a
per-controller index from 0 to npwm-1:
root@raspberrypi:/sys/class/pwm/pwmchip0# echo 1 > export
cy8c9520a 1-0020: cy8c9520a_pwm_request is called
You can see that the pwm1 channel has been created. This channel corresponds to the pin 3 of
our device:
root@raspberrypi:/sys/class/pwm/pwmchip0# ls
device export npwm power pwm1 subsystem uevent unexport
Set the total period of the PWM signal (read/write). Value is in nanoseconds:
root@raspberrypi:/sys/class/pwm/pwmchip0# echo 100000 > pwm1/period
cy8c9520a 1-0020: cy8c9520a_pwm_config is called
Set the active time of the PWM signal (read/write). Value is in nanoseconds:
root@raspberrypi:/sys/class/pwm/pwmchip0# echo 50000 > pwm1/duty_cycle
cy8c9520a 1-0020: cy8c9520a_pwm_config is called
Enable the PWM signal (0 = disabled and 1 = enabled):
root@raspberrypi:/sys/class/pwm/pwmchip0# echo 1 > pwm1/enable
cy8c9520a 1-0020: cy8c9520a_pwm_enable is called
cy8c9520a 1-0020: cy8c9520a_gpio_direction output is called
cy8c9520a 1-0020: cy8c9520a_gpio_set_value func with 1 value is called
Connect pin 0 of P0 to pin 3 of P0. You will see how interrupts are being fired in each level
change of the PWM signal:
int_gpio_expand int_gpio: interrupt received. key: P0_line0_INT
the interrupt ISR has been entered
int_gpio_expand int_gpio: interrupt received. key: P0_line0_INT
the interrupt ISR has been entered
int_gpio_expand int_gpio: interrupt received. key: P0_line0_INT
the interrupt ISR has been entered
int_gpio_expand int_gpio: interrupt received. key: P0_line0_INT
the interrupt ISR has been entered
int_gpio_expand int_gpio: interrupt received. key: P0_line0_INT
the interrupt ISR has been entered
int_gpio_expand int_gpio: interrupt received. key: P0_line0_INT
the interrupt ISR has been entered
[...]
Remove the gpio int module:
root@raspberrypi:/home/pi# rmmod int_rpi3_gpio.ko
int_gpio_expand int_gpio: my_remove() function is called.
[ 324 ]
Chapter 7 Handling Interrupts in Device Drivers
i2c1_pins: i2c1 {
brcm,pins = <2 3>;
brcm,function = <4>;
};
The first node adds several properties to the i2c1 controller master node. In the i2c1 node, you can
see the pinctrl-0 property that points to the i2c1_pins pin configuration node (second node), which
configures the pins of the i2c1 controller in I2C mode.
The i2c1 controller node is described in the bcm283x.dtsi file (included in /arch/arm/boot/dts/ in the
kernel source tree):
i2c1: i2c@7e804000 {
compatible = "brcm,bcm2835-i2c";
reg = <0x7e804000 0x1000>;
interrupts = <2 21>;
clocks = <&clocks BCM2835_CLOCK_VPU>;
[ 325 ]
Handling Interrupts in Device Drivers Chapter 7
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
In the LAB 7.5, you added the following sub-nodes and properties (in bold) to the i2c1 and gpio
nodes (included in the bcm2710-rpi-3-b.dts file):
&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins>;
clock-frequency = <100000>;
status = "okay";
cy8c9520a: cy8c9520a@20 {
compatible = "cy8c9520a";
reg = <0x20>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-controller;
#gpio-cells = <2>;
interrupts = <23 1>;
interrupt-parent = <&gpio>;
#pwm-cells = <2>;
pwm0 = <20>; // pwm not supported
pwm1 = <3>;
pwm2 = <20>; // pwm not supported
pwm3 = <2>;
pinctrl-names = "default";
pinctrl-0 = <&accel_int_pin &cy8c9520apullups &cy8c9520apulldowns
&cy8c9520adrivestrength>;
cy8c9520apullups: pinmux1 {
pins = "gpio0", "gpio1";
bias-pull-up;
};
cy8c9520apulldowns: pinmux2 {
pins = "gpio2";
bias-pull-down;
};
/* pwm channel */
cy8c9520adrivestrength: pinmux3 {
pins = "gpio3";
drive-strength;
};
};
};
[ 326 ]
Chapter 7 Handling Interrupts in Device Drivers
&gpio {
spi0_pins: spi0_pins {
brcm,pins = <9 10 11>;
brcm,function = <4>; /* alt0 */
};
spi0_cs_pins: spi0_cs_pins {
brcm,pins = <8 7>;
brcm,function = <1>; /* output */
};
i2c0_pins: i2c0 {
brcm,pins = <0 1>;
brcm,function = <4>;
};
i2c1_pins: i2c1 {
brcm,pins = <2 3>;
brcm,function = <4>;
};
[...]
accel_int_pin: accel_int_pin {
brcm,pins = <23>;
brcm,function = <0>; /* Input */
brcm,pull = <0>; /* none */
};
};
Now, you will remove or comment the previous code in bold to test the Device Tree overlay
developed in this LAB 7.6. You will also comment the int_gpio node, as shown in the following
code snippet, to avoid errors during the compilation of the Device Tree. In this lab, you will only
request CY8C9520A interrupts from user space using the gpio_int application.
/* int_gpio {
compatible = "arrow,int_gpio_expand";
pinctrl-names = "default";
interrupt-parent = <&cy8c9520a>;
interrupts = <0 IRQ_TYPE_EDGE_BOTH>;
}; */
As it has been commented previously, the purpose of the Device Tree overlay is to keep our
original Device Tree intact and dynamically add the necessary fragments that describe our new
hardware. A DT overlay comprises a number of fragments, each of which targets one node and
its subnodes. You will add the previous code in bold by using two fragments. Each fragment will
consist of two parts: a target-path property, with the absolute path to the node that the fragment
is going to modify, or a target property with the relative path to the node alias (prefixed with an
[ 327 ]
Handling Interrupts in Device Drivers Chapter 7
ampersand symbol) that the fragment is going to modify, and the __overlay__ itself, the body of
which is added to the target node. In our Device Tree overlay:
• fragment@0 is adding the accel_int_pin node to the gpio node.
• fragment@1 is adding the cy8c9520a node to the i2c1 node, and it is also modifying some
properties (like the status property) of the i2c1 node itself.
You will create a cy8c9520a-overlay.dts file and add the code below, then you will include the file in
the arch/arm/boot/dts/overlays/ folder in the kernel source tree:
/ {
compatible = "brcm,bcm2835";
fragment@0 {
target =<&gpio>;
__overlay__ {
accel_int_pin: accel_int_pin {
brcm,pins = <23>;
brcm,function = <0>; /* Input */
brcm,pull = <0>; /* none */
};
};
};
fragment@1 {
target = <&i2c1>;
__overlay__ {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
cy8c9520a: cy8c9520a@20 {
compatible = "cy8c9520a";
reg = <0x20>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-controller;
#gpio-cells = <2>;
interrupts = <23 1>;
interrupt-parent = <&gpio>;
#pwm-cells = <2>;
pwm0 = <20>; // pwm not supported
pwm1 = <3>;
pwm2 = <20>; // pwm not supported
pwm3 = <2>;
pinctrl-names = "default";
pinctrl-0 = <&accel_int_pin &cy8c9520apullups &cy8c9520apulldowns
&cy8c9520adrivestrength>;
[ 328 ]
Chapter 7 Handling Interrupts in Device Drivers
cy8c9520apullups: pinmux1 {
pins = "gpio0", "gpio1";
bias-pull-up;
};
cy8c9520apulldowns: pinmux2 {
pins = "gpio2";
bias-pull-down;
};
/* pwm channel */
cy8c9520adrivestrength: pinmux3 {
pins = "gpio3";
drive-strength;
};
};
};
};
};
The overlay will get compiled into a .dtbo file. To be compiled, the overlay needs to be referenced
in the arch/arm/boot/dts/overlays/Makefile file in the kernel source tree:
[...]
upstream.dtbo \
upstream-pi4.dtbo \
vc4-fkms-v3d.dtbo \
vc4-kms-kippah-7inch.dtbo \
vc4-kms-v3d.dtbo \
vc4-kms-v3d-pi4.dtbo \
vga666.dtbo \
w1-gpio.dtbo \
w1-gpio-pullup.dtbo \
w5500.dtbo \
wittypi.dtbo \
cy8c9520a.dtbo
With this overlay in place, you need to enable it in the config.txt file, as well as the I2C1 overlay
with a correct pin-muxing configuration. You can modify the config.txt file directly in the Raspberry
Pi using Nano or Vim editors:
root@raspberrypi:/home/pi# cd /boot
root@raspberrypi:/boot# nano config.txt
dtparam=i2c_arm=on
#dtparam=i2s=on
dtparam=spi=on
dtoverlay=spi0-cs
enable_uart=1
kernel=kernel7.img
dtoverlay=cy8c9520a
dtoverlay=i2c1,pins_2_3
[ 329 ]
Handling Interrupts in Device Drivers Chapter 7
You can see below the I2C1 overlay (i2c1-overlay.dts), which is included in the arch/arm/boot/dts/
overlays/ folder:
/dts-v1/;
/plugin/;
/{
compatible = "brcm,bcm2835";
fragment@0 {
target = <&i2c1>;
__overlay__ {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins>;
};
};
fragment@1 {
target = <&i2c1_pins>;
pins1: __overlay__ {
brcm,pins = <2 3>;
brcm,function = <4>; /* alt 0 */
};
};
fragment@2 {
target = <&i2c1_pins>;
pins2: __dormant__ {
brcm,pins = <44 45>;
brcm,function = <6>; /* alt 2 */
};
};
fragment@3 {
target = <&i2c1>;
__dormant__ {
compatible = "brcm,bcm2708-i2c";
};
};
__overrides__ {
pins_2_3 = <0>,"=1!2";
pins_44_45 = <0>,"!1=2";
combine = <0>, "!3";
};
};
To avoid the need for lots of Device Tree overlays, and to reduce the need for users of peripherals
to modify DT files, the Raspberry Pi loader supports a new feature - Device Tree parameters. These
parameters are defined in the DT by adding an __overrides__ node to the root. You can read about
the different types of parameters in the following link of the Raspberry Pi documentation:
[ 330 ]
Chapter 7 Handling Interrupts in Device Drivers
https://www.raspberrypi.org/documentation/configuration/device-tree.md
The I2C1 overlay will use overlay/fragment parameters. The DT parameter mechanism has a
number of limitations, including the inability to change the name of a node and to write arbitrary
values to arbitrary properties when a parameter is used. One way to overcome some of these
limitations is to conditionally include or exclude certain fragments. A fragment can be excluded
from the final merge process (disabled) by renaming the __overlay__ node to __dormant__. The
parameter declaration syntax has been extended to allow the otherwise illegal zero target phandle
to indicate that the following string contains operations at fragment or overlay scope. So far, four
operations have been implemented:
+<n> // Enable fragment <n>
-<n> // Disable fragment <n>
=<n> // Enable fragment <n> if the assigned parameter value is true, otherwise disable it
!<n> // Enable fragment <n> if the assigned parameter value is false, otherwise disable it
See below in bold the meaning of the properties included in the __overlay__ node of the I2C1
overlay:
__overrides__ {
pins_2_3 = <0>,"=1!2"; // the pins_2_3 parameter enables fragment 1 and disables
fragment 2 if value is true
pins_44_45 = <0>,"!1=2"; // the pins_44_45 parameter enables fragment 2 and
disables fragment 1 if value is true
combine = <0>, "!3"; // the combine parameter enables fragment 3 if the assigned
parameter value is false, otherwise disable it
};
Compile the modified Device Tree and Device Tree overlay, and copy them to the Raspberry Pi
device:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
~/linux_rpi3/linux$ scp arch/arm/boot/dts/overlays/cy8c9520a.dtbo [email protected]:/boot/overlays
Compile the CY8C9520A_pwm_pinctrl.c driver and the gpio_int.c application, and send them to the
Raspberry Pi:
~/linux_5.4_rpi3_drivers/device_tree_overlays/linux_5.4_CY8C9520A_pwm_pinctrl$ make
~/linux_5.4_rpi3_drivers/device_tree_overlays/linux_5.4_CY8C9520A_pwm_pinctrl$ make deploy
~/linux_5.4_rpi3_drivers/device_tree_overlays/linux_5.4_CY8C9520A_pwm_pinctrl/app$ scp
gpio_int.c [email protected]:/home
[ 331 ]
Handling Interrupts in Device Drivers Chapter 7
[ 332 ]
8
Allocating Kernel Memory
Linux is a virtual memory system, meaning that the addresses seen by user programs do not
directly correspond to the physical addresses used by the hardware. Kernel and user processes use
virtual addresses, and address translation is done in the hardware MMU (Memory Management
Unit). With virtual memory, programs running on the system can allocate far more memory than
is physically available; indeed, even a single process can have a virtual address space larger than
the system's physical memory.
The ARM architecture uses translation tables stored in memory to translate virtual addresses to
physical addresses. The MMU will automatically read the translation tables when necessary, this
process is known as a Table Walk.
An important function of the MMU is to enable the system to run multiple tasks as independent
programs running in their own private virtual memory space, in many cases sharing virtual
addresses. They do not need any knowledge of the physical memory map of the system, that is,
the addresses that are used by the hardware, or about other programs that might execute at the
same time. You can use the same virtual memory address space for each user program. You can
also work with a contiguous virtual memory map, even if the physical memory is fragmented. You
can write, compile and link applications to run in the virtual memory space. Physical addresses are
those used by the actual hardware system.
When a process tries to access memory in a page that is not known to the MMU, the MMU
generates a page fault exception. The page fault exception handler examines the state of the MMU
hardware and the currently running process’s memory information and determines whether the
fault is a "good" one, or a "bad" one. Good page faults cause the handler to give more memory to
the process; bad faults cause the handler to terminate the process.
[ 333 ]
Allocating Kernel Memory Chapter 8
architecture, and each process has its own virtual address space. The virtual address space
is split; the lower part is used for user space and the upper part is used for the kernel. If
you assign 1GB of virtual address space for the kernel on 32-bit processors, the split is at
0xC0000000.
2. Physical addresses -- These are the addresses used between the processor and the system's
memory. Physical addresses are 32-bit or 64-bit quantities; even 32-bit systems can use
larger physical addresses in some situations.
3. Bus addresses -- These are the addresses used between peripheral buses and memory.
Often, they are the same as the physical addresses used by the processor, but that is not
necessarily the case. Some architectures can provide an I/O memory management unit,
IOMMU, that remaps addresses between a bus and main memory. Programming the
IOMMU is an extra step that must be performed when setting up DMA operations.
4. Kernel logical addresses -- These make up the normal address space of the kernel. These
are the virtual addresses above CONFIG_PAGE_OFFSET. On most architectures, logical
addresses and their associated physical addresses differ only by a constant offset which
makes converting between physical and virtual addresses easy. The kmalloc() function
returns a pointer variable that points to a kernel logical address space that is mapped to
continuous physical pages. The kernel logical memory cannot be swapped out. Kernel
[ 334 ]
Chapter 8 Allocating Kernel Memory
logical addresses can be converted to and from physical addresses by using the macros
__pa(x) and __va(x).
5. Kernel virtual addresses -- These addresses are similar to logical addresses in that they are
a mapping from a kernel space address to a physical address. Kernel virtual addresses do
not necessarily have the linear, one-to-one mapping to physical addresses that characterize
the logical address space, however. All logical addresses are kernel virtual addresses, but
many kernel virtual addresses are not logical addresses; for example, the function vmalloc()
will return a block of virtual memory which is continuous in virtual space, but it may
not be continuous in physical space. Memory returned by ioremap() will be dynamically
placed in the kernel virtual region. Machine specific static mappings are also located here,
through iotable_init().
[ 335 ]
Allocating Kernel Memory Chapter 8
You can split the kernel physical memory into four zones:
1. ZONE_DMA -- Mapped to the kernel virtual address space (HIGHMEM). The mapped
virtual DMA memory region is returned by the dma_alloc_xxx functions.
2. ZONE_NORMAL -- Mapped to the kernel logical address space (LOWMEM). Used by
the kernel for internal data structures as well as other system and user space allocations.
kmalloc() is a memory-allocation function that returns contiguous memory from ZONE_
NORMAL.
3. ZONE_HIGHMEM -- Mapped to the kernel virtual address space (HIGHMEM). Used
exclusively for system allocations (file system buffers, user space allocations, etc.). Mapped
to kernel virtual addresses returned by the vmalloc function.
4. Memory-Mapped I/O -- Mapped to kernel virtual address space (HIGHMEM). Memory
returned by ioremap() will be dynamically placed in this kernel virtual region.
In the next figure, you can see the kernel memory mapping layout:
[ 336 ]
Chapter 8 Allocating Kernel Memory
[ 337 ]
Allocating Kernel Memory Chapter 8
Page allocator
The Page allocator is responsible for the management of page allocations to the entire system. This
code manages lists of physically contiguous pages and maps them into the MMU page tables, so
as to provide other kernel subsystems with valid physical address ranges when the kernel requests
them (physical to virtual address mapping is handled by a higher layer of the VM). The principal
algorithm used for page allocations is the Binary Buddy Allocator. This allocator is explained in
great detail in the kernel documentation at the following location:
https://www.kernel.org/doc/gorman/html/understand/understand009.html
[ 338 ]
Chapter 8 Allocating Kernel Memory
SLAB allocator
The SLAB Allocator allows creation of "caches", one cache for each object type (for example,
inode_cache, dentry_cache, buffer_head, vm_area_struct). Each cache consists of many "slabs"
(usually one page long and always contiguous), and each slab contains multiple initialized
objects.
The primary intention of the slab allocation technique was to efficiently manage the allocation of
kernel objects and prevent memory fragmentation caused by memory allocation and deallocation.
The kernel objects are the allocated and initialized objects of the same type that are usually
represented in the form of a structure in C. These objects are only used by the kernel core, modules
and drivers that run in kernel space. The object size can be smaller or greater than the page size.
The SLAB allocator takes care of increasing or reducing the size of the cache as needed, depending
on the number of allocated objects, using the page allocator to allocate and free pages.
The SLAB allocator consists of a variable number of caches that are linked together in a doubly
linked circular list called a "cache chain". In order to reduce fragmentation, the slabs are sorted into
three groups:
• Full slabs with zero free objects.
• Partial slabs.
• Empty slabs with no allocated objects.
If partial slabs exist, then new allocations come from these slabs, otherwise from empty slabs or
new slabs are allocated.
In the Linux kernel there are three different implementations of the slab allocation technique,
namely SLAB, SLUB and SLOB:
[ 339 ]
Allocating Kernel Memory Chapter 8
• CONFIG_SLAB: Legacy.
• CONFIG_SLOB: Simple allocator, saves about 0.5MB of memory, but does not scale well.
It is used for very small systems with limited memory (option activates after selecting
CONFIG_EMBEDDED).
• CONFIG_SLUB: Default since 2.6.23. Simpler than SLAB, scales better.
In the following figure, you can see the general memory layout of the SLAB allocator:
[ 340 ]
Chapter 8 Allocating Kernel Memory
To understand Linux kernel SLAB allocators, the following terms are defined, which appear
frequently in the SLAB allocator source code:
1. Cache is a group of the kernel objects of the same type. Cache is identified by a name that
is usually the same as the C structure name. The kernel uses a doubly-linked list to link the
created caches.
2. Slab is the contiguous block of memory stored in one or more physical page(s) of the main
memory. Each cache has a number of slabs that store the actual kernel objects of the same
type.
3. Kernel object is the allocated and initialized instance of a C structure. Each slab may
contain some objects (depending on the size of the slab and each object). A kernel object in
the slab can be either active (object is being used by the kernel) or free (the object is in the
memory pool and ready to be used upon request).
[ 341 ]
Allocating Kernel Memory Chapter 8
SLOB, SLAB and SLUB allocators provide two functions for allocating (taking from cache) and
freeing (putting back into the cache) a kernel object. Defined as a function in mm/slub.c, these are
mm/slob.c or mm/slab.c depending on the chosen slab technique.
1. The kmem_cache_alloc() function allocates an object of a specified type from a cache (a
cache for that specified object must be created before allocation):
void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfpflags);
2. The kmem_cache_free() function frees an object and put it back in the cache:
void kmem_cache_free(struct kmem_cache *s, void *x);
Assume that a Linux kernel module needs to allocate and release an object of a particular type
often. The module makes a request to the SLAB allocator by calling the kmem_cache_create()
function, which creates a cache of that structure type so that it can satisfy subsequent memory
allocations (and releases). Based on the size of the structure, the SLAB allocator calculates the
number of memory pages required for storing each slab cache (power of 2) and the number of
objects that can be stored on each slab. Then, it returns a pointer of type kmem_cache as a reference
to the created cache.
At the time of creating a new cache, the SLAB allocator generates a number of slabs and populates them
with the allocated and initialized objects. When the creation of a new object of the same type is needed, it
[ 342 ]
Chapter 8 Allocating Kernel Memory
makes a request to the SLAB allocator by calling the kmem_cache_alloc() function with a pointer (of type
kmem_cache) to the cache. If the cache has a free object, it immediately returns it. However, if all objects
within the cache slabs are already in use (active), the SLAB allocator increases the cache by making a request
to the Page allocator (calling the alloc_pages() function) to get free pages. After receiving free pages from the
Page allocator, the SLAB allocator creates one or more slabs (in the free physical pages) and populates them
with the new allocated and initialized objects. On the other hand, at the time of releasing the active object,
it is called the kmem_cache_free() function with the cache and object pointers as the parameters. The SLAB
allocator marks the object as free and keeps the object in the cache for the subsequent requests.
Kmalloc allocator
This is the allocator for the driver code. For large sizes, it relies on the page allocator. For smaller
sizes, it relies on generic SLAB caches, named Kmalloc-xxx in /proc/slabinfo. The allocated area
is guaranteed to be physically contiguous and uses the same flags as the page allocator (GFP_
KERNEL, GFP_ATOMIC, GFP_DMA, etc.). Maximum sizes for ARM are 4 MB per allocation and
128MB for total allocations. The kmalloc allocator should be used as the primary allocator unless
there is a strong reason to use another one. See the kmalloc allocator API below:
1. Allocate memory by using kmalloc() or kzalloc() functions:
#include <linux/slab.h>
These functions allocate size bytes and return a pointer to the virtual memory area. The
size parameter is the number of bytes to allocate, and the flags parameter uses the same
variants as the page allocator.
You will use the kfree() function to free a block of memory allocated with kmalloc():
void kfree(const void *objp);
2. Conforming to unified device model, memory allocations can be attached to the device.
The devm_kmalloc() function is a resource-managed kmalloc():
/* Automatically free the allocated buffers when the corresponding
device or module is unprobed */
void *devm_kmalloc(struct device *dev, size_t size, int flags);
[ 343 ]
Allocating Kernel Memory Chapter 8
3. Allocate the first node of the linked list in the createlist() function (createlist() is called
inside the probe() function) using the devm_kmalloc() function (all the nodes will be freed
automatically when the module is unprobed):
/* Allocate the first node */
newNode = devm_kmalloc(&device->dev, sizeof (data_node), GFP_KERNEL);
/* Allocate first node memory buffer */
[ 344 ]
Chapter 8 Allocating Kernel Memory
4. In the createlist() function, allocate the rest of the linked list nodes up to BlockNumber
through a for loop. After the for loop, link the last linked list node with the first one:
for (i = 1; i < BlockNumber; i++)
{
newNode = (data_node *)devm_kmalloc(&device->dev, sizeof (data_node), GFP_KERNEL);
newNode->buffer = (char *)devm_kmalloc(&device->dev,
BlockSize*sizeof(char),
GFP_KERNEL);
newNode->next = NULL;
previousNode->next = newNode;
previousNode = newNode;
}
newNode->next = headNode;
newListe.cur_read_node = headNode;
newListe.cur_write_node = headNode;
newListe.cur_read_offset = 0;
newListe.cur_write_offset = 0;
5. Open the bcm2710-rpi-3-b.dts DT file, and add the linked_memory node in the soc node:
&soc {
virtgpio: virtgpio {
compatible = "brcm,bcm2835-virtgpio";
gpio-controller;
#gpio-cells = <2>;
firmware = <&firmware>;
status = "okay";
};
[...]
linked_memory {
compatible = "arrow,memory";
};
}
7. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
[ 345 ]
Allocating Kernel Memory Chapter 8
newNode->next = NULL;
[ 346 ]
Chapter 8 Allocating Kernel Memory
newListe.head = newNode;
headNode = newNode;
previousNode = newNode;
newNode->next = headNode;
newListe.cur_read_node = headNode;
newListe.cur_write_node = headNode;
newListe.cur_read_offset = 0;
newListe.cur_write_offset = 0;
return 0;
}
*(offset) += size_to_copy;
newListe.cur_write_offset += size_to_copy;
if (newListe.cur_write_offset == BlockSize)
[ 347 ]
Allocating Kernel Memory Chapter 8
{
newListe.cur_write_node = newListe.cur_write_node->next;
newListe.cur_write_offset = 0;
node_count = node_count+1;
if (node_count > BlockNumber)
{
newListe.cur_read_node = newListe.cur_write_node;
newListe.cur_read_offset = 0;
node_count = 1;
cnt = 0;
size_to_read = 0;
}
}
return size_to_copy;
}
static ssize_t my_dev_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
int size_to_copy;
int read_value;
if (newListe.cur_read_offset == BlockSize)
{
cnt = cnt+1;
newListe.cur_read_node = newListe.cur_read_node->next;
newListe.cur_read_offset = 0;
}
return size_to_copy;
}
else
{
msleep(250);
newListe.cur_read_node = newListe.head;
newListe.cur_write_node = newListe.head;
newListe.cur_read_offset = 0;
newListe.cur_write_offset = 0;
node_count = 1;
cnt = 0;
[ 348 ]
Chapter 8 Allocating Kernel Memory
size_to_read = 0;
return 0;
}
}
return 0;
}
[ 349 ]
Allocating Kernel Memory Chapter 8
ret_val = platform_driver_register(&my_platform_driver);
if (ret_val !=0)
{
pr_err("platform value returned %d\n", ret_val);
return ret_val;
}
pr_info("demo_init exit\n");
return 0;
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a platform driver that writes in and read \
from a linked list of several buffers ");
[ 350 ]
Chapter 8 Allocating Kernel Memory
linkedlist_rpi3_platform.ko demonstration
Load the module:
root@raspberrypi:/home/pi# insmod linkedlist_rpi3_platform.ko
demo_init enter
platform_probe enter
mydev: got minor 60
demo_init exit
Write values to the nodes:
root@raspberrypi:/home/pi# echo abcdefg > /dev/mydev
my_dev_open() is called.
my_dev_write() is called.
node_number_1
my_dev_write() is called.
node_number_2
my_dev_close() is called.
Read values from nodes. After reading, the first node is pointed:
root@raspberrypi:/home/pi# cat /dev/mydev
my_dev_open() is called.
abcdefg
my_dev_close() is called.
Remove the module:
root@raspberrypi:/home/pi# rmmod linkedlist_rpi3_platform.ko
demo_exit enter
platform_remove exit
demo_exit exit
[ 351 ]
9
DMA in Device Drivers
Direct memory access (DMA) is a system controller available on embedded processors that allows
for some of its main peripherals (SPI, I2C, UART, general-purpose timers, DAC and ADC) to
transfer their I/O data directly to and from main memory independently of the main processing
unit. The DMA is also used to manage direct data transfers between RAM data buffers without
CPU intervention. While the DMA transfer is in progress, the CPU can continue executing code.
When the DMA transfer is completed, the DMA system controller will signal the CPU with an
interrupt.
Typical scenarios of block memory copy where DMA can be useful are network packet routing
and video streaming applications. DMA is a particular advantage in situations where the blocks
to be transferred are larger, or the transfer is a repetitive operation that would consume a large
portion of potentially useful CPU processing time.
Cache coherency
One of the main problems when using DMA in a cached system is the possibility that the contents
of the cache are not coherent with respect to the system memory. Let´s take as an example a CPU
with a cache and external memory that can be accessed by peripherals using DMA. When the
CPU tries to access data X located in the main memory, it could happen that the current data X
value has been cached by the processor, then subsequent operations on X will update the cached
copy of X, but not the external memory version of X, assuming a write-back cache. If the cache
is not flushed to the main memory before the next time a device (DMA) tries to transfer X, the
device will receive a stale value of X. Similarly, if the cached copy of X is not invalidated before a
device (DMA) writes a new value to the main memory, then the CPU will operate on a stale value
of X. Also, when the cache is flushed, the stale data will be written back to the main memory,
overwriting the new data stored by the DMA. The end result is that the data in the main memory
is not correct.
Some processors include a mechanism called bus snooping or cache snooping. This system notifies
to the cache controller the accesses to DMA memory regions, invalidating (DMA reads) or cleaning
(DMA writes) the corresponding cache lines. These systems are called coherent architectures,
[ 353 ]
DMA in Device Drivers Chapter 9
providing a hardware to take care of cache coherency related problem. Hardware will itself
maintain coherency between caches and main memory and ensure that all the subsystem (CPU
and DMA) have the same view of the memory.
For non-coherent architectures, the device driver should explicitly flush or invalidate the data
cache before initiating a transfer or making data buffers available to bus mastering peripherals.
This can also complicate the software and will cause more transfers between the cache and the
main memory, but it does allow the application to use any arbitrary region of cached memory as a
data buffer.
Linux kernel provides two dma_map_ops structures for ARM processors, one for non-coherent
architectures (arm_dma_ops) that doesn’t provide additional hardware support for coherency
management, so software needs to take care of it, and one for coherent ARM architecture (arm_
coherent_dma_ops) that provides hardware to take care of cache coherency.
struct dma_map_ops arm_dma_ops = {
.alloc = arm_dma_alloc,
.free = arm_dma_free,
.mmap = arm_dma_mmap,
.get_sgtable = arm_dma_get_sgtable,
.map_page = arm_dma_map_page,
.unmap_page = arm_dma_unmap_page,
.map_sg = arm_dma_map_sg,
.unmap_sg = arm_dma_unmap_sg,
.sync_single_for_cpu = arm_dma_sync_single_for_cpu,
.sync_single_for_device = arm_dma_sync_single_for_device,
.sync_sg_for_cpu = arm_dma_sync_sg_for_cpu,
.sync_sg_for_device = arm_dma_sync_sg_for_device,
};
EXPORT_SYMBOL(arm_dma_ops);
[ 354 ]
Chapter 9 DMA in Device Drivers
Which will find and return the DMA channel associated with the dev device. A channel
allocated via this interface is exclusive to the caller until dma_release_channel() is called.
2. Set slave and controller specific parameters: Next step is always to pass some specific
information to the DMA driver. Most of the generic information which a slave DMA can
use is in the dma_slave_config structure. This allows the clients to specify DMA direction,
DMA addresses, bus widths, DMA burst lengths, etc. for the peripheral. If some DMA
controllers have more parameters to be sent, then they should try to embed dma_slave_
config in their controller specific structure. That gives flexibility to client to pass more
parameters, if required.
int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config);
3. Get a descriptor for the transaction: For slave usage, the various modes of slave transfers
supported by the DMA-engine are:
• slave_sg: DMA a list of scatter gather buffers from/to a peripheral.
• dma_cyclic: Performs a cyclic DMA operation from/to a peripheral till the operation is
explicitly stopped.
• interleaved_dma: This is common to slave as well as M2M clients. For slave address of
devices’ fifo could be already known to the driver. Various types of operations could
be expressed by setting appropriate values to the dma_interleaved_template members.
[ 355 ]
DMA in Device Drivers Chapter 9
A non-NULL return of this transfer API represents a "descriptor" for the given transaction.
struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_data_direction direction,
unsigned long flags);
The peripheral driver is expected to have mapped the scatterlist for the DMA operation
prior to calling dmaengine_prep_slave_sg() and must keep the scatterlist mapped until the
DMA operation has completed. The scatterlist must be mapped using the DMA struct
device. If a mapping needs to be synchronized later, dma_sync_*_for_*() must be called
using the DMA struct device, too. So, normal setup should look like this:
nr_sg = dma_map_sg(chan->device->dev, sgl, sg_len);
desc = dmaengine_prep_slave_sg(chan, sgl, nr_sg, direction, flags);
Once a descriptor has been obtained, the callback information can be added and the
descriptor must then be submitted.
4. Submit the transaction: Once the descriptor has been prepared and the callback
information added, it must be placed on the DMA engine drivers pending queue.
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc);
This returns a cookie that can be used to check the progress of DMA engine activity via
other DMA engine calls. The dmaengine_submit() call will not start the DMA operation, it
merely adds it to the pending queue.
5. Issue pending DMA requests and wait for callback notification: The transactions in the
pending queue can be activated by calling the issue_pending API. If the channel is idle,
then the first transaction in the queue is started and subsequent ones queued up. On
completion of each DMA operation, the next in the queue is started and a tasklet triggered.
The tasklet will then call the client completion callback routine for notification, if set.
void dma_async_issue_pending(struct dma_chan *chan);
In the Linux DMA API, there are involved different kind of addresses. As you saw in the Chapter
8, the kernel normally uses virtual addresses. Any address returned by kmalloc(), vmalloc() and
similar interfaces is a virtual address. The virtual memory system translates virtual addresses to
CPU physical addresses, which are stored as phys_addr_t or resource_size_t.
[ 356 ]
Chapter 9 DMA in Device Drivers
The kernel manages processor resources like peripheral registers as physical addresses. These are
the addresses seen in /proc/iomem. The physical addresses must be mapped to virtual addresses
to be useful to a driver. The ioremap() function will map the physical addresses producing virtual
ones.
If the device supports DMA, the driver sets up a buffer by using kmalloc() or a similar interface,
which returns a virtual address (X). The virtual memory system maps X to a physical address (Y)
in system RAM. The driver can use virtual address X to access the buffer, but the device itself
cannot because DMA doesn't go through the CPU virtual memory system. This is part of the
reason for the DMA API: the driver can provide a virtual address X to an interface like dma_map_
single(), which returns the DMA bus address (Z). The driver then tells the device to perform DMA
to Z.
The memory accessed by the DMA should be physically contiguous. Any memory allocated by
kmalloc() (up to 128 KB) or __get_free_pages() (up to 8MB) can be used. What cannot be used is
vmalloc() memory allocation (it would have to set up DMA on each individual physical page).
The Contiguous Memory Allocator (or CMA) was developed to allocate big, physically-
contiguous memory blocks, commonly being used by DMA Engines. The CMA is integrated with
the DMA subsystem and is accessible using the dma_alloc_coherent() DMA API. The CMA can be
modified in the kernel configuration, on the kernel command line, or using the linux,cma-default DT
property that points to the kernel to use reserved memory region as a default CMA memory pool.
[ 357 ]
DMA in Device Drivers Chapter 9
If the architecture is non-coherent, the dma_alloc_coherent() function will make the memory
uncached so that coherency is maintained. The dma_alloc_coherent() function calls arm_
dma_alloc(), which in turns calls __dma_alloc(), which takes pgprot_t as argument, which is
basically the page attributes to make this memory uncached.
static inline void *dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t flag)
{
return dma_alloc_attrs(dev, size, dma_handle, flag, 0);
}
static inline void *dma_alloc_attrs(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t flag,
unsigned long attrs)
{
struct dma_map_ops *ops = get_dma_ops(dev);
void *cpu_addr;
BUG_ON(!ops);
if (!arch_dma_alloc_attrs(&dev, &flag))
return NULL;
if (!ops->alloc)
return NULL;
/*
* Allocate DMA-coherent memory space and return both the kernel remapped
* virtual and bus address for that space.
*/
void *arm_dma_alloc(struct device *dev, size_t size, dma_addr_t *handle,
gfp_t gfp, unsigned long attrs)
{
pgprot_t prot = __get_dma_pgprot(attrs, PAGE_KERNEL);
To allocate and map large (PAGE_SIZE or so) consistent DMA regions, you should do:
#include <linux/dma-mapping.h>
dma_addr_t dma_handle;
cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, gfp);
[ 358 ]
Chapter 9 DMA in Device Drivers
Where dev and size are the same as in the above dma_alloc_coherent() call, and cpu_addr and
dma_handle are the values that dma_alloc_coherent() returned to you. This function may not
be called in interrupt context.
2. Streaming DMA Mappings use cached mapping and clean or invalidate it according
to the operation using dma_map_single() and dma_unmap_single(). This is different from
coherent mapping because the mapping deals with addresses that were chosen a priori,
which are usually mapped for one DMA transfer and unmapped right after it.
For non-coherent processors, the dma_map_single() function will call dma_map_single_attrs(),
which in turn calls arm_dma_map_page(), which ensures that any data held in the cache is
appropriately discarded or written back.
#define dma_map_single(d, a, s, r) dma_map_single_attrs(d, a, s, r, 0)
static inline dma_addr_t dma_map_single_attrs(struct device *dev, void *ptr,
size_t size,
enum dma_data_direction dir,
unsigned long attrs)
{
struct dma_map_ops *ops = get_dma_ops(dev);
dma_addr_t addr;
kmemcheck_mark_initialized(ptr, size);
BUG_ON(!valid_dma_direction(dir));
/* calls arm_dma_map_page for ARM architectures */
addr = ops->map_page(dev, virt_to_page(ptr),
offset_in_page(ptr), size,
dir, attrs);
debug_dma_map_page(dev, virt_to_page(ptr),
offset_in_page(ptr), size,
dir, addr, true);
[ 359 ]
DMA in Device Drivers Chapter 9
return addr;
}
/*
* arm_dma_map_page - map a portion of a page for streaming DMA
* @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
* @page: page that buffer resides in
* @offset: offset into page for start of buffer
* @size: size of buffer to map
* @dir: DMA transfer direction
*
* Ensure that any data held in the cache is appropriately discarded
* or written back.
*
* The device owns this memory once this call has completed. The CPU
* can regain ownership by calling dma_unmap_page().
*/
static dma_addr_t arm_dma_map_page(struct device *dev, struct page *page,
unsigned long offset, size_t size, enum dma_data_direction dir,
unsigned long attrs)
{
if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0)
__dma_page_cpu_to_dev(page, offset, size, dir);
return pfn_to_dma(dev, page_to_pfn(page)) + offset;
}
The streaming DMA mapping routines can be called from interrupt context. There are two
versions of each map/unmap, one that will map/unmap a single memory region, and one
that will map/unmap a scatterlist.
To map a single region, see the code snippet below:
struct device *dev = &my_dev->dev;
dma_addr_t dma_handle;
void *addr = buffer->ptr;
size_t size = buffer->len;
dma_handle = dma_map_single(dev, addr, size, direction);
Where dev is a device pointer, addr is the pointer that contains the virtual buffer address
allocated with kmalloc(), size is the buffer size, and the direction choices are:
DMA_BIDIRECTIONAL, DMA_TO_DEVICE or DMA_FROM_DEVICE. The dma_handle
is the returned DMA bus address.
To unmap the memory region, you will use the following function:
dma_unmap_single(dev, dma_handle, size, direction);
You should call dma_unmap_single() when the DMA activity is finished, e.g., from the
interrupt service routine that indicated that the DMA transfer is done.
[ 360 ]
Chapter 9 DMA in Device Drivers
/* Functions needed to allocate a DMA slave channel, set slave and controller
* specific parameters, get a descriptor for transaction, submit the
* transaction, issue pending requests and wait for callback notification
*/
#include <linux/dmaengine.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/of_device.h>
2. Create a private structure that will store the DMA device-specific information. In this
driver, you will handle a char device, so a miscdevice structure will be created, initialized
and added to your private structure in its first field. The wbuf and rbuf pointer variables
will hold the addresses of your allocated buffers. The dma_m2m_chan pointer variable will
hold the DMA channel associated with the dev device.
struct dma_private
{
struct miscdevice dma_misc_device;
struct device *dev;
char *wbuf;
char *rbuf;
struct dma_chan *dma_m2m_chan;
[ 361 ]
DMA in Device Drivers Chapter 9
The last field of your private structure is a completion variable. A common pattern in kernel
programming involves initiating some activity outside of the current thread, then waiting
for that activity to complete. This activity can be the creation of a new kernel thread,
a request to an existing process, or some sort of hardware-based action (like a DMA
transfer). In such cases, it can be tempting to use a semaphore to synchronize both tasks,
as shown in the following code snippet:
struct semaphore sem;
init_MUTEX_LOCKED(&sem);
start_external_task(&sem);
down(&sem);
The external task can then call up(&sem) when its work is done. As is turns out,
semaphores are not the best tool to use in this situation. In normal use, code attempting
to lock a semaphore finds that semaphore is available almost all the time; if there is
significant contention for the semaphore, performance suffers, and the locking scheme
needs to be reviewed. When the task completion is communicated in the way shown
above, the thread calling down will almost always have to wait; performance will suffer
accordingly. Semaphores can also be subject to a (difficult) race condition when used in
this way if they are declared as automatic variables. In some cases, the semaphore could
vanish before the process calling up is finished with it.
These concerns inspired the addition of the completion interface in the 2.4.7 kernel.
Completions are a lightweight mechanism with one task: allowing one thread to tell another
that the job is done. The advantage of using completions is to generate more efficient code,
as both threads can continue until the result is actually needed.
3. In the probe() function, set up the capabilities for the channel that will be requested,
allocate the wbuf and rbuf buffers, and request the DMA channel from the DMA engine by
using the dma_request_channel() function. The dma_request_channel() function takes three
parameters:
• The dma_m2m_mask that holds the channel capabilities.
• The m2m_dma_data custom data structure (not needed in our driver).
• The dma_m2m_filter that helps to select a more specific channel between multiple
channel possibilities. When allocating a channel, the dma engine finds the first
channel that matches the mask and calls the filter function.
[ 362 ]
Chapter 9 DMA in Device Drivers
dma_device->dma_misc_device.minor = MISC_DYNAMIC_MINOR;
dma_device->dma_misc_device.name = "sdma_test";
dma_device->dma_misc_device.fops = &dma_fops;
dma_device->dev = &pdev->dev;
dma_cap_zero(dma_m2m_mask);
dma_cap_set(DMA_MEMCPY, dma_m2m_mask);
dma_device->dma_m2m_chan = dma_request_channel(dma_m2m_mask, 0, NULL);
misc_register(&dma_device->dma_misc_device);
platform_set_drvdata(pdev, dma_device);
return 0;
}
4. Write the sdma_write() function to communicate with user space. This function gets the
characters written to the char device by using copy_from_user() and store them in the wbuf
buffer. The dma_src and dma_dst DMA addresses are obtained by using the dma_map_
single() function, which takes as parameters the wbuf and rbuf virtual addresses previously
obtained in the probe() function and stored in your DMA private structure. These virtual
addresses are retrieved in sdma_write() by using the container_of() function.
Get a descriptor for the transaction using device_prep_dma_memcpy(). Once the descriptor
has been obtained, the callback information can be added, and the descriptor must be
submitted by using dmaengine_submit().
The dmaengine_submit() function will not start the DMA operation, it merely adds it to the
pending queue. For this, do dma_async_issue_pending(). The transactions in the pending
queue can be activated by calling the issue_pending API. If the channel is idle, then the first
transaction in the queue is started and subsequent ones queued up. On completion of each
DMA operation, the next in queue is started and a tasklet triggered. The tasklet will then
call the client driver´s completion callback routine for notification.
[ 363 ]
DMA in Device Drivers Chapter 9
dma_dev = dma_priv->dma_m2m_chan->device;
dma_m2m_desc = dma_dev->device_prep_dma_memcpy(dma_priv->dma_m2m_chan,
dma_dst,
dma_src,
SDMA_BUF_SIZE,
DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
dma_m2m_desc->callback = dma_m2m_callback;
dma_m2m_desc->callback_param = dma_priv;
init_completion(&dma_priv->dma_m2m_ok);
cookie = dmaengine_submit(dma_m2m_desc);
if (dma_submit_error(cookie)){
dev_err(dma_priv->dev, "Failed to submit DMA\n");
return -EINVAL;
};
dma_async_issue_pending(dma_priv->dma_m2m_chan);
wait_for_completion(&dma_priv->dma_m2m_ok);
dma_async_is_tx_complete(dma_priv->dma_m2m_chan, cookie, NULL, NULL);
[ 364 ]
Chapter 9 DMA in Device Drivers
return count;
}
5. Create a callback function to inform about the completion of the DMA transaction. Signal
the completion of the event inside this function:
static void dma_m2m_callback(void *data)
{
if (*(dma_priv->rbuf) != *(dma_priv->wbuf))
dev_err(dma_priv->dev, "buffer copy failed!\n");
6. Open the bcm2710-rpi-3-b.dts DT file, and add the sdma_m2m node in the soc node:
&soc {
virtgpio: virtgpio {
compatible = "brcm,bcm2835-virtgpio";
gpio-controller;
#gpio-cells = <2>;
firmware = <&firmware>;
status = "okay";
};
[...]
sdma_m2m {
compatible ="arrow,sdma_m2m";
};
};
7. Create a new sdma_rpi3_m2m.c file in the linux_5.4_rpi3_drivers folder, and add sdma_rpi3_
m2m.o to your Makefile obj-m variable, then build and deploy the module to the Raspberry
Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
8. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
[ 365 ]
DMA in Device Drivers Chapter 9
struct dma_private
{
struct miscdevice dma_misc_device;
struct device *dev;
char *wbuf;
char *rbuf;
struct dma_chan *dma_m2m_chan;
struct completion dma_m2m_ok;
};
if (*(dma_priv->rbuf) != *(dma_priv->wbuf))
dev_err(dma_priv->dev, "buffer copy failed!\n");
[ 366 ]
Chapter 9 DMA in Device Drivers
dma_dev = dma_priv->dma_m2m_chan->device;
dma_m2m_desc = dma_dev->device_prep_dma_memcpy(dma_priv->dma_m2m_chan,
dma_dst,
dma_src,
SDMA_BUF_SIZE,
DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
dma_m2m_desc->callback = dma_m2m_callback;
dma_m2m_desc->callback_param = dma_priv;
init_completion(&dma_priv->dma_m2m_ok);
cookie = dmaengine_submit(dma_m2m_desc);
if (dma_submit_error(cookie)){
dev_err(dma_priv->dev, "Failed to submit DMA\n");
return -EINVAL;
};
dma_async_issue_pending(dma_priv->dma_m2m_chan);
wait_for_completion(&dma_priv->dma_m2m_ok);
dma_async_is_tx_complete(dma_priv->dma_m2m_chan, cookie, NULL, NULL);
return count;
}
[ 367 ]
DMA in Device Drivers Chapter 9
dma_device->dma_misc_device.minor = MISC_DYNAMIC_MINOR;
dma_device->dma_misc_device.name = "sdma_test";
dma_device->dma_misc_device.fops = &dma_fops;
dma_device->dev = &pdev->dev;
dma_cap_zero(dma_m2m_mask);
dma_cap_set(DMA_MEMCPY, dma_m2m_mask);
dma_device->dma_m2m_chan = dma_request_channel(dma_m2m_mask, 0, NULL);
if (!dma_device->dma_m2m_chan) {
dev_err(&pdev->dev, "Error opening the SDMA memory to memory channel\n");
return -EINVAL;
}
retval = misc_register(&dma_device->dma_misc_device);
if (retval) return retval;
platform_set_drvdata(pdev, dma_device);
return 0;
}
[ 368 ]
Chapter 9 DMA in Device Drivers
{ .compatible = "arrow,sdma_m2m"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);
ret_val = platform_driver_register(&my_platform_driver);
if (ret_val !=0)
{
pr_err("platform value returned %d\n", ret_val);
return ret_val;
}
pr_info("demo_init exit\n");
return 0;
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a SDMA memory to memory driver");
[ 369 ]
DMA in Device Drivers Chapter 9
sdma_rpi3_m2m.ko demonstration
Load the module:
root@raspberrypi:/home/pi# insmod sdma_rpi3_m2m.ko
demo_init enter
sdma_m2m soc:sdma_m2m: platform_probe enter
sdma_m2m soc:sdma_m2m: platform_probe exit
demo_init exit
Write values to the wbuf buffer, then start a DMA transaction which copies values from wbuf to
rbuf. Finally, compare both buffers values:
root@raspberrypi:/home/pi# echo abcdefg > /dev/sdma_test
sdma_m2m soc:sdma_m2m: The wbuf string is abcdefg
sdma_m2m soc:sdma_m2m: dma_src map obtained
sdma_m2m soc:sdma_m2m: dma_dst map obtained
sdma_m2m soc:sdma_m2m: successful descriptor obtained
sdma_m2m soc:sdma_m2m: dma_m2m_callback
finished DMA transaction
sdma_m2m soc:sdma_m2m: buffer copy passed!
sdma_m2m soc:sdma_m2m: The rbuf string is abcdefg
sdma_m2m soc:sdma_m2m: wbuf is abcdefg
sdma_m2m soc:sdma_m2m: rbuf is abcdefg
Remove the module:
root@raspberrypi:/home/pi# rmmod sdma_rpi3_m2m.ko
demo_exit enter
sdma_m2m soc:sdma_m2m: platform_remove enter
sdma_m2m soc:sdma_m2m: platform_remove exit
demo_exit exit
[ 370 ]
10
Input Subsystem
Many Linux device drivers are not implemented directly as character drivers. They are
implemented under a framework, specific to a given device type (e.g., networking, MTD, RTC,
v4L2, serial, IIO). The framework factors out the common parts of drivers for the same type of
devices to reduce code duplication.
The Linux frameworks allow the provision of a coherent user space interface for every type
of device, regardless of the driver. The application can still see many of the device drivers as
character devices. For example, the Linux network subsystem provides a socket API such that an
application can connect to a network using any network driver without knowing the details of the
network driver.
Throughout this chapter, you will explore the Input subsystem in detail. You will also develop
several drivers that will help you to understand the use of this framework.
[ 371 ]
Input Subsystem Chapter 10
Observe in the following image how the driver interfaces with a framework (to expose the
hardware to the user application) and with a bus infrastructure (part of the device model that
communicates with the hardware):
[ 372 ]
Chapter 10 Input Subsystem
device drivers and makes them available through character devices in the /dev/input/
directory. The event interface will represent each input device as a /dev/input/event<X>
character device. This is the preferred interface for user space to consume user input, and
all clients are encouraged to use it.
You can use blocking and nonblocking reads and also select() on the /dev/input/eventX devices, and
you’ll always get a whole number of input events on a read. Their layout is:
struct input_event {
struct timeval time;
unsigned short type;
unsigned short code;
unsigned int value;
};
A very useful application for input device testing is evtest, which is located at http://cgit.freedesktop.org/
evtest/. The evtest application displays information on the input device specified on the command
line, including all the events supported by the device.
In the next figure, you can see an Input subsystem diagram that can be used as an example for
the next kernel module lab, where you will control an I2C accelerometer by using the Input
subsystem.
[ 373 ]
Input Subsystem Chapter 10
[ 374 ]
Chapter 10 Input Subsystem
You will allocate and free struct input_polled_dev by using the following functions:
struct input_polled_dev *input_allocate_polled_device(void);
void input_free_polled_device(struct input_polled_dev *dev);
The accelerometer driver will support EV_KEY type events, with a KEY_1 event that will be set to
0 or to 1 depending on the board´s tilt. The set_bit() call is an atomic operation, allowing it to set a
particular bit to 1.
set_bit(EV_KEY, ioaccel->polled_input->input->evbit); /* supported event types (support for
EV_KEY events) */
set_bit(KEY_1, ioaccel->polled_input->input->keybit); /* Set the event code support (event
KEY_1 ) */
The input_polled_dev structure will be handled by the poll() callback function. This function polls
the device and posts input events. The poll_interval field will be set to 50 ms in your driver. Inside
the poll() function, the event is sent to the event handler by using the input_event() function.
After submitting the event, the input core must be notified by using the input_sync() function:
void input_sync(struct input_dev *dev);
The main code sections of the driver will be described using three categories: Device Tree, Input
framework as an I2C interaction, and Input framework as an input device.
You will use the Raspberry Pi GPIO expansion connector to obtain the I2C signals. The GPIO2 and
GPIO3 pins will be used to get the SDA1 and SCL1 signals. Connect them to the pins SDA and SCL
of the ADXL345 Accel click mikroBUS™ accessory board. Do not forget to connect 3.3V and GND
between the two boards.
[ 375 ]
Input Subsystem Chapter 10
Device Tree
Open the bcm2710-rpi-3-b.dts DT file, and add the adxl345@1c node in the i2c1 controller master
node. The reg property provides the ADXL345 I2C address:
&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins>;
clock-frequency = <100000>;
status = "okay";
[...]
adxl345@1c {
compatible = "arrow,adxl345";
reg = <0x1d>;
};
};
Build the modified Device Tree, and load it to the Raspberry Pi:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
root@raspberrypi:/home/pi# reboot
[ 376 ]
Chapter 10 Input Subsystem
6. Use SMBus functions for accessing to the accelerometer registers. After VS is applied, the
ADXL345 device enters standby mode, where power consumption is minimized, and the
device waits for VDD I/O to be applied and for the command to enter measurement mode
to be received. This command can be initiated by setting the measure bit (Bit D3) in the
POWER_CTL register (Address 0x2D):
#define POWER_CTL 0x2D
#define PCTL_MEASURE (1 << 3)
#define OUT_X_MSB 0x33
/* Enter measurement mode */
i2c_smbus_write_byte_data(client, POWER_CTL, PCTL_MEASURE);
[ 377 ]
Input Subsystem Chapter 10
2. The device model needs to keep pointers between physical devices (devices as handled by the
physical bus, I2C in this case) and logical devices (devices handled by subsystems, like the Input
subsystem in this case). This need is typically implemented by creating a private data structure to
manage the device and implement such pointers between the physical and logical worlds. As you
have seen in other labs throughout this book, this private structure allows the driver to manage
multiple devices using the same driver. Add the following private structure definition to your
driver code:
struct ioaccel_dev {
struct i2c_client *i2c_client;
struct input_polled_dev *polled_input;
};
3. In the ioaccel_probe() function, declare an instance of this structure, and allocate it it using devm_
kzalloc():
struct ioaccel_dev *ioaccel;
ioaccel = devm_kzalloc(&client->dev, sizeof(struct ioaccel_dev), GFP_KERNEL);
4. To be able to access your private data structure in other functions of the driver, you need to attach it
to the i2c_client structure by using the i2c_set_clientdata() function. This function stores ioaccel in
client->dev->driver_data. You can retrieve the ioccel pointer from the private structure using the
function i2c_get_clientdata(client):
i2c_set_clientdata(client, ioaccel); /* Write it in the probe() function */
ioaccel = i2c_get_clientdata(client); /* Write it in the remove() function */
5. Allocate the input_polled_dev structure in probe() by using the following line of code:
ioaccel->polled_input = devm_input_allocate_polled_device(&client->dev);
6. Initialize the polled input device. Keep pointers between physical devices (devices as handled by
the physical bus, I2C in this case) and logical devices:
ioaccel->i2c_client = client; /* Keep pointer to the I2C device, needed for exchanging data with
the accelerometer */
ioaccel->polled_input->private = ioaccel; /* struct polled_input can store the driver-specific
data in void *private. Place the pointer to the private structure here; in this way, you will be
able to recover the ioaccel pointer later (as it can be seen for example in the ioaccel_poll()
function) */
ioaccel->polled_input->poll_interval = 50; /* Callback interval */
[ 378 ]
Chapter 10 Input Subsystem
See the links between physical and logical device structures in the next figure:
7. Set the event type and the event generated for this device:
set_bit(EV_KEY, ioaccel->polled_input->input->evbit); /* Supported event type (support
for EV_KEY events) */
set_bit(KEY_1, ioaccel->polled_input->input->keybit); /* Set the event code support
(event KEY_1 ) */
[ 379 ]
Input Subsystem Chapter 10
8. Register in probe() and unregister in remove() the polled_input device to the input core. Once
registered, the device is global for the rest of the driver functions until it is unregistered.
After this call, the device is ready to accept requests from user space applications.
input_register_polled_device(ioaccel->polled_input);
input_unregister_polled_device(ioaccel->polled_input);
9. Write the ioaccel_poll() function. This function will be called every 50ms to read the
OUT_X_MSB register (address 0x33) of the ADXL345 accelerometer by using the
i2c_smbus_read_byte_data() function. The first parameter of the i2c_smbus_read_byte_data()
function is a pointer to the i2c_client structure. This pointer will allow you to get the
ADXL345 I2C address (0x1d). The 0x1d value will be retrieved from client->address. After
binding, the I2C bus driver gets this I2C address value from the ioaccel Device Tree node
and stores it in the i2c_client structure, then this I2C address is sent to the ioaccel_probe()
function via a client pointer variable that points to this i2c_client structure.
An input event KEY_1 will be reported with values of 0 or 1, depending on the ADXL345
board´s tilt. You can use a different range of acceleration values to report these events.
static void ioaccel_poll(struct input_polled_dev * pl_dev)
{
struct ioaccel_dev * ioaccel = pl_dev->private;
int val = 0;
val = i2c_smbus_read_byte_data(ioaccel->i2c_client, OUT_X_MSB);
input_sync(ioaccel->polled_input->input);
}
10. Create a new i2c_rpi3_accel.c file in the linux_5.4_rpi3_drivers folder, and add i2c_rpi3_accel.o
to your Makefile obj-m variable, then build and deploy the module to the Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
[ 380 ]
Chapter 10 Input Subsystem
/* Poll function */
static void ioaccel_poll(struct input_polled_dev * pl_dev)
{
struct ioaccel_dev *ioaccel = pl_dev->private;
int val = 0;
val = i2c_smbus_read_byte_data(ioaccel->i2c_client, OUT_X_MSB);
input_sync(ioaccel->polled_input->input);
}
[ 381 ]
Input Subsystem Chapter 10
ioaccel->i2c_client = client;
ioaccel->polled_input->private = ioaccel;
ioaccel->polled_input->poll_interval = 50;
ioaccel->polled_input->poll = ioaccel_poll;
ioaccel->polled_input->input->dev.parent = &client->dev;
ioaccel->polled_input->input->name = "IOACCEL keyboard";
ioaccel->polled_input->input->id.bustype = BUS_I2C;
/* Register the device, now the device is global until being unregistered */
input_register_polled_device(ioaccel->polled_input);
return 0;
}
[ 382 ]
Chapter 10 Input Subsystem
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is an accelerometer INPUT framework platform driver");
i2c_rpi3_accel.ko demonstration
Scan the I2C bus with the i2c-tools suite. List the avalaible I2C buses and the devices
connected to the i2c-1 bus:
root@raspberrypi:/home/pi# i2cdetect -l
i2c-1 i2c bcm2835 (i2c@7e804000) I2C adapter
root@raspberrypi:/home/pi# i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- 1d -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
Set measurement mode for the ADXL345 device:
root@raspberrypi:/home/pi# i2cset -y 1 0x1d 0x2d 0x08
The 0x33 value corresponds to the OUT_X_MSB register address. Move the Raspberry Pi board, and
see how changes the OUT_X_MSB register value. Set a range of values to generate an event. This
range of values will be set inside the ioaccel_poll() function:
root@raspberrypi:/home/pi# while true; do i2cget -y 1 0x1d 0x33; done
Load the module:
root@raspberrypi:/home/pi# insmod i2c_rpi3_accel.ko
adxl345 1-001d: my_probe() function is called.
input: IOACCEL keyboard as /devices/platform/soc/3f804000.i2c/i2c
-1/1-001d/input/input0
Execute the evtest application to see the input devices available. Select 0. Move the Raspberry
Pi board until the event EV_KEY is generated:
root@raspberrypi:/home/pi# evtest
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0: IOACCEL keyboard
Select the device event number [0-0]: 0
Input driver version is 1.0.1
Input device ID: bus 0x18 vendor 0x0 product 0x0 version 0x0
Input device name: "IOACCEL keyboard"
Supported events:
Event type 0 (EV_SYN)
Event type 1 (EV_KEY)
Event code 2 (KEY_1)
[ 383 ]
Input Subsystem Chapter 10
Properties:
Testing ... (interrupt to exit)
Event: time 1617904839.834963, type 1 (EV_KEY), code 2 (KEY_1), value 1
Event: time 1617904839.834963, -------------- SYN_REPORT ------------
Event: time 1617904839.895074, type 1 (EV_KEY), code 2 (KEY_1), value 0
Event: time 1617904839.895074, -------------- SYN_REPORT ------------
Event: time 1617904842.054966, type 1 (EV_KEY), code 2 (KEY_1), value 1
Event: time 1617904842.054966, -------------- SYN_REPORT ------------
Event: time 1617904842.115023, type 1 (EV_KEY), code 2 (KEY_1), value 0
Event: time 1617904842.115023, -------------- SYN_REPORT ------------
Exit with ^C:
root@raspberrypi:/home/pi#
Remove the module:
root@raspberrypi:/home/pi# rmmod i2c_rpi3_accel.ko
For the development of the lab, you will use the MOD-Wii-UEXT-NUNCHUCK from Olimex:
https://www.olimex.com/Products/Modules/Sensors/MOD-WII/MOD-Wii-UEXT-NUNCHUCK/open-source-
hardware
[ 384 ]
Chapter 10 Input Subsystem
The Wii Nunchuk uses a proprietary connector with 6 pins, which exposes I2C signals, +3V
and Gnd. The MOD-Wii-UEXT-NUNCHUCK includes an UEXT adapter which offers a kind of
universal connector supporting three serial communication interfaces: I2C, SPI and RS232. In the
MOD-Wii-UEXT-NUNCHUCK, only the I2C signals are connected to the Nunchuk device.
This LAB 10.2 has been inspired by Bootlin´s Embedded Linux kernel and driver development
training materials located at https://bootlin.com/training/kernel/.
Connect the Raspberry Pi´s I2C pins to the I2C ones of the Nunchuk device:
• Connect Raspberry Pi SCL to UEXT SCL (Pin 5).
• Connect Raspberry Pi SDA to UEXT SDA (Pin 6).
Connect the next power pins between the two boards:
• Connect Raspberry Pi 3.3V to UEXT 3.3V (Pin 1).
• Connect Raspberry Pi GND to UEXT GND (Pin 2).
[ 385 ]
Input Subsystem Chapter 10
nunchuk: nunchuk@52 {
compatible = "nunchuk";
reg = <0x52>;
};
};
[ 386 ]
Chapter 10 Input Subsystem
4. Add "nunchuk" to the list of devices supported by the driver. The compatible variable
matches with the compatible property of the nunchuk DT node:
static const struct of_device_id nunchuk_of_match[] = {
{ .compatible = "nunchuk"},
{}
};
MODULE_DEVICE_TABLE(of, nunchuk_of_match);
6. You will use the i2c_master_send() and i2c_master_recv() functions to establish a plain I2C
communication with the Nunchuck controller. These routines read/write some bytes from/
to a client device. The first parameter of these functions is an I2C client pointer which
contains the I2C address of the Nunchuk device (the I2C slave address of the Nunchuk
[ 387 ]
Input Subsystem Chapter 10
is 0x52). The second parameter is the buffer to read/write, and the third parameter is the
number of bytes to read/write (must be less than the length of the buffer, also should be
less than 64k since msg.len is u16.). Returned is the actual number of bytes read/written.
To communicate with the Nunchuk, you must send a handshake signal. In the probe()
function, you will send "0xf0, 0x55" to initialize the first register of the Nunchuk and
“0xFB, 0x00” to initialize the second register. Then, send one byte "0x00" each time you
request data from the Nunchuck. You can see below the nunchuk_read_registers() function,
which will read the data from the Nunchuk in 6 byte chunks:
static int nunchuk_read_registers(struct i2c_client *client, u8 *buf, int buf_size)
{
int status;
mdelay(10);
buf[0] = 0x00;
status = i2c_master_send(client, buf, 1);
if (status >= 0 && status != 1)
return -EIO;
if (status < 0)
return status;
mdelay(10);
return 0;
}
The following image shows the data stream (six bytes) coming from the Nunchuck
controller. First 2 bytes are the X and Y axis data of the Joystick. Next 3 bytes are the X, Y
and Z axis data of the accelerometer sensor. The last byte includes the 2 lower bits of the
accelerometer axes and the c-button and z-button status.
[ 388 ]
Chapter 10 Input Subsystem
2. The device model needs to keep pointers between physical devices (devices as handled by
the physical bus, I2C in this case) and logical devices (devices handled by subsystems, like
the Input subsystem in this case). This need is typically implemented by creating a private
data structure to manage the device and implement such pointers between the physical
and logical worlds. Add the following private structure definition to your driver code:
[ 389 ]
Input Subsystem Chapter 10
struct nunchuk_dev {
struct input_polled_dev *polled_input;
struct i2c_client *client;
};
3. In the nunchuk_probe() function, declare an instance of the previous structure and allocate
it:
struct nunchuk_dev *nunchuk;
nunchuk = devm_kzalloc(&client->dev, sizeof(*nunchuk), GFP_KERNEL);
4. To be able to access your private data structure in other functions of the driver, you need
to attach it to the i2c_client structure using the i2c_set_clientdata() function. This function
stores nunchuk in client->dev->driver_data. You can retrieve the nunchuk pointer to the private
structure by using the i2c_get_clientdata(client) function:
i2c_set_clientdata(client, nunchuk); /* Write it in the probe() function */
nunchuk = i2c_get_clientdata(client); /* Write it in the remove() function */
6. Initialize the polled input device. Keep pointers between physical devices (devices as
handled by the physical bus, I2C in this case) and logical devices:
nunchuk->client = client; /* Store a pointer to the I2C device in the global structure,
needed for exchanging data with the nunchuk device */
polled_device->private = nunchuk; /* struct polled_device can store the driver-specific
data in void *private. Place the pointer to the private structure here; in this way, you
will be able to recover the nunchuk pointer later (for example, in the nunchuk_poll()
function) */
polled_device->poll_interval = 50; /* Callback interval */
polled_device->poll = nunchuk_poll; /* Callback that will be called every 50 ms interval
*/
polled_device->input->dev.parent = &client->dev; /* Keep pointers between physical
devices and logical devices */
polled_device->input->name = "WII Nunchuk"; /* Input sub-device parameters that will
appear in log on registering the device */
polled_device->input->id.bustype = BUS_I2C; /* Input sub-device parameters */
7. Set event types and event codes for the Nunchuk device:
/* Set EV_KEY type events and from those BTN_C and BTN_Z event codes */
set_bit(EV_KEY, input->evbit);
set_bit(BTN_C, input->keybit); /* buttons */
set_bit(BTN_Z, input->keybit);
/*
* Set EV_ABS type events and from those
[ 390 ]
Chapter 10 Input Subsystem
/*
* Fill additional fields in the input_dev struct for
* each absolute axis nunchuk has
*/
input_set_abs_params(input, ABS_X, 0x00, 0xff, 0, 0);
input_set_abs_params(input, ABS_Y, 0x00, 0xff, 0, 0);
8. Register in probe() and unregister in remove() the polled_input device to the input core. Once
registered, the device is global for the rest of the driver functions until it is unregistered.
After this call, the device is ready to accept requests from user space applications.
input_register_polled_device(nunchuk->polled_input);
input_unregister_polled_device(nunchuk->polled_input);
9. Write the nunchuk_poll() function. This function will be called every 50 ms. Inside nunchuk_
poll(), you will call nunchuk_read_registers(), which read data from the Nunchuk device.
The first parameter of the nunchuk_read_registers() function is a pointer to the i2c_client
structure. This pointer will allow you to get the Nunckuk I2C address (0x52). The client
pointer will be retrieved from client->address using the following lines of code:
nunchuk = polled_input->private;
client = nunchuk->client;
The first thing you should do is place a 10 ms delay at the beginning of the nunchuk_read_
registers() function using the mdelay() function. This delay will separate the following I2C
action from any previous I2C action. If you look through the Nunchuk documentation,
you will see that each time you want to read from the Nunchuk device, you must first
send the byte 0x00, then the Nunchuk will return 6 bytes of data. Therefore the next
thing your nunchuk_read_registers() function should do is send the 0x00 byte by using the
i2c_master_send() function. This action should be immediately followed by a 10 ms delay.
Finally, nunchuk_read_registers() will read six bytes of data from the Nunchuk device and
store them in buf using the i2c_master_recv() function.
You will store the buf[0] and buf[1] joystick values in the joy_x and joy_y variables. You will
also get the C button and Z button status from the buf[5] variable and store it in the c_button
[ 391 ]
Input Subsystem Chapter 10
and z_button variables. The accelerometer data for its three axes will be retrieved from
buf[2] and buf[5] and stored in the accel_x, accel_y and accel_z variables.
Finally, you will report the events to the Input subsystem. The input_sync() function will
tell those who receive the events that a complete report has been sent.
static int nunchuk_read_registers(struct i2c_client *client, u8 *buf, int buf_size)
{
mdelay(10);
buf[0] = 0x00;
i2c_master_send(client, buf, 1);
mdelay(10);
i2c_master_recv(client, buf, buf_size);
return 0;
}
/*
* Poll handler function reads the harware,
* queues events to be reported (input_report_*)
* and flushes the queued events (input_sync)
*/
static void nunchuk_poll(struct input_polled_dev *polled_input)
{
u8 buf[6];
int joy_x, joy_y, z_button, c_button, accel_x, accel_y, accel_z;
struct i2c_client *client;
struct nunchuk_dev *nunchuk;
/*
* Recover the global nunchuk structure and from it the client address
* to stablish an I2C transaction with the nunchuck device
*/
nunchuk = polled_input->private;
client = nunchuk->client;
joy_x = buf[0];
joy_y = buf[1];
/* Bit 0 indicates if Z button is pressed */
z_button = (buf[5] & BIT(0)? 0 : 1);
/* Bit 1 indicates if C button is pressed */
c_button = (buf[5] & BIT(1)? 0 : 1);
[ 392 ]
Chapter 10 Input Subsystem
/*
* Tell those who receive the events
* that a complete report has been sent
*/
input_sync(polled_input->input);
}
11. Create a new nunchuk.c file and a Makefile file in the linux_5.4_nunchuk_drivers folder, and
add nunchuk.o to your Makefile obj-m variable, then build and deploy the module to the
Raspberry Pi:
~/linux_5.4_rpi3_drivers/linux_5.4_nunchuk_drivers$ make
~/linux_5.4_rpi3_drivers/linux_5.4_nunchuk_drivers$ make deploy
12. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
[ 393 ]
Input Subsystem Chapter 10
mdelay(10);
buf[0] = 0x00;
status = i2c_master_send(client, buf, 1);
if (status >= 0 && status != 1)
return -EIO;
if (status < 0)
return status;
mdelay(10);
return 0;
}
/*
* Poll handler function reads the harware,
* queues events to be reported (input_report_*)
* and flushes the queued events (input_sync)
*/
static void nunchuk_poll(struct input_polled_dev *polled_input)
{
u8 buf[6];
int joy_x, joy_y, z_button, c_button, accel_x, accel_y, accel_z;
struct i2c_client *client;
struct nunchuk_dev *nunchuk;
/*
* Recover the global nunchuk structure and from it the client address
* to stablish an I2C transaction with the nunchuck device
*/
nunchuk = polled_input->private;
client = nunchuk->client;
joy_x = buf[0];
joy_y = buf[1];
/* Bit 0 indicates if Z button is pressed */
z_button = (buf[5] & BIT(0)? 0 : 1);
/* Bit 1 indicates if C button is pressed */
c_button = (buf[5] & BIT(1)? 0 : 1);
[ 394 ]
Chapter 10 Input Subsystem
/*
* Tell those who receive the events
* that a complete report has been sent
*/
input_sync(polled_input->input);
}
[ 395 ]
Input Subsystem Chapter 10
nunchuk->client = client;
polled_device->input->dev.parent = &client->dev;
/*
* Store the polled device in the global structure
* to recover it in the remove() function
*/
nunchuk->polled_input = polled_device;
input = polled_device->input;
/* Set EV_KEY type events and from those BTN_C and BTN_Z event codes */
set_bit(EV_KEY, input->evbit);
set_bit(BTN_C, input->keybit); /* buttons */
set_bit(BTN_Z, input->keybit);
/*
* Set EV_ABS type events and from those
* ABS_X, ABS_Y, ABS_RX, ABS_RY and ABS_RZ event codes
*/
set_bit(EV_ABS, input->evbit);
set_bit(ABS_X, input->absbit); /* joystick */
set_bit(ABS_Y, input->absbit);
set_bit(ABS_RX, input->absbit); /* accelerometer */
set_bit(ABS_RY, input->absbit);
set_bit(ABS_RZ, input->absbit);
/*
* Fill additional fields in the input_dev struct for
* each absolute axis nunchuk has
*/
input_set_abs_params(input, ABS_X, 0x00, 0xff, 0, 0);
input_set_abs_params(input, ABS_Y, 0x00, 0xff, 0, 0);
[ 396 ]
Chapter 10 Input Subsystem
buf[1] = 0x55;
ret = i2c_master_send(client, buf, 2);
if (ret >= 0 && ret != 2)
return -EIO;
if (ret < 0)
return ret;
udelay(1);
buf[0] = 0xfb;
buf[1] = 0x00;
ret = i2c_master_send(client, buf, 1);
if (ret >= 0 && ret != 1)
return -EIO;
if (ret < 0)
return ret;
return 0;
}
return 0;
}
[ 397 ]
Input Subsystem Chapter 10
},
.probe = nunchuk_probe,
.remove = nunchuk_remove,
.id_table = nunchuk_id,
};
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a Nunchuk Wii I2C driver");
nunchuk.ko demonstration
Load the nunchuk module:
root@raspberrypi:/home/pi# insmod nunchuk.ko
nunchuk: loading out-of-tree module taints kernel.
nunchuk 1-0052: nunchuck_probe() function is called.
input: WII Nunchuk as /devices/platform/soc/3f804000.i2c/i2c-1/1-
0052/input/input0
Execute the evtest application and play with the nunchuk device:
root@raspberrypi:/home/pi# evtest
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0: WII Nunchuk
Select the device event number [0-0]: 0
Input driver version is 1.0.1
Input device ID: bus 0x18 vendor 0x0 product 0x0 version 0x0
Input device name: "WII Nunchuk"
Supported events:
Event type 0 (EV_SYN)
Event type 1 (EV_KEY)
Event code 306 (BTN_C)
Event code 309 (BTN_Z)
Event type 3 (EV_ABS)
Event code 0 (ABS_X)
Value 126
Min 0
Max 255
Event code 1 (ABS_Y)
Value 130
Min 0
Max 255
Event code 3 (ABS_RX)
Value 669
Min 0
Max 1023
Event code 4 (ABS_RY)
Value 513
[ 398 ]
Chapter 10 Input Subsystem
Min 0
Max 1023
Event code 5 (ABS_RZ)
Value 634
Min 0
Max 1023
Properties:
Testing ... (interrupt to exit)
Event: time 1608594499.723581, type 3 (EV_ABS), code 3 (ABS_RX), value 669
Event: time 1608594499.723581, type 3 (EV_ABS), code 4 (ABS_RY), value 513
Event: time 1608594499.723581, type 3 (EV_ABS), code 5 (ABS_RZ), value 634
Event: time 1608594499.723581, -------------- SYN_REPORT ------------
Event: time 1608594499.803433, type 3 (EV_ABS), code 3 (ABS_RX), value 580
Event: time 1608594499.803433, type 3 (EV_ABS), code 4 (ABS_RY), value 482
Event: time 1608594499.803433, type 3 (EV_ABS), code 5 (ABS_RZ), value 665
Event: time 1608594499.803433, -------------- SYN_REPORT ------------
Event: time 1608594499.883281, type 3 (EV_ABS), code 3 (ABS_RX), value 490
Event: time 1608594499.883281, type 3 (EV_ABS), code 4 (ABS_RY), value 451
Event: time 1608594499.883281, type 3 (EV_ABS), code 5 (ABS_RZ), value 698
Event: time 1608594499.883281, -------------- SYN_REPORT ------------
Event: time 1608594499.963330, type 3 (EV_ABS), code 3 (ABS_RX), value 401
Event: time 1608594499.963330, type 3 (EV_ABS), code 4 (ABS_RY), value 421
Event: time 1608594499.963330, type 3 (EV_ABS), code 5 (ABS_RZ), value 730
Event: time 1608594499.963330, -------------- SYN_REPORT ------------
Event: time 1608594500.043247, type 3 (EV_ABS), code 3 (ABS_RX), value 387
Event: time 1608594500.043247, type 3 (EV_ABS), code 4 (ABS_RY), value 426
Event: time 1608594500.043247, type 3 (EV_ABS), code 5 (ABS_RZ), value 726
Event: time 1608594500.043247, -------------- SYN_REPORT ------------
Event: time 1608594500.123308, type 1 (EV_KEY), code 309 (BTN_Z), value 1
Event: time 1608594500.123308, type 3 (EV_ABS), code 3 (ABS_RX), value 388
Event: time 1608594500.123308, type 3 (EV_ABS), code 4 (ABS_RY), value 430
Event: time 1608594500.123308, type 3 (EV_ABS), code 5 (ABS_RZ), value 728
Event: time 1608594500.123308, -------------- SYN_REPORT ------------
Event: time 1608594500.203264, type 3 (EV_ABS), code 3 (ABS_RX), value 387
Event: time 1608594500.203264, type 3 (EV_ABS), code 4 (ABS_RY), value 429
Event: time 1608594500.203264, type 3 (EV_ABS), code 5 (ABS_RZ), value 730
Event: time 1608594500.203264, -------------- SYN_REPORT ------------
Event: time 1608594500.283249, type 3 (EV_ABS), code 3 (ABS_RX), value 389
Event: time 1608594500.283249, type 3 (EV_ABS), code 4 (ABS_RY), value 434
Event: time 1608594500.283249, type 3 (EV_ABS), code 5 (ABS_RZ), value 733
Event: time 1608594500.283249, -------------- SYN_REPORT ------------
[ 399 ]
Input Subsystem Chapter 10
Create a new python_apps folder inside the nunchuk_drivers folder, where you will store the python
applications developed in this LAB 10.3:
~/linux_5.4_rpi3_drivers/nunchuk_drivers$ mkdir python_apps
The applications developed in this LAB 10.3 will not receive the event codes ABS_RX, ABS_RY and
ABS_RZ (generated by the Nunchuk accelerometer device), so you will comment the next lines of
code in bold in the Nunchuk driver:
static int nunchuk_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret;
u8 buf[2];
struct device *dev = &client->dev;
[...]
/* Set EV_KEY type events and from those BTN_C and BTN_Z event codes */
set_bit(EV_KEY, input->evbit);
set_bit(BTN_C, input->keybit); /* buttons */
set_bit(BTN_Z, input->keybit);
/*
* Set EV_ABS type events and from those
* ABS_X, ABS_Y, ABS_RX, ABS_RY and ABS_RZ event codes
*/
set_bit(EV_ABS, input->evbit);
set_bit(ABS_X, input->absbit); /* joystick */
set_bit(ABS_Y, input->absbit);
//set_bit(ABS_RX, input->absbit); /* accelerometer */
//set_bit(ABS_RY, input->absbit);
//set_bit(ABS_RZ, input->absbit);
/*
* Fill additional fields in the input_dev struct for
* each absolute axis nunchuk has
*/
input_set_abs_params(input, ABS_X, 0x00, 0xff, 0, 0);
input_set_abs_params(input, ABS_Y, 0x00, 0xff, 0, 0);
[ 400 ]
Chapter 10 Input Subsystem
return 0;
}
/*
* Tell those who receive the events
* that a complete report has been sent
*/
input_sync(polled_input->input);
}
[ 401 ]
Input Subsystem Chapter 10
Build and deploy the modified kernel module to the Raspberry Pi:
~/linux_5.4_rpi3_drivers/nunchuk_drivers$ make
~/linux_5.4_rpi3_drivers/nunchuk_drivers$ make deploy
The python-evdev package also comes with a small command-line program for listing and
monitoring input devices. You can follow the next instructions to test the program:
Load the nunchuk module:
root@raspberrypi:/home/pi# insmod nunchuk.ko
nunchuk: loading out-of-tree module taints kernel.
nunchuk 1-0052: nunchuck_probe() function is called.
input: WII Nunchuk as /devices/platform/soc/3f804000.i2c/i2c-1/1-
0052/input/input0
Execute the evtest application included in the python-evdev package:
root@raspberrypi:/home/pi# python3 -m evdev.evtest
ID Device Name Phys
Uniq
--------------------------------------------------------------------------------
---------------------
0 /dev/input/event0 WII Nunchuk
Select devices [0-0]: 0
Listening for events (press ctrl-c to exit) ...
time 1612525084.315536 type 3 (EV_ABS), code 0 (ABS_X), value 147
time 1612525084.315536 --------- SYN_REPORT --------
time 1612525084.375653 type 3 (EV_ABS), code 0 (ABS_X), value 251
time 1612525084.375653 --------- SYN_REPORT --------
time 1612525084.435532 type 3 (EV_ABS), code 0 (ABS_X), value 255
time 1612525084.435532 --------- SYN_REPORT --------
time 1612525084.555543 type 3 (EV_ABS), code 0 (ABS_X), value 126
time 1612525084.555543 --------- SYN_REPORT --------
time 1612525087.015533 type 1 (EV_KEY), code 309 (BTN_Z), value 1
time 1612525087.015533 --------- SYN_REPORT --------
time 1612525087.915539 type 1 (EV_KEY), code 309 (BTN_Z), value 0
time 1612525087.915539 --------- SYN_REPORT --------
time 1612525090.135532 type 1 (EV_KEY), code 306 (BTN_C), value 1
time 1612525090.135532 --------- SYN_REPORT --------
time 1612525090.795537 type 1 (EV_KEY), code 306 (BTN_C), value 0
time 1612525090.795537 --------- SYN_REPORT --------
time 1612525095.295543 type 3 (EV_ABS), code 0 (ABS_X), value 255
time 1612525095.295543 --------- SYN_REPORT --------
time 1612525095.475548 type 3 (EV_ABS), code 0 (ABS_X), value 173
time 1612525095.475548 --------- SYN_REPORT --------
time 1612525095.535552 type 3 (EV_ABS), code 0 (ABS_X), value 126
time 1612525095.535552 --------- SYN_REPORT --------
time 1612525095.955535 type 3 (EV_ABS), code 1 (ABS_Y), value 26
time 1612525095.955535 --------- SYN_REPORT --------
time 1612525096.13554 type 3 (EV_ABS), code 0 (ABS_X), value 132
time 1612525096.13554 --------- SYN_REPORT --------
[ 402 ]
Chapter 10 Input Subsystem
joystick_led.py application
You will develop now your first python application called joystick_led. This application will read an
event type EV_KEY with event code BTN_C and an event type EV_ABS with event code ABS_X.
When you press the C button on the Nunchuk device, it will be generated an event code BTN_C
with value == 1, and an LED will be ON. When you release the C button on the Nunchuk device,
it will be generated an event code BTN_C with value == 0, and the LED will be OFF. If you move
the X axis of the Analog joystick, it will be generated an ABS_X event code with value == 126 if the
joystick is centered, value > 126 if you are moving it to the right, and value < 126 if you are moving
it to the left.
Connect any LED of the Color click eval board (https://www.mikroe.com/color-click) to the GPIO17
pin of the Raspberry Pi connector.
Create a joystick_led.py application, and store it in the python_apps folder which was created
previously. Write the code of the following Listing 10.3 to the joystick_led.py file, and send it to the
Raspberry Pi.
PC:~/linux_5.4_rpi3_drivers/nunchuk_drivers/python_apps$ scp joystick_led.py [email protected]:/
home/pi/python-projects
def main():
joystick = InputDevice('/dev/input/event0')
print(joystick)
if event.type == ecodes.EV_KEY:
keyevent = categorize(event)
if keyevent.keycode == 'BTN_C':
if keyevent.keystate == KeyEvent.key_down:
led.on()
elif keyevent.keystate == KeyEvent.key_up:
led.off()
[ 403 ]
Input Subsystem Chapter 10
if __name__ == '__main__':
try:
led = LED(17) // connect LED BLUE to the
main()
except (KeyboardInterrupt, EOFError):
ret = 0
led.close()
sys.exit(ret)
joystick_led.py demonstration
Load the nunchuk module:
root@raspberrypi:/home/pi# insmod nunchuk.ko
nunchuk: loading out-of-tree module taints kernel.
nunchuk 1-0052: nunchuck_probe() function is called.
input: WII Nunchuk as /devices/platform/soc/3f804000.i2c/i2c-1/1-
0052/input/input0
Execute the joystick_led.py application. Press and release the C button of the nunchuk device
to switch ON and OFF the LED. Move the x-axis of the analog joystick, and see the values on
the command line:
root@raspberrypi:/home/pi/python-projects# python3 joystick_led.py
device /dev/input/event0, name "WII Nunchuk", phys ""
right
175
right
255
right
216
centered
126
left
55
left
54
left
0
centered
126
right
133
right
174
[ 404 ]
Chapter 10 Input Subsystem
right
255
right
230
centered
126
Exit with ^C:
root@raspberrypi:/home/pi/python-projects# cd ..
Remove the nunchuk module:
root@raspberrypi:/home/pi# rmmod nunchuk.ko
nunchuk 1-0052: nunchuk_remove()
joystick_pwm.py application
You will develop now your second python application called joystick_pwm. This application will
read an event type EV_ABS with event code ABS_X. When you move the X axis of the Analog
joystick, it will be generated an ABS_X event code. The value of this event code will be used
to modify the duty cycle of a PWM signal, changing the brightness of an LED. The maximum
brightness will be set with a duty cycle of 100 (X axis far right), and the minimum brightness will
be set with a duty cycle of 0 (X axis far left).
Connect any LED of the Color click eval board (https://www.mikroe.com/color-click) to the GPIO18
pin of the Raspberry Pi connector.
Create a joystick_pwm.py application, and store it in the python_apps folder. Write the code of the
following Listing 10.4 to the joystick_pwm.py file, and send it to the Raspberry Pi:
PC:~/linux_5.4_rpi3_drivers/nunchuk_drivers/python_apps$ scp joystick_pwm.py [email protected]:/
home/pi/python-projects
def main():
joystick = InputDevice('/dev/input/event0')
print(joystick)
for event in joystick.read_loop(): # change duty cycle with the Nunchuk joystick
if event.type == ecodes.EV_ABS:
absevent = categorize(event)
[ 405 ]
Input Subsystem Chapter 10
if ecodes.bytype[absevent.event.type][absevent.event.code] == 'ABS_X':
duty = math.floor((absevent.event.value * 100)/255)
print(duty)
pwm.ChangeDutyCycle(duty) # Change duty cycle
time.sleep(0.01) # Delay of 10mS
if __name__ == '__main__':
try:
led = 12 # connect red LED to the GPIO18
GPIO.setwarnings(False) # disable warnings
GPIO.setmode(GPIO.BOARD) # set pin numbering system. Using board pin numbering
GPIO.setup(led,GPIO.OUT)
pwm = GPIO.PWM(led,1000) # create PWM instance with frequency
pwm.start(0) # started PWM at 0% duty cycle
main()
except (KeyboardInterrupt, EOFError):
ret = 0
pwm.stop()
GPIO.cleanup()
sys.exit(ret)
joystick_pwm.py demonstration
Load the nunchuk module:
root@raspberrypi:/home/pi# insmod nunchuk.ko
nunchuk: loading out-of-tree module taints kernel.
nunchuk 1-0052: nunchuck_probe() function is called.
input: WII Nunchuk as /devices/platform/soc/3f804000.i2c/i2c-1/1-
0052/input/input0
Execute the joystick_pwm.py application. Move the x-axis of the analog joystick, and see how
the LED changes its brightness:
root@raspberrypi:/home/pi/python-projects# python3 joystick_pwm.py
device /dev/input/event0, name "WII Nunchuk", phys ""
28
27
49
100
49
0
49
Exit with ^C:
root@raspberrypi:/home/pi/python-projects# cd ..
Remove the nunchuk module:
root@raspberrypi:/home/pi# rmmod nunchuk.ko
nunchuk 1-0052: nunchuk_remove()
[ 406 ]
Chapter 10 Input Subsystem
joystick_pygame.py application
In the last application of the LAB 10.3, you will develop a simple Pygame application. Pygame
is a set of Python modules designed for writing video games (https://www.pygame.org/docs/).
The application will draw the X-Y Joystick values and also the C and Z button values of the
Nunchuk device. You will connect your screen to the Raspberry Pi´s HDMI port to see the Pygame
application. You will also see the values of the Nunchuk device on the command line on your host
PC.
Create a joystick_pygame.py application, and store it in the python_apps folder. Write the code of the
following Listing 10.5 to the joystick_pygame.py file, and send it to the Raspberry Pi:
PC:~/linux_5.4_rpi3_drivers/nunchuk_drivers/python_apps$ scp joystick_pygame.py
[email protected]:/home/pi/python-projects
#
# DRAWING STEP
#
# First, clear the screen to white. Don't put other drawing commands
# above this, or they will be erased with this command.
screen.fill(WHITE)
textPrint.reset()
[ 407 ]
Input Subsystem Chapter 10
joystick_count = pygame.joystick.get_count()
try:
jid = joystick.get_instance_id()
except AttributeError:
# get_instance_id() is an SDL2 method
jid = joystick.get_id()
print("Joystick {}".format(jid))
textPrint.tprint(screen, "Joystick {}".format(jid))
textPrint.indent()
try:
guid = joystick.get_guid()
except AttributeError:
# get_guid() is an SDL2 method
pass
else:
textPrint.tprint(screen, "GUID: {}".format(guid))
# Usually axis run in pairs, up/down for one, and left/right for
# the other.
axes = joystick.get_numaxes()
print("Number of axes: {}".format(axes))
textPrint.tprint(screen, "Number of axes: {}".format(axes))
textPrint.indent()
for i in range(axes):
axis = joystick.get_axis(i)
print("Axis {} value: {:>6.3f}".format(i, axis))
textPrint.tprint(screen, "Axis {} value: {:>6.3f}".format(i, axis))
textPrint.unindent()
buttons = joystick.get_numbuttons()
print("Number of buttons: {}".format(buttons))
textPrint.tprint(screen, "Number of buttons: {}".format(buttons))
textPrint.indent()
for i in range(buttons):
button = joystick.get_button(i)
print("Button {:>2} value: {}".format(i, button))
[ 408 ]
Chapter 10 Input Subsystem
textPrint.tprint(screen,
"Button {:>2} value: {}".format(i, button))
textPrint.unindent()
#
# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT
#
joystick_pygame.py demonstration
Load the nunchuk module:
root@raspberrypi:/home/pi# insmod nunchuk.ko
nunchuk: loading out-of-tree module taints kernel.
nunchuk 1-0052: nunchuck_probe() function is called.
input: WII Nunchuk as /devices/platform/soc/3f804000.i2c/i2c-1/1-
0052/input/input0
Execute the joystick_pygame.py application. Move the x-axis and the y-axis of the analog
joystick, and press the C and Z buttons on the nunchuk device. You will see the values on the
command line and also drawn on your screen:
root@raspberrypi:/home/pi/python-projects# python3 joystick_pygame.py
Number of joysticks: 1
Joystick 0
Joystick name: WII Nunchuk
Number of axes: 2
Axis 0 value: 0.000
Axis 1 value: 0.000
Number of buttons: 2
Button 0 value: 0
Button 1 value: 0
Joystick button released.
Joystick button released.
Number of joysticks: 1
Joystick 0
Joystick name: WII Nunchuk
Number of axes: 2
Axis 0 value: -0.008
Axis 1 value: 0.024
Number of buttons: 2
Button 0 value: 0
[ 409 ]
Input Subsystem Chapter 10
Button 1 value: 0
Number of joysticks: 1
Joystick 0
Joystick name: WII Nunchuk
Number of axes: 2
Axis 0 value: -0.008
Axis 1 value: 0.024
Number of buttons: 2
Button 0 value: 0
Button 1 value: 0
Number of joysticks: 1
Joystick 0
Joystick name: WII Nunchuk
Number of axes: 2
Axis 0 value: -0.008
Axis 1 value: 0.024
Number of buttons: 2
Button 0 value: 0
Button 1 value: 0
Joystick button pressed.
Number of joysticks: 1
Joystick 0
Joystick name: WII Nunchuk
Number of axes: 2
Axis 0 value: -0.008
Axis 1 value: 0.024
Number of buttons: 2
Button 0 value: 1
Button 1 value: 0
Number of joysticks: 1
Joystick 0
Joystick name: WII Nunchuk
Number of axes: 2
Axis 0 value: -0.008
Axis 1 value: 0.024
Number of buttons: 2
Button 0 value: 1
Button 1 value: 0
Number of joysticks: 1
Joystick 0
Exit with ^C:
^CTraceback (most recent call last):
File "joystick_pygame.py", line 98, in <module>
name = joystick.get_name()
KeyboardInterrupt
root@raspberrypi:/home/pi/python-projects# cd ..
Remove the nunchuk module:
root@raspberrypi:/home/pi# rmmod nunchuk.ko
nunchuk 1-0052: nunchuk_remove()
[ 410 ]
Chapter 10 Input Subsystem
The three signal wires hold a clock (SCK, often in the range of 1-20 MHz) and parallel data lines
with "Master Out, Slave In" (MOSI) or "Master In, Slave Out" (MISO) signals. There are four
clocking modes through which data is exchanged; mode-0 and mode-3 are most commonly used.
Each clock cycle shifts data out and data in; the clock doesn't cycle except when there is a data bit
to shift. Not all data bits are used though; not every protocol uses those full duplex capabilities.
SPI masters use a "chip select" line to activate a given SPI slave device, so those three signal wires
may be connected to several chips in parallel. All SPI slaves support chipselects; they are usually
active low signals, labeled nCSx for slave 'x' (e.g., nCS0). Some devices have other signals, often
including an interrupt to the master.
The programming interface is structured around two kinds of drivers: the controller and protocol
drivers. The controller drivers support the SPI master controller and drive hardware to control
the clock and chip selects, shift data bits on/off wire, and configure basic SPI characteristics like
clock frequency and mode. The SPI controller driver for the BCM2835 SoC is located at drivers/
spi/spi-bcm2835aux.c in the kernel source tree. The protocol drivers support the SPI slave specific
functionality, are based on messages, and transfer and rely on the controller driver to program SPI
master hardware.
The I/O model is a set of queued messages. A single message (fundamental argument to all SPI
subsystem read/write APIs) is an atomic sequence of transfers built from one or more spi_transfer
objects, each of which wraps a full duplex SPI transfer which is processed and completed
synchronously or asynchronously. When using synchronous request, the caller is blocked until the
call succeeds. When using asynchronous request, you are periodically checking if the transaction is
finished. The SPI controller driver manages access to those devices through a queue of spi_message
[ 411 ]
Input Subsystem Chapter 10
transactions, copying data between CPU memory and an SPI slave device. For each such message
it queues, it calls the message’s completion function when the transaction completes.
See the at25_ee_read() function (located in drivers/misc/eeprom/at25.c) as an example of an SPI
transaction:
struct spi_transfer t[2];
struct spi_message m;
spi_message_init(&m);
memset(t, 0, sizeof t);
t[0].tx_buf = command;
t[0].len = at25->addrlen + 1;
spi_message_add_tail(&t[0], &m);
t[1].rx_buf = buf;
t[1].len = count;
spi_message_add_tail(&t[1], &m);
status = spi_sync(at25->spi, &m);
The basic I/O primitive is spi_async(). Async requests may be issued in any context (irq handler,
task, etc.), and completion is reported by using a callback provided with the message. After any
detected error, the chip is deselected and processing of that spi_message is aborted.
There are also synchronous wrappers like spi_sync(), and wrappers like spi_read(), spi_write() and
spi_write_then_read(). These may be issued only in contexts that may sleep, and they're all clean
layers over spi_async().
The spi_write_then_read() call, and convenience wrappers around it, should only be used with small
amounts of data where the cost of an extra copy may be ignored; for example, you can see below
the spi_w8r16() wrapper, which writes an eight bit command and reads a sixteen bit response.
static inline ssize_t spi_w8r16(struct spi_device *spi, u8 cmd)
{
ssize_t status;
u16 result;
[ 412 ]
Chapter 10 Input Subsystem
The SPI core API is a set of functions (spi_write_then_read(), spi_sync(), spi_async(), etc.) used
for an SPI client driver to manage SPI transactions with a device connected to an SPI bus.
2. The SPI controller drivers are located under drivers/spi/ directory in the kernel source
tree. The SPI controller is a platform device (declared in the Device Tree) that must be
registered as a device to the platform bus via the of_platform_populate() function and
registered with the SPI bus core as a driver by using the module_platform_driver() function.
static struct platform_driver bcm2835_spi_driver = {
.driver = {
.name = DRV_NAME,
.of_match_table = bcm2835_spi_match,
},
.probe = bcm2835_spi_probe,
.remove = bcm2835_spi_remove,
};
module_platform_driver(bcm2835_spi_driver);
The SPI controller driver includes a set of custom functions that issues read/writes to each
SPI controller hardware I/O addresses. The SPI controller driver provides an spi_master
structure per each probed SPI controller, then initializes the fields of struct spi_controller
with the methods that interact with the SPI core and the SPI protocol (slave) drivers. These
are some of the SPI master methods included in the struct spi_controller (declared in include/
linux/spi/spi.h):
• master->setup(struct spi_device *spi): This method sets up the device clock rate, SPI
mode, and word sizes.
• master->transfer_one(struct spi_master *master, struct spi_device *spi, struct spi_transfer
*transfer): This method executes a single transfer while queuing transfers that
[ 413 ]
Input Subsystem Chapter 10
arrive in the meantime. When the transfer has finished, the spi_finalize_current_
transfer() function is called so that the subsystem can issue the next transfer.
The spi_register_controller() function registers each SPI controller to the SPI bus core,
publishing it to the rest of the system. At that time, the device nodes for the controller, and
any pre-declared SPI devices will be made available; the driver model core will take care
of binding them to the drivers. The SPI master driver needs to implement a mechanism
to send the data on the SPI bus using the SPI device specified settings. It's the SPI master
driver's responsibilities to operate the hardware to send out the data. Normally, the SPI
master needs to implement:
• A message queue: To hold the messages from the SPI device driver.
• A workqueue and workqueue thread: To pump the messages from the message
queue and start transfer.
• A tasklet and tasklet handler: To send the data on the hardware.
• An interrupt handler: To handle the interrupts during the transfer.
In the probe() function of the drivers/spi/ spi-bcm2835.c driver, you can see the initialization
and registration of an SPI master controller:
static int bcm2835_spi_probe(struct platform_device *pdev)
{
struct spi_controller *ctlr;
struct bcm2835_spi *bs;
int err;
platform_set_drvdata(pdev, ctlr);
ctlr->use_gpio_descriptors = true;
ctlr->mode_bits = BCM2835_SPI_MODE_BITS;
ctlr->bits_per_word_mask = SPI_BPW_MASK(8);
ctlr->num_chipselect = BCM2835_SPI_NUM_CS;
ctlr->setup = bcm2835_spi_setup;
ctlr->transfer_one = bcm2835_spi_transfer_one;
ctlr->handle_err = bcm2835_spi_handle_err;
ctlr->prepare_message = bcm2835_spi_prepare_message;
ctlr->dev.of_node = pdev->dev.of_node;
bs = spi_controller_get_devdata(ctlr);
[ 414 ]
Chapter 10 Input Subsystem
clk_prepare_enable(bs->clk);
err = spi_register_controller(ctlr);
if (err) {
dev_err(&pdev->dev, "could not register SPI controller: %d\n",
err);
goto out_dma_release;
}
bcm2835_debugfs_create(bs, dev_name(&pdev->dev));
return 0;
out_dma_release:
bcm2835_dma_release(ctlr, bs);
clk_disable_unprepare(bs->clk);
return err;
}
3. The SPI protocol drivers are located throughout linux/drivers/, depending on the type
of device (for example, linux/drivers/input/ for input devices). The driver code is specific
to the device (accelerometer, digital analog converter, etc.) and uses the SPI core API to
communicate with the SPI master driver and send/receive data to/from the SPI device.
For example, if the SPI client driver calls spi_write_then_read(), then this function calls
spi_sync(), which in turn calls __spi_sync(). The __spi_sync() function calls __spi_pump_
messages(), which processes the spi message queue and checks if there is any spi message
[ 415 ]
Input Subsystem Chapter 10
in the queue that needs processing, and if so calls out to the driver to initialize the
hardware and transfer each message. The __spi_pump_messages() function is called both,
from the kthread itself and also from inside spi_sync(); the queue extraction, handled at the
top of the function should deal with this safely. Finally, __spi_pump_messages() calls the
transfer_one_message method (initialized to the bcm2835_spi_transfer_one() function for the
BCM2837 SoC).
[ 416 ]
Chapter 10 Input Subsystem
The of_match_table field (included in the spi_driver structure) is a pointer to an array of of_device_id
structures that store the compatible strings supported by the driver:
static const struct of_device_id adxl345_dt_ids[] = {
{ .compatible = "arrow,adxl345", },
{ }
};
MODULE_DEVICE_TABLE(of, adxl345_dt_ids);
The driver´s probe() function is called when the compatible field in one of the of_device_id entries
matches with the compatible property of a DT device node. The probe() function is responsible to
initialize the device with the configuration values obtained from the matched DT device node and
also to register the device to the appropriate kernel framework.
In your SPI device driver, you will also define an array of spi_device_id structures:
static const struct spi_device_id adxl345_id[] = {
{ .name = "adxl345", },
{ }
};
MODULE_DEVICE_TABLE(spi, adxl345_id);
[ 417 ]
Input Subsystem Chapter 10
You can see in the following figure an SPI controller with multiple chip selects connected to several
SPI devices:
The Device Tree declaration of the SPI devices is done as sub-nodes of the SPI master controller at
the board/platform level (arch/arm/boot/dts/ bcm2710-rpi-3-b.dts). These are the required and optional
properties:
• reg: (required) chip select address of device.
• compatible: (required) name of SPI device that should match with one of the driver´s of_device_id
compatible strings.
• spi-max-frequency: (required) maximum SPI clocking speed of device in Hz.
• spi-cpol: (optional) empty property indicating device requires inverse clock polarity (CPOL)
mode.
• spi-cpha: (optional) empty property indicating device requires shifted clock phase (CPHA) mode.
• spi-cs-high: (optional) empty property indicating device requires chip select active high.
• spi-3wire: (optional) empty property indicating device requires 3-wire mode.
• spi-lsb-first: (optional) empty property indicating device requires LSB first mode.
[ 418 ]
Chapter 10 Input Subsystem
• spi-tx-bus-width: (optional) the bus width (number of data wires) that is used for MOSI; defaults to
1 if not present.
• spi-rx-bus-width: (optional) the bus width (number of data wires) that is used for MISO; defaults to
1 if not present.
• spi-rx-delay-us: (optional) microsecond delay after a read transfer.
• spi-tx-delay-us: (optional) microsecond delay after a write transfer.
[ 419 ]
Input Subsystem Chapter 10
The GPIO23 pin is multiplexed as a GPIO signal in the accel_int_pin pin configuration node:
accel_int_pin: accel_int_pin {
brcm,pins = <23>;
brcm,function = <0>; /* Input */
brcm,pull = <0>; /* none */
};
LAB 10.4 code description of the "SPI accel input device" module
The main code sections of the driver will now be described:
1. Include the function headers:
#include <linux/module.h>
#include <linux/input.h>
#include <linux/spi/spi.h>
#include <linux/of_gpio.h>
#include <linux/spi/spi.h>
#include <linux/interrupt.h>
[ 420 ]
Chapter 10 Input Subsystem
2. Define the masks and macros which will generate the specific command byte of the SPI
transaction (spi_read(), spi_write(), spi_write_then_read(), etc.):
#define ADXL345_CMD_MULTB (1 << 6)
#define ADXL345_CMD_READ (1 << 7)
#define ADXL345_WRITECMD(reg) (reg & 0x3F)
#define ADXL345_READCMD(reg) (ADXL345_CMD_READ | (reg & 0x3F))
#define ADXL345_READMB_CMD(reg) (ADXL345_CMD_READ | ADXL345_CMD_MULTB \
| (reg & 0x3F))
4. Create the rest of #define to perform operations in the registers of the ADXL345 device and
to pass some of them as arguments to several functions of the driver:
/* DEVIDs */
#define ID_ADXL345 0xE5
/* INT_ENABLE/INT_MAP/INT_SOURCE Bits */
#define SINGLE_TAP (1 << 6)
/* TAP_AXES Bits */
#define TAP_X_EN (1 << 2)
#define TAP_Y_EN (1 << 1)
#define TAP_Z_EN (1 << 0)
/* BW_RATE Bits */
#define LOW_POWER (1 << 4)
#define RATE(x) ((x) & 0xF)
/* POWER_CTL Bits */
#define PCTL_MEASURE (1 << 3)
#define PCTL_STANDBY 0X00
/* DATA_FORMAT Bits */
[ 421 ]
Input Subsystem Chapter 10
[ 422 ]
Chapter 10 Input Subsystem
6. Initialize the adxl345_bus_ops structure with the functions that will perform the bus
operations, and send it to the adxl345_probe() function as an argument:
static const struct adxl345_bus_ops adxl345_spi_bops = {
.bustype = BUS_SPI,
.write = adxl345_spi_write,
.read = adxl345_spi_read,
.read_block = adxl345_spi_read_block,
};
static int adxl345_spi_probe(struct spi_device *spi)
{
/* Create a private structure */
struct adxl345 *ac;
/* initialize the driver and returns the initialized private struct */
ac = adxl345_probe(&spi->dev, &adxl345_spi_bops);
/* Attach the SPI device to the private structure */
spi_set_drvdata(spi, ac);
return 0;
}
7. See below an extract of the adxl345_probe() routine with the main lines of code commented:
struct adxl345 *adxl345_probe(struct device *dev, const struct adxl345_bus_ops *bops)
{
/* declare your private structure */
struct adxl345 *ac;
/* Create the input device */
[ 423 ]
Input Subsystem Chapter 10
/*
* Set EV_KEY type event with 3 events code support.
* The event is sent when a single tap interrupt is triggered
*/
__set_bit(EV_KEY, input_dev->evbit);
__set_bit(pdata->ev_code_tap[ADXL_X_AXIS], input_dev->keybit);
__set_bit(pdata->ev_code_tap[ADXL_Y_AXIS], input_dev->keybit);
__set_bit(pdata->ev_code_tap[ADXL_Z_AXIS], input_dev->keybit);
/*
* Check if any of the axis has been enabled
* and set the interrupt mask.
* In this driver is only enabled the SINGLE_TAP interrupt
*/
if (pdata->tap_axis_control & (TAP_X_EN | TAP_Y_EN | TAP_Z_EN))
ac->int_mask |= SINGLE_TAP;
/*
* Get the gpio descriptor, set the gpio pin direction to input,
* and store it in the private structure
*/
[ 424 ]
Chapter 10 Input Subsystem
/*
* Set the data rate and the axis reading power
* mode. Choose less or higher noise reducing power
*/
AC_WRITE(ac, BW_RATE, RATE(ac->pdata.data_rate) |
(pdata->low_power_mode ? LOW_POWER : 0));
/* Enables interrupts */
AC_WRITE(ac, INT_ENABLE, ac->int_mask);
8. You will write a threaded interrupt handler to service the single tap interrupt. In a
threaded interrupt, the interrupt handler is executed inside a thread. It is allowed to block
during the execution of the interrupt handler, which is often needed to communicate with
[ 425 ]
Input Subsystem Chapter 10
an SPI or I2C device inside the handler. In this interrupt handler, you will communicate
via SPI with the ADXL345 device. See below the code of the handler:
static irqreturn_t adxl345_irq(int irq, void *handle)
{
struct adxl345 *ac = handle;
struct adxl345_platform_data *pdata = &ac->pdata;
int int_stat, tap_stat;
/*
* ACT_TAP_STATUS should be read before clearing the interrupt.
* Avoid reading ACT_TAP_STATUS in case TAP detection is disabled.
* Read the ACT_TAP_STATUS if any of the axis has been enabled
*/
if (pdata->tap_axis_control & (TAP_X_EN | TAP_Y_EN | TAP_Z_EN))
tap_stat = AC_READ(ac, ACT_TAP_STATUS);
else
tap_stat = 0;
/*
* If the SINGLE_TAP event has occurred, the axl345_do_tap function
* is called with the ACT_TAP_STATUS register as an argument
*/
if (int_stat & (SINGLE_TAP)) {
dev_info(ac->dev, "single tap interrupt has occurred\n");
adxl345_do_tap(ac, pdata, tap_stat);
};
input_sync(ac->input);
return IRQ_HANDLED;
}
9. You will generate the event type EV_KEY with 3 different event codes that will be set
depending on the axis where the tap motion detection has been selected. You will send
these events in the ISR by calling the adxl345_do_tap() function.
/*
* Set EV_KEY type event with 3 events code support.
* The event is sent when a single tap interrupt is triggered
*/
__set_bit(EV_KEY, input_dev->evbit);
__set_bit(pdata->ev_code_tap[ADXL_X_AXIS], input_dev->keybit);
__set_bit(pdata->ev_code_tap[ADXL_Y_AXIS], input_dev->keybit);
__set_bit(pdata->ev_code_tap[ADXL_Z_AXIS], input_dev->keybit);
[ 426 ]
Chapter 10 Input Subsystem
int i;
10. You will create several sysfs entries to access the driver from user space. You can set and
read the sample rate, read the data of the three axes values, and show the last stored
values of the axes by using sysfs hooks.
You will create the sysfs attributes with the DEVICE ATTR(name, mode, show, store) macro:
static DEVICE_ATTR(rate, 0664, adxl345_rate_show, adxl345_rate_store);
static DEVICE_ATTR(position, S_IRUGO, adxl345_position_show, NULL);
static DEVICE_ATTR(read, S_IRUGO, adxl345_position_read, NULL);
See below the code of the adxl345_position_read() function, which will read the data of the
three axes:
static ssize_t adxl345_position_read(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct axis_triple axis;
ssize_t count;
struct adxl345 *ac = dev_get_drvdata(dev);
adxl345_get_triple(ac, &axis);
[ 427 ]
Input Subsystem Chapter 10
return count;
}
You can see that read_block (a member of the adxl345_bus_ops function) is initialized to the
adxl345_spi_read_block bus function:
static const struct adxl345_bus_ops adxl345_spi_bops = {
.bustype = BUS_SPI,
.write = adxl345_spi_write,
.read = adxl345_spi_read,
.read_block = adxl345_spi_read_block,
};
See below the code of the adxl345_spi_read_block() function. The reg parameter is the
address of the first register you want to read, and count is the total number of registers that
you will read, starting from the reg one. The buf parameter is a pointer to the buffer where
the values of the axes will be stored.
/* Read multiple registers */
static int adxl345_spi_read_block(struct device *dev,
unsigned char reg,
int count,
void *buf)
{
struct spi_device *spi = to_spi_device(dev);
ssize_t status;
[ 428 ]
Chapter 10 Input Subsystem
/*
* Write byte stored in reg (address with MB),
* read count bytes (from successive addresses),
* and stores them to buf
*/
status = spi_write_then_read(spi, ®, 1, buf, count);
return (status < 0) ? status : 0;
}
The adxl345_spi_read_block() function calls spi_write_then_read(), which sends to the SPI bus
a command byte composed by the address of the first register to read (bits A0 to A5) plus
the MB bit (set to one for multi reading) and R bit (set to one for reading), then reads the
value of six registers (count), starting from the reg (bits A0 to A5) one.
See below the macros for the SPI commands used to read from and write to your SPI
device:
13. Add an spi_driver structure that will be registered to the SPI bus:
static struct spi_driver adxl345_driver = {
.driver = {
.name = "adxl345",
.owner = THIS_MODULE,
.of_match_table = adxl345_dt_ids,
},
.probe = adxl345_spi_probe,
.remove = adxl345_spi_remove,
.id_table = adxl345_id,
};
[ 429 ]
Input Subsystem Chapter 10
15. Create a new adxl345_rpi3.c. file in the linux_5.4_rpi3_drivers folder, and add adxl345_rpi3.o
to your Makefile obj-m variable, then build and deploy the module to the Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
16. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
[ 430 ]
Chapter 10 Input Subsystem
/* DEVIDs */
#define ID_ADXL345 0xE5
/* INT_ENABLE/INT_MAP/INT_SOURCE Bits */
#define SINGLE_TAP (1 << 6)
/* TAP_AXES Bits */
#define TAP_X_EN (1 << 2)
#define TAP_Y_EN (1 << 1)
#define TAP_Z_EN (1 << 0)
/* BW_RATE Bits */
#define LOW_POWER (1 << 4)
#define RATE(x) ((x) & 0xF)
/* POWER_CTL Bits */
#define PCTL_MEASURE (1 << 3)
#define PCTL_STANDBY 0X00
/* DATA_FORMAT Bits */
#define FULL_RES (1 << 3)
/* FIFO_CTL Bits */
#define FIFO_MODE(x) (((x) & 0x3) << 6)
#define FIFO_BYPASS 0
#define FIFO_FIFO 1
#define FIFO_STREAM 2
#define SAMPLES(x) ((x) & 0x1F)
/* FIFO_STATUS Bits */
#define ADXL_X_AXIS 0
#define ADXL_Y_AXIS 1
#define ADXL_Z_AXIS 2
struct adxl345_bus_ops {
u16 bustype;
int (*read)(struct device *, unsigned char);
int (*read_block)(struct device *, unsigned char, int, void *);
int (*write)(struct device *, unsigned char, unsigned char);
};
struct axis_triple {
int x;
int y;
int z;
};
[ 431 ]
Input Subsystem Chapter 10
struct adxl345_platform_data {
/*
* low_power_mode:
* A '0' = Normal operation and a '1' = Reduced
* power operation with somewhat higher noise.
*/
u8 low_power_mode;
/*
* tap_threshold:
* holds the threshold value for tap detection/interrupts.
* The data format is unsigned. The scale factor is 62.5 mg/LSB
* (i.e. 0xFF = +16 g). A zero value may result in undesirable
* behavior if Tap/Double Tap is enabled.
*/
u8 tap_threshold;
/*
* tap_duration:
* is an unsigned time value representing the maximum
* time that an event must be above the tap_threshold threshold
* to qualify as a tap event. The scale factor is 625 us/LSB. A zero
* value will prevent Tap/Double Tap functions from working.
*/
u8 tap_duration;
/*
* TAP_X/Y/Z Enable: Setting TAP_X, Y, or Z Enable enables X,
* Y, or Z participation in Tap detection. A '0' excludes the
* selected axis from participation in Tap detection.
* Setting the SUPPRESS bit suppresses Double Tap detection if
* acceleration greater than tap_threshold is present during the
* tap_latency period, i.e. after the first tap but before the
* opening of the second tap window.
*/
#define ADXL_TAP_X_EN
(1 << 2)
#define ADXL_TAP_Y_EN
(1 << 1)
#define ADXL_TAP_Z_EN
(1 << 0)
u8 tap_axis_control;
/*
* data_rate:
* Selects device bandwidth and output data rate.
* RATE = 3200 Hz / (2^(15 - x)). Default value is 0x0A, or 100 Hz
* Output Data Rate. An Output Data Rate should be selected that
* is appropriate for the communication protocol and frequency
* selected. Selecting too high of an Output Data Rate with a low
* communication speed will result in samples being discarded.
[ 432 ]
Chapter 10 Input Subsystem
*/
u8 data_rate;
/*
* data_range:
* FULL_RES: When this bit is set with the device is
* in Full-Resolution Mode, where the output resolution increases
* with RANGE to maintain a 4 mg/LSB scale factor. When this
* bit is cleared the device is in 10-bit Mode and RANGE determine the
* maximum g-Range and scale factor.
*/
u8 data_range;
/*
* A valid BTN or KEY Code; use tap_axis_control to disable
* event reporting
*/
u32 ev_code_tap[3];
/*
* fifo_mode:
* BYPASS The FIFO is bypassed
* FIFO FIFO collects up to 32 values then stops collecting data
* STREAM FIFO holds the last 32 data values. Once full, the FIFO's
* oldest data is lost as it is replaced with newer data
* DEFAULT should be FIFO_STREAM
*/
u8 fifo_mode;
/*
* watermark:
* The Watermark feature can be used to reduce the interrupt load
* of the system. The FIFO fills up to the value stored in watermark
* [1..32] and then generates an interrupt.
* A '0' disables the watermark feature.
*/
u8 watermark;
};
[ 433 ]
Input Subsystem Chapter 10
.tap_duration = 3,
.tap_axis_control = ADXL_TAP_Z_EN,
.data_rate = 8,
.data_range = ADXL_FULL_RES,
.ev_code_tap = {BTN_TOUCH, BTN_TOUCH, BTN_TOUCH}, /* EV_KEY {x,y,z} */
.fifo_mode = FIFO_BYPASS,
.watermark = 0,
};
/*
* This function is called inside adxl34x_do_tap() in the ISR
* when there is a SINGLE_TAP event. The function checks
* the TAP_X, TAP_Y and TAP_Z bits of the ACT_TAP_STATUS (0x2B), starting
* from the TAP_X source bit. If the axis is involved in the event
* there is a EV_KEY event
*/
static void adxl345_send_key_events(struct adxl345 *ac,
struct adxl345_platform_data *pdata,
int status, int press)
{
int i;
[ 434 ]
Chapter 10 Input Subsystem
/*
* ACT_TAP_STATUS should be read before clearing the interrupt
* Avoid reading ACT_TAP_STATUS in case TAP detection is disabled
* Read the ACT_TAP_STATUS if any of the axis has been enabled
*/
if (pdata->tap_axis_control & (TAP_X_EN | TAP_Y_EN | TAP_Z_EN))
tap_stat = AC_READ(ac, ACT_TAP_STATUS);
else
tap_stat = 0;
/*
* If the SINGLE_TAP event has occurred, the axl345_do_tap function
* is called with the ACT_TAP_STATUS register as an argument
*/
if (int_stat & (SINGLE_TAP)) {
dev_info(ac->dev, "single tap interrupt has occurred\n");
adxl345_do_tap(ac, pdata, tap_stat);
};
input_sync(ac->input);
return IRQ_HANDLED;
}
[ 435 ]
Input Subsystem Chapter 10
/*
* If you set ac->pdata.low_power_mode = 1,
* then lower power mode but higher noise is selected,
* getting LOW_POWER macro. By default ac->pdata.low_power_mode = 0
* RATE(val) sets to 0 the 4 upper u8 bits
*/
ac->pdata.data_rate = RATE(val);
AC_WRITE(ac, BW_RATE, ac->pdata.data_rate | (ac->pdata.low_power_mode ? LOW_POWER : 0));
return count;
}
return count;
}
static DEVICE_ATTR(position, S_IRUGO, adxl345_position_show, NULL);
return count;
}
static DEVICE_ATTR(read, S_IRUGO, adxl345_position_read, NULL);
[ 436 ]
Chapter 10 Input Subsystem
/*
* Store the previously initialized platform data
* in your private structure
*/
pdata = &adxl345_default_init; /* Points to const platform data */
ac->pdata = *pdata; /* Store values to pdata inside ac */
pdata = &ac->pdata; /* Change where pdata points, now to pdata in private ac */
ac->input = input_dev;
ac->dev = dev; /* dev is &spi->dev */
[ 437 ]
Input Subsystem Chapter 10
if (revid == 0xE5) {
dev_info(dev, "ADXL345 is found");
}
else
{
dev_err(dev, "Failed to probe %s\n", input_dev->name);
err = -ENODEV;
goto err_out;
}
/*
* Set EV_KEY type event with 3 events code support.
* The event is sent when a single tap interrupt is triggered
*/
__set_bit(EV_KEY, input_dev->evbit);
__set_bit(pdata->ev_code_tap[ADXL_X_AXIS], input_dev->keybit);
__set_bit(pdata->ev_code_tap[ADXL_Y_AXIS], input_dev->keybit);
__set_bit(pdata->ev_code_tap[ADXL_Z_AXIS], input_dev->keybit);
/*
* Check if any of the axis has been enabled
* and set the interrupt mask.
* In this driver only is enabled the SINGLE_TAP interrupt
*/
if (pdata->tap_axis_control & (TAP_X_EN | TAP_Y_EN | TAP_Z_EN))
ac->int_mask |= SINGLE_TAP;
/*
* Get the gpio descriptor, set the gpio pin direction to input,
* and store it in the private structure
*/
ac->gpio = devm_gpiod_get_index(dev, ADXL345_GPIO_NAME, 0, GPIOD_IN);
if (IS_ERR(ac->gpio)) {
dev_err(dev, "gpio get index failed\n");
err = PTR_ERR(ac->gpio);
goto err_out;
[ 438 ]
Chapter 10 Input Subsystem
}
/* Get the Linux IRQ number associated with this gpio descriptor */
ac->irq = gpiod_to_irq(ac->gpio);
if (ac->irq < 0) {
dev_err(dev, "gpio get irq failed\n");
err = ac->irq;
goto err_out;
}
dev_info(dev, "The IRQ number is: %d\n", ac->irq);
/*
* Set the data rate
* and power mode (higher noise, less power)
*/
AC_WRITE(ac, BW_RATE, RATE(ac->pdata.data_rate) | (pdata->low_power_mode ? LOW_POWER : 0));
/* Enables interrupts */
AC_WRITE(ac, INT_ENABLE, ac->int_mask);
[ 439 ]
Input Subsystem Chapter 10
err_remove_attr:
sysfs_remove_group(&dev->kobj, &adxl345_attr_group);
/*
* This function returns a pointer
* to a struct ac or an err pointer
*/
err_out:
return ERR_PTR(err);
}
/*
* Write 2 bytes, the address
* of the register and the value to store on it
*/
static int adxl345_spi_write(struct device *dev, unsigned char reg, unsigned char val)
{
struct spi_device *spi = to_spi_device(dev);
u8 buf[2];
buf[0] = ADXL345_WRITECMD(reg);
buf[1] = val;
/*
[ 440 ]
Chapter 10 Input Subsystem
if (IS_ERR(ac))
return PTR_ERR(ac);
return 0;
}
[ 441 ]
Input Subsystem Chapter 10
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("ADXL345 Three-Axis Accelerometer SPI Bus Driver");
adxl345_rpi3.ko demonstration
Disable the Device Tree overlay for the CY8C9520A device if you have executed the LAB 7.6:
root@raspberrypi:/home/pi# cd /boot/
root@raspberrypi:/boot# nano config.txt
# Uncomment some or all of these to enable the optional hardware interfaces
dtparam=i2c_arm=on
#dtparam=i2s=on
dtparam=spi=on
dtoverlay=spi0-cs
enable_uart=1
kernel=kernel7.img
#dtoverlay=cy8c9520a
#dtoverlay=i2c1,pins_2_3
Reboot the Raspberry Pi:
root@raspberrypi:/home/pi# reboot
Load the adxl345_rpi3.ko module:
root@raspberrypi:/home/pi# insmod adxl345_rpi3.ko
adxl345_rpi3: loading out-of-tree module taints kernel.
adxl345 spi0.0: DEVID: 229
adxl345 spi0.0: ADXL345 is found
adxl345 spi0.0: The IRQ number is: 166
input: ADXL345 accelerometer as /devices/platform/soc/3f204000.sp
i/spi_master/spi0/spi0.0/input/input0
[ 442 ]
Chapter 10 Input Subsystem
[ 443 ]
Input Subsystem Chapter 10
[ 444 ]
11
Industrial I/O Subsystem
IIO (Industrial I/O) is a subsystem that was created to support Analog to Digital Converters
(ADCs), Digital to Analog Converters (DACs) and various types of sensors. The IIO subsystem can
be used from user space (through the libiio library and the IIO Linux kernel tools) and from kernel
space using the IIO kernel API.
These are some examples of sensors supported in IIO:
• Analog to digital converters (ADCs)
• Accelerometers
• Capacitance to digital converters (CDCs)
• Digital to analog converters (DACs)
• Gyroscopes
• Inertial measurement units (IMUs)
• Color and light sensors
• Magnetometers
• Pressure sensors
• Proximity sensors
• Temperature sensors
Usually, these sensors are connected via SPI or I2C. A common use case of the sensor devices is to
have combined functionality (e.g., light plus proximity sensor). However, typical DMA mastered
devices such as ones connected to a high speed synchronous serial or high speed synchronous
parallel peripherals are also subject to this subsystem.
Note: The Industrial I/O subsystem is explained in detail in the Linux driver implementer’s API
guide (https://www.kernel.org/doc/html/latest/driver-api/iio/index.html). Some parts of that
documentation have been extracted to be used in the following sections.
[ 445 ]
Industrial I/O Subsystem Chapter 11
[ 446 ]
Chapter 11 Industrial I/O Subsystem
3. The driver will call devm_iio_device_register(), which registers the device to the IIO core.
Now, the device is global to the rest of the driver functions until it is unregistered. After
this call, the device is ready to accept requests from user space applications.
[ 447 ]
Industrial I/O Subsystem Chapter 11
sensor can have two channels, one for infrared light and one for both infrared and visible
light.
2. Set the .indexed field of iio_chan_spec to 1. In this case, the channel is simply another
instance with an index specified by the .channel field.
The IIO channel definitions will generate data channel access attributes, as the ones of the example
below:
/sys/bus/iio/devices/iio:deviceX/out_voltage0_raw
/sys/bus/iio/devices/iio:deviceX/out_voltage1_raw
/sys/bus/iio/devices/iio:deviceX/out_voltage2_raw
The attribute's name is automatically generated by the IIO core with the following pattern:
{direction}_{type}_{index}_{modifier}_{info_mask}:
• direction corresponds to the attribute direction, according to the const iio_direction char
pointer array located in drivers/iio/industrialio-core.c:
static const char * const iio_direction[] = {
[0] = "in",
[1] = "out",
};
• type corresponds to the channel type, according to the const iio_chan_type_name_spec char
pointer array:
static const char * const iio_chan_type_name_spec[] = {
[IIO_VOLTAGE] = "voltage",
[IIO_CURRENT] = "current",
[IIO_POWER] = "power",
[IIO_ACCEL] = "accel",
[...]
[IIO_UVINDEX] = "uvindex",
[IIO_ELECTRICALCONDUCTIVITY] = "electricalconductivity",
[IIO_COUNT] = "count",
[IIO_INDEX] = "index",
[IIO_GRAVITY] = "gravity",
};
• index pattern depends on the channel .indexed field being set or not. If set, the index will
be taken from the .channel field in order to replace the {index} pattern.
• modifier pattern depends on the channel .modified field being set or not. If set, the modifier
will be taken from the .channel2 field, and the {modifier} pattern will be replaced according
to the const iio_modifier_names char pointer array:
static const char * const iio_modifier_names[] = {
[IIO_MOD_X] = "x",
[IIO_MOD_Y] = "y",
[ 448 ]
Chapter 11 Industrial I/O Subsystem
[IIO_MOD_Z] = "z",
[IIO_MOD_X_AND_Y] = "x&y",
[IIO_MOD_X_AND_Z] = "x&z",
[IIO_MOD_Y_AND_Z] = "y&z",
[...]
[IIO_MOD_CO2] = "co2",
[IIO_MOD_VOC] = "voc",
};
• info_mask depends on the channel info mask, private or shared, indexing value in the
const iio_chan_info_postfix char pointer array:
/* relies on pairs of these shared then separate */
static const char * const iio_chan_info_postfix[] = {
[IIO_CHAN_INFO_RAW] = "raw",
[IIO_CHAN_INFO_PROCESSED] = "input",
[IIO_CHAN_INFO_SCALE] = "scale",
[IIO_CHAN_INFO_CALIBBIAS] = "calibbias",
[...]
[IIO_CHAN_INFO_SAMP_FREQ] = "sampling_frequency", [IIO_CHAN_INFO_FREQUENCY] =
"frequency",
[...]
};
read_raw is called to request a value from the IIO device. A bitmask allows to know more precisely
which type of value is requested and for which channel if needed. The return value will specify the
type of value (val or val2) returned by the device. The val and val2 values will contain the elements
making up the returned value.
write_raw is called to write a value to the IIO device. Parameters are the same as for read_raw. When
you write, for example, a x value to the out_voltage0_raw sysfs attribute, the write_raw hook is called
with the mask argument set to IIO_CHAN_INFO_RAW, the chan argument set with the iio_chan_
[ 449 ]
Industrial I/O Subsystem Chapter 11
spec structure corresponding to the channel 0 (chan->channel is 0), and the val argument set to the x
value.
Buffers
The Industrial I/O core offers a way for continuous data capture based on a trigger source.
Multiple data channels can be read at once from /dev/iio:deviceX character device node, thus
reducing the CPU load.
A user space application will interpret the data samples, read from the buffer, as two byte little
endian signed data that needs a 4 bits right shift before masking out the 12 valid bits of data.
[ 450 ]
Chapter 11 Industrial I/O Subsystem
struct iio_chan_spec {
/* other members */
int scan_index
struct {
char sign;
u8 realbits;
u8 storagebits;
u8 shift;
u8 repeat;
enum iio_endian endianness;
} scan_type;
/* other members */
};
In the next chapter, you will write an accelerometer driver with the following channel definition
that includes IIO buffer support:
static const struct iio_chan_spec adxl345_channels[] = {
ADXL345_CHANNEL(DATAX0, X, 0),
ADXL345_CHANNEL(DATAY0, Y, 1),
ADXL345_CHANNEL(DATAZ0, Z, 2),
IIO_CHAN_SOFT_TIMESTAMP(3),
};
Here, scan_index defines the order in which the enabled channels are placed inside the buffer.
Channels with a lower scan_index will be placed before channels with a higher index. Each channel
needs to have a unique scan_index.
Setting scan_index to -1 can be used to indicate that the specific channel does not support buffered
capture. In this case, no entries will be created for the channel in the scan_elements directory.
The function that will allocate the trigger buffer for your device (usually called in the probe()
function) is iio_triggered_buffer_setup(). In the next section, you will see what an IIO trigger is.
[ 451 ]
Industrial I/O Subsystem Chapter 11
The data (i.e., the accelerometer axis values) will be pushed to the IIO device’s buffer by using the
iio_push_to_buffers_with_timestamp() function within the trigger handler. If timestamps are enabled
for the device, the function will store the supplied timestamp as the last element in the sample data
buffer before pushing it to the device buffers. The sample data buffer needs to be large enough to
hold the additional timestamp (usually the buffer should be indio->scan_bytes bytes large).
Triggers
In many situations, it is useful for a driver to be able to capture data based on some external event
(trigger) instead of periodically polling for data. An IIO trigger can be provided by a device driver
that also has an IIO device based on hardware generated events (e.g., data ready or threshold
exceeded) or provided by a separate driver from an independent interrupt source (e.g., GPIO line
connected to some external system, timer interrupt or user space writing a specific file in sysfs). A
trigger may initiate data capture for a number of sensors and may be completely unrelated to the
sensor itself.
You can develop your own trigger driver, but in this chapter, you will focus only on existing ones.
These are:
• iio-trig-interrupt: This provides support for using any IRQ as an IIO trigger. The kernel
option to enable this trigger mode is CONFIG_IIO_INTERRUPT_TRIGGER.
• iio-trig-hrtimer: This provides a frequency-based IIO trigger using HRT as the interrupt
source. The kernel option responsible for this trigger mode is IIO_HRTIMER_TRIGGER.
• iio-trig-sysfs: This allows us to use a sysfs entry to trigger a data capture. The kernel
option responsible for this trigger mode is CONFIG_IIO_SYSFS_TRIGGER.
Triggered buffers
Now that you know what buffers and triggers are, let’s see how they work together. As it was
indicated in the previous section, a trigger buffer is allocated by using the iio_triggered_buffer_
setup() function. This function combines some common tasks which will normally be performed
when setting up a triggered buffer. It will allocate the buffer and the pollfunc. Before calling this
function, the indio_dev structure should already be completely initialized, but not yet registered.
In practice, this means that this function should be called right before iio_device_register(). To free
the resources allocated by this function, you will call iio_triggered_buffer_cleanup(). You can also
use the managed functions devm_iio_triggered_buffer_setup() and devm_iio_device_register(). See a
description of the iio_triggered_buffer_setup() parameters below:
int iio_triggered_buffer_setup(struct iio_dev *indio_dev,
irqreturn_t (*h)(int irq, void *p),
irqreturn_t (*thread)(int irq, void *p),
[ 452 ]
Chapter 11 Industrial I/O Subsystem
devm_iio_device_register(dev, indio_dev);
return 0;
}
/* Read the channels that have been enabled from user space */
for_each_set_bit(i, indio_dev->active_scan_mask, indio_dev->masklength) {
ret = regmap_bulk_read(data->regmap, base + i * sizeof(sample),
&sample, sizeof(sample));
if (ret < 0)
[ 453 ]
Industrial I/O Subsystem Chapter 11
goto done;
buf[j++] = sample;
}
iio_push_to_buffers_with_timestamp(indio_dev, buf, pf->timestamp);
done:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
Where:
• type: Type of the event.
• dir: Direction of the event.
[ 454 ]
Chapter 11 Industrial I/O Subsystem
• mask_separate: Bit mask of enum iio_event_info values; attributes set in this mask will be
registered per channel.
• mask_shared_by_type: Bit mask of enum iio_event_info values; attributes set in this mask will
be shared by channel type.
• mask_shared_by_dir: Bit mask of enum iio_event_info values; attributes set in this mask will
be shared by channel type and direction.
• mask_shared_by_all: Bit mask of enum iio_event_info values; attributes set in this mask will
be shared by all channels.
See below the initialization of the iio_event_spec structure for the ADXL345 IIO driver that you will
develop in the next chapter:
static const struct iio_event_spec adxl345_event = {
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_EITHER,
.mask_separate = BIT(IIO_EV_INFO_VALUE) |
BIT(IIO_EV_INFO_PERIOD)
};
The adxl345_event structure will be integrated in each iio_chan_spec structure, as you can see in the
following line of code in bold:
static const struct iio_chan_spec adxl345_channels[] = {
ADXL345_CHANNEL(DATAX0, X, 0),
ADXL345_CHANNEL(DATAY0, Y, 1),
ADXL345_CHANNEL(DATAZ0, Z, 2),
IIO_CHAN_SOFT_TIMESTAMP(3),
};
[ 455 ]
Industrial I/O Subsystem Chapter 11
You will create the kernel hooks for the user space interactions with the event sysfs attributes:
static const struct iio_info adxl345_info = {
.driver_module = THIS_MODULE,
.read_raw = adxl345_read_raw,
.write_raw = adxl345_write_raw,
.read_event_value = adxl345_read_event,
.write_event_value = adxl345_write_event,
};
[ 456 ]
Chapter 11 Industrial I/O Subsystem
return ret;
}
}
else
tap_stat = 0;
/*
* Read the INT_SOURCE (0x30) register.
* The tap interrupt is cleared
*/
ret = regmap_read(data->regmap, INT_SOURCE, &int_stat);
if (ret) {
dev_err(data->dev, "error reading INT_SOURCE register\n");
return ret;
}
/*
* If the SINGLE_TAP event has occurred, the axl345_do_tap function
* is called with the ACT_TAP_STATUS register as an argument
*/
if (int_stat & (SINGLE_TAP)) {
dev_info(data->dev, "single tap interrupt has occurred\n");
return IRQ_HANDLED;
}
[ 457 ]
Industrial I/O Subsystem Chapter 11
IIO utils
There are some useful tools that you can use during the development of your IIO driver. They are
available under /tools/iio/ folder in the kernel source tree:
• lsiio: Enumerates IIO triggers, devices and accessible channels.
• iio_event_monitor: Monitors on IIO device's ioctl interface for IIO events.
• iio_generic_buffer: Monitors, processes and print data received from an IIO device's
buffer.
• libiio: A powerful library developed by Analog devices to interface IIO devices. It is
available at https://github.com/analogdevicesinc/libiio.
[ 458 ]
Chapter 11 Industrial I/O Subsystem
These are the parameters used during the configuration of the PIXI ports:
• Port 0 (P0) -> Single Ended ADC, Average of samples = 1, Reference Voltage = internal,
Voltage Range = 0V to 10V
• Port 1 (P1) -> Single Ended ADC, Average of samples = 1, Reference Voltage = internal,
Voltage Range = 0V to 10V.
• Port 2 (P2) -> DAC, Voltage Output Level = 0V, Voltage Range = 0V to 10V.
• Port 3 (P3) -> DAC, Voltage Output Level = 0V, Voltage Range = 0V to 10V.
• Port 4 (P4) and Port 5 (P5) -> Differential ADC, Pin info: Input Pin (-) is P5 and Input Pin
(+) is P4, Reference Voltage = internal, Voltage Range = 0V to 10V.
• Port 6 (P6) -> DAC with ADC monitoring, Reference Voltage = internal, Voltage Output
Level = 0V, Voltage Range = 0V to 10V.
[ 459 ]
Industrial I/O Subsystem Chapter 11
• Port 7 (P7) -> GPI, Interrupt: Masked, Voltage Input Threshold: 2.5V.
• Port 8 (P8) -> GPO, Voltage output Level = 3.3V.
• Port 18 (P18) -> GPI, Interrupt: Masked, Voltage Input Threshold: 2.5V.
• Port 19 (P19) -> GPO, Voltage output Level = 3.3V.
And these are the general parameters used during the configuration of the MAX11300 device:
Not all the specifications of the MAX11300 devcie will be covered during the development of this
driver. These are the main specifications that will be included:
• Funcional modes for ports: Mode 1, Mode 3, Mode 5, Mode 6, Mode 7, Mode 8, Mode 9.
• DAC Update Mode: Sequential.
• ADC Conversion Mode: Continuous Sweep.
• Default ADC Conversion Rate of 200Ksps.
• Interrupts are masked.
[ 460 ]
Chapter 11 Industrial I/O Subsystem
Connect the Raspberry Pi´s SPI pins to the SPI ones of the MAX11300 device:
• Connect Raspberry Pi GPIO8 (CE0) to MAX11300 CS (Pin 3 of Mikrobus)
• Connect Raspberry Pi SCLK to MAX11300 SCK (Pin 4 of Mikrobus)
• Connect Raspberry Pi MOSI to MAX11300 MOSI (Pin 6 of Mikrobus)
• Connect Raspberry Pi MISO to MAX11300 MISO (Pin 5 of Mikrobus)
You will also connect the next power pins between the two boards:
• Connect Raspberry Pi 3.3V to MAX11300 3.3V (Pin 7 of Mikrobus)
• Connect Raspberry Pi 5V to MAX11300 5V (Pin 10 of Mikrobus)
• Connect Raspberry Pi GNDs to MAX11300 GNDs (Pin 9 and Pin 8 of Mikrobus)
[ 461 ]
Industrial I/O Subsystem Chapter 11
Finally, find the HD2 connector in the schematics of the PIXI™ CLICK board located at
https://download.mikroe.com/documents/add-on-boards/click/pixi/pixi-click-schematic-v100.pdf. In the
following image, the connector is also shown:
[ 462 ]
Chapter 11 Industrial I/O Subsystem
[ 463 ]
Industrial I/O Subsystem Chapter 11
&spi0 {
pinctrl-names = "default";
pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
/* CE0 */
/*spidev0: spidev@0{
compatible = "spidev";
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
spi-max-frequency = <125000000>;
};*/
/* CE1 */
/*spidev1: spidev@1{
compatible = "spidev";
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
spi-max-frequency = <125000000>;
};*/
max11300@0 {
#size-cells = <0>;
#address-cells = <1>;
compatible = "maxim,max11300";
reg = <0>;
spi-max-frequency = <10000000>;
channel@0 {
reg = <0>;
port-mode = <PORT_MODE_7>;
AVR = <0>;
adc-range = <ADC_VOLTAGE_RANGE_PLUS10>;
adc-samples = <ADC_SAMPLES_1>;
};
channel@1 {
reg = <1>;
port-mode = <PORT_MODE_7>;
AVR = <0>;
adc-range = <ADC_VOLTAGE_RANGE_PLUS10>;
adc-samples = <ADC_SAMPLES_128>;
};
channel@2 {
reg = <2>;
port-mode = <PORT_MODE_5>;
dac-range = <DAC_VOLTAGE_RANGE_PLUS10>;
};
channel@3 {
reg = <3>;
port-mode = <PORT_MODE_5>;
dac-range = <DAC_VOLTAGE_RANGE_PLUS10>;
[ 464 ]
Chapter 11 Industrial I/O Subsystem
};
channel@4 {
reg = <4>;
port-mode = <PORT_MODE_8>;
AVR = <0>;
adc-range = <ADC_VOLTAGE_RANGE_PLUS10>;
adc-samples = <ADC_SAMPLES_1>;
negative-input = <5>;
};
channel@5 {
reg = <5>;
port-mode = <PORT_MODE_9>;
AVR = <0>;
adc-range = <ADC_VOLTAGE_RANGE_PLUS10>;
};
channel@6 {
reg = <6>;
port-mode = <PORT_MODE_6>;
AVR = <0>;
dac-range = <DAC_VOLTAGE_RANGE_PLUS10>;
};
channel@7 {
reg = <7>;
port-mode = <PORT_MODE_1>;
};
channel@8 {
reg = <8>;
port-mode = <PORT_MODE_3>;
};
channel@9 {
reg = <9>;
port-mode = <PORT_MODE_0>;
};
channel@10 {
reg = <10>;
port-mode = <PORT_MODE_0>;
};
channel@11 {
reg = <11>;
port-mode = <PORT_MODE_0>;
};
channel@12 {
reg = <12>;
port-mode = <PORT_MODE_0>;
};
channel@13 {
reg = <13>;
port-mode = <PORT_MODE_0>;
};
channel@14 {
reg = <14>;
port-mode = <PORT_MODE_0>;
};
channel@15 {
[ 465 ]
Industrial I/O Subsystem Chapter 11
reg = <15>;
port-mode = <PORT_MODE_0>;
};
channel@16 {
reg = <16>;
port-mode = <PORT_MODE_0>;
};
channel@17 {
reg = <17>;
port-mode = <PORT_MODE_0>;
};
channel@18 {
reg = <18>;
port-mode = <PORT_MODE_1>;
};
channel@19 {
reg = <19>;
port-mode = <PORT_MODE_3>;
};
};
/*Accel: ADXL345@0 {
compatible = "arrow,adxl345";
spi-max-frequency = <5000000>;
spi-cpol;
spi-cpha;
reg = <0>;
pinctrl-0 = <&accel_int_pin>;
int-gpios = <&gpio 23 0>;
interrupts = <23 1>;
interrupt-parent = <&gpio>;
};*/
};
You also have to include the next header file in bold inside the bcm2710-rpi-3-b.dts file:
/dts-v1/;
#include "bcm2710.dtsi"
#include "bcm2709-rpi.dtsi"
#include "bcm283x-rpi-smsc9514.dtsi"
#include "bcm283x-rpi-csi1-2lane.dtsi"
#include "bcm283x-rpi-i2c0mux_0_44.dtsi"
#include "bcm271x-rpi-bt.dtsi"
#include <dt-bindings/iio/maxim,max11300.h>
The maxim,max11300.h file includes the values of the DT binding properties that will be used for
the channel sub-nodes. You have to place the maxim,max11300.h file under the next iio folder in the
kernel source tree:
~/linux_rpi3/linux/include/dt-bindings/iio/
[ 466 ]
Chapter 11 Industrial I/O Subsystem
#ifndef _DT_BINDINGS_MAXIM_MAX11300_H
#define _DT_BINDINGS_MAXIM_MAX11300_H
#define PORT_MODE_0 0
#define PORT_MODE_1 1
#define PORT_MODE_2 2
#define PORT_MODE_3 3
#define PORT_MODE_4 4
#define PORT_MODE_5 5
#define PORT_MODE_6 6
#define PORT_MODE_7 7
#define PORT_MODE_8 8
#define PORT_MODE_9 9
#define PORT_MODE_10 10
#define PORT_MODE_11 11
#define PORT_MODE_12 12
#define ADC_SAMPLES_1 0
#define ADC_SAMPLES_2 1
#define ADC_SAMPLES_4 2
#define ADC_SAMPLES_8 3
#define ADC_SAMPLES_16 4
#define ADC_SAMPLES_32 5
#define ADC_SAMPLES_64 6
#define ADC_SAMPLES_128 7
#endif /* _DT_BINDINGS_MAXIM_MAX11300_H */
[ 467 ]
Industrial I/O Subsystem Chapter 11
module_spi_driver(max11300_spi_driver);
4. Add "maxim,max11300" to the list of devices supported by the driver. The compatible
variable matches with the compatible property of the max11300 DT node:
static const struct of_device_id max11300_of_match[] = {
{ .compatible = "maxim,max11300", },
{},
};
MODULE_DEVICE_TABLE(of, max11300_of_match);
6. Initialize the max11300_rw_ops structure with the read and write functions that will access
via SPI to the registers of the MAX11300 device. See below the code of these functions:
[ 468 ]
Chapter 11 Industrial I/O Subsystem
/*
* Initialize the struct max11300_rw_ops with callback functions
* that will write/read via SPI the MAX11300 registers
*/
static const struct max11300_rw_ops max11300_rw_ops = {
.reg_write = max11300_reg_write,
.reg_read = max11300_reg_read,
.reg_read_differential = max11300_reg_read_differential,
};
/* To transmit via SPI, the LSB bit of the command byte must be 0 */
st->tx_cmd = (reg << 1);
/*
* In little endian CPUs, the byte stored in the higher address of the
* "val" variable (MSB of the DAC) will be stored in the lower address of the
* "st->tx_msg" variable using cpu_to_be16()
*/
st->tx_msg = cpu_to_be16(val);
[ 469 ]
Industrial I/O Subsystem Chapter 11
/* To receive via SPI the LSB bit of the command byte must be 1 */
st->tx_cmd = ((reg << 1) | 1);
/*
* In little endian CPUs the first byte (MSB of the ADC) received via
* SPI (in BE format) will be stored in the lower address of "st->rx_msg"
* variable. This byte is copied to the higher address of the "value"
* variable using be16_to_cpu(). The second byte received via SPI is
* copied from the higher address of "st->rx_msg" to the lower address
* of the "value" variable in little endian CPUs.
* In big endian CPUs the addresses are not swapped
*/
*value = be16_to_cpu(st->rx_msg);
return 0;
}
/* Function to read the MAX11300 registers in differential mode (2's complement) */
static int max11300_reg_read_differential(struct max11300_state *st, u8 reg, int *value)
{
struct spi_device *spi = container_of(st->dev, struct spi_device, dev);
int ret;
struct spi_transfer t[] = {
{
.tx_buf = &st->tx_cmd,
.len = 1,
}, {
.rx_buf = &st->rx_msg,
.len = 2,
},
};
/*
* Extend to an int 2's complement value the received SPI value in 2's
* complement value, which is stored in the "st->rx_msg" variable
*/
*value = sign_extend32(be16_to_cpu(st->rx_msg), 11);
return 0;
}
[ 470 ]
Chapter 11 Industrial I/O Subsystem
2. Create a global private data structure to manage the device from any function of the
driver:
struct max11300_state {
struct device *dev; // pointer to SPI device
const struct max11300_rw_ops *ops; // pointer to spi callback functions
struct gpio_chip gpiochip; // gpio_chip controller
struct mutex gpio_lock;
u8 num_ports; // number of ports of the MAX11300 device = 20
u8 num_gpios; // number of ports declared in the DT as GPIOs
u8 gpio_offset[20]; // gpio port numbers (0 to 19) for the "offset" values in the
range 0..(@ngpio - 1)
u8 gpio_offset_mode[20]; // gpio port modes (1 and 3) for the "offset" values in
the range 0..(@ngpio - 1)
u8 port_modes[20]; // port modes for the 20 ports of the MAX11300
u8 adc_range[20]; // voltage range for ADC related modes
u8 dac_range[20]; // voltage range for DAC related modes
u8 adc_reference[20]; // ADC voltage reference: 0: Internal, 1: External
u8 adc_samples[20]; // number of samples for ADC related modes
u8 adc_negative_port[20]; // negative port number for ports configured in mode 8
u8 tx_cmd; // command byte for SPI transactions
__be16 tx_msg; // transmit value for SPI transactions in BE format
__be16 rx_msg; // value received in SPI transactions in BE format
};
3. In the max11300_probe() function, declare an instance of the private structure, and allocate
the iio_dev structure:
struct iio_dev *indio_dev;
struct max11300_state *st;
indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
4. Initialize the iio_device and the data private structure within the max11300_probe() function.
The data private structure will be previously allocated by using the iio_priv() function.
Keep pointers between physical devices (devices as handled by the physical bus, SPI in
this case) and logical devices:
st = iio_priv(indio_dev); /* To be able to access the private data structure in other
parts of the driver, you need to attach it to the iio_dev structure using the iio_priv()
function. You will retrieve the pointer to the private structure using the same function
iio_priv() */
st->dev = dev; /* Store pointer to the SPI device, needed for exchanging data with the
MAX11300 device */
[ 471 ]
Industrial I/O Subsystem Chapter 11
dev_set_drvdata(dev, iio_dev); /* Link the spi device with the iio device */
iio_dev->name = name; /* Store the iio_dev name. Before doing this within your probe()
function, you will get the spi_device_id that triggered the match using spi_get_device_
id() */
iio_dev->dev.parent = dev; /* Keep pointers between physical devices (devices as handled
by the physical bus, SPI in this case) and logical devices */
indio_dev->info = &max11300_info; /* Store the address of the iio_info structure, which
contains a pointer variable to the IIO raw reading/writing callbacks */
max11300_alloc_ports(st); /* Configure the IIO channels of the device to generate the IIO
sysfs entries. This function will be described in more detail in the next point */
5. The max11300_alloc_ports() function reads the DT properties from the channel sub-nodes
of the max11300 node by using the fwnode_property_read_u32() function, and it stores the
values of these properties into the variables of the data global structure. The max11300_set_
port_modes() function uses these variables to configure the ports of the MAX11300 device.
The max11300_alloc_ports() function generates the different IIO sysfs entries by using the
max11300_setup_port_*_mode() functions:
/*
* This function will allocate and configure the iio channels of the iio device.
* It will also read the DT properties of each port (channel) and will store
* them in the global structure of the device
*/
static int max11300_alloc_ports(struct max11300_state *st)
{
unsigned int i, curr_port = 0, num_ports = st->num_ports, port_mode_6_count = 0,
offset = 0;
st->num_gpios = 0;
/*
* Walks for each MAX11300 child node in the DT;
* if an error is found in the node, then walks to
* the following one (continue)
*/
device_for_each_child_node(st->dev, child) {
ret = fwnode_property_read_u32(child, "reg", ®);
if (ret || reg >= ARRAY_SIZE(st->port_modes))
[ 472 ]
Chapter 11 Industrial I/O Subsystem
continue;
/*
* Store the value of the DT "port,mode" property
* in the global structure to know the mode of each port in
* other functions of the driver
*/
ret = fwnode_property_read_u32(child, "port-mode", &tmp);
if (!ret)
st->port_modes[reg] = tmp;
/*
* You will store other DT properties
* depending of the used "port,mode" property
*/
switch (st->port_modes[reg]) {
case PORT_MODE_7:
ret = fwnode_property_read_u32(child, "adc-range", &tmp);
if (!ret)
st->adc_range[reg] = tmp;
else
dev_info(st->dev, "Get default ADC range\n");
break;
case PORT_MODE_8:
ret = fwnode_property_read_u32(child, "adc-range", &tmp);
if (!ret)
st->adc_range[reg] = tmp;
else
dev_info(st->dev, "Get default ADC range\n");
[ 473 ]
Industrial I/O Subsystem Chapter 11
break;
case PORT_MODE_5: case PORT_MODE_6:
ret = fwnode_property_read_u32(child, "dac-range", &tmp);
if (!ret)
st->dac_range[reg] = tmp;
else
dev_info(st->dev, "Get default DAC range\n");
/*
* A port in mode 6 will generate two IIO sysfs entries,
* one for writing the DAC port, and another for reading
* the ADC port
*/
if ((st->port_modes[reg]) == PORT_MODE_6) {
ret = fwnode_property_read_u32(child, "AVR", &tmp);
if (!ret)
st->adc_reference[reg] = tmp;
else
dev_info(st->dev, "Get default internal
ADC reference\n");
/*
[ 474 ]
Chapter 11 Industrial I/O Subsystem
break;
/*
* Increment the gpio offset and number of configured
* ports as GPIOs
*/
offset++;
st->num_gpios++;
break;
/*
* Store the port_mode for each gpio offset,
* starting with offset = 0
*/
st->gpio_offset_mode[offset] = PORT_MODE_3;
/*
* Increment the gpio offset and
* number of configured ports as GPIOs
*/
offset++;
st->num_gpios++;
break;
case PORT_MODE_0:
dev_info(st->dev, "the channel %d is set in default port
[ 475 ]
Industrial I/O Subsystem Chapter 11
mode_0\n", reg);
break;
default:
dev_info(st->dev, "bad port mode for channel %d\n", reg);
}
}
/*
* Allocate space for the storage of all the IIO channels specs.
* Returns a pointer to this storage
*/
devm_kcalloc(st->dev, num_ports + port_mode_6_count, sizeof(*ports), GFP_KERNEL);
/*
* i is the number of the channel, &ports[curr_port] is a pointer
* variable that will store the "iio_chan_spec structure" address of
* each port
*/
for (i = 0; i < num_ports; i++) {
switch (st->port_modes[i]) {
case PORT_MODE_5:
max11300_setup_port_5_mode(iio_dev, &ports[curr_port],
true, i, PORT_MODE_5);
curr_port++;
break;
case PORT_MODE_6:
max11300_setup_port_6_mode(iio_dev, &ports[curr_port],
true, i, PORT_MODE_6);
curr_port++;
max11300_setup_port_6_mode(iio_dev, &ports[curr_port],
false, i, PORT_MODE_6);
curr_port++;
break;
case PORT_MODE_7:
max11300_setup_port_7_mode(iio_dev, &ports[curr_port],
false, i, PORT_MODE_7);
curr_port++;
break;
case PORT_MODE_8:
max11300_setup_port_8_mode(iio_dev, &ports[curr_port],
false, i, st->adc_negative_port[i],
PORT_MODE_8);
curr_port++;
break;
case PORT_MODE_0:
dev_info(st->dev, "the channel is set in default port mode_0\n");
break;
[ 476 ]
Chapter 11 Industrial I/O Subsystem
case PORT_MODE_1:
dev_info(st->dev, "the channel %d is set in port mode_1\n", i);
break;
case PORT_MODE_3:
dev_info(st->dev, "the channel %d is set in port mode_3\n", i);
break;
default:
dev_info(st->dev, "bad port mode for channel %d\n", i);
}
}
iio_dev->num_channels = curr_port;
iio_dev->channels = ports;
return 0;
}
6. Write the iio_info structure. The read/write user space operations to the sysfs data channel
access attributes are mapped to the following kernel callbacks:
static const struct iio_info max11300_info = {
.read_raw = max11300_read_adc,
.write_raw = max11300_write_dac,
};
[ 477 ]
Industrial I/O Subsystem Chapter 11
2. Initialize the gpio_chip structure with the different callbacks that will control the gpio lines
of the GPIO controller, and register the gpio chip with the kernel by using the gpiochip_
add_data() function:
static int max11300_gpio_init(struct max11300_state *st)
{
st->gpiochip.label = "gpio-max11300";
st->gpiochip.base = -1;
st->gpiochip.ngpio = st->num_gpios;
st->gpiochip.parent = st->dev;
st->gpiochip.can_sleep = true;
st->gpiochip.direction_input = max11300_gpio_direction_input;
st->gpiochip.direction_output = max11300_gpio_direction_output;
st->gpiochip.get = max11300_gpio_get;
st->gpiochip.set = max11300_gpio_set;
[ 478 ]
Chapter 11 Industrial I/O Subsystem
st->gpiochip.owner = THIS_MODULE;
/* Register a gpio_chip */
return gpiochip_add_data(&st->gpiochip, st);
}
3. These are the callback functions that will control the GPIO lines of the MAX11300 GPIO
controller:
/*
* struct gpio_chip get callback function.
* It gets the input value of the GPIO line (0=low, 1=high)
* accessing to the GPI_DATA registers of the MAX11300
*/
static int max11300_gpio_get(struct gpio_chip *chip, unsigned int offset)
{
struct max11300_state *st = gpiochip_get_data(chip);
int ret = 0;
u16 read_val;
u8 reg;
int val;
mutex_lock(&st->gpio_lock);
if (st->gpio_offset_mode[offset] == PORT_MODE_3)
dev_info(st->dev, "the gpio %d cannot be configured in input mode\n", offset);
mutex_unlock(&st->gpio_lock);
return val;
}
else {
reg = GPI_DATA_15_TO_0_ADDRESS;
ret = st->ops->reg_read(st, reg, &read_val);
if (ret)
goto err_unlock;
[ 479 ]
Industrial I/O Subsystem Chapter 11
mutex_unlock(&st->gpio_lock);
return val;
}
err_unlock:
mutex_unlock(&st->gpio_lock);
return ret;
}
/*
* struct gpio_chip set callback function.
* It sets the output value of the GPIO line with
* GPIO ACTIVE_HIGH mode (0=low, 1=high)
* writing to the GPO_DATA registers of the max11300
*/
static void max11300_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
{
struct max11300_state *st = gpiochip_get_data(chip);
u8 reg;
unsigned int val = 0;
mutex_lock(&st->gpio_lock);
if (st->gpio_offset_mode[offset] == PORT_MODE_1)
dev_info(st->dev, "the gpio %d cannot accept this output\n", offset);
if (value == 1 && (st->gpio_offset[offset] > 0x0F)) {
dev_info(st->dev,
"The GPIO ouput is set high and port_number is %d. Pin is > 0x0F\n",
st->gpio_offset[offset]);
val |= BIT(st->gpio_offset[offset]);
val = val >> 16;
reg = GPO_DATA_19_TO_16_ADDRESS;
st->ops->reg_write(st, reg, val);
}
else if (value == 0 && (st->gpio_offset[offset] > 0x0F)) {
dev_info(st->dev,
"The GPIO ouput is set low and port_number is %d. Pin is > 0x0F\n",
st->gpio_offset[offset]);
val &= ~BIT(st->gpio_offset[offset]);
val = val >> 16;
reg = GPO_DATA_19_TO_16_ADDRESS;
st->ops->reg_write(st, reg, val);
}
else if (value == 1 && (st->gpio_offset[offset] < 0x0F)) {
dev_info(st->dev,
"The GPIO ouput is set high and port_number is %d. Pin is < 0x0F\n",
st->gpio_offset[offset]);
val |= BIT(st->gpio_offset[offset]);
[ 480 ]
Chapter 11 Industrial I/O Subsystem
reg = GPO_DATA_15_TO_0_ADDRESS;
st->ops->reg_write(st, reg, val);
}
else if (value == 0 && (st->gpio_offset[offset] < 0x0F)) {
dev_info(st->dev,
"The GPIO ouput is set low and port_number is %d. Pin is < 0x0F\n",
st->gpio_offset[offset]);
val &= ~BIT(st->gpio_offset[offset]);
reg = GPO_DATA_15_TO_0_ADDRESS;
st->ops->reg_write(st, reg, val);
}
else
dev_info(st->dev, "the gpio %d cannot accept this value\n", offset);
mutex_unlock(&st->gpio_lock);
}
/*
* struct gpio_chip direction_input callback function.
* It configures the GPIO port as an input (GPI),
* writing to the PORT_CFG register of the max11300
*/
static int max11300_gpio_direction_input(struct gpio_chip *chip, unsigned int offset)
{
struct max11300_state *st = gpiochip_get_data(chip);
int ret;
u8 reg;
u16 port_mode, val;
mutex_lock(&st->gpio_lock);
mdelay(1);
[ 481 ]
Industrial I/O Subsystem Chapter 11
err_unlock:
mutex_unlock(&st->gpio_lock);
return ret;
}
/*
* struct gpio_chip direction_output callback function.
* It configures the GPIO port as an output (GPO) writing to
* the PORT_CFG register of the max11300, and it sets output value of the
* GPIO line with GPIO ACTIVE_HIGH mode (0=low, 1=high)
* writing to the GPO data registers of the max11300
*/
static int max11300_gpio_direction_output(struct gpio_chip *chip,
unsigned int offset, int value)
{
struct max11300_state *st = gpiochip_get_data(chip);
int ret;
u8 reg;
u16 port_mode, val;
mutex_lock(&st->gpio_lock);
if (st->gpio_offset_mode[offset] == PORT_MODE_1)
dev_info(st->dev, "the gpio %d only can be set in input mode\n", offset);
mutex_unlock(&st->gpio_lock);
[ 482 ]
Chapter 11 Industrial I/O Subsystem
Create the max11300.c, max11300-base.c and max11300-base.h files and also a Makefile file in the
linux_5.4_max11300_driver folder, and add max11300.o and max11300-base.o to your Makefile obj-m
variable, then build and deploy the modules to the Raspberry Pi:
~/linux_5.4_rpi3_drivers/linux_5.4_max11300_driver$ make
~/linux_5.4_rpi3_drivers/linux_5.4_max11300_driver$ make deploy
Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
#include <linux/types.h>
#include <linux/cache.h>
#include <linux/mutex.h>
#include <linux/gpio/driver.h>
struct max11300_state;
/*
* Declare a structure with pointers to the functions that will read and write
* via SPI the registers of the MAX11300 device
[ 483 ]
Industrial I/O Subsystem Chapter 11
*/
struct max11300_rw_ops {
int (*reg_write)(struct max11300_state *st, u8 reg, u16 value);
int (*reg_read)(struct max11300_state *st, u8 reg, u16 *value);
int (*reg_read_differential)(struct max11300_state *st, u8 reg, int *value);
};
/* Declare the global structure that will store the info of the device */
struct max11300_state {
struct device *dev;
const struct max11300_rw_ops *ops;
struct gpio_chip gpiochip;
struct mutex gpio_lock;
u8 num_ports;
u8 num_gpios;
u8 gpio_offset[20];
u8 gpio_offset_mode[20];
u8 port_modes[20];
u8 adc_range[20];
u8 dac_range[20];
u8 adc_reference[20];
u8 adc_samples[20];
u8 adc_negative_port[20];
u8 tx_cmd;
__be16 tx_msg;
__be16 rx_msg;
};
#endif /* __DRIVERS_IIO_DAC_max11300_BASE_H__ */
[ 484 ]
Chapter 11 Industrial I/O Subsystem
#define PORT_MODE_0 0
#define PORT_MODE_1 1
#define PORT_MODE_2 2
#define PORT_MODE_3 3
#define PORT_MODE_4 4
#define PORT_MODE_5 5
#define PORT_MODE_6 6
#define PORT_MODE_7 7
#define PORT_MODE_8 8
#define PORT_MODE_9 9
#define PORT_MODE_10 10
#define PORT_MODE_11 11
#define PORT_MODE_12 12
#define ADC_SAMPLES_1 0
#define ADC_SAMPLES_2 1
#define ADC_SAMPLES_4 2
#define ADC_SAMPLES_8 3
#define ADC_SAMPLES_16 4
#define ADC_SAMPLES_32 5
#define ADC_SAMPLES_64 6
#define ADC_SAMPLES_128 7
#endif /* _DT_BINDINGS_MAXIM_MAX11300_H */
[ 485 ]
Industrial I/O Subsystem Chapter 11
/* To transmit via SPI, the LSB bit of the command byte must be 0 */
st->tx_cmd = (reg << 1);
/*
* In little endian CPUs the byte stored in the higher address of
* the "val" variable (MSB of the DAC) will be stored in the lower address
* of the "st->tx_msg" variable using cpu_to_be16()
*/
st->tx_msg = cpu_to_be16(val);
[ 486 ]
Chapter 11 Industrial I/O Subsystem
/* to receive via SPI the LSB bit of the command byte must be 1 */
st->tx_cmd = ((reg << 1) | 1);
return 0;
}
return 0;
}
[ 487 ]
Industrial I/O Subsystem Chapter 11
/*
* Initialize struct max11300_rw_ops with callback functions
* that will write/read via SPI the MAX11300 registers
*/
static const struct max11300_rw_ops max11300_rw_ops = {
.reg_write = max11300_reg_write,
.reg_read = max11300_reg_read,
.reg_read_differential = max11300_reg_read_differential,
};
[ 488 ]
Chapter 11 Industrial I/O Subsystem
#include <dt-bindings/iio/maxim,max11300.h>
#include "max11300-base.h"
/*
* struct gpio_chip get callback function.
* It gets the input value of the GPIO line (0=low, 1=high)
* accessing to the GPI_DATA registers of max11300
*/
static int max11300_gpio_get(struct gpio_chip *chip, unsigned int offset)
{
struct max11300_state *st = gpiochip_get_data(chip);
int ret = 0;
u16 read_val;
u8 reg;
int val;
mutex_lock(&st->gpio_lock);
if (st->gpio_offset_mode[offset] == PORT_MODE_3)
dev_info(st->dev, "the gpio %d cannot be configured in input mode\n", offset);
mutex_unlock(&st->gpio_lock);
return val;
}
else {
[ 489 ]
Industrial I/O Subsystem Chapter 11
reg = GPI_DATA_15_TO_0_ADDRESS;
ret = st->ops->reg_read(st, reg, &read_val);
if (ret)
goto err_unlock;
mutex_unlock(&st->gpio_lock);
return val;
}
err_unlock:
mutex_unlock(&st->gpio_lock);
return ret;
}
/*
* struct gpio_chip set callback function.
* It sets the output value of the GPIO line in
* GPIO ACTIVE_HIGH mode (0=low, 1=high)
* writing to the GPO_DATA registers of max11300
*/
static void max11300_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
{
struct max11300_state *st = gpiochip_get_data(chip);
u8 reg;
unsigned int val = 0;
mutex_lock(&st->gpio_lock);
if (st->gpio_offset_mode[offset] == PORT_MODE_1)
dev_info(st->dev, "the gpio %d cannot accept this output\n", offset);
if (value == 1 && (st->gpio_offset[offset] > 0x0F)) {
dev_info(st->dev,
"The GPIO ouput is set high and port_number is %d. Pin is > 0x0F\n",
st->gpio_offset[offset]);
val |= BIT(st->gpio_offset[offset]);
val = val >> 16;
reg = GPO_DATA_19_TO_16_ADDRESS;
st->ops->reg_write(st, reg, val);
}
else if (value == 0 && (st->gpio_offset[offset] > 0x0F)) {
dev_info(st->dev,
"The GPIO ouput is set low and port_number is %d. Pin is > 0x0F\n",
st->gpio_offset[offset]);
val &= ~BIT(st->gpio_offset[offset]);
[ 490 ]
Chapter 11 Industrial I/O Subsystem
mutex_unlock(&st->gpio_lock);
}
/*
* struct gpio_chip direction_input callback function.
* It configures the GPIO port as an input (GPI)
* writing to the PORT_CFG register of max11300
*/
static int max11300_gpio_direction_input(struct gpio_chip *chip, unsigned int offset)
{
struct max11300_state *st = gpiochip_get_data(chip);
int ret;
u8 reg;
u16 port_mode, val;
mutex_lock(&st->gpio_lock);
[ 491 ]
Industrial I/O Subsystem Chapter 11
if (ret)
goto err_unlock;
mdelay(1);
err_unlock:
mutex_unlock(&st->gpio_lock);
return ret;
}
/*
* struct gpio_chip direction_output callback function.
* It configures the GPIO port as an output (GPO) writing to
* the PORT_CFG register of max11300 and sets output value of the
* GPIO line in GPIO ACTIVE_HIGH mode (0=low, 1=high)
* writing to the GPO data registers of max11300
*/
static int max11300_gpio_direction_output(struct gpio_chip *chip,
unsigned int offset, int value)
{
struct max11300_state *st = gpiochip_get_data(chip);
int ret;
u8 reg;
u16 port_mode, val;
mutex_lock(&st->gpio_lock);
if (st->gpio_offset_mode[offset] == PORT_MODE_1)
dev_info(st->dev,
"the gpio %d only can be set in input mode\n",
offset);
[ 492 ]
Chapter 11 Industrial I/O Subsystem
mutex_unlock(&st->gpio_lock);
/*
* Initialize the MAX11300 gpio controller (struct gpio_chip)
* and register it to the kernel
*/
static int max11300_gpio_init(struct max11300_state *st)
{
if (!st->num_gpios)
return 0;
st->gpiochip.label = "gpio-max11300";
st->gpiochip.base = -1;
st->gpiochip.ngpio = st->num_gpios;
st->gpiochip.parent = st->dev;
st->gpiochip.can_sleep = true;
st->gpiochip.direction_input = max11300_gpio_direction_input;
st->gpiochip.direction_output = max11300_gpio_direction_output;
st->gpiochip.get = max11300_gpio_get;
st->gpiochip.set = max11300_gpio_set;
st->gpiochip.owner = THIS_MODULE;
mutex_init(&st->gpio_lock);
/* register a gpio_chip */
return gpiochip_add_data(&st->gpiochip, st);
}
/*
* Configure the port configuration registers of each port with the values
* retrieved from the DT properties.These DT values were read and stored in
* the device global structure using the max11300_alloc_ports() function.
* The ports in GPIO mode will be configured in the gpiochip.direction_input
* and gpiochip.direction_output callback functions.
*/
static int max11300_set_port_modes(struct max11300_state *st)
{
const struct max11300_rw_ops *ops = st->ops;
int ret;
unsigned int i;
u8 reg;
u16 adc_range, dac_range, adc_reference, adc_samples, adc_negative_port;
[ 493 ]
Industrial I/O Subsystem Chapter 11
mutex_lock(&iio_dev->mlock);
if ((st->port_modes[i]) == PORT_MODE_5)
val = (port_mode | dac_range);
else
val = (port_mode | dac_range | adc_reference);
mdelay(1);
break;
case PORT_MODE_7:
reg = PORT_CFG_BASE_ADDRESS + i;
port_mode = (st->port_modes[i] << 12);
adc_range = (st->adc_range[i] << 8);
adc_reference = st->adc_reference[i];
adc_samples = (st->adc_samples[i] << 5);
dev_info(st->dev,
"the value of adc cfg addr for channel %d in port mode %d is %x\n",
i, st->port_modes[i], reg);
dev_info(st->dev,
"the channel %d is set in port mode %d\n",
i, st->port_modes[i]);
dev_info(st->dev,
"the value of adc cfg val for channel %d in port mode %d is %x\n",
i, st->port_modes[i], val);
[ 494 ]
Chapter 11 Industrial I/O Subsystem
mdelay(1);
break;
case PORT_MODE_8:
reg = PORT_CFG_BASE_ADDRESS + i;
port_mode = (st->port_modes[i] << 12);
adc_range = (st->adc_range[i] << 8);
adc_reference = st->adc_reference[i];
adc_samples = (st->adc_samples[i] << 5);
adc_negative_port = st->adc_negative_port[i];
dev_info(st->dev,
"the value of adc cfg addr for channel %d in port mode %d is %x\n",
i, st->port_modes[i], reg);
dev_info(st->dev,
"the channel %d is set in port mode %d\n",
i, st->port_modes[i]);
dev_info(st->dev,
"the value of adc cfg val for channel %d in port mode %d is %x\n",
i, st->port_modes[i], val);
mdelay(1);
break;
case PORT_MODE_9: case PORT_MODE_10:
reg = PORT_CFG_BASE_ADDRESS + i;
port_mode = (st->port_modes[i] << 12);
adc_range = (st->adc_range[i] << 8);
adc_reference = st->adc_reference[i];
dev_info(st->dev,
"the value of adc cfg addr for channel %d in port mode %d is %x\n",
i, st->port_modes[i], reg);
dev_info(st->dev,
"the channel %d is set in port mode %d\n",
i, st->port_modes[i]);
dev_info(st->dev,
"the value of adc cfg val for channel %d in port mode %d is %x\n",
i, st->port_modes[i], val);
[ 495 ]
Industrial I/O Subsystem Chapter 11
mdelay(1);
break;
case PORT_MODE_0:
dev_info(st->dev, "the port %d is set in default port mode_0\n", i);
break;
case PORT_MODE_1:
dev_info(st->dev, "the port %d is set in port mode_1\n", i);
break;
case PORT_MODE_3:
dev_info(st->dev, "the port %d is set in port mode_3\n", i);
break;
default:
dev_info(st->dev, "bad port mode is selected\n");
return -EINVAL;
}
}
err_unlock:
mutex_unlock(&iio_dev->mlock);
return ret;
}
switch (mask) {
case IIO_CHAN_INFO_RAW:
if (!chan->output)
return -EINVAL;
mutex_lock(&iio_dev->mlock);
ret = st->ops->reg_write(st, reg, val);
mutex_unlock(&iio_dev->mlock);
break;
default:
return -EINVAL;
}
return ret;
}
[ 496 ]
Chapter 11 Industrial I/O Subsystem
switch (m) {
case IIO_CHAN_INFO_RAW:
mutex_lock(&iio_dev->mlock);
ret = IIO_VAL_INT;
break;
default:
ret = -EINVAL;
}
unlock:
mutex_unlock(&iio_dev->mlock);
return ret;
}
/* Create kernel hooks to read/write IIO sysfs attributes from user space */
static const struct iio_info max11300_info = {
.read_raw = max11300_read_adc,
.write_raw = max11300_write_dac,
};
[ 497 ]
Industrial I/O Subsystem Chapter 11
[ 498 ]
Chapter 11 Industrial I/O Subsystem
/*
* This function will allocate and configure the iio channels of the iio device.
* It will also read the DT properties of each port (channel) and will store them
* in the device global structure
*/
static int max11300_alloc_ports(struct max11300_state *st)
{
unsigned int i, curr_port = 0, num_ports = st->num_ports, port_mode_6_count = 0, offset =
0;
st->num_gpios = 0;
/*
* Walks for each MAX11300 child node in the DT. If there is an error, then
* walks to the following node (continue)
*/
device_for_each_child_node(st->dev, child) {
ret = fwnode_property_read_u32(child, "reg", ®);
if (ret || reg >= ARRAY_SIZE(st->port_modes))
continue;
/*
* Store the value of the DT "port,mode" property in the global struct
[ 499 ]
Industrial I/O Subsystem Chapter 11
/*
* You will store other DT properties depending
* of the used "port,mode" property
*/
switch (st->port_modes[reg]) {
case PORT_MODE_7:
ret = fwnode_property_read_u32(child, "adc-range", &tmp);
if (!ret)
st->adc_range[reg] = tmp;
else
dev_info(st->dev, "Get default ADC range\n");
[ 500 ]
Chapter 11 Industrial I/O Subsystem
st->adc_samples[reg] = tmp;
else
dev_info(st->dev, "Get default internal ADC sampling\n");
/*
* A port in mode 6 will generate two IIO sysfs entries,
* one for writing the DAC port, and another for reading
* the ADC port
*/
if ((st->port_modes[reg]) == PORT_MODE_6) {
ret = fwnode_property_read_u32(child, "AVR", &tmp);
if (!ret)
st->adc_reference[reg] = tmp;
else
dev_info(st->dev, "Get default internal ADC reference\n");
/*
* Get the number of ports set in mode_6 to allocate
* space for the related iio channels
*/
port_mode_6_count++;
dev_info(st->dev, "there are %d channels in mode_6\n",
[ 501 ]
Industrial I/O Subsystem Chapter 11
port_mode_6_count);
}
/*
* link the gpio offset with the port number,
* starting with offset = 0
*/
st->gpio_offset[offset] = reg;
/*
* store the port_mode for each gpio offset,
* starting with offset = 0
*/
st->gpio_offset_mode[offset] = PORT_MODE_1;
dev_info(st->dev,
"the gpio number %d is using the gpio offset number %d\n",
st->gpio_offset[offset], offset);
/*
* Increment the gpio offset and number
* of configured ports as GPIOs
*/
offset++;
st->num_gpios++;
break;
/* The port is configured as a GPO in the DT */
case PORT_MODE_3:
dev_info(st->dev, "the channel %d is set in port mode %d\n",
reg, st->port_modes[reg]);
/*
* link the gpio offset with the port number,
* starting with offset = 0
*/
st->gpio_offset[offset] = reg;
/*
* Store the port_mode for each gpio offset,
* starting with offset = 0
*/
st->gpio_offset_mode[offset] = PORT_MODE_3;
dev_info(st->dev,
"the gpio number %d is using the gpio offset number %d\n",
st->gpio_offset[offset], offset);
[ 502 ]
Chapter 11 Industrial I/O Subsystem
/*
* Increment the gpio offset and
* number of configured ports as GPIOs
*/
offset++;
st->num_gpios++;
break;
case PORT_MODE_0:
dev_info(st->dev, "the channel %d is set in default port mode_0\n", reg);
break;
default:
dev_info(st->dev, "bad port mode for channel %d\n", reg);
}
}
/*
* Allocate space for the storage of all the IIO channels specs.
* Returns a pointer to this storage
*/
ports = devm_kcalloc(st->dev, num_ports + port_mode_6_count, sizeof(*ports), GFP_KERNEL);
if (!ports)
return -ENOMEM;
/*
* i is the number of the channel, &ports[curr_port] is a pointer variable that
* will store the "iio_chan_spec structure" address of each port
*/
for (i = 0; i < num_ports; i++) {
switch (st->port_modes[i]) {
case PORT_MODE_5:
dev_info(st->dev, "the port %d is configured as MODE 5\n", i);
max11300_setup_port_5_mode(iio_dev, &ports[curr_port],
true, i, PORT_MODE_5); // true = out
curr_port++;
break;
case PORT_MODE_6:
dev_info(st->dev, "the port %d is configured as MODE 6\n", i);
max11300_setup_port_6_mode(iio_dev, &ports[curr_port],
true, i, PORT_MODE_6); // true = out
curr_port++;
max11300_setup_port_6_mode(iio_dev, &ports[curr_port],
false, i, PORT_MODE_6); // false = in
curr_port++;
break;
case PORT_MODE_7:
dev_info(st->dev, "the port %d is configured as MODE 7\n", i);
max11300_setup_port_7_mode(iio_dev, &ports[curr_port],
false, i, PORT_MODE_7); // false = in
curr_port++;
break;
case PORT_MODE_8:
[ 503 ]
Industrial I/O Subsystem Chapter 11
iio_dev->num_channels = curr_port;
iio_dev->channels = ports;
return 0;
}
int max11300_probe(struct device *dev, const char *name, const struct max11300_rw_ops *ops)
{
/* Create the global structure that will store the info of the device */
struct max11300_state *st;
u16 write_val;
u16 read_val;
u8 reg;
int ret;
write_val = 0;
[ 504 ]
Chapter 11 Industrial I/O Subsystem
/*
* Store in the global structure the pointer to the
* MAX11300 SPI read and write functions
*/
st->ops = ops;
iio_dev->dev.parent = dev;
iio_dev->name = name;
/*
* Store the address of the iio_info structure,
* which contains pointer variables
* to the IIO write/read callbacks
*/
iio_dev->info = &max11300_info;
iio_dev->modes = INDIO_DIRECT_MODE;
[ 505 ]
Industrial I/O Subsystem Chapter 11
ret = max11300_set_port_modes(st);
if (ret)
goto error_reset_device;
ret = iio_device_register(iio_dev);
if (ret)
goto error;
ret = max11300_gpio_init(st);
if (ret)
goto error_dev_unregister;
return 0;
error_dev_unregister:
iio_device_unregister(iio_dev);
error_reset_device:
/* reset the device */
reg = DCR_ADDRESS;
write_val = RESET;
ret = ops->reg_write(st, reg, write_val);
if (ret != 0)
return ret;
error:
return ret;
}
EXPORT_SYMBOL_GPL(max11300_probe);
iio_device_unregister(iio_dev);
return 0;
}
EXPORT_SYMBOL_GPL(max11300_remove);
[ 506 ]
Chapter 11 Industrial I/O Subsystem
The tools provided with libgpiod allow the access to the GPIO driver from the command line. There
are six commands included in the libgpiod tools:
• gpiodetect: Lists all the gpiochips present on the system, their names, labels and the
number of GPIO lines. In the lab, the MAX11300 gpio chip will be shown with the name of
gpiochip3.
• gpioinfo: Lists all the lines of the specified gpiochips, their names, consumers, directions,
active states and additional flags.
• gpioget: Reads the values of the specified GPIO lines. This tool will call the
gpiochip.direction_input and gpiochip.get callback functions which were declared in struct
gpio_chip.
• gpioset: Sets the values of the specified GPIO lines. This tool will call the
gpiochip.direction_output callback function which was declared in struct gpio_chip.
• gpiofind: Finds the gpiochip name and line offset given the line name.
• gpiomon: Waits for events on GPIO lines, specifies which events to watch, how many
events to process before exiting or if the events should be reported to the console.
Follow the next instructions to test the driver:
Load the modules:
root@raspberrypi:/home/pi# insmod max11300-base.ko
root@raspberrypi:/home/pi# insmod max11300.ko
max11300 spi0.0: max11300_probe() function is called
max11300 spi0.0: the value of DCR_ADDRESS is 10
max11300 spi0.0: the value of reset is 8000
max11300 spi0.0: read SE channel
max11300 spi0.0: the value of device ID is 424
max11300 spi0.0: the value of DACREF_CONT_SWEEP is 43
max11300 spi0.0: the setup of the device is done
[...]
[ 507 ]
Industrial I/O Subsystem Chapter 11
[ 508 ]
Chapter 11 Industrial I/O Subsystem
[ 509 ]
Industrial I/O Subsystem Chapter 11
[ 510 ]
Chapter 11 Industrial I/O Subsystem
In this section, you have seen how to control the GPIOs by using the tools provided with libgpiod.
In the next section, you will see how to write applications that control the GPIOs by using two
different methods. The first method will control a GPIO by using a device node, and the second
method will control a GPIO by using the functions of the libgpiod library.
Create a new gpio_device_app.c file in the application_code folder, send the application to the
Raspberry Pi, and compile the application on the Pi:
~/linux_5.4_rpi3_drivers/linux_5.4_max11300_driver/application_code$ scp gpio_device_app.c
[email protected]:/home/pi
root@raspberrypi:/home/pi# gcc -o gpio_device_app gpio_device_app.c
[ 511 ]
Industrial I/O Subsystem Chapter 11
Finally, execute the application on the target. You can see the red LED flashes!
Load the modules:
root@raspberrypi:/home/pi# insmod max11300-base.ko
root@raspberrypi:/home/pi# insmod max11300.ko
Execute the application:
root@raspberrypi:/home/pi# ./gpio_device_app
[ 512 ]
Chapter 11 Industrial I/O Subsystem
return ret;
}
return ret;
}
[ 513 ]
Industrial I/O Subsystem Chapter 11
Finally, execute the compiled application on the target. You can see the red LED flashes!:
Load the modules:
root@raspberrypi:/home/pi# insmod max11300-base.ko
root@raspberrypi:/home/pi# insmod max11300.ko
Execute the application:
root@raspberrypi:/home/pi# ./libgpiod_max11300_app
/* Open /dev/gpiochip3 */
output_chip = gpiod_chip_open_by_number(3);
if (!output_chip)
return -1;
[ 514 ]
Chapter 11 Industrial I/O Subsystem
gpiod_chip_close(output_chip);
return ret;
}
sleep(1);
}
gpiod_line_release(output_line);
gpiod_chip_close(output_chip);
return 0;
}
[ 515 ]
Industrial I/O Subsystem Chapter 11
};
4. Initialize the iio_device and the nunchuk_accel private structure within the nunchuk_accel_
probe() function. The nunchuk_accel private structure will be previously allocated by using
the iio_priv() function. Keep pointers between physical devices (devices as handled by the
physical bus, I2C in this case) and logical devices:
nunchuk_accel = iio_priv(indio_dev); /* To be able to access the private structure in
other functions of the driver you need to attach it to the iio_dev structure using
the iio_priv() function.You will retrieve the pointer "nunchuk_accel" to the private
structure using the same function iio_priv() */
nunchuck_accel->client = client; /* Keep pointer to the I2C device, needed for exchanging
data with the Nunchuk device */
indio_dev->name = "Nunchuk Accel"; /* Store the iio_dev name */
indio_dev->dev.parent = &client->dev; /* keep pointers between physical devices (devices
as handled by the physical bus, I2C in this case) and logical devices */
indio_dev->info = &nunchuk_info; /* Store the address of the iio_info structure, which
contains a pointer variable to the IIO raw reading callback */
indio_dev->channels = nunchuk_channels; /* Store address of the iio_chan_spec array of
structures */
indio_dev->num_channels = 3; /* Set number of channels of the device */
indio_dev->modes = INDIO_DIRECT_MODE; /* Device operating mode. DIRECT_MODE indicates
that the collected data is not cached, and the single data can be read directly under
sysfs */
5. Register the device to the IIO core. Now, the device is global to the rest of the driver
functions until it is unregistered. After this call, the device is ready to accept requests from
user space applications.
devm_iio_device_register(&client->dev, indio_dev);
6. Create the iio_chan_spec structures to expose to user space the sysfs attributes of each
channel. The type variable specifies what type of measurement the channel makes,
acceleration in the case of our driver. The modified field of iio_chan_spec is set to 1.
Modifiers are specified by using the channel2 field of the same struct iio_chan_spec and are
used to indicate a physically unique characteristic of the channel, such as the acceleration
axis in the case of our driver. The info_mask_separate variable indicates which information
should be unique to the channel.
[ 516 ]
Chapter 11 Industrial I/O Subsystem
#define NUNCHUK_IIO_CHAN(axis) { \
.type = IIO_ACCEL, \
.modified = 1, \
.channel2 = IIO_MOD_##axis, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
}
The IIO channel definitions above will generate the following data channel access
attributes below:
/sys/bus/iio/devices/iio:deviceX/in_accel_x_raw
/sys/bus/iio/devices/iio:deviceX/in_accel_y_raw
/sys/bus/iio/devices/iio:deviceX/in_accel_z_raw
7. Create the iio_info structure to declare the hooks that the IIO core will use for this device.
There is only one kernel hook available for interactions from user space with the sysfs
attributes of each channel.
static const struct iio_info nunchuk_info = {
.read_raw = nunchuk_accel_read_raw,
};
The nunchuk_accel_read_raw() function returns the value of each axis when any of the
in_accel_X_raw entries is read from user space. The axis of the accelerometer will be filtered
using the channel2 modifier.
static int nunchuk_accel_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
char buf[6];
struct nunchuk_accel *nunchuk_accel = iio_priv(indio_dev);
struct i2c_client *client = nunchuk_accel->client;
/* Read data from nunchuk */
nunchuk_read_registers(client, buf, ARRAY_SIZE(buf));
/* Data needs to be written to 'val'and 'val2' is ignored */
switch (chan->channel2) {
case IIO_MOD_X:
*val = (buf[2] << 2) | ((buf[5] >> 2) & 0x3);
break;
case IIO_MOD_Y:
*val = (buf[3] << 2) | ((buf[5] >> 4) & 0x3);
break;
case IIO_MOD_Z:
*val = (buf[4] << 2) | ((buf[5] >> 6) & 0x3);
[ 517 ]
Industrial I/O Subsystem Chapter 11
break;
default:
return -EINVAL;
}
return IIO_VAL_INT;
}
10. Add an i2c_driver structure that will be registered to the I2C bus:
static struct i2c_driver nunchuk_accel_driver = {
.driver = {
.name = "nunchuk_accel",
.owner = THIS_MODULE,
.of_match_table = nunchuk_accel_of_match,
},
.probe = nunchuk_accel_probe,
.remove = nunchuk_accel_remove,
.id_table = nunchuk_accel_id,
};
[ 518 ]
Chapter 11 Industrial I/O Subsystem
struct nunchuk_accel {
struct i2c_client *client;
};
#define NUNCHUK_IIO_CHAN(axis) { \
.type = IIO_ACCEL, \
.modified = 1, \
.channel2 = IIO_MOD_##axis, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
}
mdelay(10);
buf[0] = 0x00;
ret = i2c_master_send(client, buf, 1);
if (ret >= 0 && ret != 1)
return -EIO;
if (ret < 0)
return ret;
mdelay(10);
[ 519 ]
Industrial I/O Subsystem Chapter 11
return IIO_VAL_INT;
}
nunchuk_accel = iio_priv(indio_dev);
/* Associate client->dev with nunchuk private structure */
i2c_set_clientdata(client, nunchuk_accel);
nunchuk_accel->client = client;
[ 520 ]
Chapter 11 Industrial I/O Subsystem
indio_dev->dev.parent = &client->dev;
indio_dev->info = &nunchuk_info;
indio_dev->channels = nunchuk_channels;
indio_dev->num_channels = 3;
indio_dev->modes = INDIO_DIRECT_MODE;
/* Nunchuk handshake */
buf[0] = 0xf0;
buf[1] = 0x55;
ret = i2c_master_send(client, buf, 2);
if (ret >= 0 && ret != 2)
return -EIO;
if (ret < 0)
return ret;
udelay(1);
buf[0] = 0xfb;
buf[1] = 0x00;
ret = i2c_master_send(client, buf, 1);
if (ret >= 0 && ret != 1)
return -EIO;
if (ret < 0)
return ret;
ret = devm_iio_device_register(&client->dev, indio_dev);
if (ret)
return ret;
return 0;
}
[ 521 ]
Industrial I/O Subsystem Chapter 11
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a nunchuk Accelerometer IIO framework I2C driver");
2. In the nunchuk_probe() function, you will get the pointers to the iio_channels of the provider
device and store them in your private structure:
/* Get pointer to channel "accel_x" of the provider device */
nunchuk->accel_x = devm_iio_channel_get(dev, "accel_x");
/* Get pointer to channel "accel_y" of the provider device */
nunchuk->accel_y = devm_iio_channel_get(dev, "accel_y");
/* Get pointer to channel "accel_z" of the provider device */
nunchuk->accel_z = devm_iio_channel_get(dev, "accel_z");
3. In the nunchuk_poll() function, you will get the IIO channel raw values from the provider
device and report the ABS_RX, ABS_RY and ABS_RZ events to the Input subsystem:
[ 522 ]
Chapter 11 Industrial I/O Subsystem
nunchuk = polled_input->private;
/* Read IIO "accel_x" channel raw value from the provider device */
iio_read_channel_raw(nunchuk->accel_x, &accel_x);
/* Read IIO "accel_y" channel raw value from the provider device */
iio_read_channel_raw(nunchuk->accel_y, &accel_y);
/* Report ABS_RY event to the input system */
input_report_abs(polled_input ->input, ABS_RY, accel_y);
/* Read IIO "accel_z" channel raw value from the provider device */
iio_read_channel_raw(nunchuk->accel_x, &accel_z);
/* Report ABS_RZ event to the input system */
input_report_abs(polled_input->input, ABS_RZ, accel_z);
input_sync(polled_input->input);
}
/*
* The poll handler function reads the hardware,
* queues events to be reported (input_report_*)
* and flushes the queued events (input_sync)
*/
static void nunchuk_poll(struct input_polled_dev *polled_input)
{
[ 523 ]
Industrial I/O Subsystem Chapter 11
/* Read IIO "accel_x" channel raw value from the provider device */
ret = iio_read_channel_raw(nunchuk->accel_x, &accel_x);
if (unlikely(ret < 0))
return;
/* Read IIO "accel_y" channel raw value from the provider device */
ret = iio_read_channel_raw(nunchuk->accel_y, &accel_y);
if (unlikely(ret < 0))
return;
/* Report ABS_RY event to the input system */
input_report_abs(polled_input ->input, ABS_RY, accel_y);
/* Read IIO "accel_z" channel raw value from the provider device */
ret = iio_read_channel_raw(nunchuk->accel_x, &accel_z);
if (unlikely(ret < 0))
return;
[ 524 ]
Chapter 11 Industrial I/O Subsystem
if (nunchuk == NULL)
return -ENOMEM;
if (!nunchuk->accel_x->indio_dev)
return -ENXIO;
if (type != IIO_ACCEL) {
dev_err(dev, "not accelerometer channel %d\n", type);
return -EINVAL;
}
if (!nunchuk->accel_y->indio_dev)
return -ENXIO;
if (type != IIO_ACCEL) {
dev_err(dev, "not accel channel %d\n", type);
return -EINVAL;
}
if (!nunchuk->accel_z->indio_dev)
return -ENXIO;
if (type != IIO_ACCEL) {
dev_err(dev, "not accel channel %d\n", type);
[ 525 ]
Industrial I/O Subsystem Chapter 11
return -EINVAL;
}
/*
* Store the polled device in the global structure
* to recover it in the remove() function
*/
nunchuk->polled_input = polled_device;
input = polled_device->input;
/*
* Set EV_ABS type events and from those
* ABS_X, ABS_Y, ABS_RX, ABS_RY and ABS_RZ event codes
*/
set_bit(EV_ABS, input->evbit);
set_bit(ABS_RX, input->absbit); /* accelerometer */
set_bit(ABS_RY, input->absbit);
set_bit(ABS_RZ, input->absbit);
/*
* Fill additional fields in the input_dev struct for
* each absolute axis nunchuk has
*/
input_set_abs_params(input, ABS_RX, 0x00, 0x3ff, 0, 0);
input_set_abs_params(input, ABS_RY, 0x00, 0x3ff, 0, 0);
input_set_abs_params(input, ABS_RZ, 0x00, 0x3ff, 0, 0);
[ 526 ]
Chapter 11 Industrial I/O Subsystem
return ret;
return 0;
}
return 0;
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a Nunchuk consumer platform driver");
[ 527 ]
Industrial I/O Subsystem Chapter 11
Now, you will remove or comment out the nunchuk node coming from the LAB 10.3 and add the
nunchuk_accel node of our provider driver to the i2c1 controller node.
You can see below in bold the Device Tree configuration for our nunchuk_accel device. The io-
channel-cells property provides the number of cells in an IIO specifier, typically 0 for nodes with
a single IIO output and 1 for nodes with multiple IIO outputs (as it is the case of our Nunchuk
provider driver).
&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins>;
clock-frequency = <100000>;
status = "okay";
nunchuk_accel: nunchuk_accel@52 {
#io-channel-cells = <1>;
compatible = "nunchuk_accel";
reg = <0x52>;
};
/* nunchuk: nunchuk@52 {
compatible = "nunchuk";
reg = <0x52>;
}; */
};
Now, add the nunchuk_consumer node of our consumer driver to the soc node. The io-channels
property provides a list of phandle and IIO specifier pairs, one pair for each IIO input to the
device. The io-channel-names property provides a list of IIO input name strings sorted in the same
order as the io-channels property. Consumer drivers will use the io-channel-names property to match
IIO input names with IIO specifiers.
&soc {
virtgpio: virtgpio {
compatible = "brcm,bcm2835-virtgpio";
gpio-controller;
#gpio-cells = <2>;
firmware = <&firmware>;
status = "okay";
};
nunchuk_consumer {
compatible = "nunchuk_consumer";
io-channels = <&nunchuk_accel 0>, <&nunchuk_accel 1>, <&nunchuk_accel2>;
io-channel-names = "accel_x", "accel_y", "accel_z";
};
};
[ 528 ]
Chapter 11 Industrial I/O Subsystem
Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb [email protected]:/boot/
[ 529 ]
Industrial I/O Subsystem Chapter 11
Input device ID: bus 0x19 vendor 0x0 product 0x0 version 0x0
Input device name: "WII accel consumer"
Supported events:
Event type 0 (EV_SYN)
Event type 3 (EV_ABS)
Event code 3 (ABS_RX)
Value 650
Min 0
Max 1023
Event code 4 (ABS_RY)
Value 500
Min 0
Max 1023
Event code 5 (ABS_RZ)
Value 582
Min 0
Max 1023
Properties:
Testing ... (interrupt to exit)
Event: time 1608593843.374146, type 3 (EV_ABS), code 3 (ABS_RX), value 650
Event: time 1608593843.374146, type 3 (EV_ABS), code 4 (ABS_RY), value 500
Event: time 1608593843.374146, type 3 (EV_ABS), code 5 (ABS_RZ), value 582
Event: time 1608593843.374146, -------------- SYN_REPORT ------------
Event: time 1608593843.493549, type 3 (EV_ABS), code 3 (ABS_RX), value 499
Event: time 1608593843.493549, type 3 (EV_ABS), code 4 (ABS_RY), value 374
Event: time 1608593843.493549, type 3 (EV_ABS), code 5 (ABS_RZ), value 421
Event: time 1608593843.493549, -------------- SYN_REPORT ------------
Event: time 1608593843.613407, type 3 (EV_ABS), code 3 (ABS_RX), value 425
Event: time 1608593843.613407, type 3 (EV_ABS), code 4 (ABS_RY), value 379
Event: time 1608593843.613407, type 3 (EV_ABS), code 5 (ABS_RZ), value 438
Event: time 1608593843.613407, -------------- SYN_REPORT ------------
Event: time 1608593843.733405, type 3 (EV_ABS), code 3 (ABS_RX), value 452
Event: time 1608593843.733405, type 3 (EV_ABS), code 4 (ABS_RY), value 340
Event: time 1608593843.733405, type 3 (EV_ABS), code 5 (ABS_RZ), value 233
Event: time 1608593843.733405, -------------- SYN_REPORT ------------
Event: time 1608593843.853410, type 3 (EV_ABS), code 3 (ABS_RX), value 158
Event: time 1608593843.853410, type 3 (EV_ABS), code 4 (ABS_RY), value 469
Event: time 1608593843.853410, type 3 (EV_ABS), code 5 (ABS_RZ), value 517
Press ^C to exit:
root@raspberrypi:/home/pi#
Remove the consumer module:
root@raspberrypi:/home/pi# rmmod nunchuk_consumer.ko
Remove the provider module:
root@raspberrypi:/home/pi# rmmod nunchuk_accel.ko
[ 530 ]
12
Using the Regmap API in Device
Drivers
As you have seen throughout the different chapters of this book, Linux has subsystems such as
I2C and SPI which are used to connect to devices that reside on these buses. Both these buses have
the common function of reading and writing registers from the devices connected to them. This
often causes redundant code to be present in the subsystems that have this register read and write
functionality.
To avoid this and to factor out common code, as well as for easy driver maintenance and
development, Linux developers introduced a new kernel API from version 3.1, which is called
regmap. This infrastructure was previously present in the Linux ASoC (ALSA) subsystem, but
has now been made available for the entire Linux through the regmap API (defined in include/
linux/regmap.h and implemented in drivers/based/regmap/ in the kernel source tree). The regmap
subsystem abstracts the access to the registers of SPI and I2C devices and also to memory-mapped
registers (MMIO). The regmap subsystem also provides a caching mechanism that can reduce the
number of accesses to the device and can handle IRQ chips and IRQs.
So far, you have developed several I2C and SPI device drivers by using the specific core APIs
implemented for each of these buses. Now, you will use the regmap API to do so. The regmap
subsystem takes care of calling the relevant calls of the SPI or I2C subsystem.
The regmap subsystem mainly includes struct regmap (declared in drivers/base/regmap/internal.h) ,
struct regmap_bus (declared in include/linux/regmap.h) and struct regmap_config (declared in include/
linux/regmap.h). The regmap structure represents the mapping of the register operation of a slow i/o
device, and the regmap_bus structure represents the register operation of a type of slow i/o device
(for example, an SPI or an I2C device). The regmap_bus structure is bound to the regmap structure.
The regmap_config structure is a per device and bus configuration structure used by the regmap
subsystem to communicate with the device. It is defined by the driver code and contains all the
information related to the registers of the device.
[ 531 ]
Using the Regmap API in Device Drivers Chapter 12
A device that can be accessed using an SPI or an I2C bus, such as the ADXL345 accelerometer, is
a good candidate to use the regmap API. For this ADXL345 device, you can develop two simple
drivers using the regmap API, one for I2C bus support (adxl345-i2c.c) and another for SPI bus
support (adxl345-spi.c), writing specific code for each bus. This specific code configures the bus
using a regmap_config structure and initializes struct regmap using the functions devm_regmap_
init_spi() or devm_regmap_init_i2c().
The devm_regmap_init_spi() function initializes a regmap data structure, taking as a first parameter
an spi_device structure and as a second parameter a regmap_config structure (defined in our driver
code) which has been configured to access to the SPI registers of the device.
struct regmap * devm_regmap_init_spi(struct spi_device *spi, const struct regmap_config);
The devm_regmap_init_i2c() function initializes a regmap data structure, taking as a first parameter
an i2c_client structure and as a second parameter a regmap_config structure (defined in our driver
code) which has been configured to access to the I2C registers of the device.
struct regmap * devm_regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config);
The devm_regmap_init_spi() function, for example, will initialize struct regmap with the regmap_spi
structure, which has been initialized by the regmap subsystem with specific functions that will call
to the corresponding functions of the SPI subsystem:
static const struct regmap_bus regmap_spi = {
.write = regmap_spi_write,
.gather_write = regmap_spi_gather_write,
.async_write = regmap_spi_async_write,
.async_alloc = regmap_spi_async_alloc,
.read = regmap_spi_read,
.read_flag_mask = 0x80,
.reg_format_endian_default = REGMAP_ENDIAN_BIG,
.val_format_endian_default = REGMAP_ENDIAN_BIG,
};
The regmap_spi_write() function will call the spi_write() function, which in turn calls spi_sync_
transfer(). You can see below the code of the regmap_spi_write() and spi_write() functions:
static int regmap_spi_write(void *context, const void *data, size_t count)
{
struct device *dev = context;
struct spi_device *spi = to_spi_device(dev);
static inline int spi_write(struct spi_device *spi, const void *buf, size_t len)
{
struct spi_transfer t = {
.tx_buf = buf,
[ 532 ]
Chapter 12 Using the Regmap API in Device Drivers
.len = len,
};
In the two previous regmap initialization routines, the regmap_config configuration is taken, then
the regmap structure is allocated, and the configuration is copied to it. The read/write functions of
the respective buses are also copied in the regmap structure.
See below the main lines of code of the proposed adxl345-i2c.c driver for the I2C register map
configuration and initialization:
static const struct regmap_config adxl345_i2c_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
};
See below the main lines of code of the proposed adxl345-spi.c driver for the SPI register map
configuration and initialization:
static const struct regmap_config adxl345_spi_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
/* Setting bits 7 and 6 enables multiple-byte read */
.read_flag_mask = BIT(7) | BIT(6),
};
After you implement the regmap configuration and initialization using two specific SPI and I2C
drivers, you should develop a common core driver (adxl345-accel-core.c) that can talk to the device
using the following functions:
int regmap_write(struct regmap *map, unsigned int reg, unsigned int val);
int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val);
int regmap_update_bits(struct regmap *map, unsigned int reg,
[ 533 ]
Using the Regmap API in Device Drivers Chapter 12
As you have seen before, the regmap_config structure is the configuration for the register map of a
device. Its most important fields are described below:
• reg_bits: This is the number of bits in the registers of the device, e.g., in case of 1 byte
registers it will be set to the value 8.
• val_bits: This is the number of bits in the value that will be set in the device register.
• writeable_reg: This is an optional callback function written in driver code, which is called
whenever a register is to be written. Whenever the driver calls the regmap subsystem to
write to a register, this driver function is called; it will return "false" if this register is not
writeable and the write operation will return an error to the driver.
• wr_table: If the driver does not provide the writeable_reg callback, then wr_table is checked
by regmap before doing the write operation. If the register address lies in the range
provided by the wr_table, then the write operation is performed. This is also optional, and
the driver can omit its definition and can set it to NULL.
• readable_reg: This is an optional callback function written in driver code, which is called
whenever a register is to be read. Whenever the driver calls the regmap subsystem to
read a register, this driver function is called to ensure the register is readable. The driver
function will return "false" if this register is not readable, and the read operation will
return an error to the driver.
• rd_table: If a driver does not provide a readable_reg callback, then the rd_table is checked
by regmap before doing the read operation. If the register address lies in the range
provided by rd_table, then the read operation is performed. This is also optional, and the
driver can omit its definition and can set it to NULL.
• reg_read: Optional callback that if filled will be used to perform all the reads from
the registers. Should only be provided for devices whose read operation cannot be
represented as a simple read operation on a bus such as SPI, I2C, etc. Most of the devices
do not need this.
• reg_write: Same as above for writing.
• volatile_reg: This is a callback function called whenever a register is written or read
through the cache. Whenever a driver reads or writes a register through the regmap cache,
this function is called first, and if it returns "false" only then is the cache method used; else,
the registers are written or read directly, since the register is volatile and caching is not to
be used.
[ 534 ]
Chapter 12 Using the Regmap API in Device Drivers
• volatile_table: If a driver does not provide a volatile_reg callback, then the volatile_table
is checked by regmap to see if the register is volatile or not. If the register address lies in
the range provided by the volatile_table then the cache operation is not used. This is also
optional, and the driver can omit its definition and can set it to NULL.
• max_register: Whenever any read or write operation is to be performed, regmap checks
whether the register address is less than max_register first, and only if it is, is the operation
performed. The max_register is ignored if it is set to 0.
• read_flag_mask: Normally, in SPI or I2C, a write or read transaction will have the highest
bit set in the top byte to differentiate write and read operations. This mask is set in the
higher byte of the register value.
• write_flag_mask: This mask is also set in the higher byte of the register value. If both
read_flag_mask and write_flag_mask are empty, the regmap_bus default masks are used.
The regmap infrastructure also provides APIs that are declared in include/linux/regmap.h and
defined under drivers/base/regmap/. The following are the details of the regmap_write and the
regmap_read APIs, taken from the very informative opensource article located at
https://opensourceforu.com/2017/01/regmap-reducing-redundancy-linux-code/.
1. regmap_write: This function is used to write data to the device. It takes in the regmap
structure returned during initialization and registers the address and the value to be set.
The following are the steps performed by the regmap_write routine:
• First, regmap_write takes the lock, which will be spinlock if fast_io in regmap_config was
set; else, it will be mutex.
• Next, if max_register is set in regmap_config, the function checks the register address
to be written. If the register address is less than or equal to max_register, the write
operation will be executed; otherwise, the regmap core will return an invalid I/O error.
• After that, if the writeable_reg callback is set in regmap_config, then that callback is
called. If that callback returns "true", then further operations are done; if it returns
"false", then an error -EIO is returned.
• If writeable_reg is not set, but wr_table is set, then there’s a check on whether the
register address lies in no_ranges, in which case an -EIO error is returned; else, it is
checked whether it lies in the yes_ranges. If it is not present there, then an -EIO error
is returned and the operation is terminated. If it lies in the yes_ranges, then further
operations are performed. This step is only performed if wr_table is set; else, it is
skipped.
[ 535 ]
Using the Regmap API in Device Drivers Chapter 12
• Whether caching is permitted is now checked. If it is permitted, then the register value
is cached instead of writing directly to hardware, and the operation finishes at this
step. If caching is not permitted, it goes to the next step.
• After the above steps are taken, the hardware write routine (for example, regmap_spi_
write()) is called to write the value in the hardware register. This function writes the
write_flag_mask to the first byte of the value, and the value is written to the device.
• After completing the write operation, the lock that was taken before writing is
released, and the function returns.
2. regmap_read: This function is used to read data from the device. It takes in the regmap
structure returned during initialization and registers the address and a pointer to the
variable in which the data is to be read. The following are the steps performed by the
regmap_read routine:
• First, the read function will take a lock before performing the read operation. This will
be a spinlock if fast_io is set in regmap_config; else, regmap will use mutex.
• Next, it will check whether the passed register address is less than max_register; if it is
not, then -EIO is returned. This step is only done if max_register is set greater than zero.
• Then, it will check if the readable_reg callback is set. If it is, then that callback is called,
and if this callback returns "false", the read operation is terminated returning an -EIO
error. If this callback returns "true", then further operations are performed. This step is
only performed if readable_reg is set.
• What is checked next is whether the register address lies in the no_ranges of the
rd_table in config. If it does, then an –EIO error is returned. If it doesn’t lie either in
the no_ranges or in the yes_ranges, then too an -EIO error is returned. Only if it lies in
the yes_ranges can further operations be performed. This step is only performed if the
rd_table is set.
• Now, if caching is permitted, then the register value is read from the cache and the
function returns the value being read. If caching is set to bypass, then the next step is
performed.
• After the above steps have been taken, the hardware read operation (for example,
regmap_spi_read()) is called to read the register value, and the value of the variable
which was passed is updated with the value returned.
• The lock that was taken before starting this operation is now released, and the function
returns.
[ 536 ]
Chapter 12 Using the Regmap API in Device Drivers
[ 537 ]
Using the Regmap API in Device Drivers Chapter 12
3. Create the rest of #define to perform operations in the ADXL345 registers and to pass some
of them as arguments to several functions of the driver:
#define ADXL345_GPIO_NAME "int"
/* DEVIDs */
#define ID_ADXL345 0xE5
/* INT_ENABLE/INT_MAP/INT_SOURCE Bits */
#define SINGLE_TAP (1 << 6)
#define WATERMARK (1 << 1)
/* TAP_AXES Bits */
#define TAP_X_EN (1 << 2)
#define TAP_Y_EN (1 << 1)
#define TAP_Z_EN (1 << 0)
/* BW_RATE Bits */
#define LOW_POWER (1 << 4)
#define RATE(x) ((x) & 0xF)
/* POWER_CTL Bits */
#define PCTL_MEASURE (1 << 3)
#define PCTL_STANDBY 0X00
/* DATA_FORMAT Bits */
#define ADXL_FULL_RES (1 << 3)
/* FIFO_CTL Bits */
#define FIFO_MODE(x) (((x) & 0x3) << 6)
#define FIFO_BYPASS 0
#define FIFO_FIFO 1
#define FIFO_STREAM 2
#define SAMPLES(x) ((x) & 0x1F)
/* FIFO_STATUS Bits */
#define ADXL_X_AXIS 0
#define ADXL_Y_AXIS 1
#define ADXL_Z_AXIS 2
[ 538 ]
Chapter 12 Using the Regmap API in Device Drivers
5. Create the iio_chan_spec and iio_event_spec structures to expose to user space the channel
and event sysfs attributes. The scan_index variable defines the order in which the enabled
channels are placed inside the IIO trigger buffer. The channels with a lower scan_index will
be placed before the channels with a higher index. Each channel needs to have a unique
scan_index.
/*
* Each axis will have two event sysfs attributes
* You will set THRESH_TAP register value associated to the specific axis
* writing to the sysfs attribute with bitmask IIO_EV_INFO_VALUE
* You will modify DUR register associated to the specific axis writing to the
* sysfs attribute with bitmask IIO_EV_INFO_PERIOD
* The THRESH_TAP and DUR registers are shared for all the axis so it
* could have had more sense to use mask_shared_by_type instead mask_separate
*/
static const struct iio_event_spec adxl345_event = {
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_EITHER,
.mask_separate = BIT(IIO_EV_INFO_VALUE) |
BIT(IIO_EV_INFO_PERIOD)
};
/*
* Each axis will have is own channel sysfs attribute and there are two shared
* sysfs attributes for the IIO_ACCEL type
* You will get each axis value reading each channel sysfs attribute with
[ 539 ]
Using the Regmap API in Device Drivers Chapter 12
* bitmask IIO_CHAN_INFO_RAW
* There is a shared attribute to read the scale value with bitmask
* IIO_CHAN_INFO_SCALE
* There is a shared attribute to write the accel data rate with bitmask
* IIO_CHAN_INFO_SAMP_FREQ
*/
#define ADXL345_CHANNEL(reg, axis, idx) { \
.type = IIO_ACCEL, \
.modified = 1, \
.channel2 = IIO_MOD_##axis, \
.address = reg, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
.scan_index = idx, \
.scan_type = { \
.sign = 's', \
.realbits = 13, \
.storagebits = 16, \
.endianness = IIO_LE, \
}, \
.event_spec = &adxl345_event, \
.num_event_specs = 1 \
}
6. Create the iio_info structure to declare the hooks that the IIO core will use for this device.
There are four hooks available, corresponding to user space interactions through the channel
and event sysfs attributes.
static const struct iio_info adxl345_info = {
.driver_module = THIS_MODULE,
.read_raw = adxl345_read_raw,
.write_raw = adxl345_write_raw,
.read_event_value = adxl345_read_event,
.write_event_value = adxl345_write_event,
};
See below a brief description of each of these callback functions:
• adxl345_read_raw: This function returns an axis value when a channel sysfs attribute with
bitmask IIO_CHAN_INFO_RAW is read from user space. It also returns the accelerometer
scale when the shared sysfs attribute with bitmask IIO_CHAN_INFO_SCALE is read from
user space. You can see in the code below the regmap_bulk_read() function, which is used to
access via SPI to the two registers of each axis.
[ 540 ]
Chapter 12 Using the Regmap API in Device Drivers
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
*val = 0;
*val2 = adxl345_uscale;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
• adxl345_write_raw: This function sets the data rate and power mode control of the
ADXL345 device (BW_RATE register) whenever user space writes to the shared sysfs
attribute with bitmask IIO_CHAN_INFO_SAMP_FREQ. You can see in the following code
snippet the regmap_write() function used to access via SPI to the register BW_RATE of the
ADXL345 device.
static int adxl345_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct adxl345_data *data = iio_priv(indio_dev);
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
data->data_rate = RATE(val);
return regmap_write(data->regmap, BW_RATE, data->data_rate |
(data->low_power_mode ? LOW_POWER : 0));
default :
return -EINVAL;
}
}
• adxl345_read_event: This function returns the value of the THRESH_TAP register or the
DUR register whenever a sysfs attribute with bitmask IIO_EV_INFO_VALUE or IIO_EV_
INFO_PERIOD is read from user space.
[ 541 ]
Using the Regmap API in Device Drivers Chapter 12
switch (info) {
case IIO_EV_INFO_VALUE:
*val = data->tap_threshold;
break;
case IIO_EV_INFO_PERIOD:
*val = data->tap_duration;
break;
default:
return -EINVAL;
}
return IIO_VAL_INT;
}
• adxl345_write_event: This function sets the value of the THRESH_TAP register or the
DUR register whenever a sysfs attribute with bitmask IIO_EV_INFO_VALUE or IIO_EV_
INFO_PERIOD is written from user space.
static int adxl345_write_event(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir,
enum iio_event_info info,
int val, int val2)
{
struct adxl345_data *data = iio_priv(indio_dev);
switch (info) {
case IIO_EV_INFO_VALUE:
data->tap_threshold = val;
return regmap_write(data->regmap, THRESH_TAP,
data->tap_threshold);
case IIO_EV_INFO_PERIOD:
data->tap_duration = val;
return regmap_write(data->regmap, DUR, data->tap_duration);
default:
return -EINVAL;
}
}
7. See below in bold the main lines of code for the SPI register map configuration and
initialization:
[ 542 ]
Chapter 12 Using the Regmap API in Device Drivers
8. In the adxl345_core_probe() routine request a threaded interrupt. You will add a threaded
interrupt to the driver to service the single tap interrupt. In a threaded interrupt, the
adxl345_event_handler interrupt handler is executed inside a thread. It is allowed to block
during the interrupt handler, which is often needed for SPI devices, as the interrupt
handler needs to communicate with them. In the interrupt handler, you will communicate
via SPI with the ADXL345 device by using the regmap_read() function. The SINGLE_TAP
events will be sent to user space by using the iio_push_event() function.
/* Request threaded interrupt */
devm_request_threaded_irq(dev, data->irq, NULL, adxl345_event_handler,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT, dev_name(dev), indio_dev);
/* Interrupt service routine */
static irqreturn_t adxl345_event_handler(int irq, void *handle)
{
u32 tap_stat, int_stat;
struct iio_dev *indio_dev = handle;
struct adxl345_data *data = iio_priv(indio_dev);
data->timestamp = iio_get_time_ns(indio_dev);
if (data->tap_axis_control & (TAP_X_EN | TAP_Y_EN | TAP_Z_EN)) {
regmap_read(data->regmap, ACT_TAP_STATUS, &tap_stat);
}
else
tap_stat = 0;
/*
* Read the INT_SOURCE (0x30) register
* The tap interrupt is cleared
*/
[ 543 ]
Using the Regmap API in Device Drivers Chapter 12
/*
* if the SINGLE_TAP event has occurred the axl345_do_tap function
* is called with the ACT_TAP_STATUS register as an argument
*/
if (int_stat & (SINGLE_TAP)) {
dev_info(data->dev, "single tap interrupt has occurred\n");
return IRQ_HANDLED;
}
[ 544 ]
Chapter 12 Using the Regmap API in Device Drivers
context. The most common operation is the recording of the current timestamp, and for
this reason, you can use the iio_pollfunc_store_time() function. Before calling devm_iio_
triggered_buffer_setup(), the indio_dev structure should already be completely initialized but
not registered yet. In practice, this means that this function should be called right before
devm_iio_device_register().
int adxl345_core_probe(struct device *dev, struct regmap *regmap, const char *name)
{
struct iio_dev *indio_dev;
struct adxl345_data *data;
[...]
/* iio_pollfunc_store_time do pf->timestamp = iio_get_time_ns(); */
devm_iio_triggered_buffer_setup(dev, indio_dev,
&iio_pollfunc_store_time,
adxl345_trigger_handler, NULL);
devm_iio_device_register(dev, indio_dev);
return 0;
}
/* Read the channels that have been enabled from user space */
for_each_set_bit(i, indio_dev->active_scan_mask, indio_dev->masklength) {
ret = regmap_bulk_read(data->regmap, base + i * sizeof(sample),
&sample, sizeof(sample));
if (ret < 0)
goto done;
buf[j++] = sample;
}
iio_push_to_buffers_with_timestamp(indio_dev, buf, pf->timestamp);
done:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
[ 545 ]
Using the Regmap API in Device Drivers Chapter 12
12. Add an spi_driver structure that will be registered to the SPI bus:
static struct spi_driver adxl345_driver = {
.driver = {
.name = "adxl345",
.owner = THIS_MODULE,
.of_match_table = adxl345_dt_ids,
},
.probe = adxl345_spi_probe,
.remove = adxl345_spi_remove,
.id_table = adxl345_id,
};
14. Create a new adxl345_rpi3_iio.c file in the linux_5.4_rpi3_drivers folder, and add adxl345_rpi3_
iio.o to your Makefile obj-m variable, then build and deploy the module to the Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
[ 546 ]
Chapter 12 Using the Regmap API in Device Drivers
enum adxl345_accel_axis {
AXIS_X,
AXIS_Y,
AXIS_Z,
AXIS_MAX,
};
/* DEVIDs */
#define ID_ADXL345 0xE5
/* INT_ENABLE/INT_MAP/INT_SOURCE Bits */
#define SINGLE_TAP (1 << 6)
#define WATERMARK (1 << 1)
/* TAP_AXES Bits */
#define TAP_X_EN (1 << 2)
#define TAP_Y_EN (1 << 1)
#define TAP_Z_EN (1 << 0)
[ 547 ]
Using the Regmap API in Device Drivers Chapter 12
/* BW_RATE Bits */
#define LOW_POWER (1 << 4)
#define RATE(x) ((x) & 0xF)
/* POWER_CTL Bits */
#define PCTL_MEASURE (1 << 3)
#define PCTL_STANDBY 0X00
/* DATA_FORMAT Bits */
#define ADXL_FULL_RES (1 << 3)
/* FIFO_CTL Bits */
#define FIFO_MODE(x) (((x) & 0x3) << 6)
#define FIFO_BYPASS 0
#define FIFO_FIFO 1
#define FIFO_STREAM 2
#define SAMPLES(x) ((x) & 0x1F)
/* FIFO_STATUS Bits */
#define ADXL_X_AXIS 0
#define ADXL_Y_AXIS 1
#define ADXL_Z_AXIS 2
struct axis_triple {
int x;
int y;
int z;
};
struct adxl345_data {
struct gpio_desc *gpio;
struct regmap *regmap;
struct iio_trigger *trig;
struct device *dev;
struct axis_triple saved;
u8 data_range;
u8 tap_threshold;
u8 tap_duration;
u8 tap_axis_control;
u8 data_rate;
u8 fifo_mode;
u8 watermark;
u8 low_power_mode;
int irq;
int ev_enable;
u32 int_mask;
[ 548 ]
Chapter 12 Using the Regmap API in Device Drivers
s64 timestamp;
};
switch (mask) {
case IIO_CHAN_INFO_RAW: /* Add an entry in the sysfs */
/*
* Data is stored in adjacent registers:
* ADXL345_REG_DATA(X0/Y0/Z0) contain the least significant byte
* and ADXL345_REG_DATA(X0/Y0/Z0) + 1 the most significant byte.
* We are reading 2 bytes and storing in a __le16
*/
[ 549 ]
Using the Regmap API in Device Drivers Chapter 12
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
data->data_rate = RATE(val);
return regmap_write(data->regmap, BW_RATE,
data->data_rate | (data->low_power_mode ? LOW_POWER : 0));
default :
return -EINVAL;
}
}
switch (info) {
case IIO_EV_INFO_VALUE:
*val = data->tap_threshold;
break;
case IIO_EV_INFO_PERIOD:
*val = data->tap_duration;
break;
default:
return -EINVAL;
}
[ 550 ]
Chapter 12 Using the Regmap API in Device Drivers
return IIO_VAL_INT;
}
switch (info) {
case IIO_EV_INFO_VALUE:
data->tap_threshold = val;
return regmap_write(data->regmap, THRESH_TAP, data->tap_threshold);
case IIO_EV_INFO_PERIOD:
data->tap_duration = val;
return regmap_write(data->regmap, DUR, data->tap_duration);
default:
return -EINVAL;
}
}
data->timestamp = iio_get_time_ns(indio_dev);
[ 551 ]
Using the Regmap API in Device Drivers Chapter 12
/*
* ACT_TAP_STATUS should be read before clearing the interrupt
* Avoid reading ACT_TAP_STATUS in case TAP detection is disabled
* Read the ACT_TAP_STATUS if any of the axis has been enabled
*/
if (data->tap_axis_control & (TAP_X_EN | TAP_Y_EN | TAP_Z_EN)) {
ret = regmap_read(data->regmap, ACT_TAP_STATUS, &tap_stat);
if (ret) {
dev_err(data->dev, "error reading ACT_TAP_STATUS register\n");
return ret;
}
}
else
tap_stat = 0;
/*
* Read the INT_SOURCE (0x30) register
* to clear the tap interrupt
*/
ret = regmap_read(data->regmap, INT_SOURCE, &int_stat);
if (ret) {
dev_err(data->dev, "error reading INT_SOURCE register\n");
return ret;
}
/*
* If the SINGLE_TAP event has occurred, the axl345_do_tap function
* is called with the ACT_TAP_STATUS register as an argument
*/
if (int_stat & (SINGLE_TAP)) {
dev_info(data->dev, "single tap interrupt has occurred\n");
[ 552 ]
Chapter 12 Using the Regmap API in Device Drivers
0,
IIO_MOD_Z,
IIO_EV_TYPE_THRESH,
0),
data->timestamp);
}
}
return IRQ_HANDLED;
}
/* Read the channels that have been enabled from user space */
for_each_set_bit(i, indio_dev->active_scan_mask, indio_dev->masklength) {
ret = regmap_bulk_read(data->regmap, base + i * sizeof(sample),
&sample, sizeof(sample));
if (ret < 0)
goto done;
buf[j++] = sample;
}
done:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
int adxl345_core_probe(struct device *dev, struct regmap *regmap, const char *name)
{
struct iio_dev *indio_dev;
struct adxl345_data *data;
u32 regval;
int ret;
[ 553 ]
Using the Regmap API in Device Drivers Chapter 12
data->irq = gpiod_to_irq(data->gpio);
if (data->irq < 0)
return data->irq;
dev_info(dev, "The IRQ number is: %d\n", data->irq);
indio_dev->dev.parent = dev;
indio_dev->name = name;
indio_dev->info = &adxl345_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->available_scan_masks = adxl345_accel_scan_masks;
indio_dev->channels = adxl345_channels;
indio_dev->num_channels = ARRAY_SIZE(adxl345_channels);
/* Initialize the ADXL345 registers */
/* 13-bit full resolution right justified */
ret = regmap_write(data->regmap, DATA_FORMAT, data->data_range);
if (ret < 0)
goto error_standby;
[ 554 ]
Chapter 12 Using the Regmap API in Device Drivers
/*
* Set the data rate and the axis reading power
* mode (higher noise less power).
* In the initial settings is NO low power
*/
ret = regmap_write(data->regmap, BW_RATE,
RATE(data->data_rate) | (data->low_power_mode ? LOW_POWER : 0));
if (ret < 0)
goto error_standby;
/* Enables interrupts */
if (data->tap_axis_control & (TAP_X_EN | TAP_Y_EN | TAP_Z_EN))
data->int_mask |= SINGLE_TAP;
[ 555 ]
Using the Regmap API in Device Drivers Chapter 12
return 0;
error_standby:
dev_info(dev, "set standby mode due to an error\n");
regmap_write(data->regmap, POWER_CTL, PCTL_STANDBY);
return ret;
}
[ 556 ]
Chapter 12 Using the Regmap API in Device Drivers
};
MODULE_DEVICE_TABLE(spi, adxl345_id);
module_spi_driver(adxl345_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("ADXL345 Three-Axis Accelerometer Regmap SPI Bus Driver");
[ 557 ]
Using the Regmap API in Device Drivers Chapter 12
adxl345_rpi3_iio.ko demonstration
In the Linux host build the IIO tools. Open the Makefile under ~/linux_rpi3/linux/tools/iio/
folder, and add the following lines in bold:
~/linux_rpi3/linux/tools/iio$ ls
Build iio_generic_buffer.c iio_utils.h Makefile
iio_event_monitor.c iio_utils.c lsiio.c
~/linux_rpi3/linux/tools/iio$ make
root@raspberrypi:/home/pi# cd /sys/bus/iio/devices/iio:device0
root@raspberrypi:/sys/bus/iio/devices/iio:device0# ls
buffer in_accel_x_raw scan_elements
current_timestamp_clock in_accel_y_raw subsystem
dev in_accel_z_raw trigger
events name uevent
in_accel_sampling_frequency of_node
in_accel_scale power
[ 558 ]
Chapter 12 Using the Regmap API in Device Drivers
root@raspberrypi:/sys/bus/iio/devices/iio:device0#
adxl345 spi0.0: single tap interrupt has occurred
adxl345 spi0.0: single tap interrupt has occurred
adxl345 spi0.0: single tap interrupt has occurred
Read the values of the adxl345 axes using a sysfs trigger interface.
root@raspberrypi:/sys/bus/iio/devices/iio:device0# cd /sys/bus/iio/devices/
root@raspberrypi:/sys/bus/iio/devices# ls
iio:device0 iio_sysfs_trigger
root@raspberrypi:/sys/bus/iio/devices# ls
iio:device0 iio_sysfs_trigger trigger0
Set the number of sample sets that may be held by the buffer:
root@raspberrypi:/sys/bus/iio/devices#
[ 559 ]
Using the Regmap API in Device Drivers Chapter 12
root@raspberrypi:/sys/bus/iio/devices# cd /home/pi
root@raspberrypi:/home/pi# rmmod adxl345_rpi3_iio.ko
adxl345 spi0.0: my_remove() function is called.
root@raspberrypi:/home/pi# reboot
Use the iio_generic_buffer application to set the buffer length, the number of adquisitions
and to enable the scan elements.
[ 560 ]
Chapter 12 Using the Regmap API in Device Drivers
root@raspberrypi:/home/pi#
root@raspberrypi:/sys/bus/iio/devices# cd /home/pi
root@raspberrypi:/home/pi# rmmod adxl345_rpi3_iio.ko
root@raspberrypi:/home/pi# reboot
Use the iio_generic_buffer application to set the buffer length and number of conversions;
the application enables the scan elements, does the adquisitions and shows them; after that,
disables the scan elements:
[ 561 ]
Using the Regmap API in Device Drivers Chapter 12
Enabling: in_accel_x_en
Enabling: in_timestamp_en
Enabling: in_accel_z_en
/sys/bus/iio/devices/iio:device0 trigger0
-0.957500 8.579200 1.608600 1619800613157990969
-0.957500 8.579200 1.608600 1619800613177988625
root@raspberrypi:/home/pi# -0.995800 8.655800 1.723500 1619800613197988834
-0.957500 8.694100 1.685200 1619800613217988938
-0.957500 8.694100 1.685200 1619800613237987844
-0.995800 8.847300 1.570300 1619800613257987896
-0.995800 8.847300 1.570300 1619800613277987844
-1.034100 8.847300 1.532000 1619800613297987740
-1.034100 8.847300 1.532000 1619800613317987740
-0.957500 8.962200 1.570300 1619800613337987792
Disabling: in_accel_y_en
Disabling: in_accel_x_en
Disabling: in_timestamp_en
Disabling: in_accel_z_en
root@raspberrypi:/home/pi#
Execute the iio_event_monitor application, and move the accel board until you see several IIO
events:
root@raspberrypi:/home/pi#
[ 562 ]
13
USB Device Drivers
USB (abbreviation of Universal Serial Bus) was designed as a low cost, serial interface solution
with bus power provided from the USB host to support a wide range of peripheral devices. The
original bus speeds for USB were low speed of 1.5 Mbps, followed by full speed of 12 Mbps, and
then high speed at 480 Mbps. With the advent of the USB 3.0 specification, the super speed was
defined at 4.8 Gbps. Maximum data throughput, i.e. the line rate minus overhead is approximately
384 Kbps, 9.728 Mbps, and 425.984 Mbps for low, full and high speed respectively. Note that this
is the maximum data throughput and it can be adversely affected by a variety of factors, including
software processing, other USB bandwidth utilization on the same bus, etc.
One of the biggest advantages of USB is that it supports dynamic attachment and removal, which
is a type of interface referred to as "plug and play". Following attachment of a USB peripheral
device, the host and the device communicate to automatically advance the externally visible device
state from the attached state through powered, default, addressed and finally to the configured
states. Additionally, all devices must conform to the suspend state in which a very low bus power
consumption specification must be met. Power conservation in the suspended state is another USB
benefit.
Throughout this chapter, we will focus on the USB 2.0 specification, which includes low, full and
high speed device specifications. Compliance to USB 2.0 specification for peripheral devices, does
not necessarily indicate that the device is a high speed device, however a hub advertised as USB
2.0 compliant, must be high speed capable. A USB 2.0 device can be High Speed, Full Speed or
Low Speed.
[ 563 ]
USB Device Drivers Chapter 13
The physical USB interconnection is accomplished for all USB 2.0 (up to high speed) devices via
a simple 4-wire interface with bi-directional differential data (D+ and D-), power (VBUS) and
ground. The VBUS power is nominally +5V. An "A-type" connector and mating plug are used for
all host ports as well as downstream facing hub ports. A "B-type" connector and mating plug are
used for all peripheral devices as well as the upstream facing port of a hub. Cable connections
between host, hubs and devices can each be a maximum of 5 meters or ~16 feet. With the
maximum of 7 tiers, cabling connections can be up to 30 meters or ~ 98 feet total.
[ 564 ]
Chapter 13 USB Device Drivers
An attached device is recognized by the host and its speed (low, full or high) is identified via a
signaling mechanism using the D+/D- USB data pair. When a new USB device is connected to the
bus through a hub, the device enumeration process starts. Each hub provides an IN endpoint,
which is used to inform the host about newly attached devices. The host continually polls on this
endpoint to receive device attachment and removal events from the hub. Once a new device was
attached, and the hub notified the host about this event, the USB bus driver of the host enables the
attached device and starts requesting information from the device. This is done with standard USB
requests which are sent through the default control pipe to endpoint zero of the device.
Information is requested in terms of descriptors. USB descriptors are data structures that are
provided by devices to describe all of their attributes. This includes, e.g., the product/vendor
ID, any device class affiliation and strings describing the product and vendor. Additionally,
information about all available endpoints is provided. After the host reads all the necessary
information from the device, it tries to find a matching device driver. The details of this process are
dependant on the used operating system. After the first descriptors were read from the attached
USB device, the host uses the vendor and product ID from the device descriptor to find a matching
device driver.
The attached device will initially utilize the default USB address of 0. Additionally, all USB
devices are comprised of a number of independent endpoints, which provide a terminus for
communication flow between the host and device.
Endpoints can be categorized into control and data endpoints. Every USB device must provide at
least one control endpoint at address 0 called the default endpoint or Endpoint0. This endpoint
is bidirectional, that is, the host can send data to the endpoint and receive data from it within
one transfer. The purpose of a control transfer is to enable the host to obtain device information,
configure the device, or perform control operations that are unique to the device.
The endpoint is a buffer that typically consists of a block of memory or registers which stores
received data or contain data which is ready to transmit. Each endpoint is assigned a unique
endpoint number determined at design time, however, all devices must support the default control
endpoint (ep0) which is assigned number 0 and may transfer data in both directions. All other
endpoints may transfer data in one direction (always from the host perspective), labeled "Out", i.e.,
data from the host, or "In", i.e., data to the host. The endpoint number is a 4-bit integer associated
with an endpoint (0-15); the same endpoint number is used to describe two endpoints, for
example, EP 1 IN and EP 1 OUT. An endpoint address is the combination of an endpoint number
and an endpoint direction, for example, EP 1 IN, EP 1 OUT and EP 3 IN. The endpoint addresses
are encoded with the direction and number in a single byte, where the direction is the MSB (1=IN,
0=OUT) and the number is the lower four bits. For example:
• EP 1 IN = 0x81
[ 565 ]
USB Device Drivers Chapter 13
• EP 1 OUT = 0x01
• EP 3 IN = 0x83
• EP 3 OUT = 0x03
A USB configuration defines the capabilities and features of a device, mainly its power capabilities
and interfaces. The device can have multiple configurations, but only one is active at a time. A
configuration can have one or more USB interfaces that define the functionality of the device.
Typically, there is a one-to-one correlation between a function and an interface. However,
certain devices expose multiple interfaces related to one function. For example, a USB device
that comprises a keyboard with a built-in speaker will offer an interface for playing audio and
an interface for key presses. In addition, the interface contains alternate settings that define the
bandwidth requirements of the function associated with the interface. Each interface contains one
or more endpoints, which are used to transfer data to and from the device. To sum up, a group of
endpoints, form an interface, and a set of interfaces constitutes a configuration in the device. The
following image shows a multiple-interfaces USB device:
After a matched device driver was loaded, it’s the task of the device driver to select one of the provided
device configurations, one or more interfaces within that configuration, and an alternate setting for each
interface. Most USB devices don’t provide multiple interfaces or multiple alternate settings. The device
driver selects one of the configurations based on its own capabilities and the available bandwidth on
[ 566 ]
Chapter 13 USB Device Drivers
the bus and activates this configuration on the attached device. At this point, all interfaces and their
endpoints of the selected configuration are set up and the device is ready for use.
Communication from the host to each device endpoint uses a communication "pipe" which is
established during enumeration. The pipe is a logical association between the host and the device.
The pipe is purely a software term. A pipe talks to an endpoint on a device, and that endpoint has
an address. The other end of a pipe is always the host controller. A pipe for an endpoint is opened
when the device is configured either by selecting a configuration and an interface’s alternate
setting. Therefore they become targets for I/O operations. A pipe has all the properties of an
endpoint, but it is active and is used to communicate with the host. The combination of the device
address, endpoint number and direction allows the host to uniquely reference each endpoint.
[ 567 ]
USB Device Drivers Chapter 13
the rate received to maintain its timing and additionally may be sensitive to delivery
delays. A typical use for isochronous transfers would be for streaming audio or video.
[ 568 ]
Chapter 13 USB Device Drivers
USB descriptors
The host software obtains descriptors from an attached device by sending various standard control
requests to the default endpoint during enumeration, immediately upon a device being attached.
Those requests specify the type of descriptor to retrieve. In response to such requests, the device
sends descriptors that include information about the device, its configurations, interfaces and the
related endpoints. Device descriptors contain information about the whole device.
Every USB device exposes a device descriptor that indicates the device’s class information,
vendor and product identifiers and number of configurations. Each configuration exposes it’s
configuration descriptor that indicates the number of interfaces and power characteristics. Each
interface exposes an interface descriptor for each of its alternate settings that contains information
about the class and the number of endpoints. Each endpoint within each interface exposes
endpoint descriptors that indicate the endpoint type and the maximum packet size.
Descriptors begin with a byte describing the descriptor length in bytes. This length equals the total
number of bytes in the descriptor, including the byte storing the length. The next byte indicates
the descriptor type, which allows the host to correctly interpret the rest of the bytes contained in
the descriptor. The content and values of the rest of the bytes are specific to the type of descriptor
being transmitted. The descriptor structure must follow specifications exactly; the host will ignore
received descriptors containing errors in size or value, potentially causing enumeration to fail and
prohibiting further communication between the device and the host.
[ 569 ]
USB Device Drivers Chapter 13
} USB_DEVICE_DESCRIPTOR
The first item blength describes the descriptor length and should be common to all USB device
descriptors.
The item bDescriptorType is the constant one-byte designator for device descriptors and should
be common to all device descriptors.
The BCD-encoded two-byte bcdUSB item tells the system which USB specification release
guidelines the device follows. This number might need to be altered in devices that take advantage
of additions or changes included in future revisions of the USB specification, as the host will use
this item to help determine what driver to load for the device.
If the USB device class is to be defined inside the device descriptor, this item bDeviceClass would
contain a constant defined in the USB specification. Device classes defined in other descriptors
should set the device class item in the device descriptor to 0x00.
If the device class item discussed above is set to 0x00, then the device bDeviceSubClass item should
also be set to 0x00. This item can tell the host information about the device’s subclass setting.
The item bDeviceProtocol can tell the host whether the device supports high-speed transfers. If
the above two items are set to 0x00, this one should also be set to 0x00.
The item bMaxPacketSize0 tells the host the maximum number of bytes that can be contained
inside a single control endpoint transfer. For low-speed devices, this byte must be set to 8, while
full-speed devices can have maximum endpoint 0 packet sizes of 8, 16, 32, or 64.
The two-byte item idVendor identifies the vendor ID for the device. Vendor IDs can be acquired
through the USB.org website. Host applications will search attached USB devices’ vendor IDs to
find a particular device needed for an application.
Like the vendor ID, the two-byte item idProduct uniquely identifies the attached USB device.
Product IDs can be acquired through the USB.org web site.
[ 570 ]
Chapter 13 USB Device Drivers
The item bcdDevice is used along with the vendor ID and the Product ID to uniquely identify each
USB device.
The next three one-byte items tell the host which string array index to use when retrieving
UNICODE strings describing attached devices that are displayed by the system on-screen. This
string describes the manufacturer of the attached device. An iManufacturer string index value of
0x00 indicates to the host that the device does not have a value for this string stored in memory.
The index iProduct will be used when the host wants to retrieve the string that describes the
attached product. For example the string could read "USB Keyboard".
The string pointed to by the index iSerialNumber can contain the UNICODE text for the product’s
serial number.
This item bNumConfigurations tells the host how many configurations the device supports. A
configuration is the definition of the device’s functional capabilities, including endpoint configuration.
All devices must contain at least one configuration, but more than one can be supported.
} USB_CONFIGURATION_DESCRIPTOR;
The item blenght defines the length of the configuration descriptor. This is a standard length.
The item bDescriptorTye is the constant one-byte 0x02 designator for configuration descriptors.
The two-byte wTotalLength item defines the length of this descriptor and all of the other
descriptors associated with this configuration. For example, the length could be calculated by
adding the length of the configuration descriptor, the interface descriptor, the HID class descriptor,
and two endpoint descriptors associated with this interface. This two-byte item follows a "little
[ 571 ]
USB Device Drivers Chapter 13
endian" data format. The item defines the length of this descriptor and all of the other descriptors
associated with this configuration.
The bNumInterfaces item defines the number of interface settings contained in this configuration.
The bConfigurationValue item is used by the SetConfiguration request to select this configuration.
The iConfiguration item is a index to a string descriptor describing the configuration in human
readable form.
The bmAttributes item tells the host whether the device supports USB features such as remote
wake-up. Item bits are set or cleared to describe these conditions. Check the USB specification for a
detailed discussion on this item.
The bMaxPower item tells the host how much current the device will require to function properly
at this configuration.
[ 572 ]
Chapter 13 USB Device Drivers
} USB_INTERFACE_DESCRIPTOR;
} USB_ENDPOINT_DESCRIPTOR;
[ 573 ]
USB Device Drivers Chapter 13
String descriptors are UNICODE encoded characters so that multiple languages can be supported
with a single product. When requesting a string descriptor, the requester specifies the desired
language using a 16-bit language ID (LANGID) defined by the USB-IF. String index zero is used
for all languages and returns a string descriptor that contains an array of two-byte LANGID codes
supported by the device.
The UNICODE string descriptor is not NULL-terminated. The string length is computed by
subtracting two from the value of the first byte of the descriptor.
[ 574 ]
Chapter 13 USB Device Drivers
} USB_HID_DESCRIPTOR;
The bLength item describes the size of the HID descriptor. It can vary depending on the number
of subordinate descriptors, such as report descriptors, that are included in this HID configuration
definition.
The bDescriptorType 0x21 value is the constant one-byte designator for device descriptors and
should be common to all HID descriptors.
The two-byte bcdHID item tells the host which version of the HID class specification the device
follows. USB specification requires that this value be formatted as a binary coded decimal digit,
meaning that the upper and lower nibbles of each byte represent the number 0...9. For example,
0x0101 represents the number 0101, which equals a revision number of 1.01 with an implied
decimal point.
If the device was designed to be localized to a specific country, the bCountryCode item tells the
host which country. Setting the item to 0x00 tells the host that the device was not designed to be
localized to any country.
The bNumDescriptors item tells the host how many report descriptors are contained in this HID
configuration. The following two-byte pairs of items describe each contained report descriptor.
[ 575 ]
USB Device Drivers Chapter 13
The bReportDescriptorType item describes the first descriptor which will follow the transfer of
this HID descriptor. For example, if the value is "0x22", indicates that the descriptor to follow is a
report descriptor.
The wItemLength item tells the host the size of the descriptor that is described in the preceding
item.
The HID report descriptor is a hard coded array of bytes that describe the device’s data packets.
This includes: how many packets the device supports, how large are the packets and the purpose
of each byte and bit in the packet. For example, a keyboard with a calculator program button can
tell the host that the button’s pressed/released state is stored as the 2nd bit in the 6th byte in data
packet number 4.
[ 576 ]
Chapter 13 USB Device Drivers
The Linux USB API supports synchronous calls for control and bulk messages. It also supports
asynchronous calls for all kinds of data transfer by using request structures called "URBs" (USB
Request Blocks).
The only host-side drivers that actually touch hardware (reading/writing registers, handling
IRQs and so on) are the Host Controller Devices (HCDs) drivers. In theory, all HCDs provide
the same functionality through the same API. In practice, that’s becoming more true, but there
are still differences that crop up, especially with fault handling on the less common controllers.
Different controllers don’t necessarily report the same aspects of failures, and recovery from faults
(including software-induced ones like to unlink an URB) isn’t yet fully consistent.
The main focus of this chapter is the development of Linux USB device drivers. All the sections
that follow are related to the development of this type of drivers.
[ 577 ]
USB Device Drivers Chapter 13
The variable name is a string that describes the driver. It is used in informational messages printed
to the system log. The probe() and disconnect() hotplugging callbacks are called when a device that
matches the information provided in the id_table variable is either seen or removed.
The probe() function is called by the USB core into the driver to see if the driver is willing to
manage a particular interface on a device. If it is, probe() returns zero and uses usb_set_intfdata()
to associate driver specific data with the interface. It may also use usb_set_interface() to specify the
appropriate altsetting. If unwilling to manage the interface, return -ENODEV, if genuine IO errors
occurred, an appropriate negative errno value.
int (* probe) (struct usb_interface *intf,const struct usb_device_id *id);
The disconnect() callback is called when the interface is no longer accessible, usually because its
device has been (or is being) disconnected, or the driver module is being unloaded.
void disconnect(struct usb device *dev, void *drv context);
In the usb_driver structure, some power management (PM) callbacks are defined:
• suspend: Called when the device is going to be suspended.
• resume: Called when the device is being resumed.
• reset_resume: Called when the suspended device has been reset instead of being
resumed.
There are also defined some device level operations in the usb_driver structure:
• pre_reset: Called when the device is about to be reset.
• post_reset: Called after the device has been reset.
[ 578 ]
Chapter 13 USB Device Drivers
The USB device drivers use an ID table to support hotplugging. The pointer variable id_table,
which is included in the usb_driver structure, points to an array of structures of type usb_device_id
that announce the devices that the USB device driver supports. Most drivers use the USB_
DEVICE() macro to create usb_device_id structures. These structures are registered to the USB core
by using the MODULE_DEVICE_TABLE(usb, xxx) macro. The following lines of code, included in
the drivers/usb/misc/usbsevseg.c driver, create and register a USB device to the USB core:
#define VENDOR_ID 0x0fc5
#define PRODUCT_ID 0x1227
The usb_driver structure is registered to the bus core by using the module_usb_driver() function:
module_usb_driver(sevseg_driver);
[ 579 ]
USB Device Drivers Chapter 13
/*
* array of desc.bNumEndpoints endpoints associated with this
* interface setting. These will be in no particular order.
*/
struct usb_host_endpoint *endpoint;
char *string; /* iInterface string, if present */
};
[ 580 ]
Chapter 13 USB Device Drivers
The usb_endpoint_descriptor structure contains all the USB-specific data announced by the device
itself:
struct usb_endpoint_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bEndpointAddress;
__u8 bmAttributes;
__le16 wMaxPacketSize;
__u8 bInterval;
You can use the following code snippet to obtain the IN and OUT endpoint addresses from the
IN and OUT endpoint descriptors which are included in the current altsetting of the USB interface:
struct usb_host_interface *altsetting = intf->cur_altsetting;
int ep_in, ep_out;
/* There are two usb_host_endpoint structures in this interface altsetting. Each usb_host_
endpoint structure contains a usb_endpoint_descriptor */
ep_in = altsetting->endpoint[0].desc.bEndpointAddress;
ep_out = altsetting->endpoint[1].desc.bEndpointAddress;
[ 581 ]
USB Device Drivers Chapter 13
supports seamless streaming of data to (or from) devices when using periodic transfer
modes.
These are some important fields of the urb structure:
struct urb
{
// (IN) device and pipe specify the endpoint queue
struct usb_device *dev; // pointer to associated USB device
unsigned int pipe; // endpoint information
// ISO only: packets are only "best effort"; each can have errors
int error_count; // number of errors
struct usb_iso_packet_descriptor iso_frame_desc[0];
};
The USB driver must create a "pipe" using values from the appropriate endpoint descriptor in an
interface that it’s claimed.
URBs are allocated by calling usb_alloc_urb():
struct urb *usb_alloc_urb(int isoframes, int mem_flags);
The return value is a pointer to the allocated URB, 0 if allocation failed. The parameter isoframes
specifies the number of isochronous transfer frames you want to schedule. For CTRL/BULK/
INT, use 0. The parameter mem_flags holds standard memory allocation flags, letting you control
(among other things) whether the underlying code may block or not.
[ 582 ]
Chapter 13 USB Device Drivers
Interrupt transfers are periodic and happen in intervals that are powers of two (1, 2, 4 etc) units.
Units are frames for full and low speed devices and microframes for high speed ones. You can
use the usb_fill_int_urb() macro to fill INT transfer fields. When the write urb is filled up with the
proper information by using the usb_fill_int_urb() function, you should point the urb’s completion
callback to call your own callback function. This function is called when the urb is finished by
the USB subsystem. The callback function is called in interrupt context, so caution must be taken
not to do very much processing at that time. The usb_submit_urb() call modifies urb->interval to the
implemented interval value that is less than or equal to the requested interval value.
An URB is submitted by using the function usb_submit_urb():
int usb_submit_urb(struct urb *urb, int mem_flags);
The parameter mem_flags, such as GFP_ATOMIC, controls memory allocation, such as whether the
lower levels may block when memory is tight. It immediately returns, either with status 0 (request
queued) or some error code, usually caused by the following:
• Out of memory (-ENOMEM).
• Unplugged device (-ENODEV).
• Stalled endpoint (-EPIPE).
• Too many queued ISO transfers (-EAGAIN).
• Too many requested ISO frames (-EFBIG).
• Invalid INT interval (-EINVAL).
• More than one packet for INT (-EINVAL).
After submission, urb->status is -EINPROGRESS; however, you should never look at that value
except in your completion callback.
There are two ways to cancel an URB you’ve submitted but which hasn’t been returned to your
driver yet. For an asynchronous cancel, call usb_unlink_urb():
int usb_unlink_urb(struct urb *urb);
The usb_unlink_urb() function removes the urb from the internal list and frees all allocated HW
descriptors. The status is changed to reflect unlinking. Note that the URB will not normally have
finished when usb_unlink_urb() returns; you must still wait for the completion handler to be called.
To cancel an URB synchronously, call usb_kill_urb():
[ 583 ]
USB Device Drivers Chapter 13
The usb_kill_urb() function does everything usb_unlink_urb() does, and in addition it waits until after
the URB has been returned and the completion handler has finished.
The completion handler is of the following type:
typedef void (*usb_complete_t)(struct urb *);
In the completion handler, you should have a look at urb->status to detect any USB errors. Since the
context parameter is included in the URB, you can pass information to the completion handler.
[ 584 ]
Chapter 13 USB Device Drivers
In this lab, you will use the MPLAB® Harmony Configurator Tool to create a USB Device that can
be enumerated as a HID device and communicate with the Linux USB driver that you will develop
in the following lab. These are the needed steps to create the USB device:
[ 585 ]
USB Device Drivers Chapter 13
[ 586 ]
Chapter 13 USB Device Drivers
At the USB Library option of Harmony Framework Configuration, select hid_basic_demo as Product ID
Selection. Select also the Vendor ID, Product ID, Manufacturer String and Product String as shown in
the following screen capture. You have to select a USB Device stack. The USB device will have
one control endpoint (ep0) and one interrupt endpoint (composed of IN and OUT endpoints), so
you will have to write the value two in the Number of Endpoints Used field. There will be only one
configuration and one interface associated with the device.
[ 587 ]
USB Device Drivers Chapter 13
Generate the code, save the modified configuration, and generate the project:
[ 588 ]
Chapter 13 USB Device Drivers
After initialization, the HID device needs to wait for a command from the host; scheduling a read
request will enable the HID device to receive a report. The state machine needs to wait for the host
to send the report. After receiving the report, the application needs to check the first byte of the
report. If this byte is 0x01, 0x02 or 0x03, then LED1, LED2 and LED3 must be toggled. If the first
byte is 0x00, a response report with the switch status must be sent to the host, then a new read
must be scheduled.
[ 589 ]
USB Device Drivers Chapter 13
typedef enum
{
/* Application’s state machine’s initial state. */
APP_STATE_INIT=0,
APP_STATE_SERVICE_TASKS,
} APP_STATES;
} USB_STATES;
/*
* USB variables used by the HID device application:
*
* handleUsbDevice : USB Device driver handle
[ 590 ]
Chapter 13 USB Device Drivers
} APP_DATA;
[ 591 ]
USB Device Drivers Chapter 13
Find the APP_Initialize() function in app.c file, and add the code to initialize the USB State Machine state
member and the two buffer pointers. The state variable needs to be set to the initial state of the USB
State Machine. The two pointers need to point to the corresponding buffers you declared in Step 6.
The other members will be initialized just before their use.
void APP_Initialize ( void )
{
/* Place the App state machine in its initial state. */
appData.state = APP_STATE_INIT;
[ 592 ]
Chapter 13 USB Device Drivers
made; for this purpose, you can compare the transfer handle of the request with the transfer
handle available in the event. If they match, the event is related to the ongoing request.
Find the APP_USBDeviceHIDEventHandler() function in the app.c file, add a local variable to cast the
eventData parameter, and update the two flags, one in the report received event and one in the
report sent event; don’t forget to check if the transfer handles are matched before setting the flag
to true. To match the transfer handle, you need to cast the eventData parameter to the USB Device
HID Report Event Data Type. There are two events and two types, one for report received and one
for report sent.
static void APP_USBDeviceHIDEventHandler
(
USB_DEVICE_HID_INDEX hidInstance,
USB_DEVICE_HID_EVENT event,
void * eventData,
uintptr_t userData
)
{
APP_DATA * appData = (APP_DATA *)userData;
switch(event)
{
case USB_DEVICE_HID_EVENT_REPORT_SENT:
{
/*
* This means a Report has been sent. We are free to send next
* report. An application flag can be updated here.
*/
/* Handle the HID Report Sent event */
USB_DEVICE_HID_EVENT_DATA_REPORT_SENT * report =
(USB_DEVICE_HID_EVENT_DATA_REPORT_SENT *)eventData;
if(report->handle == appData->txTransferHandle)
{
// Transfer progressed.
appData->hidDataTransmitted = true;
}
break;
}
case USB_DEVICE_HID_EVENT_REPORT_RECEIVED:
{
/* This means Report has been received from the Host. Report
* received can be over Interrupt OUT or Control endpoint based on
* Interrupt OUT endpoint availability. An application flag can be
* updated here.
*/
[ 593 ]
USB Device Drivers Chapter 13
{
// Transfer progressed.
appData->hidDataReceived = true;
}
break;
}
[...]
switch (appData.stateUSB)
{
case USB_STATE_INIT:
[ 594 ]
Chapter 13 USB Device Drivers
appData.hidDataTransmitted = true;
appData.txTransferHandle = USB_DEVICE_HID_TRANSFER_HANDLE_INVALID;
appData.rxTransferHandle = USB_DEVICE_HID_TRANSFER_HANDLE_INVALID;
appData.stateUSB = USB_STATE_SCHEDULE_READ;
break;
case USB_STATE_SCHEDULE_READ:
appData.hidDataReceived = false;
USB_DEVICE_HID_ReportReceive (USB_DEVICE_HID_INDEX_0,
&appData.rxTransferHandle, appData.receiveDataBuffer, 64);
appData.stateUSB = USB_STATE_WAITING_FOR_DATA;
break;
case USB_STATE_WAITING_FOR_DATA:
if( appData.hidDataReceived )
{
if (appData.receiveDataBuffer[0]==0x01)
{
BSP_LED_1Toggle();
appData.stateUSB = USB_STATE_SCHEDULE_READ;
}
else if (appData.receiveDataBuffer[0]==0x02)
{
BSP_LED_2Toggle();
appData.stateUSB = USB_STATE_SCHEDULE_READ;
}
else if (appData.receiveDataBuffer[0]==0x03)
{
BSP_LED_3Toggle();
appData.stateUSB = USB_STATE_SCHEDULE_READ;
}
else if (appData.receiveDataBuffer[0]==0x00)
{
appData.stateUSB = USB_STATE_SEND_REPORT;
}
else
{
appData.stateUSB = USB_STATE_SCHEDULE_READ;
}
}
break;
case USB_STATE_SEND_REPORT:
if(appData.hidDataTransmitted)
{
if( BSP_SwitchStateGet(BSP_SWITCH_1) == BSP_SWITCH_STATE_PRESSED )
{
[ 595 ]
USB Device Drivers Chapter 13
appData.transmitDataBuffer[0] = 0x00;
}
else
{
appData.transmitDataBuffer[0] = 0x01;
}
appData.hidDataTransmitted = false;
USB_DEVICE_HID_ReportSend (USB_DEVICE_HID_INDEX_0,
&appData.txTransferHandle, appData.transmitDataBuffer, 1);
appData.stateUSB = USB_STATE_SCHEDULE_READ;
}
break;
}
}
else
{
}
}
appData.hidDataReceived = false;
USB_DEVICE_HID_ReportReceive (USB_DEVICE_HID_INDEX_0,
&appData.rxTransferHandle, appData.receiveDataBuffer, 64);
appData.stateUSB = USB_STATE_WAITING_FOR_DATA;
break;
[ 596 ]
Chapter 13 USB Device Drivers
if( appData.hidDataReceived )
{
if (appData.receiveDataBuffer[0]==0x01)
{
BSP_LED_1Toggle();
appData.stateUSB = USB_STATE_SCHEDULE_READ;
}
else if (appData.receiveDataBuffer[0]==0x02)
{
BSP_LED_2Toggle();
appData.stateUSB = USB_STATE_SCHEDULE_READ;
}
else if (appData.receiveDataBuffer[0]==0x03)
{
BSP_LED_3Toggle();
appData.stateUSB = USB_STATE_SCHEDULE_READ;
}
else if (appData.receiveDataBuffer[0]==0x00)
{
appData.stateUSB = USB_STATE_SEND_REPORT;
}
else
{
appData.stateUSB = USB_STATE_SCHEDULE_READ;
}
}
break;
case USB_STATE_SEND_REPORT:
if(appData.hidDataTransmitted)
{
[ 597 ]
USB Device Drivers Chapter 13
appData.hidDataTransmitted = false;
USB_DEVICE_HID_ReportSend (USB_DEVICE_HID_INDEX_0,
&appData.txTransferHandle, appData.transmitDataBuffer, 1);
appData.stateUSB = USB_STATE_SCHEDULE_READ;
}
[ 598 ]
Chapter 13 USB Device Drivers
2. Create the ID table to support hotplugging. The Vendor ID and Product ID values have to
match with the ones used in the PIC32MX USB HID device.
#define USBLED_VENDOR_ID 0x04D8
#define USBLED_PRODUCT_ID 0x003F
4. See below an extract of the probe() routine with the main lines of code to set up the driver
commented:
[ 599 ]
USB Device Drivers Chapter 13
return 0;
}
5. Write the led_store() function. Every time your user space application writes to the led
sysfs entry ((/sys/bus/usb/devices/1-1.3:1.0/led) under the USB device, the driver´s led_store()
function is called. The usb_led structure associated to the USB device is recovered by
using the usb_get_intfdata() function. The command written in the led sysfs entry is stored
in the val variable. Finally, you will send the command via USB using the usb_bulk_msg()
function.
The kernel provides the usb_bulk_msg() and usb_control_msg() helper functions, which
make it possible to transfer simple bulk and control messages without having to create an
urb structure, initialize it, submit it and wait for its completion handler. These functions
are synchronous and will make your code sleep. You must not call them from interrupt
context or with a spinlock held.
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *data,
int len, int *actual_length, int timeout);
[ 600 ]
Chapter 13 USB Device Drivers
led->led_number = val;
/* Toggle led */
usb_bulk_msg(led->udev, usb_sndctrlpipe(led->udev, 1),
&led->led_number,
1,
NULL,
0);
return count;
}
static DEVICE_ATTR_RW(led);
9. Create a new usb_led.c file and a Makefile file in the linux_5.4_USB_drivers folder, and
add usb_led.o to your Makefile obj-m variable, then build and deploy the module to the
Raspberry Pi:
~/linux_5.4_rpi3_drivers/linux_5.4_USB_drivers$ make
~/linux_5.4_rpi3_drivers/linux_5.4_USB_drivers$ make deploy
[ 601 ]
USB Device Drivers Chapter 13
struct usb_led {
struct usb_device *udev;
u8 led_number;
};
static ssize_t led_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct usb_interface *intf = to_usb_interface(dev);
struct usb_led *led = usb_get_intfdata(intf);
return sprintf(buf, "%d\n", led->led_number);
}
led->led_number = val;
[ 602 ]
Chapter 13 USB Device Drivers
/* Toggle led */
retval = usb_bulk_msg(led->udev, usb_sndctrlpipe(led->udev, 1),
&led->led_number,
1,
NULL,
0);
if (retval) {
retval = -EFAULT;
return retval;
}
return count;
}
static DEVICE_ATTR_RW(led);
dev->udev = usb_get_dev(udev);
usb_set_intfdata(interface, dev);
return 0;
error_create_file:
usb_put_dev(udev);
usb_set_intfdata(interface, NULL);
error:
kfree(dev);
return retval;
}
dev = usb_get_intfdata(interface);
device_remove_file(&interface->dev, &dev_attr_led);
[ 603 ]
USB Device Drivers Chapter 13
usb_set_intfdata(interface, NULL);
usb_put_dev(dev->udev);
kfree(dev);
module_usb_driver(led_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a synchronous led usb controlled module");
usb_led.ko demonstration
Connect the PIC32MX470 Curiosity Development Board USB Micro-B port (J12) to one of the four
USB HostType-A connectors of the Raspberry Pi board. Power the Raspberry Pi board to boot the
processor. Keep the PIC32MX470 board powered off.
Load the module:
root@raspberrypi:/home# insmod usb_led.ko
usbcore: registered new interface driver usbled
Power now the PIC32MX Curiosity board:
root@raspberrypi:/home# usb 1-1.3: new full-speed USB device number 5 using dwc_otg
usb 1-1.3: New USB device found, idVendor=04d8, idProduct=003f, bcdDevice= 1.00
usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
usb 1-1.3: Product: LED_USB HID Demo
usb 1-1.3: Manufacturer: Microchip Technology Inc.
usbled 1-1.3:1.0: led_probe() function is called.
Check the new created USB device:
root@raspberrypi:/home# cd /sys/bus/usb/devices/1-1.3:1.0
root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# ls
authorized bInterfaceProtocol ep_01 power
bAlternateSetting bInterfaceSubClass ep_81 subsystem
bInterfaceClass bNumEndpoints led supports_autosuspend
bInterfaceNumber driver modalias uevent
Read the configurations of the USB device:
root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# cat bNumEndpoints
02
root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0/ep_01# cat direction
out
[ 604 ]
Chapter 13 USB Device Drivers
[ 605 ]
USB Device Drivers Chapter 13
LAB 13.3 code description of the "USB LED and Switch" module
The main code sections of the driver will now be described:
1. Include the function headers:
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/usb.h>
2. Create the ID table to support hotplugging. The Vendor ID and Product ID values have to
match with the ones used in the PIC32MX USB HID device.
#define USBLED_VENDOR_ID 0x04D8
#define USBLED_PRODUCT_ID 0x003F
4. See below the code of the probe() routine with the main lines of code to configure the
driver commented:
static int led_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
/* Get the current altsetting of the USB interface */
struct usb_host_interface *altsetting = intf->cur_altsetting;
struct usb_endpoint_descriptor *endpoint;
struct usb_led *dev = NULL;
int ep;
int ep_in, ep_out;
int size;
[ 606 ]
Chapter 13 USB Device Drivers
/*
* Find the last interrupt out endpoint descriptor
* to check its number and its size.
* Just for teaching purposes
*/
usb_find_last_int_out_endpoint(altsetting, &endpoint);
/* Get the endpoint’s number */
ep = usb_endpoint_num(endpoint); /* value from 0 to 15, it is 1 */
size = usb_endpoint_maxp(endpoint);
/* Validate endpoint and size */
if (size <= 0) {
dev_info(&intf->dev, "invalid size (%d)", size);
return -ENODEV;
}
[ 607 ]
USB Device Drivers Chapter 13
1,
led_urb_in_callback, dev, 1);
/* Create the led sysfs entry to interact with the user space */
device_create_file(&intf->dev, &dev_attr_led);
5. Write the led_store() function. Every time your user space application writes to the led
sysfs entry (/sys/bus/usb/devices/1-1.3:1.0/led) under the USB device, the driver´s led_store()
function is called. The usb_led structure associated to the USB device is recovered by using
the usb_get_intfdata() function. The command written in the led sysfs entry is stored in the
irq_data variable. Finally, you will send the command value via USB by using the
usb_submit_urb() function.
See below an extract of the led_store() routine:
static ssize_t led_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct usb_interface *intf = to_usb_interface(dev);
struct usb_led *led = usb_get_intfdata(intf);
u8 val;
led->irq_data = val;
6. Create OUT and IN URB’s completion callbacks. The interrupt OUT completion callback
merely checks the URB status and returns. The interrupt IN completion callback checks the
URB status, then reads the ibuffer to know the status received from the PIC32MX board´s
S1 switch, and finally, re-submits the interrupt IN URB.
static void led_urb_out_callback(struct urb *urb)
{
struct usb_led *dev;
[ 608 ]
Chapter 13 USB Device Drivers
dev = urb->context;
/* sync/async unlink faults aren’t errors */
if (urb->status) {
if (!(urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN))
dev_err(&dev->udev->dev,
"%s - nonzero write status received: %d\n",
__func__, urb->status);
}
}
dev = urb->context;
if (urb->status) {
if (!(urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN))
dev_err(&dev->udev->dev,
"%s - nonzero write status received: %d\n",
__func__, urb->status);
}
if (dev->ibuffer == 0x00)
pr_info ("switch is ON.\n");
else if (dev->ibuffer == 0x01)
pr_info ("switch is OFF.\n");
else
pr_info ("bad value received\n");
usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL);
}
9. Create a new usb_urb_int_led.c file in the linux_5.4_USB_drivers folder, and add usb_urb_int_
led.o to your Makefile obj-m variable, then build and deploy the module to the Raspberry Pi:
[ 609 ]
USB Device Drivers Chapter 13
~/linux_5.4_rpi3_drivers/linux_5.4_USB_drivers$ make
~/linux_5.4_rpi3_drivers/linux_5.4_USB_drivers$ make deploy
struct usb_led {
struct usb_device *udev;
struct usb_interface *intf;
struct urb *interrupt_out_urb;
struct urb *interrupt_in_urb;
struct usb_endpoint_descriptor *interrupt_out_endpoint;
struct usb_endpoint_descriptor *interrupt_in_endpoint;
u8 irq_data;
u8 led_number;
u8 ibuffer;
int interrupt_out_interval;
int ep_in;
int ep_out;
};
static ssize_t led_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct usb_interface *intf = to_usb_interface(dev);
struct usb_led *led = usb_get_intfdata(intf);
return sprintf(buf, "%d\n", led->led_number);
}
[ 610 ]
Chapter 13 USB Device Drivers
led->led_number = val;
led->irq_data = val;
if (val == 0)
dev_info(&led->udev->dev, "read status\n");
else if (val == 1 || val == 2 || val == 3)
dev_info(&led->udev->dev, "led = %d\n", led->led_number);
else {
dev_info(&led->udev->dev, "unknown value %d\n", val);
retval = -EINVAL;
return retval;
}
/* Send the data out */
retval = usb_submit_urb(led->interrupt_out_urb, GFP_KERNEL);
if (retval) {
dev_err(&led->udev->dev, "Couldn’t submit interrupt_out_urb %d\n", retval);
return retval;
}
return count;
}
static DEVICE_ATTR_RW(led);
dev = urb->context;
[ 611 ]
USB Device Drivers Chapter 13
dev = urb->context;
if (urb->status) {
if (!(urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN))
dev_err(&dev->udev->dev,
"%s - nonzero write status received: %d\n",
__func__, urb->status);
}
if (dev->ibuffer == 0x00)
pr_info ("switch is ON.\n");
else if (dev->ibuffer == 0x01)
pr_info ("switch is OFF.\n");
else
pr_info ("bad value received\n");
[ 612 ]
Chapter 13 USB Device Drivers
ep_in = altsetting->endpoint[0].desc.bEndpointAddress;
ep_out = altsetting->endpoint[1].desc.bEndpointAddress;
if (!dev)
return -ENOMEM;
dev->ep_in = ep_in;
dev->ep_out = ep_out;
dev->udev = usb_get_dev(udev);
dev->intf = intf;
/* Initialize int_out_urb */
usb_fill_int_urb(dev->interrupt_out_urb,
dev->udev,
usb_sndintpipe(dev->udev, ep_out),
(void *)&dev->irq_data,
1,
led_urb_out_callback, dev, 1);
/* Initialize int_in_urb */
usb_fill_int_urb(dev->interrupt_in_urb,
dev->udev,
usb_rcvintpipe(dev->udev, ep_in),
(void *)&dev->ibuffer,
1,
led_urb_in_callback, dev, 1);
usb_set_intfdata(intf, dev);
[ 613 ]
USB Device Drivers Chapter 13
dev_err(&dev->udev->dev,
"Couldn’t submit interrupt_in_urb %d\n", retval);
device_remove_file(&intf->dev, &dev_attr_led);
goto error_create_file;
}
dev_info(&dev->udev->dev,"int_in_urb submitted\n");
return 0;
error_create_file:
usb_free_urb(dev->interrupt_out_urb);
usb_free_urb(dev->interrupt_in_urb);
usb_put_dev(udev);
usb_set_intfdata(intf, NULL);
error_out:
kfree(dev);
return retval;
}
dev = usb_get_intfdata(interface);
device_remove_file(&interface->dev, &dev_attr_led);
usb_free_urb(dev->interrupt_out_urb);
usb_free_urb(dev->interrupt_in_urb);
usb_set_intfdata(interface, NULL);
usb_put_dev(dev->udev);
kfree(dev);
module_usb_driver(led_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a led/switch usb controlled module with irq in/out endpoints");
[ 614 ]
Chapter 13 USB Device Drivers
usb_urb_int_led.ko demonstration
Connect the PIC32MX470 Curiosity Development Board USB Micro-B port (J12) to one of the four
USB HostType-A connectors of the Raspberry Pi board. Power the Raspberry Pi board to boot the
processor. Keep the PIC32MX470 board powered off.
Load the module:
root@raspberrypi:/home# insmod usb_urb_int_led.ko
usb_urb_int_led: loading out-of-tree module taints kernel.
usbcore: registered new interface driver usbled
Power now the PIC32MX Curiosity board:
root@raspberrypi:/home# usb 1-1.3: new full-speed USB device number 4 using dwc_otg
usb 1-1.3: New USB device found, idVendor=04d8, idProduct=003f, bcdDevice= 1.00
usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
usb 1-1.3: Product: LED_USB HID Demo
usb 1-1.3: Manufacturer: Microchip Technology Inc.
usbled 1-1.3:1.0: led_probe() function is called.
usbled 1-1.3:1.0: endpoint size is (64)
usbled 1-1.3:1.0: endpoint number is (1)
usbled 1-1.3:1.0: endpoint in address is (129)
usbled 1-1.3:1.0: endpoint out address is (1)
usb 1-1.3: int_in_urb submitted
Go to the new created USB device:
root@raspberrypi:/home# cd /sys/bus/usb/devices/1-1.3:1.0
Switch on the LED1 of the PIC32MX Curiosity board:
root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# echo 1 > led
usbled 1-1.3:1.0: led_store() function is called.
usb 1-1.3: led = 1
usb 1-1.3: led_urb_out_callback() function is called.
Switch on the LED2 of the PIC32MX Curiosity board:
root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# echo 2 > led
usbled 1-1.3:1.0: led_store() function is called.
usb 1-1.3: led = 2
usb 1-1.3: led_urb_out_callback() function is called.
Switch on the LED3 of the PIC32MX Curiosity board:
root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# echo 3 > led
usbled 1-1.3:1.0: led_store() function is called.
usb 1-1.3: led = 3
usb 1-1.3: led_urb_out_callback() function is called.
Press the S1 switch of the PIC32MX Curiosity board and get the switch status:
root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# echo 0 > led
usbled 1-1.3:1.0: led_store() function is called.
usb 1-1.3: read status
usb 1-1.3: led_urb_out_callback() function is called.
usb 1-1.3: led_urb_in_callback() function is called.
[ 615 ]
USB Device Drivers Chapter 13
switch is ON.
Release the S1 switch of PIC32MX Curiosity board and get the switch status:
root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# echo 0 > led
usbled 1-1.3:1.0: led_store() function is called.
usb 1-1.3: read status
usb 1-1.3: led_urb_out_callback() function is called.
usb 1-1.3: led_urb_in_callback() function is called.
switch is OFF.
Remove the module:
root@raspberrypi:/home# rmmod usb_urb_int_led.ko
usbcore: deregistering interface driver usbled
usb 1-1.3: led_urb_in_callback() function is called.
switch is OFF.
usb 1-1.3: Couldn't submit interrupt_in_urb -1
usbled 1-1.3:1.0: USB LED now disconnected
[ 616 ]
Chapter 13 USB Device Drivers
This recursive model will be simplified in the driver of this LAB 13.4, where you are only going
to execute the second step of the three previously mentioned. In this driver, the communication
between the host and the device is done asynchronously by using an interrupt OUT URB.
Before developing the Linux driver, you must first add new Harmony configurations to the previous
project ones. You must select the I2C Drivers option inside the Harmony Framework Configuration:
[ 617 ]
USB Device Drivers Chapter 13
In the Pin Table of the MPLAB Harmony Configurator, activate the SCL1 and SDA1 pins of the I2C1
controller:
Generate the code, save the modified configuration, and generate the project:
Now, you have to modify the generated app.c code. Go to the USB_STATE_WAITING_FOR_DATA
case inside the USB_Task() function. Basically, it is waiting for I2C data which has been encapsulated
inside a USB interrupt OUT URB. Once the PIC32MX USB device receives the information,
it forwards it via I2C to the LTC3206 device connected to the MikroBus 1 of the PIC32MX470
Curiosity Development Board.
static void USB_Task (void)
{
if(appData.usbDeviceIsConfigured)
{
[ 618 ]
Chapter 13 USB Device Drivers
switch (appData.stateUSB)
{
case USB_STATE_INIT:
appData.hidDataTransmitted = true;
appData.txTransferHandle = USB_DEVICE_HID_TRANSFER_HANDLE_INVALID;
appData.rxTransferHandle = USB_DEVICE_HID_TRANSFER_HANDLE_INVALID;
appData.stateUSB = USB_STATE_SCHEDULE_READ;
break;
case USB_STATE_SCHEDULE_READ:
appData.hidDataReceived = false;
case USB_STATE_WAITING_FOR_DATA:
if( appData.hidDataReceived )
{
DRV_I2C_Transmit (appData.drvI2CHandle_Master,
0x36,
&appData.receiveDataBuffer[0],
3,
NULL);
appData.stateUSB = USB_STATE_SCHEDULE_READ;
}
break;
}
}
else
{
appData.stateUSB = USB_STATE_INIT;
}
}
You also need to open the I2C driver inside the APP_Tasks() function:
/* Application’s initial state. */
case APP_STATE_INIT:
{
bool appInitialized = true;
[ 619 ]
USB Device Drivers Chapter 13
if (appData.drvI2CHandle_Master == (DRV_HANDLE)NULL)
{
appInitialized = false;
}
if(appData.handleUsbDevice != USB_DEVICE_HANDLE_INVALID)
{
appInitialized = true;
}
else
{
appInitialized = false;
}
}
}
Now, you must build the code and program the PIC32MX with the new aplication. You can
download this new project, called USB_I2C, from the GitHub of the book. It is included in the
PIC32MX_usb_projects.zip file inside the linux_5.4_USB_drivers folder.
[ 620 ]
Chapter 13 USB Device Drivers
Note: For the Curiosity PIC32MX470 Development Board, verify that the value of the series
resistors mounted on the SCL and SDA lines of the mikroBUS 1 socket J5 are set to zero Ohms. If
not, replace them with zero Ohm resistors. You can also take the SDA and SCL signals from the J6
connector if you do not want to replace the resistors.
[ 621 ]
USB Device Drivers Chapter 13
LAB 13.4 code description of the "I2C to USB Multidisplay LED" module
The main code sections of the driver will now be described:
1. Include the function headers:
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/i2c.h>
2. Create the ID table to support hotplugging. The Vendor ID and Product ID values have to
match with the ones used in the PIC32MX USB HID device.
#define USBLED_VENDOR_ID 0x04D8
#define USBLED_PRODUCT_ID 0x003F
4. See below the code of the probe() routine with the main lines of code to configure the
driver commented:
static int ltc3206_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
/* Get the current altsetting of the USB interface */
struct usb_host_interface *hostif = interface->cur_altsetting;
struct i2c_ltc3206 *dev; /* the data structure */
[ 622 ]
Chapter 13 USB Device Drivers
dev->usb_dev = usb_get_dev(interface_to_usbdev(interface));
dev->interface = interface;
return 0;
}
5. Write the ltc3206_init() function. Inside this function, you will allocate and initialize the
interrupt OUT URB which is used for the communication between the host and the device.
See below the code of the ltc3206_init() routine:
static int ltc3206_init(struct i2c_ltc3206 *dev)
{
/* Allocate int_out_urb structure */
interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL);
return 0;
}
6. Create an i2c_algorithm structure that represents the I2C transfer method. You will initialize
two variables inside this structure:
[ 623 ]
USB Device Drivers Chapter 13
• master_xfer: Issues a set of I2C transactions defined by the msgs array, with num
messages available to transfer using the adapter specified by adap.
• functionality: Returns the flags that the adapter supports.
static const struct i2c_algorithm ltc3206_usb_algorithm = {
.master_xfer = ltc3206_usb_i2c_xfer,
.functionality = ltc3206_usb_func,
};
7. Write the ltc3206_usb_i2c_xfer() function, which will be called each time you write to the
I2C adapter from user space. The ltc3206_usb_i2c_xfer() function will call ltc32016_i2c_
write(), which stores the I2C data received from user space in the obuffer[] char array, then
calls ltc3206_ll_cmd(), which submits the interrupt OUT URB to the USB device and waits
for the URB´s completion.
static int ltc3206_usb_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
/* get the private data structure */
struct i2c_ltc3206 *dev = i2c_get_adapdata(adap);
struct i2c_msg *pmsg;
int ret, count;
abort:
return ret;
}
pSrc = &pmsg->buf[0];
pDst = &dev->obuffer[0];
memcpy(pDst, pSrc, ucXferLen);
[ 624 ]
Chapter 13 USB Device Drivers
return 0;
}
static int ltc3206_ll_cmd(struct i2c_ltc3206 *dev)
{
int rv;
/*
* tell everybody to leave the URB alone
* we are going to write to the LTC3206
*/
dev->ongoing_usb_ll_op = 1; /* doing USB communication */
/* wait for its completion, the USB URB callback will signal it */
rv = wait_event_interruptible(dev->usb_urb_completion_wait,
(!dev->ongoing_usb_ll_op));
if (rv < 0) {
dev_err(&dev->interface->dev, "ltc3206(ll): wait
interrupted\n");
goto ll_exit_clear_flag;
}
return 0;
ll_exit_clear_flag:
dev->ongoing_usb_ll_op = 0;
return rv;
}
8. Create the interrupt OUT URB’s completion callback. The completion callback checks the
URB status and re-submits the URB if there was an error status. If the transmission was
successful, the callback wakes up the sleeping process and returns.
static void ltc3206_usb_cmpl_cbk(struct urb *urb)
{
struct i2c_ltc3206 *dev = urb->context;
[ 625 ]
USB Device Drivers Chapter 13
switch (status) {
case 0: /* success */
break;
case -ECONNRESET: /* unlink */
case -ENOENT:
case -ESHUTDOWN:
return;
/* -EPIPE: should clear the halt */
default: /* error */
goto resubmit;
}
/*
* wake up the waiting function
* modify the flag indicating the ll status
*/
dev->ongoing_usb_ll_op = 0; /* communication is OK */
wake_up_interruptible(&dev->usb_urb_completion_wait);
return;
resubmit:
retval = usb_submit_urb(urb, GFP_ATOMIC);
if (retval) {
dev_err(&dev->interface->dev,
"ltc3206(irq): can’t resubmit intrerrupt urb, retval %d\n",
retval);
}
}
11. Create a new usb_ltc3206.c file in the linux_5.4_USB_drivers folder, and add usb_ltc3206.o to
your Makefile obj-m variable, then build and deploy the module to the Raspberry Pi:
~/linux_5.4_rpi3_drivers/linux_5.4_USB_drivers$ make
~/linux_5.4_rpi3_drivers/linux_5.4_USB_drivers$ make deploy
[ 626 ]
Chapter 13 USB Device Drivers
/*
* Return list of supported functionality.
*/
static u32 ltc3206_usb_func(struct i2c_adapter *a)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_FUNC_
SMBUS_BLOCK_PROC_CALL;
}
switch (status) {
case 0: /* success */
break;
case -ECONNRESET: /* unlink */
case -ENOENT:
case -ESHUTDOWN:
[ 627 ]
USB Device Drivers Chapter 13
return;
/* -EPIPE: should clear the halt */
default: /* error */
goto resubmit;
}
/*
* Wake up the waiting function.
* Modify the flag indicating the ll status
*/
dev->ongoing_usb_ll_op = 0; /* communication is OK */
wake_up_interruptible(&dev->usb_urb_completion_wait);
return;
resubmit:
retval = usb_submit_urb(urb, GFP_ATOMIC);
if (retval) {
dev_err(&dev->interface->dev,
"ltc3206(irq): can’t resubmit intrerrupt urb, retval %d\n",
retval);
}
}
/*
* Tell everybody to leave the URB alone.
* We are going to write to the LTC3206 device
*/
dev->ongoing_usb_ll_op = 1; /* doing USB communication */
/* Wait for the transmit completion. The USB URB callback will signal it */
rv = wait_event_interruptible(dev->usb_urb_completion_wait, (!dev->ongoing_usb_ll_op));
if (rv < 0) {
dev_err(&dev->interface->dev, "ltc3206(ll): wait interrupted\n");
goto ll_exit_clear_flag;
}
return 0;
ll_exit_clear_flag:
dev->ongoing_usb_ll_op = 0;
return rv;
}
[ 628 ]
Chapter 13 USB Device Drivers
ret = 0;
goto init_no_error;
init_error:
dev_err(&dev->interface->dev, "ltc3206_init: Error = %d\n", ret);
return ret;
init_no_error:
dev_info(&dev->interface->dev, "ltc3206_init: Success\n");
return ret;
}
pSrc = &pmsg->buf[0];
pDst = &dev->obuffer[0];
[ 629 ]
USB Device Drivers Chapter 13
return 0;
}
[ 630 ]
Chapter 13 USB Device Drivers
dev->usb_dev = usb_get_dev(interface_to_usbdev(interface));
dev->interface = interface;
init_waitqueue_head(&dev->usb_urb_completion_wait);
snprintf(dev->adapter.name, sizeof(dev->adapter.name),
DRIVER_NAME " at bus %03d device %03d",
dev->usb_dev->bus->busnum, dev->usb_dev->devnum);
dev->adapter.dev.parent = &dev->interface->dev;
[ 631 ]
USB Device Drivers Chapter 13
return 0;
error_init:
usb_free_urb(dev->interrupt_out_urb);
error_i2c:
usb_set_intfdata(interface, NULL);
ltc3206_free(dev);
error:
return ret;
}
i2c_del_adapter(&dev->adapter);
usb_kill_urb(dev->interrupt_out_urb);
usb_free_urb(dev->interrupt_out_urb);
usb_set_intfdata(interface, NULL);
ltc3206_free(dev);
module_usb_driver(ltc3206_driver);
[ 632 ]
Chapter 13 USB Device Drivers
usb_ltc3206.ko demonstration
Connect the PIC32MX470 Curiosity Development Board USB Micro-B port (J12) to one of the four
USB HostType-A connectors of the Raspberry Pi board. Power the Raspberry Pi board to boot the
processor. Keep the PIC32MX470 board powered off.
Check the I2C adapters of the Raspberry Pi board:
root@raspberrypi:/home# i2cdetect -l
i2c-1 i2c bcm2835 (i2c@7e804000) I2C adapter
Load the module:
root@raspberrypi:/home# insmod usb_ltc3206.ko
usb_ltc3206: loading out-of-tree module taints kernel.
usbcore: registered new interface driver usb-ltc3206
Power now the PIC32MX Curiosity board:
root@raspberrypi:/home# usb 1-1.3: new full-speed USB device number 4 using dwc_otg
usb 1-1.3: New USB device found, idVendor=04d8, idProduct=003f, bcdDevice= 1.00
usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
usb 1-1.3: Product: USB to I2C demo
usb 1-1.3: Manufacturer: Microchip Technology Inc.
usb-ltc3206 1-1.3:1.0: ltc3206_probe() function is called.
usb-ltc3206 1-1.3:1.0: LTC3206 at USB bus 001 address 004 -- ltc3206_init()
usb-ltc3206 1-1.3:1.0: ltc3206_init: Success
usb-ltc3206 1-1.3:1.0: ltc3206_probe() -> chip connected -> Success
Check again the I2C adapters of the Raspberry board. You will find a new adapter:
root@raspberrypi:/home# i2cdetect -l
i2c-1 i2c bcm2835 (i2c@7e804000) I2C adapter
i2c-11 i2c usb-ltc3206 at bus 001 device 004 I2C adapter
See the entries under the new USB device:
root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# ls
authorized bInterfaceProtocol ep_01 power
bAlternateSetting bInterfaceSubClass ep_81 subsystem
bInterfaceClass bNumEndpoints i2c-4 supports_autosuspend
bInterfaceNumber driver modalias uevent
Verify the communication between the USB host and the USB device. The next commands toggle the
three leds of the PIC32MX board and set maximum brightness of the LTC3206 blue LED:
root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# i2cset -y 11 0x1b 0x00 0xf0 0x00 i
number of i2c msgs is = 1
oubuffer[0] = 0
oubuffer[1] = 240
oubuffer[2] = 0
Set maximum brightness of the LTC3206 red LED:
root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# i2cset -y 11 0x1b 0xf0 0x00 0x00 i
Decrease the brightness of the LTC3206 red LED:
root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# i2cset -y 11 0x1b 0x10 0x00 0x00 i
[ 633 ]
USB Device Drivers Chapter 13
[ 634 ]
References
1. Raspberry Pi Linux documentation.
https://www.raspberrypi.org/documentation/linux/
2. Broadcom, "BCM2835 ARM Peripherals guide".
https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.
pdf
3. Rubini, Corbet, and Kroah-Hartman, "Linux Device Drivers", third edition, O’Reilly, 02/2005.
https://lwn.net/Kernel/LDD3/
4. bootlin, "Linux Kernel and Driver Development Training".
https://bootlin.com/doc/training/linux-kernel/
5. Corbet, LWN.net, Kroah-Hartman, The Linux Foundation, "Linux Kernel Development report",
seventh edition, 08/2016.
https://www.linuxfoundation.org/events/2016/08/linux-kernel-development-2016/
6. The Linux kernel Device Tree documentation.
https://www.kernel.org/doc/Documentation/devicetree/usage-model.txt
7. The Device Tree specification.
https://github.com/devicetree-org/devicetree-specification/releases
8. The Raspberry Pi Device Tree documentation.
https://www.raspberrypi.org/documentation/configuration/device-tree.md
9. The Linux kernel General Purpose Input/Output (GPIO) documentation.
https://www.kernel.org/doc/html/v5.4/driver-api/gpio/index.html
10. The Linux kernel PINCTRL (PIN CONTROL) subsystem documentation.
https://www.kernel.org/doc/html/v4.15/driver-api/pinctl.html
11. Hans-Jürgen Koch, "The Userspace I/O HOWTO".
http://www.hep.by/gnu/kernel/uio-howto/
12. The Linux kernel I2C/SMBus subsystem documentation.
https://www.kernel.org/doc/html/v5.4/i2c/index.html
13. The Linux kernel IRQ domain documentation.
https://www.kernel.org/doc/Documentation/IRQ-domain.txt
14. The Linux kernel generic IRQ documentation.
https://www.kernel.org/doc/html/latest/core-api/genericirq.html
[ 635 ]
References
[ 636 ]
Index
A register_chrdev_region() function 72-73
class_create() function 80
alloc_chrdev_region() function 72-73
class_destroy() function 80
device_create() function 80
B device_destroy() function 80
binding copy_from_user() function 113
matching, device and driver 44, 91, 167, 380, copy_to_user() function 113
414 create_singlethread_workqueue() function 225-
Bootloader 20-21 226
bus_register() function 42-43, 163, 413 create_workqueue() function 225-226
bus_type structure 41, 42-45, 163, 413
D
C DECLARE_WAIT_QUEUE_HEAD() function 228
C runtime library DECLARE_WORK() function 224
about 25 deferred work
glibc 25-26 about 214-215
cdev structure 49, 50 bottom-half, about 215
cdev_add() function 73 softirqs 215-216
cdev_init() function 73 tasklets 217
character device threaded interrupts 221-222
about 69-70 timers 217-218
devtmpfs, creation 79-80 top-half, about 215
major and minor numbers 72 workqueues 223-226
misc framework, creation 85-86 delayed_work structure 223
character device driver del_timer() function 218
alloc_chrdev_region() function 72-73 del_timer_sync() function 218
cdev_add() function 73 destroy_workqueue() function 225
cdev_init() function 73 device node 69
copy_from_user() function 113 Device Tree
copy_to_user() function 113 about 53
file_operations structure 70 building, on Raspberry Pi 39
MAKEDEV script 71-72 chosen node 56
[ 637 ]
Index
[ 638 ]
Index
E H
ethernet, setting up 36 hardware irq’s (hwirq) 195, 197, 199, 200, 209,
exit() function 61 269
F I
file_operations structure 70 I2C, definition 161
flush_scheduled_work() function 224 I2C device driver
flush_workqueue() function 225 i2c_add_driver() function 166
for_each_child_of_node() function 136, 139, 182, i2c_device_id structure 166-167
244 i2c_driver structure 165-166
free_irq() function 111, 206 registering 165-166
fwnode_handle structure 180, 182, 244 I2C subsystem, Linux
I2C bus core 163
[ 639 ]
Index
[ 640 ]
Index
[ 641 ]
Index
Q
P queue_work() function 225
pad
definition 96
PAGE allocator, kernel memory 338 R
PAGE allocator API, kernel memory 338-339 read() system call 70, 71
pin configuration node, Device Tree 295-299 register_chrdev_region() function 72-73
pin control subsystem, about 97 Regmap
[ 642 ]
Index
S start_kernel() function 29
sysfs filesystem
schedule_delayed_work_on() function 224 about 47-48
schedule_on_each_cpu() function 224 system call, interface
schedule_work() function 224 about 25
setup_arch() function 54 open() system call 70
setup_machine_fdt() function 54, 55, 56 read() system call 70
set_bit() function 375, 379, 390-391 write() system call 70
SLAB allocator, kernel memory 339-341 system shared libraries
SLAB allocator API, kernel memory 342-343 about 26
sleeping, kernel locations 27
about 228-229
DECLARE_WAIT_QUEUE_HEAD() function 228
init_waitqueue_head() function 228, 232 T
wait queue 228 tasklets, deferred work 217
wait_event() function 228-229 threaded interrupts, deferred work 221-222
wait_event_interruptible() function 229 timers, deferred work 217-218
wait_queue_head_t structure 229, 232 timer_list structure 217
wake_up() function 229 toolchain
SMBus, definition 161 about 29
setting up, on Raspberry Pi 37
[ 643 ]
Index
U advantages 147
disadvantages 147
UIO framework
U-Boot
APIs 151-152
about 20
definition 148-150
main features 20-21
working 150-151
uio_register_device() function 152
unflatten_device_tree() function 58 V
Unified device properties, API virtual file, about 65, 69
about 180 virtual interrupt ID 195
functions 180 virtual memory layout, user space process
unregister_chrdev_region() function 72 data segment 335
USB, about 563 memory mapping segment 335
USB descriptors stack segment 335
about 569 text segment 335
USB configuration descriptor 571-572 virtual to physical, memory mapping, kernel
USB device descriptors 569-571 336-337
USB endpoint descriptor 573
USB HID descriptor 574-576
USB interface descriptor 572-573
W
USB string descriptor 573-574 wait queue, kernel sleeping 228
USB device driver wait_event() function 228-229
completion handler 581, 583-584, 600 wait_event_interruptible() function 229
registering 578-579 wake_up() function 229
urb structure 582 workqueues, deferred work
usb_alloc_urb() function 582 about 223-226
usb_endpoint_descriptor structure 580-581 create_singlethread_workqueue() function 225
usb_free_urb() function 583 create_workqueue() function 225-226
usb_host_endpoint structure 580 DECLARE_WORK() function 224
usb_interface structure 579-580 destroy_workqueue() function 225
usb_kill_urb() function 583-584 flush_scheduled_work() function 224
usb_submit_urb() function 581, 583 flush_workqueue() function 225
usb_unlink_urb() function 581, 583-584 INIT_WORK() function 224
USB request block (URB) 581-584 queue_work() function 225
USB subsystem, Linux schedule_delayed_work_on() function 224
USB bus core drivers 576-577 schedule_on_each_cpu() function 224
USB device drivers 577-579 schedule_work() function 224
USB host-side drivers 579-581 work item 223
User space, drivers worker 223
[ 644 ]
Index
[ 645 ]