Chapter 5
Chapter 5
Platform Drivers
So far, you have been building your driver as a loadable driver module which was loaded during
run time. The character driver is complete and has been tested thoroughly with a user space
application. In your next assignment, you will convert the character driver to a platform driver.
On embedded systems, devices are often not connected through a bus, allowing enumeration or
hotplugging for these devices.
However, you still want all of these devices to be part of the device model. Such devices, instead
of being dynamically detected, must be statically described described by using two different
methods:
1. By direct instantiation of platform_device structures, as done on a few old ARM
non-Device Tree based platforms. The definition is done in the board-specific or SoC
specific code.
2. In the Device Tree, a hardware description file used on some architectures. The device
driver matches with the physical devices described in the .dts file. After this matching, the
driver´s probe() function is called. An .of_match_table has to be included in the driver code
to allow this matching.
Amongst the non-discoverable devices, a huge family is directly part of a system-on-chip: UART
controllers, Ethernet controllers, SPI controllers, graphic or audio devices, etc. In the Linux
kernel, a special bus, called the platform bus, has been created to handle such devices. It supports
platform drivers that handle platform devices. It works like any other bus (USB, PCI), except that
the devices are enumerated statically instead of being discovered dynamically.
Each platform driver is responsible for instantiating and registering an instance of a platform_
driver structure within the device model core. Platform drivers follow the standard driver model
convention, where discovery/enumeration is handled outside the drivers, and drivers provide
probe() and remove() methods. Platform drivers support power management and shutdown
notifications using the standard conventions. The most important members of the platform_driver
structure are shown below:
struct platform_driver {
int (*probe)(struct platform_device *);
[ 89 ]
Platform Drivers Chapter 5
At a minimum, the probe() and remove() callbacks must be supplied. The probe() function is called
when the "bus driver" pairs the "device" to the "device driver". The probe() function is responsible
for initializing the device and registering it in the appropriate kernel framework:
1. It gets a pointer to a device structure as an argument (for example, struct pci_dev *, struct
usb_dev *, struct platform_device *, struct i2c_client *).
2. It initializes the device, maps I/O memory, allocates buffers, registers interrupt handlers,
etc.
3. It registers the device to specific framework(s).
The suspend() and resume() functions are used by devices that support low power management
features.
The platform driver responsible for the platform device should be registered to the platform core
by using the platform_driver_register() function. Register your platform driver in the module init()
function, and unregister it in the module exit() function, as shown in the following code snippet:
static int hello_init(void)
{
pr_info("demo_init enter\n");
platform_driver_register(&my_platform_driver);
pr_info("hello_init exit\n");
return 0;
}
module_init(hello_init);
module_exit(hello_exit);
You can also use the module_platform_driver() macro. This is a helper macro for drivers that don't
do anything special in the module init()/exit(). This eliminates a lot of boilerplate. Each module may
only use this macro once, and calling it replaces module_init() and module_exit().
[ 90 ]
Chapter 5 Platform Drivers
/*
* module_platform_driver() - Helper macro for drivers that don't do
* anything special in module init/exit. This eliminates a lot of
* boilerplate. Each module may only use this macro once, and
* calling it replaces module_init() and module_exit()
*/
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, platform_driver_unregister)
2. Declare a list of devices supported by the driver. Create an array of structures of type of_
device_id, and initialize their compatible fields with strings that will be used by the kernel to
bind your driver with the devices declared in the Device Tree that include the same strings
in their compatible properties. The platform bus driver will trigger the driver´s probe()
function if a match between device and driver occurs.
static const struct of_device_id my_of_ids[] = {
{ .compatible = "arrow,hellokeys"},
{},
}
[ 91 ]
Platform Drivers Chapter 5
MODULE_DEVICE_TABLE(of, my_of_ids);
3. Add a platform_driver structure that will be registered with the platform bus:
static struct platform_driver my_platform_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "hellokeys",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
4. After loading the kernel module, the function my_probe() will be called when a device
matching one of the supported device ids is discovered. The function my_remove() will be
called when the driver is unloaded. Therefore my_probe() does the role of the hello_init()
function, and my_remove() does the role of the hello_exit() function. So, it makes sense to
replace hello_init() with my_probe() and hello_exit() with my_remove():
static int __init my_probe(struct platform_device *pdev)
{
int ret_val;
pr_info("my_probe() function is called.\n");
ret_val = misc_register(&helloworld_miscdevice);
if (ret_val != 0) {
pr_err("could not register the misc device mydev");
return ret_val;
}
pr_info("mydev: got minor %i\n",helloworld_miscdevice.minor);
return 0;
}
6. Modify the Device Tree files (located in arch/arm/boot/dts/ in the kernel source tree),
including your DT device nodes. There must be a DT device node´s compatible property
identical to the compatible string stored in one of the driver´s of_device_id structures. Open
the bcm2710-rpi-3-b.dts DT file and add the hellokeys node in the soc node:
&soc {
virtgpio: virtgpio {
compatible = "brcm,bcm2835-virtgpio";
[ 92 ]
Chapter 5 Platform Drivers
gpio-controller;
#gpio-cells = <2>;
firmware = <&firmware>;
status = "okay";
};
hellokeys {
compatible = "arrow,hellokeys";
};
}
7. Create a new hellokeys_rpi3.c file in the linux_5.4_rpi3_drivers folder, and add hellokeys_rpi3.o
to your Makefile obj-m variable, then build and deploy the module to the Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
8. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb root@10.0.0.10:/boot/
static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
pr_info("my_dev_ioctl() is called. cmd = %d, arg = %ld\n", cmd, arg);
return 0;
}
[ 93 ]
Platform Drivers Chapter 5
.release = my_dev_close,
.unlocked_ioctl = my_dev_ioctl,
};
if (ret_val != 0) {
pr_err("could not register the misc device mydev");
return ret_val;
}
[ 94 ]
Chapter 5 Platform Drivers
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <aliberal@arroweurope.com>");
MODULE_DESCRIPTION("This is the simplest platform driver");
hellokeys_rpi3.ko demonstration
See the Device Tree nodes under the root node:
root@raspberrypi:/home/pi# ls /proc/device-tree
'#address-cells' cpus model soc
aliases fixedregulator_3v3 name __symbols__
arm-pmu fixedregulator_5v0 __overrides__ system
axi interrupt-parent phy thermal-zones
chosen leds reserved-memory timer
clocks memory@0 serial-number
compatible memreserve '#size-cells'
root@raspberrypi:/home/pi# ls -l /sys/bus/platform/drivers/hellokeys/
total 0
--w------- 1 root root 4096 Apr 8 18:48 bind
lrwxrwxrwx 1 root root 0 Apr 8 18:48 module -> ../../../../module/hellokeys_rpi3
lrwxrwxrwx 1 root root 0 Apr 8 18:48 soc:hellokeys -> ../../../../devices/platform/soc/
soc:hellokeys
--w------- 1 root root 4096 Apr 8 18:43 uevent
--w------- 1 root root 4096 Apr 8 18:48 unbind
[ 95 ]
Platform Drivers Chapter 5
root@raspberrypi:/home/pi# ls -l /sys/module/hellokeys_rpi3/drivers/
total 0
lrwxrwxrwx 1 root root 0 Apr 8 18:49 platform:hellokeys -> ../../../bus/platform/drivers/hellokeys
root@raspberrypi:/home/pi# ls /sys/class/misc/
autofs cpu_dma_latency hw_random mydev vcsm-cma
cachefiles fuse loop-control rfkill watchdog
See the assigned mayor and minor numbers for the device mydev. The mayor number 10 is assigned by
the misc framework:
root@raspberrypi:/home/pi# ./ioctl_test
my_dev_open() is called.
my_dev_ioctl() is called. cmd = 100, arg = 110
my_dev_close() is called.
[ 96 ]
Chapter 5 Platform Drivers
[ 97 ]
Platform Drivers Chapter 5
[ 98 ]
Chapter 5 Platform Drivers
All the pins of the BCM2837 SoC are named in the pinctrl-bcm2835.c pinctrl driver, as shown in the
following code snippet. The pinctrl_pin_desc structure describes the pin name, pin index, etc.
/* pins are just named GPIO0..GPIO53 */
#define BCM2835_GPIO_PIN(a) PINCTRL_PIN(a, "gpio" #a)
static struct pinctrl_pin_desc bcm2835_gpio_pins[] = {
BCM2835_GPIO_PIN(0),
BCM2835_GPIO_PIN(1),
BCM2835_GPIO_PIN(2),
BCM2835_GPIO_PIN(3),
BCM2835_GPIO_PIN(4),
BCM2835_GPIO_PIN(5),
BCM2835_GPIO_PIN(6),
BCM2835_GPIO_PIN(7),
BCM2835_GPIO_PIN(8),
[...]
BCM2835_GPIO_PIN(45),
BCM2835_GPIO_PIN(46),
BCM2835_GPIO_PIN(47),
BCM2835_GPIO_PIN(48),
BCM2835_GPIO_PIN(49),
BCM2835_GPIO_PIN(50),
BCM2835_GPIO_PIN(51),
BCM2835_GPIO_PIN(52),
BCM2835_GPIO_PIN(53),
};
The pinctrl_dev structure will be used to instantiate a Pin Controller and register a descriptor to the
Pinctrl subsystem. This descriptor is represented by struct pinctrl_desc, which is used to describe the
information of a Pin Controller. The pinctrl_desc structure (declared in include/linux/pinctrl/pinctrl.h
in the kernel source tree) contains the pin definition, the pin configuration operation interface,
the pin muxing operation interface, and the pin group operation interface supported by the Pin
Controller of a SoC. The pinctrl_desc structure is declared as follows:
struct pinctrl_desc {
const char *name;
const struct pinctrl_pin_desc *pins;
unsigned int npins;
const struct pinctrl_ops *pctlops;
const struct pinmux_ops *pmxops;
const struct pinconf_ops *confops;
struct module *owner;
#ifdef CONFIG_GENERIC_PINCONF
unsigned int num_custom_params;
const struct pinconf_generic_params *custom_params;
const struct pin_config_item *custom_conf_items;
#endif
bool link_consumers;
};
[ 99 ]
Platform Drivers Chapter 5
Each physical pin is described through struct pin_desc. This structure is mainly used to record
the use count of the pin and describes the current configuration information of the pin (function
and group information the pin currently belongs to). The pin_desc structure is used by the
Pinctrl subsystem to determine whether a pin is used in multiple configurations with different
multiplexing conditions. The pin_desc structure is declared as follows:
/*
* struct pin_desc - pin descriptor for each physical pin in the arch
* @pctldev: corresponding pin control device
* @name: a name for the pin, e.g. the name of the pin/pad/finger on a
* datasheet or such
* @dynamic_name: if the name of this pin was dynamically allocated
* @drv_data: driver-defined per-pin data. pinctrl core does not touch this
* @mux_usecount: If zero, the pin is not claimed, and @owner should be NULL.
* If non-zero, this pin is claimed by @owner. This field is an integer
* rather than a boolean, since pinctrl_get() might process multiple
* mapping table entries that refer to, and hence claim, the same group
* or pin, and each of these will increment the @usecount.
* @mux_owner: The name of device that called pinctrl_get().
* @mux_setting: The most recent selected mux setting for this pin, if any.
* @gpio_owner: If pinctrl_gpio_request() was called for this pin, this is
* the name of the GPIO that "owns" this pin.
*/
struct pin_desc {
struct pinctrl_dev *pctldev;
const char *name;
bool dynamic_name;
void *drv_data;
/* These fields only added when supporting pinmux drivers */
#ifdef CONFIG_PINMUX
unsigned mux_usecount;
const char *mux_owner;
const struct pinctrl_setting_mux *mux_setting;
const char *gpio_owner;
#endif
};
[ 100 ]
Chapter 5 Platform Drivers
2. struct pinconf_ops: Pins can sometimes be configured in multiple ways by software, most
of which are related to their electrical characteristics when used as input/output. For
example, an output pin can be placed in a high-impedance state or "tri-state" (meaning
it is effectively disconnected). You can connect an input pin to VDD or GND by setting
a specific register value—pull-up/pull-down—so that there is a certain value on the pin
when there is no signal to drive the pin or when it is not connected. The pinconf_ops
structure is declared as follows:
/*
* struct pinconf_ops - pin config operations, to be implemented by
* pin configuration capable drivers.
* @is_generic: for pin controllers that want to use the generic interface,
* this flag tells the framework that it's generic.
* @pin_config_get: get the config of a certain pin, if the requested config
* is not available on this controller this should return -ENOTSUPP
* and if it is available but disabled it should return -EINVAL
* @pin_config_set: configure an individual pin
* @pin_config_group_get: get configurations for an entire pin group; should
* return -ENOTSUPP and -EINVAL using the same rules as pin_config_get.
* @pin_config_group_set: configure all pins in a group
* @pin_config_dbg_show: optional debugfs display hook that will provide
* per-device info for a certain pin in debugfs
* @pin_config_group_dbg_show: optional debugfs display hook that will provide
[ 101 ]
Platform Drivers Chapter 5
3. struct pinmux_ops: Pinmux, also known as padmux or ballmux is a way for chip
manufacturers to use a specific physical pin for multiple mutually exclusive functions.
The SoC will contain several I2C, SPI, SDIO/MMC, and other peripheral controllers which
can be routed to different pins through pin multiplexing settings. Because the number
of GPIOs is often insufficient, many of the unused pins of the peripheral controllers are
usually used as GPIO. The Pinctrl subsystem uses struct pinmux_ops to abstract pinmux
related operations. The pinmux_ops structure is declared as follows:
/*
* struct pinmux_ops - pinmux operations, to be implemented by pin controller
* drivers that support pinmuxing
* @request: called by the core to see if a certain pin can be made
* available for muxing. This is called by the core to acquire the pins
* before selecting any actual mux setting across a function. The driver
* is allowed to answer "no" by returning a negative error code
* @free: the reverse function of the request() callback, frees a pin after
* being requested
* @get_functions_count: returns number of selectable named functions available
* in this pinmux driver
[ 102 ]
Chapter 5 Platform Drivers
[ 103 ]
Platform Drivers Chapter 5
The Pinctrl driver needs to implement the callback operations which are specific to the Pin
Controller of the SoC. For example, the pinctrl-bcm2835.c pinctrl driver declares the following
callback operations:
static struct pinctrl_desc bcm2835_pinctrl_desc = {
.name = MODULE_NAME,
.pins = bcm2835_gpio_pins,
.npins = ARRAY_SIZE(bcm2835_gpio_pins),
.pctlops = &bcm2835_pctl_ops,
.pmxops = &bcm2835_pmx_ops,
.confops = &bcm2835_pinconf_ops,
.owner = THIS_MODULE,
};
static const struct pinctrl_ops bcm2835_pctl_ops = {
.get_groups_count = bcm2835_pctl_get_groups_count,
.get_group_name = bcm2835_pctl_get_group_name,
.get_group_pins = bcm2835_pctl_get_group_pins,
.pin_dbg_show = bcm2835_pctl_pin_dbg_show,
.dt_node_to_map = bcm2835_pctl_dt_node_to_map,
.dt_free_map = bcm2835_pctl_dt_free_map,
};
static const struct pinconf_ops bcm2835_pinconf_ops = {
.is_generic = true,
.pin_config_get = bcm2835_pinconf_get,
.pin_config_set = bcm2835_pinconf_set,
};
static const struct pinmux_ops bcm2835_pmx_ops = {
.free = bcm2835_pmx_free,
.get_functions_count = bcm2835_pmx_get_functions_count,
.get_function_name = bcm2835_pmx_get_function_name,
.get_function_groups = bcm2835_pmx_get_function_groups,
.set_mux = bcm2835_pmx_set,
.gpio_disable_free = bcm2835_pmx_gpio_disable_free,
.gpio_set_direction = bcm2835_pmx_gpio_set_direction,
};
Finally, struct pinctrl_desc is registered against the Pinctrl subsystem by calling the devm_pinctrl_
register() function (defined in drivers/pinctrl/core.c in the kernel source tree), which is called in the
probe() function of the pinctrl driver. In the following code snippet, extracted from the pinctrl-
bcm2835.c pinctrl driver, you can see the registration of the pinctrl_desc for the Rasperry Pi device.
pc->pctl_dev = devm_pinctrl_register(dev, &bcm2835_pinctrl_desc, pc);
if (IS_ERR(pc->pctl_dev)) {
gpiochip_remove(&pc->gpio_chip);
return PTR_ERR(pc->pctl_dev);
}
The devm_pinctrl_register() function calls pinctrl_register(), which is mainly divided into two
functions: pinctrl_init_controller() and pinctrl_enable(), as you can see in the following code snippet:
[ 104 ]
Chapter 5 Platform Drivers
error = pinctrl_enable(pctldev);
if (error)
return ERR_PTR(error);
return pctldev;
[ 105 ]
Platform Drivers Chapter 5
[ 106 ]
Chapter 5 Platform Drivers
.free = gpiochip_generic_free,
.direction_input = bcm2835_gpio_direction_input,
.direction_output = bcm2835_gpio_direction_output,
.get_direction = bcm2835_gpio_get_direction,
.get = bcm2835_gpio_get,
.set = bcm2835_gpio_set,
.set_config = gpiochip_generic_config,
.base = -1,
.ngpio = BCM2835_NUM_GPIOS,
.can_sleep = false,
};
The code implementing a gpio_chip structure should support multiple instances of the controller by
using the driver model. That code will configure each gpio_chip structure and issue gpiochip_add_
data(). In the following code snippet, extracted from the pinctrl-bcm2835.c pinctrl driver, you can see
the registration of the gpio_chip structure with the GPIOlib subsystem.
static int bcm2835_pinctrl_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct bcm2835_pinctrl *pc;
[...]
pc->gpio_chip = bcm2835_gpio_chip;
pc->gpio_chip.parent = dev;
pc->gpio_chip.of_node = np;
[...]
[...]
pc->gpio_range = bcm2835_pinctrl_gpio_range;
pc->gpio_range.base = pc->gpio_chip.base;
pc->gpio_range.gc = &pc->gpio_chip;
pinctrl_add_gpio_range(pc->pctl_dev, &pc->gpio_range);
return 0;
}
GPIO controllers can also provide interrupts, usually cascaded off a parent interrupt controller.
The IRQ portions of the GPIO block are implemented via an irq_chip structure (declared in include/
linux/irq.h in the kernel source tree). You will see a detailed description of the irq_chip structure in
Chapter 7.
[ 107 ]
Platform Drivers Chapter 5
In the following code snippet, extracted from the pinctrl-bcm2835.c pinctrl driver, you can
see the interrupt handler for the BCM2837 SoC:
static void bcm2835_gpio_irq_handler(struct irq_desc *desc)
{
struct gpio_chip *chip = irq_desc_get_handler_data(desc);
struct bcm2835_pinctrl *pc = gpiochip_get_data(chip);
struct irq_chip *host_chip = irq_desc_get_chip(desc);
int irq = irq_desc_get_irq(desc);
int group, i;
chained_irq_enter(host_chip, desc);
switch (group) {
case 0: /* IRQ0 covers GPIOs 0-27 */
bcm2835_gpio_irq_handle_bank(pc, 0, 0x0fffffff);
break;
case 1: /* IRQ1 covers GPIOs 28-45 */
bcm2835_gpio_irq_handle_bank(pc, 0, 0xf0000000);
bcm2835_gpio_irq_handle_bank(pc, 1, 0x00003fff);
break;
case 2: /* IRQ2 covers GPIOs 46-53 */
bcm2835_gpio_irq_handle_bank(pc, 1, 0x003fc000);
break;
}
chained_irq_exit(host_chip, desc);
}
[ 108 ]
Chapter 5 Platform Drivers
2. GENERIC CHAINED GPIO irqchips: These are the same as "CHAINED GPIO irqchips",
but chained IRQ handlers are not used. Instead, GPIO IRQs dispatching is performed by
the generic IRQ handler, which is configured using request_irq(). The GPIO irqchip will
then end up calling something like this sequence in its interrupt handler:
static irqreturn_t gpio_rcar_irq_handler(int irq, void *dev_id)
for each detected GPIO IRQ
generic_handle_irq(...);
3. NESTED THREADED GPIO irqchips: These are off-chip GPIO expanders and any other
GPIO irqchip residing on the other side of a sleeping bus. Of course, such drivers that need
slow bus traffic to read out the IRQ status and similar, traffic which may in turn incurs
other IRQs to happen, cannot be handled in a quick IRQ handler with IRQs disabled.
Instead, they need to spawn a thread and mask the parent IRQ line until the interrupt
is handled by the driver. The hallmark of this driver is to call something like this in its
interrupt handler:
static irqreturn_t foo_gpio_irq(int irq, void *data)
[...]
handle_nested_irq(irq);
In the LAB 7.4 of this book, you will implement a GPIO expander driver which includes a
nested threaded GPIO irqchip.
[ 109 ]
Platform Drivers Chapter 5
The devm_gpiod_get_index() variant of the devm_gpiod_get() function allows to access several GPIOs
defined inside a specific GPIO function. The devm_gpiod_get_index() function returns the GPIO
descriptor looking up the GPIO function (con_id) and its index (idx) from the Device Tree by using
the of_find_gpio() function:
struct gpio_desc *devm_gpiod_get_index(struct device *dev,
const char *con_id,
unsigned int idx,
enum gpiod_flags flags);
The flags parameter can specify a direction and initial value for the GPIO. These are some of its
most significant values:
• GPIOD_ASIS or 0 to not initialize the GPIO at all. The direction must be set later with one
of the dedicated functions.
• GPIOD_IN to initialize the GPIO as input.
• GPIOD_OUT_LOW to initialize the GPIO as output with a value of 0.
• GPIOD_OUT_HIGH to initialize the GPIO as output with a value of 1.
A GPIO descriptor can be disposed of by using the devm_gpiod_put() function.
Using GPIOs
Whenever you write a Linux driver that needs to control a GPIO, you must specify the GPIO
direction. This can be done using the parameter flags of a devm_gpiod_get*() function or calling later
a gpiod_direction_*() function if you have set the flags parameter to GPIOD_ASIS:
[ 110 ]
Chapter 5 Platform Drivers
The return value is zero for success, else a negative errno. For output GPIOs, the value provided
becomes the initial output value. This helps avoid signal glitching during system startup.
Most GPIO controllers can be accessed with memory read/write instructions. Those don't need to
sleep and can safely be done from inside hard (non-threaded) IRQ handlers.
You can use the following functions to access GPIOs from an atomic context:
int gpiod_get_value(const struct gpio_desc *desc);
void gpiod_set_value(struct gpio_desc *desc, int value);
As a driver should not have to care about the physical line level, all of the gpiod_set_value_xxx()
functions operate with the *logical* value. With this, they take the active-low property into
account. This means that they check whether the GPIO is configured to be active-low, and if so,
they manipulate the passed value before the physical line level is driven.
All the gpiod_set_value_xxx() functions interpret the parameter "value" as "active" ("1") or "inactive"
("0"). The physical line level will be driven accordingly.
As an example, if the active-low property for a dedicated GPIO is set and the gpiod_set_value_xxx()
passes "active" ("1"), the physical line level will be driven low.
To summarize:
Function (example) active-low property physical line
gpiod_set_value(desc, 0); default (active-high) low
gpiod_set_value(desc, 1); default (active-high) high
gpiod_set_value(desc, 0); active-low high
gpiod_set_value(desc, 1); active-low low
You will pass as a parameter the GPIO descriptor previously obtained using a devm_gpiod_get*()
function, then the gpiod_to_irq() function will return the Linux IRQ number corresponding to that
GPIO, or a negative errno code if the mapping can't be done. The gpiod_to_irq() function is not
allowed to sleep.
Non-error values returned from gpiod_to_irq() can be passed to the request_irq() or free_irq()
functions, which will request to obtain or release an interrupt. You will learn about these functions
in the Chapter 7.
[ 111 ]
Platform Drivers Chapter 5
Where &gpioa and &gpiob are the phandles to the specific gpio-controller nodes. Numbers 15, 16, 17
and 1 are the line offset for each gpio-controller, and GPIO_ACTIVE_HIGH is one of the flags used
for the GPIO.
The led-gpios property will make GPIOs 15, 16 and 17 of the gpioa controller available to the Linux
driver, and power-gpios will make GPIO 1 of the gpiob controller available to the driver:
struct gpio_desc *red, *green, *blue, *power;
red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);
green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH);
blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);
power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);
The second parameter of the gpiod_get*() functions, the con_id string, has to be identical to the
function prefix of the gpios suffixes used in the Device Tree. In the previous Device Tree example,
you will use the next con_id parameters: "led" and "power" to obtain each GPIO descriptor from
the foo_device. For the led-gpios property, in addition to the con_id parameter "led", you will also
need to set the index (idx) with values 0, 1 or 2 in the gpiod_get_index() function to get each GPIO
descriptor.
[ 112 ]
Chapter 5 Platform Drivers
APIs in user space. When a process executes a system call, the kernel will execute in process
context on behalf of the calling process. When the kernel responds to interrupts, the interrupt
handler runs asynchronously in interrupt context.
A driver for a device is the interface between an application and hardware. Accessing process
address space from the kernel cannot be done directly (by de-referencing a user-space pointer).
Direct access of a user space pointer can lead to incorrect behavior (depending on architecture,
a user-space pointer may not be valid or mapped to kernel space), a kernel oops (the user-mode
pointer can refer to a non-resident memory area) or security issues. Proper access to user space
data is done by calling the macros/functions below:
1. A single value:
get_user(type val, type *address);
The val kernel variable gets the value pointed by the address user space pointer.
put_user(type val, type *address);
The value pointed by the address user space pointer is set to the contents of the val kernel
variable.
2. A buffer:
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
copy_to_user() copies n bytes from the kernel space address pointed by from, to the user-
space address pointed by to.
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
copy_from_user() copies n bytes from the user space address pointed by from, to the kernel
space address pointed by to.
[ 113 ]
Platform Drivers Chapter 5
2. PIO
• Different address spaces for memory and I/O devices.
• Uses a special class of CPU instructions to access I/O devices.
The BCM2837 SoC uses the MMIO access, so this method will be described in more detail during
this section.
The Linux driver cannot access physical I/O addresses directly - MMU mapping is needed. For
accessing I/O memory, drivers need to have a virtual address that the processor can handle,
because I/O memory is not mapped by default in virtual memory.
You can obtain this I/O virtual address by using two different functions:
1. Map and remove mapping by using ioremap()/iounmap() functions. The ioremap() function
accepts the physical address and the size of the area. It returns a pointer to virtual memory
that can be dereferenced (or NULL if mapping is impossible).
void __iomem *ioremap(phys_addr_t offset, unsigned long size);
void iounmap(void *address);
2. Map and remove mapping attached to the driver device by using the devm_ioremap() and
devm_iounmap() managed functions (declared in include/linux/io.h and defined in lib/devres.c
in the kernel source tree), which simplify the driver code and error handling. Using
ioremap() in device drivers is now deprecated.
void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,
unsigned long size);
void devm_iounmap(struct device *dev, void __iomem *addr);
Each device (basic device structure) manages a linked list of resources via its included list_head
devres_head structure. Calling a managed resource allocator involves adding the resource to the
list. The resources are released in reverse order when the probe() function exits with an error
status or after the remove() function returns. The use of managed functions in the probe() function
removes the needed resource´s releases on error handling, replacing goto's and other resource´s
releases with just a return. It also removes resource´s releases in the remove() function.
Dereferencing the pointer returned by devm_ioremap() is not reliable. Cache and synchronization
issues may occur. The kernel provides functions to read and write to virtual addresses. To do
PCI-style, little-endian accesses, conversion is being done automatically using the functions below:
unsigned read[bwl](void *addr);
void write[bwl](unsigned val, void *addr);
[ 114 ]
Chapter 5 Platform Drivers
There are "generic" interfaces for doing new-style memory-mapped or PIO accesses. Architectures
may do their own arch-optimized versions. These just act as wrappers around the old-style IO
register access functions read[bwl]/write[bwl]/in[bwl]/out[bwl]:
unsigned int ioread8(void __iomem *addr);
unsigned int ioread16(void __iomem *addr);
unsigned int ioread32(void __iomem *addr);
void iowrite8(u8 value, void __iomem *addr);
void iowrite16(u16 value, void __iomem *addr);
void iowrite32(u32 value, void __iomem *addr);
The following figure represents the GPIO base address (0x3F200000) mapping for the BCM2837
SoC. In the driver of the LAB 5.3, you can see how this register address is mapped to a virtual
address by using the devm_ioremap() function.
[ 115 ]
Platform Drivers Chapter 5
LEDs using write() and read() driver´s operations. You will use the copy_to_user() and copy_from_
user() functions to exchange character arrays between the kernel and user spaces.
[ 116 ]
Chapter 5 Platform Drivers
To obtain the LEDs, you will use the Color click™ accessory board with mikroBUS™ form factor.
See the Color click™ accessory board at https://www.mikroe.com/color-click. You can download the
schematics from that link or from the GitHub repository of this book.
Connect the Raspberry Pi GPIO27 pin to the Color click™ RD pin, GPIO22 pin to GR and GPIO26
to BL. Connect 5V power and GND between both boards.
[ 117 ]
Platform Drivers Chapter 5
gpio: gpio@7e200000 {
compatible = "brcm,bcm2835-gpio";
reg = <0x7e200000 0xb4>;
/*
* The GPIO IP block is designed for 3 banks of GPIOs.
* Each bank has a GPIO interrupt for itself.
* There is an overall "any bank" interrupt.
* In order, these are GIC interrupts 17, 18, 19, 20.
* Since the BCM2835 only has 2 banks, the 2nd bank
* interrupt output appears to be mirrored onto the
* 3rd bank's interrupt signal.
* So, a bank0 interrupt shows up on 17, 20, and
* a bank1 interrupt shows up on 18, 19, 20!
*/
interrupts = <2 17>, <2 18>, <2 19>, <2 20>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
i2c0_gpio0: i2c0_gpio0 {
brcm,pins = <0 1>;
brcm,function = <BCM2835_FSEL_ALT0>;
};
i2c0_gpio28: i2c0_gpio28 {
brcm,pins = <28 29>;
brcm,function = <BCM2835_FSEL_ALT0>;
};
[...]
spi0_gpio7: spi0_gpio7 {
[ 118 ]
Chapter 5 Platform Drivers
uart1_gpio40: uart1_gpio40 {
brcm,pins = <40 41>;
brcm,function = <BCM2835_FSEL_ALT5>;
};
uart1_ctsrts_gpio42: uart1_ctsrts_gpio42 {
brcm,pins = <42 43>;
brcm,function = <BCM2835_FSEL_ALT5>;
};
};
The compatible property value of the gpio@7e200000 node matches with the compatible field value
included in the pinctrl-bcm2835.c pinctrl driver.
The pin controller device should contain the pin configuration nodes for each client device. The
contents of each of those pin configuration child nodes are defined entirely by the binding for
the individual pin controller device. There exists no common standard for this content. Each pin
configuration node lists the pin(s) to which it applies, one or more of the mux function to select
on those pin(s), and pull-up/down configuration. These are the required properties for the pin
controller of the BCM2837 SoC:
• brcm,pins: An array of cells. Each cell contains the ID of a pin. Valid IDs are the integer
GPIO IDs; 0==GPIO0, 1==GPIO1, ... 53==GPIO53.
• brcm,function: Integer, containing the function to mux to the pin(s):
0: GPIO in
1: GPIO out
2: alt5
3: alt4
4: alt0
5: alt1
6: alt2
7: alt3
• brcm,pull: Integer, representing the pull-down/up to apply to the pin(s):
0: none
1: down
2: up
[ 119 ]
Platform Drivers Chapter 5
Each of brcm,function and brcm,pull may contain either a single value which will be applied to all
pins in brcm,pins, or 1 value for each entry in brcm,pins.
For further info, go to the Device Tree pinctrl documentation binding folder, located at
Documentation/devicetree/bindings/pinctrl/, and examine the brcm,bcm2835-gpio.txt file.
For this lab, modify the bcm2710-rpi-3-b.dts Device Tree file by adding the led_pins pin
configuration node in the gpio node and the ledred, ledgreen and ledblue device nodes in the SoC
node:
/ {
model = "Raspberry Pi 3 Model B";
};
&gpio {
spi0_pins: spi0_pins {
brcm,pins = <9 10 11>;
brcm,function = <4>; /* alt0 */
};
spi0_cs_pins: spi0_cs_pins {
brcm,pins = <8 7>;
brcm,function = <1>; /* output */
};
i2c0_pins: i2c0 {
brcm,pins = <0 1>;
brcm,function = <4>;
};
[...]
led_pins: led_pins {
brcm,pins = <27 22 26>;
brcm,function = <1>; /* Output */
brcm,pull = <1 1 1>; /* Pull down */
};
};
&soc {
virtgpio: virtgpio {
compatible = "brcm,bcm2835-virtgpio";
gpio-controller;
#gpio-cells = <2>;
firmware = <&firmware>;
status = "okay";
};
hellokeys {
compatible = "arrow,hellokeys";
};
[ 120 ]
Chapter 5 Platform Drivers
ledred {
compatible = "arrow,RGBleds";
label = "ledred";
pinctrl-0 = <&led_pins>;
};
ledgreen {
compatible = "arrow,RGBleds";
label = "ledgreen";
};
ledblue {
compatible = "arrow,RGBleds";
label = "ledblue";
};
[...]
};
LAB 5.2 code description of the "RGB LED platform device" module
The main code sections of the driver will now be described:
1. Include the function headers:
#include <linux/module.h>
#include <linux/fs.h> /* struct file_operations */
#include <linux/platform_device.h> /* platform_driver_register(), platform_set_drvdata()
*/
#include <linux/io.h> /* devm_ioremap(), iowrite32() */
#include <linux/of.h> /* of_property_read_string() */
#include <linux/uaccess.h> /* copy_from_user(), copy_to_user() */
#include <linux/miscdevice.h> /* misc_register() */
2. Define the GPIO masks that will be used to configure the GPIO registers. See below the masks
for the BCM2837 processor:
#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26
[ 121 ]
Platform Drivers Chapter 5
/*
* To select GPIO pin 22, you will set to 0b111 the bits 8-6 (FSEL22) of the
* GPIO Alternate function select register 2. The GPIO pin 22 will control the green LED
*/
#define FSEL_22_MASK 0b111 << ((GPIO_22 % 10) * 3)
/*
* To select GPIO pin 26, you will set to 0b111 the bits 20-18 (FSEL26) of the
* GPIO Alternate function select register 2. The GPIO pin 26 will control the blue LED
*/
#define FSEL_26_MASK 0b111 << ((GPIO_26 % 10) * 3) /* blue since bit 18 (FSEL26) */
4. Declare the __iomen pointers that will hold the virtual addresses returned by the ioremap()
function:
static void __iomem *GPFSEL2_V;
static void __iomem *GPSET0_V;
static void __iomem *GPCLR0_V;
5. You will create an led_dev private structure to hold each device-specific information. In this
driver, you will handle multiple char devices, so a miscdevice structure will be created for each
device, then initialized and added to your device-specific data structure. The second field of the
data structure is an led_mask variable that will hold a red, green or blue mask depending on the
device. The last field of the private structure is a char array that will hold the command sent by
the user space application to turn the LED on/off.
struct led_dev
{
struct miscdevice led_misc_device; /* assign device for each led */
u32 led_mask; /* different mask if led is R,G or B */
[ 122 ]
Chapter 5 Platform Drivers
6. Now, in your probe() routine, declare an instance of the private structure, and allocate it for
each new probed device. The probe() function will be called three times (once per each DT node
(ledred, ledgreen and ledblue) that matches its compatible property value with the compatible field
value of the driver), allocating the corresponding devices.
struct led_dev *led_device;
led_device = devm_kzalloc(&pdev->dev, sizeof(struct led_dev), GFP_KERNEL);
7. In the led_init() routine, obtain the virtual addresses of the register addresses by using the
ioremap() function, and store them in the __iomem pointers:
GPFSEL2_V = ioremap(GPFSEL2, sizeof(u32));
GPSET0_V = ioremap(GPSET0, sizeof(u32));
GPCLR0_V = ioremap(GPCLR0, sizeof(u32));
8. Initialize each miscdevice structure within the probe() routine. As you have seen in the
Chapter 4, the miscellaneous framework provides a simple layer above character files
for devices that don’t have any other framework to connect to. Registering with the misc
subsystem simplifies the creation of a character file. The of_property_read_string() function
will find and read a string from the label property of each probed DT device node. The
third argument of the function is a pointer to a char pointer variable. The of_property_read_
string() function will store the "label" string in the address pointed by the led_name pointer
variable.
of_property_read_string(pdev->dev.of_node, "label", &led_device->led_name);
led_device->led_misc_device.minor = MISC_DYNAMIC_MINOR;
led_device->led_misc_device.name = led_device->led_name;
led_device->led_misc_device.fops = &led_fops;
9. When you are creating a character file, a file_operations structure is needed to define which
driver functions to call when a user opens, closes, reads and writes to the char file. This
structure will be stored in the miscdevice structure and passed to the misc subsystem when
you register a device to it. One thing to note is that when you use the misc subsystem, it
will automatically handle the "open" function for you. Inside the automatically created
"open" function, it will tie the miscdevice structure to the private data field of the file that’s
been opened. This is useful to get access to the parent structure of the miscdevice structure
by using the container_of() macro.
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.read = led_read,
.write = led_write,
};
[ 123 ]
Platform Drivers Chapter 5
10. In the probe() routine, register each device with the kernel by using the misc_register()
function. The platform_set_drvdata() function will attach each private structure to each
platform_device structure. This will allow you to access your private data structure in other
functions of the driver. You will recover the private structure in each remove() function call
(called three times) by using the platform_get_drvdata() function:
ret_val = misc_register(&led_device->led_misc_device);
platform_set_drvdata(pdev, led_device);
11. Create the led_write() function that gets called whenever a write operation occurs on
one of the character files. At the time you registered each misc device, you didn’t keep
any pointer to the led_dev structure. However, as the miscdevice structure is accessible
through file->private_data and is a member of the lev_dev structure, you can use a magic
macro to compute the address of the parent structure; the container_of() macro gets the
structure that miscdevice is stored inside of (which is your led_dev structure). The copy_
from_user() function will get the on/off command from user space, then you will write to
the corresponding register of the processor to switch on/off the LED using the iowrite32()
function:
static ssize_t led_write(struct file *file, const char __user *buff,
size_t count, loff_t *ppos)
{
const char *led_on = "on";
const char *led_off = "off";
struct led_dev *led_device;
led_device = container_of(file->private_data,
struct led_dev, led_misc_device);
led_device->led_value[count-1] = '\0';
if(!strcmp(led_device->led_value, led_on)) {
iowrite32(led_device->led_mask, GPSET0_V);
}
else if (!strcmp(led_device->led_value, led_off)) {
iowrite32(led_device->led_mask, GPCLR0_V);
}
else {
pr_info("Bad value\n");
return -EINVAL;
[ 124 ]
Chapter 5 Platform Drivers
return count;
}
12. Create the led_read() function that gets called whenever a read operation occurs on one
of the character device files. You will use the container_of() macro to recover the private
structure and the copy_to_user() function to return the led_value variable (on/off) to the user
application:
static ssize_t led_read(struct file *file, char __user *buff,
size_t count, loff_t *ppos)
{
struct led_dev *led_device;
if(*ppos == 0) {
if(copy_to_user(buff, &led_device->led_value,
sizeof(led_device->led_value))) {
pr_info("Failed to return led_value to user space\n");
return -EFAULT;
}
*ppos+=1;
return sizeof(led_device->led_value);
}
return 0;
}
13. Declare a list of devices supported by the driver. Create an array of structures of type
of_device_id where you initialize with strings the compatible fields that will be used
by the kernel to bind your driver with the compatible Device Tree devices. This will
automatically trigger your driver´s probe() function if the Device Tree contains a compatible
device entry (the probing happens three times in this driver).
static const struct of_device_id my_of_ids[] = {
{ .compatible = " arrow,RGBleds"},
{},
}
MODULE_DEVICE_TABLE(of, my_of_ids);
14. Add a platform_driver structure that will be registered to the platform bus:
static struct platform_driver led_platform_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "RGBleds",
.of_match_table = my_of_ids,
[ 125 ]
Platform Drivers Chapter 5
.owner = THIS_MODULE,
}
};
15. In the init() function, register your driver with the platform bus driver by using the
platform_driver_register() function:
platform_driver_register(&led_platform_driver);
16. Create a new ledRGB_rpi3_platform.c file in the linux_5.4_rpi3_drivers folder, and add ledRGB_
rpi3_platform.o to your Makefile obj-m variable, then build and deploy the module to the
Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
17. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb root@10.0.0.10:/boot/
struct led_dev
{
struct miscdevice led_misc_device;
u32 led_mask; /* different mask if led is R,G or B */
const char *led_name;
char led_value[8];
};
#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26
[ 126 ]
Chapter 5 Platform Drivers
pr_info("led_write() is called.\n");
/*
* In the command line “echo” add a \n character.
* led_device->led_value = "on\n" or "off\n after copy_from_user()"
*/
if(copy_from_user(led_device->led_value, buff, count)) {
pr_info("Bad copied value\n");
return -EFAULT;
}
if(!strcmp(led_device->led_value, led_on)) {
[ 127 ]
Platform Drivers Chapter 5
iowrite32(led_device->led_mask, GPSET0_V);
}
else if (!strcmp(led_device->led_value, led_off)) {
iowrite32(led_device->led_mask, GPCLR0_V);
}
else {
pr_info("Bad value\n");
return -EINVAL;
}
pr_info("led_write() is exit.\n");
return count;
}
static ssize_t led_read(struct file *file, char __user *buff, size_t count, loff_t *ppos)
{
struct led_dev *led_device;
pr_info("led_read() is called.\n");
if(*ppos == 0){
if(copy_to_user(buff, &led_device->led_value, sizeof(led_device->led_value))) {
pr_info("Failed to return led_value to user space\n");
return -EFAULT;
}
*ppos+=1;
return sizeof(led_device->led_value);
}
pr_info("led_read() is exit.\n");
return 0;
}
pr_info("leds_probe enter\n");
[ 128 ]
Chapter 5 Platform Drivers
led_device->led_misc_device.name = led_device->led_name;
led_device->led_misc_device.fops = &led_fops;
if (strcmp(led_device->led_name,"ledred") == 0) {
led_device->led_mask = GPIO_27_INDEX;
}
else if (strcmp(led_device->led_name,"ledgreen") == 0) {
led_device->led_mask = GPIO_22_INDEX;
}
else if (strcmp(led_device->led_name,"ledblue") == 0) {
led_device->led_mask = GPIO_26_INDEX;
}
else {
pr_info("Bad device tree value\n");
return -EINVAL;
}
ret_val = misc_register(&led_device->led_misc_device);
if (ret_val) return ret_val; /* misc_register returns 0 if success */
platform_set_drvdata(pdev, led_device);
pr_info("leds_probe exit\n");
return 0;
}
pr_info("leds_remove enter\n");
misc_deregister(&led_device->led_misc_device);
pr_info("leds_remove exit\n");
return 0;
}
[ 129 ]
Platform Drivers Chapter 5
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
ret_val = platform_driver_register(&led_platform_driver);
if (ret_val !=0)
{
pr_err("platform value returned %d\n", ret_val);
return ret_val;
}
/*
* Set FSEL27, FSEL26 and FSEL22 of GPFSEL2 register to 0,
* keeping the value of the rest of GPFSEL2 bits,
* then set to 1 the first bit of FSEL27, FSEL26 and FSEL22 to set
* the direction of GPIO27, GPIO26 and GPIO22 to output
*/
GPFSEL_write = (GPFSEL_read & ~GPIO_MASK_ALL_LEDS) | (GPIO_SET_FUNCTION_LEDS & GPIO_MASK_
ALL_LEDS);
pr_info("demo_init exit\n");
return 0;
}
iounmap(GPFSEL2_V);
iounmap(GPSET0_V);
iounmap(GPCLR0_V);
platform_driver_unregister(&led_platform_driver);
[ 130 ]
Chapter 5 Platform Drivers
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <aliberal@arroweurope.com>");
MODULE_DESCRIPTION("This is a platform driver that turns on/off three led devices");
ledRGB_rpi3_platform.ko demonstration
Load the module. The probe() function is called three times:
root@raspberrypi:/home/pi# insmod ledRGB_rpi3_platform.ko
demo_init enter
leds_probe enter
leds_probe exit
leds_probe enter
leds_probe exit
leds_probe enter
leds_probe exit
demo_init exit
See the led devices that have been created:
root@raspberrypi:/home/pi# ls /dev/led*
/dev/ledblue /dev/ledgreen /dev/ledred
Switch ON and OFF the LEDs and check their status:
[ 131 ]
Platform Drivers Chapter 5
[ 132 ]
Chapter 5 Platform Drivers
The first parameter of the previous function tells the function which device you are
interested in so that it can extract the info needed. The second parameter depends on what
kind of resource you are handling. If it is memory (or anything that can be mapped as
memory), then it's IORESOURCE_MEM. You can see all the macros in include/linux/ioport.h
in the kernel source tree. The last parameter determines which resource of that type is
desired, with zero indicating the first one. Thus, for example, a driver could find and map
its second MMIO region using the following lines of code:
struct resource *r;
r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
The return value r is a pointer to the resource variable. The resource_size() function will
return from the resource structure the memory size that will be mapped:
static inline resource_size_t resource_size(const struct resource *res)
{
return res->end - res->start + 1;
}
2. platform_get_irq() -- This function extracts the resource structure from the platform_device
structure, retrieving one of the interrupts properties declared in the Device Tree node. This
function will be explained in more detail in the Chapter 7.
[ 133 ]
Platform Drivers Chapter 5
driver in the more general sense of the word. The device model has specific objects called "drivers"
but a "class" is not one of those.
All the devices in a particular class tend to expose much the same interface, either to other devices
or in user space (via sysfs or otherwise). Exactly how uniform the included devices are, is really
up to the class though. Some aspects of the interfaces are optional, and not all devices implement
them. It is not unheard-of for some devices in the same class to be completely different from
others.
The LED class supports the blinking, flashing and brightness control features of physical LEDs.
This class requires an underlying device to be available (/sys/class/leds/<device>/). This underlying
device must be able to turn the LED on or off, may be able to set the brightness and might even
provide timer functionality to autonomously blink the LED with a given period and duty cycle.
Using the brightness file under each device subdirectory, the appropriate LED could be set to
different brightness levels, for example, not just turned on and off but also dimmed. The data type
used for passing the brightness level, enum led_brightness, defines only the levels LED_OFF,
LED_HALF and LED_FULL:
enum led_brightness {
LED_OFF = 0,
LED_HALF = 127,
LED_FULL = 255,
};
The LED class introduces the optional concept of LED trigger. A trigger is a kernel based source
of LED events. The timer trigger is an example that will periodically change the LED brightness
between LED_OFF and the current brightness setting. The "on" and "off" time can be specified via
/sys/class/leds/<device>/delay_{on,off} sysfs entry in milliseconds. You can change the brightness
value of an LED independently of the timer trigger. However, if you set the brightness value to
LED_OFF, it will also disable the timer trigger.
A driver registering an LED class device will first allocate and fill an led_classdev structure
(declared in include/linux/leds.h in the kernel source tree), then it will call devm_led_classdev_
register(), which registers a new LED class object. The struct led_classdev is declared as follows:
struct led_classdev {
const char *name;
enum led_brightness brightness;
enum led_brightness max_brightness;
[...]
/*
* Set LED brightness level. Use brightness_set_blocking for drivers
* that can sleep while setting brightness.
*/
[ 134 ]
Chapter 5 Platform Drivers
/*
* Activate hardware accelerated blink, delays are in milliseconds
* and if both are zero then a sensible default should be chosen.
* The call should adjust the timings in that case and if it can't
* match the values specified exactly.
* Deactivate blinking again when the brightness is set to LED_OFF
* via the brightness_set() callback.
*/
int (*blink_set)(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off);
[...]
#ifdef CONFIG_LEDS_TRIGGERS
/* Protects the trigger data below */
struct rw_semaphore trigger_lock;
/*
* devm_led_classdev_register - resource managed led_classdev_register()
* @parent: The device to register.
* @led_cdev: the led_classdev structure for this device.
*/
int devm_led_classdev_register(struct device *parent,
struct led_classdev *led_cdev)
{
struct led_classdev **dr;
int rc;
[ 135 ]
Platform Drivers Chapter 5
rc = led_classdev_register(parent, led_cdev);
if (rc) {
devres_free(dr);
return rc;
}
*dr = led_cdev;
devres_add(parent, dr);
return 0;
}
[ 136 ]
Chapter 5 Platform Drivers
virtgpio: virtgpio {
compatible = "brcm,bcm2835-virtgpio";
gpio-controller;
#gpio-cells = <2>;
firmware = <&firmware>;
status = "okay";
};
[...]
ledclassRGB {
compatible = "arrow,RGBclassleds";
reg = <0x7e200000 0xb4>;
pinctrl-names = "default";
pinctrl-0 = <&led_pins>;
red {
label = "red";
};
green {
label = "green";
};
blue {
label = "blue";
linux,default-trigger = "heartbeat";
};
};
};
2. Define the GPIO masks that will be used to configure the GPIO registers of the SoC. You
will take the base address stored in the DT reg property as a reference, then you will add
an offset to it to set each register address. See below the masks for the BCM2837 processor:
#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26
[ 137 ]
Platform Drivers Chapter 5
/*
* To select GPIO pin 27, you will set to 0b111 the bits 23-21 (FSEL7) of the
* GPIO Alternate function select register 2. The GPIO pin 27 will control the red LED
*/
#define FSEL_27_MASK 0b111 << ((GPIO_27 % 10) * 3)
/*
* To select GPIO pin 22, you will set to 0b111 the bits 8-6 (FSEL22) of the
* GPIO Alternate function select register 2. The GPIO pin 22 will control the green LED
*/
#define FSEL_22_MASK 0b111 << ((GPIO_22 % 10) * 3)
/*
* To select GPIO pin 26, you will set to 0b111 the bits 20-18 (FSEL26) of the
* GPIO Alternate function select register 2. The GPIO pin 26 will control the blue LED
*/
#define FSEL_26_MASK 0b111 << ((GPIO_26 % 10) * 3) /* blue since bit 18 (FSEL26) */
#define GPIO_SET_FUNCTION_LEDS (GPIO_27_FUNC | GPIO_22_FUNC | GPIO_26_FUNC)
#define GPIO_MASK_ALL_LEDS (FSEL_27_MASK | FSEL_22_MASK | FSEL_26_MASK)
#define GPIO_SET_ALL_LEDS (GPIO_27_INDEX | GPIO_22_INDEX | GPIO_26_INDEX)
3. You will create a private structure to hold the the specific data of the RGB LED device. In
this driver, the first field of the private structure is an led_mask variable that will hold a
red, green or blue mask depending of the LED device under control. The second field of
the private structure is an __iomem pointer that will hold the GPIO register base address.
The last field of the private structure is an led_classdev structure that will be initialized with
some specific device settings. You will allocate a private structure for each sub-node device
found.
struct led_dev
{
u32 led_mask; /* different mask if led is R,G or B */
void __iomem *base;
struct led_classdev cdev;
};
[ 138 ]
Chapter 5 Platform Drivers
4. See below a code snippet, extracted from the probe() routine, with the main lines of code
marked in bold:
• The platform_get_resource() function gets the I/O registers resource described by the DT
reg property.
• The dev_ioremap() function maps the area of register addresses to kernel virtual
addresses.
• The for_each_child_of_node() function walks for each sub-node of the main node,
allocating a private structure for each one by using the devm_kzalloc() function, then
initializes the led_classdev field of each private structure.
• The devm_led_classdev_register() function registers each LED class device to the LED
subsystem.
static int ledclass_probe(struct platform_device *pdev)
{
void __iomem *g_ioremap_addr;
struct device_node *child;
struct resource *r;
u32 GPFSEL_read, GPFSEL_write;
struct device *dev = &pdev->dev;
int ret = 0;
int count;
pr_info("platform_probe enter\n");
[...]
for_each_child_of_node(dev->of_node, child) {
cdev = &led_device->cdev;
led_device->base = g_ioremap_addr;
of_property_read_string(child, "label", &cdev->name);
if (strcmp(cdev->name,"red") == 0) {
led_device->led_mask = GPIO_27_INDEX;
}
else if (strcmp(cdev->name,"green") == 0) {
led_device->led_mask = GPIO_22_INDEX;
[ 139 ]
Platform Drivers Chapter 5
}
else if (strcmp(cdev->name,"blue") == 0) {
led_device->led_mask = GPIO_26_INDEX;
}
else {
pr_info("Bad device tree value\n");
return -EINVAL;
}
pr_info("leds_probe exit\n");
return 0;
}
5. Write the led_control() function. Every time you write to the brightness sysfs entry (/sys/
class/leds/<device>/brightness) under each device, the led_control() function is called. The
LED subsystem hides the complexity of creating a class, the devices under the class, and
the sysfs entries under each of the devices. Every time you write to the brightness sysfs
entry, the container_of() function recovers the private structure associated with each device,
then you can write to each register by using the iowrite32() function, which takes as a first
parameter the led_mask value (stored in the private structure) associated to each LED. The
following code snippet shows the led_control() function:
static void led_control(struct led_classdev *led_cdev, enum led_brightness b)
{
struct led_dev *led = container_of(led_cdev, struct led_dev, cdev);
if (b != LED_OFF) /* LED ON */
iowrite32(led->led_mask, led->base + GPSET0_offset);
else
iowrite32(led->led_mask, led->base + GPCLR0_offset); /* LED OFF */
}
[ 140 ]
Chapter 5 Platform Drivers
8. In the init() function, register your driver with the platform bus by using the platform_
driver_register() function:
static int led_init(void)
{
ret_val = platform_driver_register(&led_platform_driver);
return 0;
}
10. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb root@10.0.0.10:/boot/
#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26
[ 141 ]
Platform Drivers Chapter 5
#define FSEL_27_MASK 0b111 << ((GPIO_27 % 10) * 3) /* red since bit 21 (FSEL27) */
#define FSEL_22_MASK 0b111 << ((GPIO_22 % 10) * 3) /* green since bit 6 (FSEL22) */
#define FSEL_26_MASK 0b111 << ((GPIO_26 % 10) * 3) /* blue since bit 18 (FSEL26) */
struct led_dev
{
u32 led_mask; /* there are different masks if led is R,G or B */
void __iomem *base;
struct led_classdev cdev;
};
if (b != LED_OFF) /* LED ON */
iowrite32(led->led_mask, led->base + GPSET0_offset);
else
iowrite32(led->led_mask, led->base + GPCLR0_offset); /* LED OFF */
}
pr_info("platform_probe enter\n");
[ 142 ]
Chapter 5 Platform Drivers
if (!r) {
pr_err("IORESOURCE_MEM, 0 does not exist\n");
return -EINVAL;
}
pr_info("r->start = 0x%08lx\n", (long unsigned int)r->start);
pr_info("r->end = 0x%08lx\n", (long unsigned int)r->end);
count = of_get_child_count(dev->of_node);
if (!count)
return -EINVAL;
for_each_child_of_node(dev->of_node, child) {
cdev = &led_device->cdev;
led_device->base = g_ioremap_addr;
of_property_read_string(child, "label", &cdev->name);
if (strcmp(cdev->name,"red") == 0) {
led_device->led_mask = GPIO_27_INDEX;
//led_device->cdev.default_trigger = "heartbeat";
}
else if (strcmp(cdev->name,"green") == 0) {
led_device->led_mask = GPIO_22_INDEX;
}
else if (strcmp(cdev->name,"blue") == 0) {
led_device->led_mask = GPIO_26_INDEX;
[ 143 ]
Platform Drivers Chapter 5
}
else {
pr_info("Bad device tree value\n");
return -EINVAL;
}
pr_info("leds_probe exit\n");
return 0;
}
return 0;
}
ret_val = platform_driver_register(&led_platform_driver);
if (ret_val !=0)
[ 144 ]
Chapter 5 Platform Drivers
{
pr_err("platform value returned %d\n", ret_val);
return ret_val;
}
pr_info("demo_init exit\n");
return 0;
}
platform_driver_unregister(&led_platform_driver);
module_init(ledRGBclass_init);
module_exit(ledRGBclass_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <aliberal@arroweurope.com>");
MODULE_DESCRIPTION("This is a driver that turns on/off RGB leds using the LED subsystem");
ledRGB_rpi3_class_platform.ko demonstration
Load the module:
root@raspberrypi:/home/pi# insmod ledRGB_rpi3_class_platform.ko
demo_init enter
platform_probe enter
r->start = 0x3f200000
r->end = 0x3f2000b3
there are 3 nodes
leds_probe exit
demo_init exit
See the devices under the LED class:
root@raspberrypi:/home/pi# ls /sys/class/leds/
blue default-on green led0 led1 mmc0 red
Switch ON and OFF the LEDs, and blink the green LED:
root@raspberrypi:/home/pi# echo 1 > /sys/class/leds/red/brightness
root@raspberrypi:/home/pi# echo 1 > /sys/class/leds/blue/brightness
root@raspberrypi:/home/pi# echo 1 > /sys/class/leds/green/brightness
root@raspberrypi:/home/pi# cd /sys/class/leds/green
root@raspberrypi:/sys/class/leds/green# ls
brightness device max_brightness power subsystem trigger uevent
root@raspberrypi:/sys/class/leds/green# echo timer > trigger
[ 145 ]
Platform Drivers Chapter 5
[ 146 ]
Chapter 5 Platform Drivers
The main advantages and disadvantages of using user space and kernel space drivers are
summarized below:
1. User space driver advantages:
• Easy to debug, as debug tools are more readily available for application development.
• User space services such as floating point are available.
• Device access is very efficient, as there is no system call required.
• The application API on Linux is very stable.
• The driver can be written in any language, not just C.
2. User space driver disadvantages:
• No access to the kernel frameworks and services.
• Interrupt handling cannot be done in user space. It must be handled by a kernel
driver.
• There is no predefined API to allow applications to access the device driver.
3. Kernel space driver advantages:
• Run in kernel space in the highest privilege mode to allow access to interrupts and
hardware resources.
• There are a lot of kernel services such that kernel space drivers can be designed for
complex devices.
• The kernel provides an API to user space which allows multiple applications to access
a kernel space driver simultaneously.
4. Kernel space driver disadvantages:
• System call overhead to access drivers.
• Challenging to debug.
• Frequent kernel API changes. Kernel drivers built for one kernel version may not build
for another.
[ 147 ]
Platform Drivers Chapter 5
The following image shows how a user space driver might be designed. The application interfaces
to the user space part of the driver. The user space part handles the hardware, but uses its kernel
space part for startup, shutdown and receiving interrupts.
[ 148 ]
Chapter 5 Platform Drivers
[ 149 ]
Platform Drivers Chapter 5
The UIO platform device driver can be replaced by a user provided kernel driver. The kernel space
driver is a platform driver configured from the Device Tree that registers a UIO device inside the
probe() function. The Device Tree node can use whatever you want in the compatible property, as
it only has to match with the compatible string used in the kernel space driver, as with any other
platform device driver.
The UIO drivers must be enabled in the kernel. Configure the kernel with menuconfig. Navigate
from the main menu -> Device Drivers -> Userspace I/O drivers. Hit <spacebar> once to see a <*>
appear next to the new configuration. Hit <Exit> until you exit the menuconfig GUI and remember
to save the new configuration. In the Building the Linux kernel section of Chapter 1, you enabled the
UIO drivers in the kernel, built the new kernel image and loaded it onto your Raspberry Pi.
[ 150 ]
Chapter 5 Platform Drivers
[ 151 ]
Platform Drivers Chapter 5
generated interrupt but want to trigger the interrupt handler in some other way, set
irq to UIO_IRQ_CUSTOM. If you had no interrupt at all, you could set irq to
UIO_IRQ_NONE, though this rarely makes sense.
• unsigned long irq_flags: Required if you’ve set irq to a hardware interrupt number.
The flags given here will be used in the call to request_irq().
• int (*mmap)(struct uio_info *info, struct vm_area_struct *vma): Optional. If you need
a special mmap() function, you can set it here. If this pointer is not NULL, your mmap()
will be called instead of the built-in one.
• int (*open)(struct uio_info *info, struct inode *inode): Optional. You might want to
have your own open(), e.g. to enable interrupts only when your device is actually used.
• int (*release)(struct uio_info *info, struct inode *inode): Optional. If you define your
own open(), you will probably also want a custom release() function.
• int (*irqcontrol)(struct uio_info *info, s32 irq_on): Optional. If you need to be able
to enable or disable interrupts from user space by writing to /dev/uioX, you can
implement this function.
Usually, your device will have one or more memory regions that can be mapped to user
space. For each region, you have to set up a uio_mem structure in the mem[] array. Here’s a
description of the fields of the uio_mem structure:
• int memtype: Required if mapping is used. Set this to UIO_MEM_PHYS if you have
physical memory to be mapped. Use UIO_MEM_LOGICAL for logical memory (for
example, allocated with kmalloc()). There’s also UIO_MEM_VIRTUAL for virtual
memory.
• unsigned long size: Fill in the size of the memory block that addr points to. If the size
is zero, the mapping is considered unused. Note that you must initialize size with zero
for all unused mappings.
• void *internal_addr: If you have to access this memory region from within your
kernel module, you will want to map it internally by using something like ioremap().
Addresses returned by this function cannot be mapped to user space, so you must not
store it in addr. Use internal_addr instead to remember such an address.
2. The function uio_register_device() connects the driver to the UIO framework:
• Requires a uio_info structure as an input.
• It is typically called from the probe() function of a platform device driver.
• It creates the device file /dev/uio# (#starting from 0) and all the associated sysfs file
attributes.
• The function uio_unregister_device() disconnects the driver from the UIO framework,
deleting the device file /dev/uio#.
[ 152 ]
Chapter 5 Platform Drivers
virtgpio: virtgpio {
compatible = "brcm,bcm2835-virtgpio";
gpio-controller;
#gpio-cells = <2>;
firmware = <&firmware>;
status = "okay";
};
[...]
UIO {
compatible = "arrow,UIO";
reg = <0x7e200000 0x1000>;
pinctrl-names = "default";
pinctrl-0 = <&led_pins>;
};
};
[ 153 ]
Platform Drivers Chapter 5
3. In the probe() function, the platform_get_resource() function returns the resource structure
filled with the values described by the DT reg property. The dev_ioremap() function maps
the area of register addresses to kernel virtual addresses:
struct resource *r;
void __iomem *g_ioremap_addr;
[ 154 ]
Chapter 5 Platform Drivers
}
};
9. Create a new led_rpi3_UIO_platform.c file in the linux_5.4_rpi3_drivers folder, and add led_
rpi3_UIO_platform.o to your Makefile obj-m variable, then build and deploy the module to
the Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
10. Build the modified Device Tree, and load it to the target processor:
~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb root@10.0.0.10:/boot/
The main code sections of the UIO user space driver will now be described:
1. Include the function headers:
#include <sys/mman.h> /* mmap() */
2. Define the path to the sysfs parameter, from where you will obtain the size of memory that
is going to be mapped:
#define UIO_SIZE "/sys/class/uio/uio0/maps/map0/size"
[ 155 ]
Platform Drivers Chapter 5
[ 156 ]
Chapter 5 Platform Drivers
return 0;
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <aliberal@arroweurope.com>");
MODULE_DESCRIPTION("This is a UIO platform driver that turns the LED on/off \
without using system calls");
#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26
[ 157 ]
Platform Drivers Chapter 5
#define FSEL_27_MASK 0b111 << ((GPIO_27 % 10) * 3) /* red since bit 21 (FSEL27) */
#define FSEL_22_MASK 0b111 << ((GPIO_22 % 10) * 3) /* green since bit 6 (FSEL22) */
#define FSEL_26_MASK 0b111 << ((GPIO_26 % 10) * 3) /* blue since bit 18 (FSEL26) */
int main()
{
int ret, devuio_fd;
int mem_fd;
unsigned int uio_size;
void *temp;
int GPFSEL_read, GPFSEL_write;
void *demo_driver_map;
char sendstring[BUFFER_LENGHT];
char *led_on = "on";
char *led_off = "off";
char *Exit = "exit";
[ 158 ]
Chapter 5 Platform Drivers
/* Do the mapping */
demo_driver_map = mmap(0, uio_size, PROT_READ | PROT_WRITE, MAP_SHARED, devuio_fd, 0);
if(demo_driver_map == MAP_FAILED) {
perror("devuio mmap error");
close(devuio_fd);
exit(EXIT_FAILURE);
}
if(strncmp(led_on, sendstring, 3) == 0)
{
temp = demo_driver_map + GPSET0_offset;
*(int *)temp = GPIO_27_INDEX;
}
else if(strncmp(led_off, sendstring, 2) == 0)
{
temp = demo_driver_map + GPCLR0_offset;
*(int *)temp = GPIO_27_INDEX;
}
else if(strncmp(Exit, sendstring, 4) == 0)
printf("Exit application\n");
else {
printf("Bad value\n");
return -EINVAL;
}
[ 159 ]
Platform Drivers Chapter 5
close(devuio_fd);
printf("Application termined\n");
exit(EXIT_SUCCESS);
}
[ 160 ]