Linux 5.4 Rpi3 Practical Labs
Linux 5.4 Rpi3 Practical Labs
Linux 5.4 Rpi3 Practical Labs
Development for
Embedded Processors
Raspberry Pi 3 Practical Labs
Building a Linux embedded system for the Raspberry
Pi 3 Model B
The BCM2837 processor is the Broadcom chip used in the Raspberry Pi 3, and in later models of
the Raspberry Pi 2. The underlying architecture of the BCM2837 is identical to the BCM2836. The
only significant difference is the replacement of the ARMv7 quad core cluster with a quad-core
ARM Cortex A53 (ARMv8) cluster.
The ARM cores run at 1.2GHz, making the device about 50% faster tha n the Raspberry Pi 2.
The VideoCore IV runs at 400MHz. You can see the documentation for BCM2836 at
https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/README.md and for
BCM2835 at https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/README.md.
For the development of the labs the Raspberry Pi 3 Model B: Single-board computer with
wireless LAN and Bluetooth connectivity will be used. You can see more information about this
board at
https://www.raspberrypi.org/products/raspberry-pi-3-model-b/.
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 in a uSD a Raspberry Pi OS image based on kernel 5.4.y. Go to
https://www.raspberrypi.org/software/operating-systems/ and download Raspberry Pi OS with desktop
and recommended software image.
To write the compressed image on the uSD 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/
Follow the steps of the Writing an image to the SD card section at
https://www.raspberrypi.org/documentation/installation/installing-images/README.md
Enable UART, SPI and I2C peripherals in the programmed uSD:
~$ lsblk
~$ mkdir ~/mnt
~$ mkdir ~/mnt/fat32
~$ mkdir ~/mnt/ext4
~$ sudo mount /dev/mmcblk0p1 ~/mnt/fat32
~$ ls -l ~/mnt/fat32/ /* see the files in the fat32 partition, check that
config.txt is included */
You can also update previous settings (after booting the Raspberry Pi 3 board) through the
Raspberry Pi 3 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:
Connect and set up hardware
Now get everything connected to your Raspberry Pi 3. It’s important to do this in the right
order, so that all your components are safe.
Insert the uSD card you’ve set up with Raspberry Pi OS into the microSD card slot on the
underside of your Raspberry Pi 3.
Connect your screen to the single Raspberry Pi 3’s HDMI port. You can also connect a mouse to
an USB port and keyboard in the same way.
Connect your Raspberry Pi 3 to the internet via Ethernet, use an Ethernet cable to connect the
Ethernet port on Raspberry Pi 3 to an Ethernet socket on the host PC.
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.
Plug the USB power supply into a socket and connect it to your Raspberry Pi’s power port.
You should see a red LED light up on the Raspberry Pi 3, which indicates that Raspberry Pi 3 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.
Launch a terminal on the host Linux PC by clicking on the Terminal icon. Type dmesg at the
command prompt:
~$ dmesg
In the log message you can see that the new USB device is found and installed, for example
ttyUSB0.
Launch and configure a serial console, for example minicom in your host to see the booting of
the system. Through this console, you can access and control the Linux based system on the
Raspberry Pi 3 Model B. Set the following configuration: “115.2 kbaud, 8 data bits, 1 stop bit, no
parity”.
For the official Raspberry Pi OS, the default user name is pi, with password raspberry.
Reset the board. You can disconnect your screen from the Raspberry Pi 3’s HDMI port during
the development of the labs.
pi@raspberrypi:~$ sudo reboot
To see Linux boot messages on the console change the loglevel to 8 in the file cmdline.txt under
/boot
pi@raspberrypi:~$ sudo sudo nano /boot/cmdline.txt // loglevel=8
To change your current console_loglevel simply write to this file:
pi@raspberrypi:~$ echo <loglevel> > /proc/sys/kernel/printk
For example:
pi@raspberrypi:~$ echo 8 > /proc/sys/kernel/printk
In that case, every kernel messages 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 configuration permanently just append following line to /etc/sysctl.conf file in the
Raspberry Pi 3:
kernel.printk = 8 4 1 3
pi@raspberrypi:~$ sudo nano /etc/sysctl.conf
Raspbian has the SSH server disabled by default. You have to start the service:
pi@raspberrypi:~# sudo /etc/init.d/ssh restart
Now, verify that you can ping your Linux host machine from the Raspberry Pi 3 Model B. 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
By default the root account is disabled, but you can enable it by using this command and giving
it a password:
pi@raspberrypi:~$ sudo passwd root /* set for instance password to “pi” */
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
Get the kernel sources. The git clone command below will download the current active branch
(the one we are building Raspberry Pi OS images from) 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.
~$ 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$ KERNEL=kernel7
~/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$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
Device drivers >
[*] SPI support --->
<*> User mode SPI device driver support
Having built the kernel, you need to copy it onto your Raspberry Pi and install the modules;
insert the uSD into a SD card reader:
~$ lsblk
~$ mkdir ~/mnt
~$ mkdir ~/mnt/fat32
~$ mkdir ~/mnt/ext4
~$ sudo mount /dev/mmcblk0p1 ~/mnt/fat32/
~$ sudo mount /dev/mmcblk0p2 ~/mnt/ext4/
~/linux$ sudo env PATH=$PATH make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
INSTALL_MOD_PATH=~/mnt/ext4 modules_install
To find out the version of your new kernel, boot the system and run uname -r:
pi@raspberrypi:~$ uname -r
5.4.80-v7+
If you modify later kernel or device tree files, you can copy them to the Raspberry Pi 3 remotely
using SSH:
~/linux$ scp arch/arm/boot/zImage root@10.0.0.10:/boot/kernel7.img
~/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb root@10.0.0.10:/boot/
The kernel 5.4 modules developed for the Raspberry Pi 3 Model B board are included in the
linux_5.4_rpi3_drivers.zip file and can be downloaded from the GitHub repository at
https://github.com/ALIBERA/linux_book_2nd_edition.
Since the end of November 2020, the Linux drivers included in this book have been adapted to
run on the Raspberry Pi 3 Model B board using Linux kernel version 5.4. The Raspberry Pi 3
Linux drivers and device tree settings can be downloaded from the Github repository of this
book.
LAB 11.5: "IIO Mixed-Signal I/O Device" module
This new lab has been added to the labs of Chapter 11 to reinforce the concepts of creating IIO
drivers explained during this chapter, and apply in a practical way how to create a gpio
controller reinforcing thus the theory developed during Chapter 5. You will also develop several
user applications to control GPIOs from user space.
A new low cost evaluation board based on the MAX11300 device will be used, thus expanding
the number of evaluation boards that can be adquired to practice with the theory explained in
the Chapter 11.
This new kernel module will control the Maxim MAX11300 device. The MAX11300 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). This device offers
20 mixed-signal high-voltage, bipolar ports, which are configurable as an ADC analog input, a
DAC analog output, a general-purpose input port (GPI), a general-purpose output port (GPO),
or an analog switch terminal. You can check all the info related to this device at
https://www.maximintegrated.com/en/products/analog/data-converters/analog-to-digital-
converters/MAX11300.html
The hardware platforms used in this lab are the Raspberry Pi 3 Model B board and the PIXI™
CLICK from MIKROE. The documentation of these boards can be found at
https://www.raspberrypi.org/products/raspberry-pi-3-model-b/?resellerType=home and
https://www.mikroe.com/pixi-click
Before developing the driver, you can first create a custom design using the MAX11300
configuration GUI software. You will download this tool from Maxim’s website. The
MAX11300ConfigurationSetupV1.4.zip tool and the custom design used as a starting point for the
development of the driver is included in the lab folder.
In the nex screenshot of the tool you can see the configuration that will be used during the
development of the driver:
These are the parameters used during the configuration of the MAX11300 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.
• 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 MAX11300 specifications were included during the development of this driver. These
are the main specifications that have been 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.
&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>;
};*/
/*ADC: ltc2422@0 {
compatible = "arrow,ltc2422";
spi-max-frequency = <2000000>;
reg = <0>;
pinctrl-0 = <&key_pin>;
int-gpios = <&gpio 23 0>;
};*/
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>;
};
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 {
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 DT 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 DT channel children nodes. You have to place the maxim,max11300.h file under the next iio
folder inside the kernel sources:
~/linux/include/dt-bindings/iio/
#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
4. Add "maxim,max11300" to the list of devices supported by the driver. The compatible
variable matchs 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);
/* 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) is stored in the lower address of the
* "st->tx_msg" variable using cpu_to_be16()
*/
st->tx_msg = cpu_to_be16(val);
/* 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) is 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 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;
/*
* 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
*/
return 0;
}
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
};
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 "data" to the private
structure using the same function iio_priv() */
st->dev = dev; /* Keep pointer to the SPI device, needed for exchanging data
with the MAX11300 device */
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 will read the properties from the DT channel
children nodes of the DT max11300 node by using the fwnode_property_read_u32()
function, and will store the values of these properties into the variables of the data
global structure. The function max11300_set_port_modes() will use these variables to
configure the ports of the MAX11300 device. The max11300_alloc_ports() function will
also generate the different IIO sysfs entries 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 from 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))
continue;
/*
* 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");
break;
break;
/*
* 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 realated iio channels
*/
port_mode_6_count++;
}
break;
/*
* store the port_mode for each gpio offset,
* starting with offset = 0
*/
st->gpio_offset_mode[offset] = PORT_MODE_1;
/*
* 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
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;
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 struct iio_info structure. The read/write user space operations to 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,
};
Our MAX11300 IIO driver will include a basic GPIO controller, which will configure the ports of
the MAX11300 device as GPIOs, set the direction of the GPIOs (input or output) and control the
ouput level of the GPIO lines (low or high ouput level).
These are the main steps to create the GPIO controller in our MAX11300 IIO driver:
1. Include the following header, which defines the structures used to define a GPIO driver:
#include <linux/gpio/driver.h>
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 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;
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;
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);
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);
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 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);
return ret;
}
See in the next Listings the complete " IIO Mixed-Signal I/O Device" driver source code for the
Raspberry Pi 3 Model B processor.
Note: The " IIO Mixed-Signal I/O Device" driver source code developed for the Raspberry Pi 3
Model B board is included in the linux_5.4_rpi3_drivers.zip file inside the linux_5.4_max11300_driver
folder and can be downloaded from the GitHub repository at
https://github.com/ALIBERA/linux_book_2nd_edition
#include <linux/types.h>
#include <linux/cache.h>
#include <linux/mutex.h>
#include <linux/gpio/driver.h>
struct max11300_state;
/*
* declare the struct with pointers to the functions that will read and write
* via SPI the registers of the MAX11300 device
*/
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__ */
#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 */
#include <linux/bitops.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/spi/spi.h>
/*
* In little endian CPUs the byte stored in the higher address of
* the "val" variable (MSB of the DAC) is stored in the lower address
* of the "st->tx_msg" variable using cpu_to_be16()
*/
st->tx_msg = cpu_to_be16(val);
/* 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) is 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;
}
/*
* 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;
}
/*
* Initialize the struct max11300_rw_ops with read and write
* callback functions to write/read via SPI from 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,
};
#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 {
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);
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);
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);
mutex_unlock(&st->gpio_lock);
return ret;
}
/*
* 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;
u16 val, port_mode;
struct iio_dev *iio_dev = iio_priv_to_dev(st);
mutex_lock(&iio_dev->mlock);
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);
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);
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);
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;
}
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,
};
/*
* 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 from the DT, if there is an error
* 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))
continue;
/*
* store the value of the DT "port,mode" property in the global struct
* 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");
/*
* 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",
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);
/*
* 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:
dev_info(st->dev, "the port %d is configured as MODE 8\n", i);
max11300_setup_port_8_mode(iio_dev, &ports[curr_port],
false, i, st->adc_negative_port[i],
PORT_MODE_8); // false = in
curr_port++;
break;
case PORT_MODE_0:
dev_info(st->dev,
"the channel is set in default port mode_0\n");
break;
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;
}
/* 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;
/*
* 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 IIO write/read callbacks
*/
iio_dev->info = &max11300_info;
iio_dev->modes = INDIO_DIRECT_MODE;
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);
The tools provided with libgpiod allow accessing the GPIO driver from the command line. There
are six commands in libgpiod tools:
• gpiodetect: list all gpiochips present on the system, their names, labels, and number of
GPIO lines. In the lab, the MAX11300 gpio chip will appear with the name of gpiochip10.
• gpioinfo: list all lines of specified gpiochips, their names, consumers, direction, active
state, and additional flags.
• gpioget: read values of specified GPIO lines. This tool will call to the
gpiochip.direction_input and gpiochip.get callback functions declared in the struct gpio_chip
of the driver.
• gpioset: set values of specified GPIO lines, potentially keep the lines exported and wait
until timeout, user input or signal. This tool will call to the gpiochip.direction_output
callback function declared in the struct gpio_chip of the driver.
• gpiofind: find the gpiochip name and line offset given the line name.
• gpiomon: wait for events on GPIO lines, specify which events to watch, how many
events to process before exiting or if the events should be reported to the console.
Download the linux_5.4_rpi3_drivers.zip file from the github of the book and unzip it in the home
folder of your Linux host:
PC:~$ cd ~/linux_5.4_rpi3_drivers/linux_5.4_max11300_driver/
root@raspberrypi:/home# cd /sys/bus/iio/devices/iio:device0
/* check the IIO sysfs entries under the IIO MAX11300 device */
root@raspberrypi:/sys/bus/iio/devices/iio:device0# ls
dev name power
in_voltage0_mode_7_ADC_raw of_node subsystem
in_voltage1_mode_7_ADC_raw out_voltage2_mode_5_DAC_raw uevent
in_voltage4-voltage5_mode_8_ADC_raw out_voltage3_mode_5_DAC_raw
in_voltage6_mode_6_DAC_ADC_raw out_voltage6_mode_6_DAC_ADC_raw
Connect port2 (DAC) to port0 (ADC)
In this section, you have seen how to control GPIOs using the tools provided with libgpiod. In
the next section, you will see how to write applications to control GPIOs by using two different
methods. The first method will control the GPIO using a device node and the second method
will control the GPIO using the functions of the libgpiod library.
Finally, execute the application on the target. You can see the red LED flashing!
root@raspberrypi:/home# ./gpio_device_app
[ 387.963017] max11300 spi0.0: The GPIO is set as an output
[ 387.970755] max11300 spi0.0: The GPIO ouput is set
[ 387.975638] max11300 spi0.0: The GPIO ouput is set high and port_number is 19.
Pin is > 0x0F
[ 387.985031] max11300 spi0.0: The GPIO ouput is set
[ 387.989977] max11300 spi0.0: The GPIO ouput is set low and port_number is 19.
Pin is > 0x0F
[ 388.998930] max11300 spi0.0: The GPIO ouput is set
[ 389.003817] max11300 spi0.0: The GPIO ouput is set high and port_number is 19.
Pin is > 0x0F
[ 390.012625] max11300 spi0.0: The GPIO ouput is set
[ 390.017547] max11300 spi0.0: The GPIO ouput is set low and port_number is 19.
Pin is > 0x0F
[ 391.026219] max11300 spi0.0: The GPIO ouput is set
[ 391.031142] max11300 spi0.0: The GPIO ouput is set high and port_number is 19.
Pin is > 0x0F
[ 392.039912] max11300 spi0.0: The GPIO ouput is set
[ 392.044797] max11300 spi0.0: The GPIO ouput is set low and port_number is 19.
Pin is > 0x0F
[ 393.053507] max11300 spi0.0: The GPIO ouput is set
[ 393.058435] max11300 spi0.0: The GPIO ouput is set high and port_number is 19.
Pin is > 0x0F
[ 394.067208] max11300 spi0.0: The GPIO ouput is set
[ 394.072145] max11300 spi0.0: The GPIO ouput is set low and port_number is 19.
Pin is > 0x0F
[ 395.080982] max11300 spi0.0: The GPIO ouput is set
[ 395.085867] max11300 spi0.0: The GPIO ouput is set high and port_number is 19.
Pin is > 0x0F
[ 396.094677] max11300 spi0.0: The GPIO ouput is set
[ 396.099601] max11300 spi0.0: The GPIO ouput is set low and port_number is 19.
Pin is > 0x0F
[ 397.108285] max11300 spi0.0: The GPIO ouput is set
[ 397.113168] max11300 spi0.0: The GPIO ouput is set high and port_number is 19.
Pin is > 0x0F
}
sleep(1);
}
return ret;
}
Finally, execute the compiled application on the target. You can see the red LED flashing!
root@raspberrypi:/home# ./libgpiod_max11300_app
[ 897.034026] max11300 spi0.0: The GPIO is set as an output
[ 897.041828] max11300 spi0.0: The GPIO ouput is set
[ 897.046711] max11300 spi0.0: The GPIO ouput is set high and port_number is 19.
Pin is > 0x0F
[ 897.060675] max11300 spi0.0: The GPIO ouput is set
[ 897.065562] max11300 spi0.0: The GPIO ouput is set low and port_number is 19.
Pin is > 0x0F
[ 898.077778] max11300 spi0.0: The GPIO ouput is set
[ 898.082668] max11300 spi0.0: The GPIO ouput is set high and port_number is 19.
Pin is > 0x0F
[ 899.094982] max11300 spi0.0: The GPIO ouput is set
[ 899.099920] max11300 spi0.0: The GPIO ouput is set low and port_number is 19.
Pin is > 0x0F
[ 900.112112] max11300 spi0.0: The GPIO ouput is set
[ 900.117002] max11300 spi0.0: The GPIO ouput is set high and port_number is 19.
Pin is > 0x0F
[ 901.129335] max11300 spi0.0: The GPIO ouput is set
[ 901.134223] max11300 spi0.0: The GPIO ouput is set low and port_number is 19.
Pin is > 0x0F
[ 902.146373] max11300 spi0.0: The GPIO ouput is set
[ 902.151310] max11300 spi0.0: The GPIO ouput is set high and port_number is 19.
Pin is > 0x0F
[ 903.160406] max11300 spi0.0: The GPIO ouput is set
[ 903.165292] max11300 spi0.0: The GPIO ouput is set low and port_number is 19.
Pin is > 0x0F
[ 904.174362] max11300 spi0.0: The GPIO ouput is set
[ 904.179291] max11300 spi0.0: The GPIO ouput is set high and port_number is 19.
Pin is > 0x0F
[ 905.191664] max11300 spi0.0: The GPIO ouput is set
[ 905.196553] max11300 spi0.0: The GPIO ouput is set low and port_number is 19.
Pin is > 0x0F
[ 906.210534] max11300 spi0.0: The GPIO ouput is set
[ 906.215423] max11300 spi0.0: The GPIO ouput is set high and port_number is 19.
Pin is > 0x0F
/* open /dev/gpiochip3 */
output_chip = gpiod_chip_open_by_number(3);
if (!output_chip)
return -1;
gpiod_line_release(output_line);
gpiod_chip_close(output_chip);
return 0;
}
Note: The source code of the applications developed during this lab is included in the
linux_5.4_rpi3_drivers.zip file inside the linux_5.4_max11300_driver folder under application_code
folder, and can be downloaded from the GitHub repository at
https://github.com/ALIBERA/linux_book_2nd_edition
LAB 7.4: "GPIO expander device" module
This new LAB 7.4 has been added to the labs of Chapter 7 to reinforce the concepts of creating
NESTED THREADED GPIO irqchips drivers, which were explained during the chapter seven
of this book, and apply in a practical way how to create a gpio controller with interrupt
capabilities. You will also develop an user application that request GPIO interrupts from user
space using the GPIOlib APIs.
A new low cost evaluation board based on the CY8C9520A device will be used, thus expanding
the number of evaluation boards that can be adquired to practice with the theory explained in
Chapter 7.
This new kernel module will control the Cypress CY8C9520A device. The CY8C9520A is a multi-
port IO expander with on board user available EEPROM and several PWM outputs. The IO
expander's data pins can be independently assigned as inputs, outputs, quasi-bidirectional
input/outputs or PWM ouputs. The individual data pins can be configured as open drain or
collector, strong drive (10 mA source, 25 mA sink), resistively pulled up or down, or high
impedance. The factory default configuration is pulled up internally. You can check all the info
related to this device at https://www.cypress.com/products/cy8c95xx
The hardware platforms used in this lab are the Raspberry Pi 3 Model B board and the EXPAND
6 Click from MIKROE. The documentation of these boards can be found at
https://www.raspberrypi.org/products/raspberry-pi-3-model-b/?resellerType=home and
https://www.mikroe.com/expand-6-click
Not all the CY8C9520A features are included in this driver. The driver will configure the
CY8C9520A port pins as input and outputs and will handle GPIO interrupts.
ltc2607@72 {
compatible = "arrow,ltc2607";
reg = <0x72>;
};
ltc2607@73 {
compatible = "arrow,ltc2607";
reg = <0x73>;
};
ioexp@38 {
compatible = "arrow,ioexp";
reg = <0x38>;
};
ioexp@39 {
compatible = "arrow,ioexp";
reg = <0x39>;
};
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";
};
};
adxl345@1d {
compatible = "arrow,adxl345";
reg = <0x1d>;
};
cy8c9520a: cy8c9520a@20 {
compatible = "cy8c9520a";
reg = <0x20>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-controller;
#gpio-cells = <2>;
};
4. Add "cy8c9520a" to the list of devices supported by the driver. The compatible variable
matchs 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);
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 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;
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,
};
4. Write the interrupt setup function for the CY8C9520A device. The
gpiochip_set_nested_irqchip() function sets up a nested cascaded irq handler for a
gpio_chip from a parent IRQ. The The gpiochip_set_nested_irqchip() 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. You can find all the complete information about irq-flow
methods at https://www.kernel.org/doc/html/latest/core-api/genericirq.html
The interrupt handler for the GPIO child device will be called inside of a new thread
created by the handle_nested_irq() function, which is called inside the interrupt handler
of the driver.
The devm_request_threaded_irq() function inside cy8c9520a_irq_setup() will allocate
the interrupt line taking as parameters the driver´s interrupt handler
cy8c9520a_irq_handler(), the linux IRQ number (client->irq), flags that will instruct the
kernel about the desired behaviour (IRQF_ONESHOT | IRQF_TRIGGER_HIGH), and a
pointer to the cygpio global structure that will be recovered in the interrupt handler of
the driver.
static int cy8c9520a_irq_setup(struct cy8c9520a *cygpio)
{
struct i2c_client *client = cygpio->client;
struct gpio_chip *chip = &cygpio->gpio_chip;
u8 dummy[NPORTS];
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);
/*
* Initialise 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));
/* Disable interrupts in all the gpio lines */
for (i = 0; i < NPORTS; i++) {
i2c_smbus_write_byte_data(client, REG_PORT_SELECT, i);
i2c_smbus_write_byte_data(client, REG_INTR_MASK,
cygpio->irq_mask[i]);
}
/*
* 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 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,
cygpio->irq);
return 0;
err:
mutex_destroy(&cygpio->irq_lock);
return ret;
}
5. Write the interrupt handler for the CY8C9520A device. 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
has requested this 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, and 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);
return ret;
}
See in the next Listing 7-4 the complete "GPIO expander device" driver source code for the
Raspberry Pi 3 Model B processor.
Note: The "GPIO expander device" driver source code developed for the Raspberry Pi 3 Model B
board is included in the linux_5.4_rpi3_drivers.zip file inside the linux_5.4_CY8C9520A_driver folder,
and can be downloaded from the GitHub repository at
https://github.com/ALIBERA/linux_book_2nd_edition
/* 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
/*
* 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;
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
/* get the input port address address (in_reg) for the GPIO */
port = cypress_get_port(gpio);
in_reg = REG_INPUT_PORT0 + port;
mutex_lock(&cygpio->lock);
dev_info(chip->parent,
"cy8c9520a_gpio_get function with %d value is returned\n",
ret);
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);
dev_info(chip->parent,
"cy8c9520a_gpio_set_value func with %d value is called\n",
val);
/* 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));
}
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);
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;
}
/*
* 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);
}
}
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");
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 int unmasked
* and whose bits have been enabled in their REG_INTR_STAT_PORT
* register due to an interrupt in the GPIO, and store the new
* value in the pending register
*/
pending = stat[port] & (~cygpio->irq_mask[port]);
mutex_unlock(&cygpio->irq_lock);
}
}
return ret;
}
/* 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);
/*
* 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;
}
/*
* Initialise 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) {
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
*/
gpiochip_set_nested_irqchip(chip,
&cy8c9520a_irq_chip,
cygpio->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;
}
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);
return 0;
err:
mutex_destroy(&cygpio->lock);
return ret;
}
return 0;
}
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Alberto Liberal <aliberal@arroweurope.com>");
MODULE_DESCRIPTION("This is a driver that controls the \
cy8c9520a I2C GPIO expander");
LAB 7.4 GPIO child driver description
You will develop a GPIO child driver (int_rpi3_gpio) now, which will request a GPIO IRQ from
the CY8C9520A gpio controller. You will use the LAB 7.1: "button interrupt device" Module of this
book as a starting point for the development of the driver. Whenever there is a change in the first
input line of the CY8C9520A P0 port, the IRQ interrupt will be asserted by the CY8C9520A GPIO
controller, and its interrupt handler cy8c9520a_irq_handler() will be called. The CY8C9520A
driver’s interrupt handler will call handle_nested_irq(), which in turn calls the interrupt handler
P0_line0_isr() of our GPIO child driver.
The GPIO child driver will request the GPIO INT by using the devm_request_threaded_irq()
function. Before calling this function, the driver will return the Linux IRQ number from the
device tree by using the platform_get_irq() function.
See below the device-tree configuration for the int_rpi4_gpio device that should be included in the
the bcm2710-rpi-3-b.dts DT file. Check the differences with the int_key node of the LAB 7.1: "button
interrupt device" Module that was taken as a reference for the development of this driver.
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
matchs 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>;
};
See in the next Listing 7-5 the complete "GPIO child device" driver source code for the
Raspberry Pi 3 Model B processor.
Note: The "GPIO child device" driver source code developed for the Raspberry Pi 3 Model B
board is included in the linux_5.4_rpi3_drivers.zip, inside the linux_5.4_CY8C9520A_driver folder
under the linux_5.4_gpio_int_driver folder, and can be downloaded from the GitHub repository at
https://github.com/ALIBERA/linux_book_2nd_edition
/* 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;
}
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;
}
MODULE_DEVICE_TABLE(of, my_of_ids);
module_platform_driver(my_platform_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <aliberal@arroweurope.com>");
MODULE_DESCRIPTION("This is a GPIO INT platform driver");
/* open gpio */
fd = open(DEV_GPIO, O_RDWR);
if (fd < 0) {
printf("ERROR: open %s ret=%d\n", DEV_GPIO, fd);
return -1;
}
/* Request GPIO P0 first line interrupt */
req.lineoffset = 0;
req.handleflags = GPIOHANDLE_REQUEST_INPUT;
req.eventflags = GPIOEVENT_REQUEST_BOTH_EDGES;
strncpy(req.consumer_label, "gpio_irq", sizeof(req.consumer_label) - 1);
for (;;) {
fdset.fd = fd_in;
fdset.events = POLLIN;
fdset.revents = 0;
/* close gpio */
close(fd);
return 0;
}
LAB 7.4 driver demonstration
Download the linux_5.4_rpi3_drivers.zip file from the github of the book and unzip it in the home
folder of your Linux host:
~/linux_5.4_rpi3_drivers$ cd linux_5.4_CY8C9520A_driver
Compile and deploy the drivers and the application to the Raspberry Pi 3 Model B board:
~/linux_5.4_rpi3_drivers/linux_5.4_CY8C9520A_driver$ make
~/linux_5.4_rpi3_drivers/linux_5.4_CY8C9520A_driver$ make deploy
~/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
~/linux_5.4_rpi3_drivers/linux_5.4_CY8C9520A_driver/app$ scp gpio_int.c
root@10.0.0.10:/home
root@raspberrypi:/home# gcc -o gpio_int gpio_int.c
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;
cygpio->pctldev = devm_pinctrl_register(&client->dev,
&cygpio->pinctrl_desc, cygpio);
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 will parse 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.
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 CY8C9520A´s 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;
}
mutex_unlock(&cygpio->lock);
return ret;
}
In the following image, extracted from the Bootlin “Linux Kernel and Driver Development
training” (https://bootlin.com/doc/training/linux-kernel/linux-kernel-slides.pdf), you can see the pinctrl
subsystem diagram. The image shows the location of the pinctrl´s main files and structures
inside the kernel sources, and also the interaction between the pinctrl and GPIO drivers with the
Pinctrl subsystem core. You can also see the interaction of the GPIO driver with the GPIO
subsystem core and the IRQ subsystem core if the driver has interrupt capabilities, as is the case
of our CY8C9520A 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>;
cy8c9520apullups: pinmux1 {
pins = "gpio0", "gpio1";
bias-pull-up;
};
cy8c9520apulldowns: pinmux2 {
pins = "gpio2";
bias-pull-down;
};
/* pwm channel */
cy8c9520adrivestrength: pinmux3 {
pins = "gpio3";
drive-strength;
};
};
The pinctrl-x properties link to a pin configuration 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. The name default is selected by our device
driver without having to make a pinctrl function call.
In our DT device node, the pinctrl-0 property list 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 of the CY8C9520A device). 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 through the
cygpio_pinconf_set callback function, which was 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 will configure, enable and disable the PWM
channels of the CY8C9520A device.
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 can be called from the user space using
the sysfs method or from the kernel space (API) using a PWM user kernel driver. You will use
the syfs method during the driver´s demonstration section.
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;
};
};
The pwmX property will select 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 to each port pin of the device. In our device tree, we 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 20. This configuration is
just an example, you can of course add your own configuration.
You will recover the values of the pwmX properties using the device_property_read_u32() function
inside the probe() function.
/* 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;
};
See in the next Listing 7-7 the complete "GPIO-PWM-PINCTRL expander device" driver source
code for the Raspberry Pi 3 Model B processor. You can see in bold the lines that have been
added to the "GPIO child device" driver.
Note: The "GPIO-PWM-PINCTRL expander device" driver source code developed for the
Raspberry Pi 3 Model B board is included in the linux_5.4_rpi3_drivers.zip file under the
linux_5.4_CY8C9520A_pwm_pinctrl folder, and can be downloaded from the GitHub repository at
https://github.com/ALIBERA/linux_book_2nd_edition
Listing 7-7: CY8C9520A_pwm_pinctrl.c
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio/driver.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinconf-generic.h>
/* 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
#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
#define REG_DEVID_STAT 0x2e
/* 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
/*
* global pin control operations, to be 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,
.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;
}
end:
mutex_unlock(&cygpio->lock);
return ret;
}
/*
* pin config operations, to be 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 */
port = cypress_get_port(gpio);
in_reg = REG_INPUT_PORT0 + port;
mutex_lock(&cygpio->lock);
dev_info(chip->parent,
"cy8c9520a_gpio_get function with %d value is returned\n",
ret);
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);
dev_info(chip->parent,
"cy8c9520a_gpio_set_value func with %d value is called\n",
val);
/* 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));
}
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;
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
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);
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;
}
/*
* 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);
}
}
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");
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 int unmasked
* and whose bits have been enabled in their REG_INTR_STAT_PORT
* register due to an interrupt in the GPIO, and store the new
* value in the pending register
*/
pending = stat[port] & (~cygpio->irq_mask[port]);
mutex_unlock(&cygpio->irq_lock);
}
}
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)
* 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;
/*
* Set pin as output driving high and select the port
* where the pwm will be set
*/
ret = cy8c9520a_gpio_direction_output(&cygpio->gpio_chip, gpio, 1);
if (val < 0) {
dev_err(&client->dev, "can't set pwm%u as output\n", pwm->pwm);
return ret;
}
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;
}
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;
}
/* 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);
/*
* 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;
}
/*
* Initialise 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) {
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
*/
gpiochip_set_nested_irqchip(chip,
&cy8c9520a_irq_chip,
cygpio->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;
}
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);
ret = pwmchip_add(&cygpio->pwm_chip);
if (ret) {
dev_err(&client->dev, "pwmchip_add failed %d\n", ret);
goto err;
}
dev_info(&client->dev,
"the setup for the cy8c9520a pwm_chip controller is done\n");
cygpio->pinctrl_desc.pins = cy8c9520a_pins;
cygpio->pinctrl_desc.owner = THIS_MODULE;
cygpio->pctldev = devm_pinctrl_register(&client->dev,
&cygpio->pinctrl_desc,
cygpio);
if (IS_ERR(cygpio->pctldev)) {
ret = PTR_ERR(cygpio->pctldev);
goto err;
}
dev_info(&client->dev,
"the setup for the cy8c9520a pinctl descriptor is done\n");
/* link the I2C device with the cygpio device */
i2c_set_clientdata(client, cygpio);
err:
mutex_destroy(&cygpio->lock);
return ret;
}
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Alberto Liberal <aliberal@arroweurope.com>");
MODULE_DESCRIPTION("This is a driver that controls the \
cy8c9520a I2C GPIO expander");
LAB 7.5 driver demonstration
Download the linux_5.4_rpi3_drivers.zip file from the github of the book and unzip it in the home
folder of your Linux host:
~/linux_5.4_rpi3_drivers$ cd linux_5.4_CY8C9520A_pwm_pinctrl
Compile and deploy the drivers and the application to the Raspberry Pi 3 Model B board:
~/linux_5.4_rpi3_drivers/linux_5.4_CY8C9520A_pwm_pinctrl$ make
~/linux_5.4_rpi3_drivers/linux_5.4_CY8C9520A_pwm_pinctrl$ make deploy
~/linux_5.4_rpi3_drivers/linux_5.4_CY8C9520A_pwm_pinctrl/linux_5.4_gpio_int_driver
$ make
~/linux_5.4_rpi3_drivers/linux_5.4_CY8C9520A_pwm_pinctrl/linux_5.4_gpio_int_driver
$ make deploy
~/linux_5.4_rpi3_drivers/linux_5.4_CY8C9520A_pwm_pinctrl/app$ scp gpio_int.c
root@10.0.0.10:/home
root@raspberrypi:/home# gcc -o gpio_int gpio_int.c
In the host PC, enable UART, SPI and I2C peripherals in the programmed uSD:
~$ lsblk
~$ mkdir ~/mnt
~$ mkdir ~/mnt/fat32
~$ mkdir ~/mnt/ext4
~$ sudo mount /dev/mmcblk0p1 ~/mnt/fat32
~$ ls -l ~/mnt/fat32/ /* see the files in the fat32 partition, check that
config.txt is included */
In 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
(the one we are building Raspberry Pi OS images from) 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
Having built the kernel, you need to copy it onto your Raspberry Pi device, and also install the
modules; insert the uSD into a SD card reader:
~$ lsblk
~$ mkdir ~/mnt
~$ mkdir ~/mnt/fat32
~$ mkdir ~/mnt/ext4
~$ sudo mount /dev/mmcblk0p1 ~/mnt/fat32/
~$ sudo mount /dev/mmcblk0p2 ~/mnt/ext4/
~/linux_rpi3/linux$ sudo env PATH=$PATH make ARCH=arm CROSS_COMPILE=arm-linux-
gnueabihf- INSTALL_MOD_PATH=~/mnt/ext4 modules_install
Insert the uSD card you’ve set up into the uSD card slot on the underside of your Raspberry Pi
and connect the Raspberry Pi´s UART (through an 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 later kernel or device tree files, you can copy them to the Raspberry Pi 3 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 root@10.0.0.10:/boot/kernel7.img
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb
root@10.0.0.10:/boot/
Open the bcm2710-rpi-3-b.dts file, which is included in the Linux kernel sources under
/arch/arm/boot/dts folder and look for the i2c1 nodes. You will see the following nodes:
&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins>;
clock-frequency = <100000>;
};
i2c1_pins: i2c1 {
brcm,pins = <2 3>;
brcm,function = <4>;
};
The first node above adds several properties to the i2c1 controller master node. Inside the i2c1
node, you can see the pinctrl-0 property, which points to the i2c1_pins property (second node
above), which configures the pins in I2C mode.
The i2c1 controller node is described in the bcm283x.dtsi file, which is included in the Linux
kernel sources under /arch/arm/boot/dts folder:
i2c1: i2c@7e804000 {
compatible = "brcm,bcm2835-i2c";
reg = <0x7e804000 0x1000>;
interrupts = <2 21>;
clocks = <&clocks BCM2835_CLOCK_VPU>;
#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;
};
};
};
&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 */
};
};
As we have 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 code in bold above, 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
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, with several properties, to the gpio node.
• fragment@1 is adding the cy8c9520a node to the i2c1 node, and is also modifying some
properties (like the status property) of the i2c1 node itself.
You will create a cy8c9520a-overlay.dts file, adding the code below, and include the file to the
arch/arm/boot/dts/overlays folder inside the kernel sources.
/ {
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>;
#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;
};
};
};
};
};
This 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 inside the kernel sources .
[...]
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, we 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 board using Nano or Vim editors.
root@raspberrypi:/home# 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
You can see below the I2C1 overlay (i2c1-overlay.dts), which is included inside 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";
};
};
You can see in the I2C1 overlay the use of device tree parameters. To avoid the need for lots of
device tree overlays, and to reduce the need for users of peripherals to modify DTS files, the
Raspberry Pi loader supports a new feature - device tree parameters. Parameters are defined in
the DTS 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´s documentation:
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
};
Compile now our device tree overlay and copy it to the Raspberry Pi device:
~/linux_rpi3/linux$ make -j4 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/overlays/cy8c9520a.dtbo
root@10.0.0.10:/boot/overlays
Follow the instructions of the “LAB 7.5 driver demonstration” section to test the driver.
root@raspberrypi:/home# insmod CY8C9520A_pwm_pinctrl.ko
[ 601.583868] CY8C9520A_pwm_pinctrl: loading out-of-tree module taints kernel.
[ 601.594880] cy8c9520a 1-0020: cy8c9520a_probe() function is called
[ 601.603814] cy8c9520a 1-0020: dev_id=0x20
[ 601.639410] cy8c9520a 1-0020: the cy8c9520a_setup is done
[ 601.644896] cy8c9520a 1-0020: the initial setup for the cy8c9520a is done
[ 601.655971] cy8c9520a 1-0020: the setup for the cy8c9520a gpio controller done
[ 601.663396] cy8c9520a 1-0020: the cy8c9520a_irq_setup function is entered
[ 601.674274] cy8c9520a 1-0020: the interrupt state registers are cleared
[ 601.691072] cy8c9520a 1-0020: the interrupt mask port registers are set
[ 601.698897] cy8c9520a 1-0020: the interrupt setup is done
[ 601.704390] cy8c9520a 1-0020: the interrupt setup for the cy8c9520a is done
[ 601.711615] cy8c9520a 1-0020: the setup for the cy8c9520a pwm_chip controller
is done
[ 601.720072] cy8c9520a 1-0020: cygpio_pinconf_set function is called
[ 601.726451] cy8c9520a 1-0020: The pin 0 drive mode is PIN_CONFIG_BIAS_PULL_UP
[ 601.739951] cy8c9520a 1-0020: cygpio_pinconf_set function is called
[ 601.746315] cy8c9520a 1-0020: The pin 1 drive mode is PIN_CONFIG_BIAS_PULL_UP
[ 601.759975] cy8c9520a 1-0020: cygpio_pinconf_set function is called
[ 601.766338] cy8c9520a 1-0020: The pin 2 drive mode is PIN_CONFIG_BIAS_PULL_DOWN
[ 601.779998] cy8c9520a 1-0020: cygpio_pinconf_set function is called
[ 601.786359] cy8c9520a 1-0020: The pin 3 drive mode is PIN_CONFIG_DRIVE_STRENGTH
[ 601.799998] cy8c9520a 1-0020: the setup for the cy8c9520a pinctl descriptor is
done
[...]
13
Linux 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 at 1.5 Mbps, followed by full speed at 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 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.
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.
USB Descriptors
The host software obtains descriptors from an attached device by sending various standard
control requests to the default endpoint during enumeration, inmediately 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 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. 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.
} 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.
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 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.
} USB_ENDPOINT_DESCRIPTOR;
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.
} 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.
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.
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 struct usb_driver there are defined some power management (PM) callbacks:
• 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.
And there are also defined some device level operations:
• pre_reset: called when the device is about to be reset.
• post_reset: called after the device has been reset.
The USB device drivers use ID table to support hotplugging. The pointer variable id_table
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
/linux/drivers/usb/misc/usbsevseg.c driver create and register an 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);
int extralen;
unsigned char *extra; /* Extra descriptors */
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 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;
// 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)
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 mem_flags parameter holds standard memory allocation flags, letting you control
(among other things) whether the underlying code may block or not.
To free an URB, use usb_free_urb():
void usb_free_urb(struct urb *urb)
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 mem_flags parameter, 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)
It 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():
void usb_kill_urb(struct urb *urb)
It 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.
} APP_STATES;
} USB_STATES;
/*
* USB variables used by the HID device application:
*
* handleUsbDevice : USB Device driver handle
* usbDeviceIsConfigured : If true, USB Device is configured
* activeProtocol : USB HID active Protocol
* idleRate : USB HID current Idle
*/
USB_DEVICE_HANDLE handleUsbDevice;
bool usbDeviceIsConfigured;
uint8_t activeProtocol;
uint8_t idleRate;
} APP_DATA;
STEP 6: Declare the Reception and Transmission Buffers
To schedule a report receive or a report send request, you need to provide a pointer to a buffer
to store the received data and the data that has to be transmitted. Find the section Global Data
Definitions in app.c file and declare two 64 byte buffers.
APP_DATA appData;
Find the APP_USBDeviceHIDEventHandler() function in app.c file, add a local variable to cast the
eventData parameter and update the two flags, one in the report received event, one in the report
sent event; don’t forget to check if the transfer handles are matching 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. */
[...]
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;
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 )
{
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;
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 )
{
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;
}
You have modified the kernel sources, so you have to compile the new kernel and send it to the
Raspberry Pi 3:
~/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage
~/linux$ scp arch/arm/boot/zImage root@10.0.0.10:/boot/kernel7.img
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
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 to the led sysfs
entry is stored in the val variable. Finally, you will send the command value via USB by
using the usb_bulk_msg() function.
The kernel provides two usb_bulk_msg() and usb_control_msg() helper functions that
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);
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);
6. Add a struct usb_driver structure that will be registered to the USB core:
static struct usb_driver led_driver = {
.name = "usbled",
.probe = led_probe,
.disconnect = led_disconnect,
.id_table = id_table,
};
8. Build the module and load it to the target processor. Download the
linux_5.4_rpi3_drivers.zip file from the github of the book and unzip it in the home folder
of your Linux host:
~/linux_5.4_rpi3_drivers$ cd Chapter13_USB_drivers
See in the next Listing 13-1 the "USB LED" driver source code (usb_led.c).
struct usb_led {
struct usb_device *udev;
u8 led_number;
};
led->led_number = val;
/* 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);
retval = device_create_file(&interface->dev, &dev_attr_led);
if (retval)
goto error_create_file;
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);
usb_set_intfdata(interface, NULL);
usb_put_dev(dev->udev);
kfree(dev);
module_usb_driver(led_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <aliberal@arroweurope.com>");
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 3 Model B board.
* Power the Raspberry Pi 3 Model B board to boot the processor. Keep the
* PIC32MX470 board powered off
*/
root@raspberrypi:/home# insmod usb_led.ko /* load the module */
usb_led: loading out-of-tree module taints kernel.
usbcore: registered new interface driver usbled
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 an extract 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);
/*
* 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);
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 to 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;
return count;
}
static DEVICE_ATTR_RW(led);
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;
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);
}
7. Add a struct usb_driver structure that will be registered to the USB core:
static struct usb_driver led_driver = {
.name = "usbled",
.probe = led_probe,
.disconnect = led_disconnect,
.id_table = id_table,
};
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;
};
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;
}
return count;
}
static DEVICE_ATTR_RW(led);
dev = urb->context;
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");
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);
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;
}
static void led_disconnect(struct usb_interface *interface)
{
struct usb_led *dev;
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 <aliberal@arroweurope.com>");
MODULE_DESCRIPTION("This is a led/switch usb controlled module with irq in/out
endpoints");
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 3 Model B board.
* Power the Raspberry Pi 3 Model B board to boot the processor. Keep the
* PIC32MX470 board powered off
*/
root@raspberrypi:/home# insmod usb_urb_int_led.ko /* load the module */
usb_urb_int_led: loading out-of-tree module taints kernel.
usbcore: registered new interface driver usbled
/* Keep pressed the S1 switch of PIC32MX Curiosity board and get SW 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 ON.
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 an 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)
{
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;
Now, you must build the code and program the PIC32MX with the new aplication. You can
download this new project from the book´s Github.
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 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 */
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 an extract 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 a struct i2c_algorithm that represents the I2C transfer method. You will initialize
two variables inside this structure:
• master_xfer: Issues a set of i2c transactions to the given I2C adapter defined by the
msgs array, with num messages available to transfer via the adapter specified by
adap.
• functionality: Returns the flags that this algorithm/adapter pair supports from the
I2C_FUNC_* flags.
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. This function will be called each time you write
to the I2C adapter from the Linux user space. This function will call ltc32016_i2c_write()
which stores the I2C data received from the Linux user space in the obuffer[] char array,
then ltc32016_i2c_write() will call 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;
pSrc = &pmsg->buf[0];
pDst = &dev->obuffer[0];
memcpy(pDst, pSrc, ucXferLen);
rv = ltc3206_ll_cmd(dev);
if (rv < 0)
return -EFAULT;
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;
int status = urb->status;
int retval;
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);
}
}
9. Add a struct usb_driver structure that will be registered to the USB core:
static struct usb_driver ltc3206_driver = {
.name = DRIVER_NAME,
.probe = ltc3206_probe,
.disconnect = ltc3206_disconnect,
.id_table = ltc3206_table,
};
/*
* 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:
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;
}
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];
memcpy(pDst, pSrc, ucXferLen);
rv = ltc3206_ll_cmd(dev);
if (rv < 0)
return -EFAULT;
return 0;
}
dev->usb_dev = usb_get_dev(interface_to_usbdev(interface));
dev->interface = interface;
init_waitqueue_head(&dev->usb_urb_completion_wait);
dev->adapter.dev.parent = &dev->interface->dev;
dev_info(&dev->interface->dev,
"ltc3206_probe() -> chip connected -> Success\n");
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);
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 3 Model B board.
* Power the Raspberry Pi 3 Model B board to boot the processor. Keep the
* PIC32MX470 board powered off
*/
/* check the i2c adapters of the Raspberry Pi 3 Model B board */
root@raspberrypi:/home# i2cdetect -l
i2c-1 i2c bcm2835 (i2c@7e804000) I2C adapter
/* check again the i2c adapters of the Raspberry Pi 3 Model B board, find the new
one */
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
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 host and device
* these commands toggle the three leds of the PIC32MX board and
* set maximum brightness of the LTC3206 LED BLUE
*/
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 LED GREEN and SUB display */
root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# i2cset -y 11 0x1b 0x00 0x0f 0x0f
i