KEMBAR78
Chapter 5 | PDF | Device Driver | Kernel (Operating System)
0% found this document useful (0 votes)
33 views72 pages

Chapter 5

Uploaded by

duyhuy362003
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
33 views72 pages

Chapter 5

Uploaded by

duyhuy362003
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 72

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

int (*remove)(struct platform_device *);


void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};

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;
}

static void hello_exit(void)


{
pr_info("demo_exit enter\n");
platform_driver_unregister(&my_platform_driver);
pr_info("demo_exit exit\n");
}

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)

LAB 5.1: "platform device" module


The functionality of this platform driver is the same as the misc char driver, but this time you
will register your char device in the probe() function instead of the init() function. When the kernel
module is loaded, the platform device driver registers itself with the platform bus driver by using
the platform_driver_register() function. The probe() function is called when the platform device driver
matches the value of one of its compatible char strings (included in one of its of_device_id structures)
with the compatible property value of the DT device node. The process of associating a device with
a device driver is called binding.
The of_device_id structure is declared in include/linux/mod_devicetable.h in the kernel source tree:
/*
* Struct used for matching a device
*/
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};

The main code sections of the driver will now be described:


1. Include the platform device header file which contains the structure and function
declarations required by the platform devices/drivers:
#include <linux/platform_device.h>

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;
}

static int __exit my_remove(struct platform_device *pdev)


{
pr_info("my_remove() function is called.\n");
misc_deregister(&helloworld_miscdevice);
return 0;
}

5. Register your platform driver to the platform bus:


module_platform_driver(my_platform_driver);

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/

9. Reboot the Raspberry Pi:


root@raspberrypi:/home/pi# reboot

Listing 5-1: hellokeys_rpi3.c


#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>

static int my_dev_open(struct inode *inode, struct file *file)


{
pr_info("my_dev_open() is called.\n");
return 0;
}

static int my_dev_close(struct inode *inode, struct file *file)


{
pr_info("my_dev_close() is called.\n");
return 0;
}

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;
}

static const struct file_operations my_dev_fops = {


.owner = THIS_MODULE,
.open = my_dev_open,

[ 93 ]
Platform Drivers Chapter 5

.release = my_dev_close,
.unlocked_ioctl = my_dev_ioctl,
};

static struct miscdevice helloworld_miscdevice = {


.minor = MISC_DYNAMIC_MINOR,
.name = "mydev",
.fops = &my_dev_fops,
};

/* Add probe() function */


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;
}

/* Add remove() function */


static int __exit my_remove(struct platform_device *pdev)
{
pr_info("my_remove() function is called.\n");
misc_deregister(&helloworld_miscdevice);
return 0;
}

/* Declare a list of devices supported by the driver */


static const struct of_device_id my_of_ids[] = {
{ .compatible = "arrow,hellokeys"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);

/* Create a platform_driver structure */


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,
}
};

[ 94 ]
Chapter 5 Platform Drivers

/* Register your platform driver */


module_platform_driver(my_platform_driver);

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'

Check the Raspberry Pi model:

root@raspberrypi:/home/pi# cat /proc/device-tree/model


Raspberry Pi 3 Model B Rev 1.2

Load the module. The probe() function is called:

root@raspberrypi:/home/pi# insmod hellokeys_rpi3


my_probe() function is called.
mydev: got minor 60

See "hellokeys" sysfs entries:

root@raspberrypi:/home/pi# find /sys -name "*hellokeys*"


/sys/devices/platform/soc/soc:hellokeys
/sys/firmware/devicetree/base/soc/hellokeys
/sys/bus/platform/devices/soc:hellokeys
/sys/bus/platform/drivers/hellokeys
/sys/bus/platform/drivers/hellokeys/soc:hellokeys
/sys/module/hellokeys_rpi3
/sys/module/hellokeys_rpi3/drivers/platform:hellokeys

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

Check that mydev is created under the misc class folder:

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# cat /sys/class/misc/mydev/dev


10:60

Run the application ioctl_test:

root@raspberrypi:/home/pi# ./ioctl_test
my_dev_open() is called.
my_dev_ioctl() is called. cmd = 100, arg = 110
my_dev_close() is called.

Remove the module:

root@raspberrypi:/home/pi# rmmod hellokeys_rpi3.ko


Hello world exit

Documentation to interact with the hardware


In the following drivers, you will interact with some of the peripheral registers of the processor, so
it will be necessary to download the Technical Reference Manual of the Raspberry Pi SoC to know
its peripheral addresses. Go to the Raspberry Pi site (located at www.raspberrypi.org), and download
the BCM2835 ARM Peripherals guide manual (located at https://www.raspberrypi.org/documentation/
hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf).

Hardware naming convention


A pin represents a physical input or output carrying an electrical signal. Every input or output
signal goes through a physical pin from or into a component. Contact pads are designated surface
areas of a printed circuit board or die of an integrated circuit. Some processors have a lot of
functionality, but a limited number of pins (or pads). Even though a single pin can only perform
one function at a time, it can be configured internally to perform different functions. This is called
pin multiplexing. Every processor includes a Pin Controller, which enables that one pad can share
several functional blocks. This sharing is done by multiplexing the pad's input and output signals,
as shown in the following image:

[ 96 ]
Chapter 5 Platform Drivers

Pin control subsystem


In the old Linux pin muxing code, each architecture had its own pin-muxing code with a specific
API. A lot of similar functionality was implemented in different ways. The pin-muxing had to be
done at the SoC level, and it couldn´t be requested by device drivers.
The Pinctrl subsystem aims at solving these problems. It is implemented in drivers/pinctrl/ in the
kernel source tree and provides:
• An API to register a pinctrl driver, for example, entities knowing the list of pins, their
functions and how to configure them. This API is used by specific drivers of the SoC,
for example, pinctrl-bcm2835.c (located in drivers/pinctrl/bcm/ in the kernel source tree) to
expose pin-muxing capabilities.
• An API for device drivers to request the muxing of a certain set of pins.
• Interaction with the GPIO drivers of the SoC.
Most pinctrl drivers provide a Device Tree binding, and the pin muxing must be described in the
Device Tree. The exact Device Tree binding depends on each pinctrl driver. For the Broadcom
BCM2835 GPIO (and pinmux) controller, the Device Tree binding is described in Documentation/
devicetree/bindings/pinctrl/brcm,bcm2835-gpio.txt in the kernel source tree. In the next image, you can
see the interaction of the BCM2837 pinctrl driver with the Device Tree and the Pinctrl, GPIO and
IRQ subsystems:

[ 97 ]
Platform Drivers Chapter 5

The Pinctrl subsystem in Linux deals with:


• Enumerating and naming controllable pins.
• Multiplexing of pins. In the SoC, several pins can also form a pin group for a specific
function. The pin control subsystem must manage all pin groups.
• Configuration of pins, such as software-controlled biasing, and driving mode specific pins,
such as pull-up/down, open drain, load capacitance, etc.

[ 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
};

Following is a brief description of few important fields of the pinctrl_desc structure:


1. struct pinctrl_ops: Sometimes, it is necessary to combine several pins to achieve specific
functions, such as SPI interface, I2C interface, etc. Therefore the Pin Controller needs
to access and control multiple pins by group. The pin group is accessed and operated
through the interface defined by pinctrl_ops. The pinctrl_ops structure is declared as follows:
/*
* struct pinctrl_ops - global pin control operations, to be implemented by
* pin controller drivers.
* @get_groups_count: Returns the count of total number of groups registered.
* @get_group_name: return the group name of the pin group
* @get_group_pins: return an array of pins corresponding to a certain
* group selector @pins, and the size of the array in @num_pins

[ 100 ]
Chapter 5 Platform Drivers

* @pin_dbg_show: optional debugfs display hook that will provide per-device


* info for a certain pin in debugfs
* @dt_node_to_map: parse a device tree "pin configuration node", and create
* mapping table entries for it. These are returned through the @map and
* @num_maps output parameters. This function is optional, and may be
* omitted for pinctrl drivers that do not support device tree.
* @dt_free_map: free mapping table entries created via @dt_node_to_map. The
* top-level @map pointer must be freed, along with any dynamically
* allocated members of the mapping table entries themselves. This
* function is optional, and may be omitted for pinctrl drivers that do
* not support device tree.
*/
struct pinctrl_ops {
int (*get_groups_count) (struct pinctrl_dev *pctldev);
const char *(*get_group_name) (struct pinctrl_dev *pctldev,
unsigned selector);
int (*get_group_pins) (struct pinctrl_dev *pctldev,
unsigned selector,
const unsigned **pins,
unsigned *num_pins);
void (*pin_dbg_show) (struct pinctrl_dev *pctldev, struct seq_file *s,
unsigned offset);
int (*dt_node_to_map) (struct pinctrl_dev *pctldev,
struct device_node *np_config,
struct pinctrl_map **map, unsigned *num_maps);
void (*dt_free_map) (struct pinctrl_dev *pctldev,
struct pinctrl_map *map, unsigned num_maps);
};

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

* per-device info for a certain group in debugfs


* @pin_config_config_dbg_show: optional debugfs display hook that will decode
* and display a driver's pin configuration parameter
*/
struct pinconf_ops {
#ifdef CONFIG_GENERIC_PINCONF
bool is_generic;
#endif
int (*pin_config_get) (struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long *config);
int (*pin_config_set) (struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long *configs,
unsigned num_configs);
int (*pin_config_group_get) (struct pinctrl_dev *pctldev,
unsigned selector,
unsigned long *config);
int (*pin_config_group_set) (struct pinctrl_dev *pctldev,
unsigned selector,
unsigned long *configs,
unsigned num_configs);
void (*pin_config_dbg_show) (struct pinctrl_dev *pctldev,
struct seq_file *s,
unsigned offset);
void (*pin_config_group_dbg_show) (struct pinctrl_dev *pctldev,
struct seq_file *s,
unsigned selector);
void (*pin_config_config_dbg_show) (struct pinctrl_dev *pctldev,
struct seq_file *s,
unsigned long config);
};

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

* @get_function_name: return the function name of the muxing selector,


* called by the core to figure out which mux setting it shall map a
* certain device to
* @get_function_groups: return an array of groups names (in turn
* referencing pins) connected to a certain function selector. The group
* name can be used with the generic @pinctrl_ops to retrieve the
* actual pins affected. The applicable groups will be returned in
* @groups and the number of groups in @num_groups
* @set_mux: enable a certain muxing function with a certain pin group. The
* driver does not need to figure out whether enabling this function
* conflicts some other use of the pins in that group, such collisions
* are handled by the pinmux subsystem. The @func_selector selects a
* certain function whereas @group_selector selects a certain set of pins
* to be used. On simple controllers the latter argument may be ignored
* @gpio_request_enable: requests and enables GPIO on a certain pin.
* Implement this only if you can mux every pin individually as GPIO. The
* affected GPIO range is passed along with an offset(pin number) into that
* specific GPIO range - function selectors and pin groups are orthogonal
* to this, the core will however make sure the pins do not collide.
* @gpio_disable_free: free up GPIO muxing on a certain pin, the reverse of
* @gpio_request_enable
* @gpio_set_direction: Since controllers may need different configurations
* depending on whether the GPIO is configured as input or output,
* a direction selector function may be implemented as a backing
* to the GPIO controllers that need pin muxing.
* @strict: do not allow simultaneous use of the same pin for GPIO and another
* function. Check both gpio_owner and mux_owner strictly before approving
* the pin request.
*/
struct pinmux_ops {
int (*request) (struct pinctrl_dev *pctldev, unsigned offset);
int (*free) (struct pinctrl_dev *pctldev, unsigned offset);
int (*get_functions_count) (struct pinctrl_dev *pctldev);
const char *(*get_function_name) (struct pinctrl_dev *pctldev,
unsigned selector);
int (*get_function_groups) (struct pinctrl_dev *pctldev,
unsigned selector,
const char * const **groups,
unsigned *num_groups);
int (*set_mux) (struct pinctrl_dev *pctldev, unsigned func_selector,
unsigned group_selector);
int (*gpio_request_enable) (struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset);
void (*gpio_disable_free) (struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset);
int (*gpio_set_direction) (struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset,
bool input);
bool strict;
};

[ 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

struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,


struct device *dev, void *driver_data)
{
struct pinctrl_dev *pctldev;
int error;

pctldev = pinctrl_init_controller(pctldesc, dev, driver_data);


if (IS_ERR(pctldev))
return pctldev;

error = pinctrl_enable(pctldev);
if (error)
return ERR_PTR(error);

return pctldev;

The pinctrl_init_controller() function is used to describe the information of a Pin Controller,


including the description of its supported pins, its pin multiplexing operation interfaces and its
group related pin configuration interfaces. The pinctrl_init_controller() function will create a struct
pin_desc for each pin and add it to the cardinal tree pinctrl_dev->pin_desc_tree. It will also check
whether the callback operations in the pinmux_ops, pinctrl_ops and pinconf_ops variables are defined
and do legality detection.
The pinctrl_enable function mainly adds the pinctrl device to the pinctrldev_list. At this time, the
registration of the pinctrl device driver is basically completed.

[ 105 ]
Platform Drivers Chapter 5

GPIO controller driver


In the Linux kernel, the GPIO subsystem is implemented by GPIOlib. GPIOlib is a framework that
provides an internal Linux kernel API for managing and configuring GPIOs, acting as a bridge
between the Linux GPIO controller drivers and the Linux GPIO client drivers.
After years of development, the GPIOlib subsystem has produced two different mechanisms
for gpio management, one is based on gpio num, and the other is based on gpio_desc descriptor
form. The former mechanism has been abandoned due to various problems, but for backward
compatibility, the kernel retains this mechanism, but for new gpio drivers is highly recommended
to use the new interface.
Inside each GPIO controller driver, individual GPIOs are identified by their hardware number,
which is a unique number between 0 and n, n being the number of GPIOs managed by the chip.
This number is only internal to the driver. In addition to this internal number, each GPIO will also
have a global number which can be used with the legacy GPIO interface. Each GPIO chip has a
"base" number, and the GPIO global number will be calculated adding a hardware number to this
base number. Although the integer representation is considered deprecated, it still has many users
and thus needs to be maintained.
Each GPIO controller driver needs to include the header linux/gpio/driver.h, which defines the
different structures that can be used to develop a GPIO controller driver.
The main structure of a GPIO controller driver is struct gpio_chip (declared in include/linux/gpio/
driver.h in the kernel source tree), which includes GPIO operations specific to each GPIO controller
of a SoC:
• Methods to establish GPIO line direction.
• Methods used to access GPIO line values.
• Method to set the electrical configuration of a given GPIO line.
• Method to return the IRQ number associated with a given GPIO line.
• Flag saying whether calls to its methods may sleep.
• Optional line names array to identify lines.
• Optional debugfs dump method (showing extra state like pullup config).
• Optional base number (will be automatically assigned if omitted).
• Optional label for diagnostics and GPIO chip mapping using platform data.
See below the GPIO operations for the BCM2837 SoC, extracted from the pinctrl-bcm2835.c pinctrl
driver:
static const struct gpio_chip bcm2835_gpio_chip = {
.label = MODULE_NAME,
.owner = THIS_MODULE,
.request = gpiochip_generic_request,

[ 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;

[...]

err = gpiochip_add_data(&pc->gpio_chip, pc);


if (err) {
dev_err(dev, "could not add GPIO chip\n");
return err;
}

[...]

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

GPIO irqchips are usually included in one of three categories:


1. CHAINED GPIO irqchips: These are usually the type that is embedded on a SoC. This
means that there is a fast IRQ flow handler for the GPIOs that gets called in a chain from
the parent IRQ handler, most typically the system interrupt controller. This means that the
GPIO irqchip handler will be called immediately from the parent irqchip while holding
the IRQs disabled. The GPIO irqchip will then end up calling something like this sequence
in its interrupt handler:
static irqreturn_t foo_gpio_irq(int irq, void *data)
chained_irq_enter(...);
generic_handle_irq(...);
chained_irq_exit(...);

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;

for (i = 0; i < BCM2835_NUM_IRQS; i++) {


if (chip->irq.parents[i] == irq) {
group = i;
break;
}
}
/* This should not happen, every IRQ has a bank */
if (i == BCM2835_NUM_IRQS)
BUG();

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

static void bcm2835_gpio_irq_handle_bank(struct bcm2835_pinctrl *pc,


unsigned int bank, u32 mask)
{
unsigned long events;
unsigned offset;
unsigned gpio;

events = bcm2835_gpio_rd(pc, GPEDS0 + bank * 4);


events &= mask;
events &= pc->enabled_irq_map[bank];
for_each_set_bit(offset, &events, 32) {
gpio = (32 * bank) + offset;
generic_handle_irq(irq_linear_revmap(pc->gpio_chip.irq.domain, gpio));
}
}

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

GPIO descriptor consumer interface


This section describes the descriptor-based GPIO interface. For a description of the deprecated
integer-based GPIO interface, please refer to gpio-legacy.txt under Documentation/gpio/ folder.
All the functions that work with the descriptor-based GPIO interface are prefixed with gpiod_.
The gpio_ prefix is used for the legacy interface. No other function in the kernel should use these
prefixes. The use of the legacy functions is strongly discouraged, new code should use <linux/gpio/
consumer.h> and descriptors exclusively.

Obtaining and disposing GPIOs


The GPIO descriptor interface identifies each GPIO through a gpio_desc structure that is returned
by using the devm_gpiod_get() function. This function takes as parameters: the GPIO consumer
device (dev), the function within the GPIO consumer (con_id) and different optional GPIO
initialization flags (flags):
struct gpio_desc *devm_gpiod_get(struct device *dev, const char *con_id,
enum gpiod_flags flags)

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

int gpiod_direction_input(struct gpio_desc *desc);


int gpiod_direction_output(struct gpio_desc *desc, int value);

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

GPIOs mapped to IRQs


An interrupt request can come in over a GPIO. You can get the Linux IRQ number corresponding
to a given GPIO by using the following function:
int gpiod_to_irq(const struct gpio_desc *desc)

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

GPIOs in the Device Tree


GPIOs are mapped to devices and functions in the Device Tree. The exact way to do it depends on
the GPIO controller providing the GPIOs (see the Device Tree bindings for your controller).
GPIOs mappings are defined in the consumer device node using a property named <function>-
gpios, where <function> is requested by the Linux driver by calling the gpiod_get() function. For
example:
foo_device {
compatible = "acme,foo";
...
led-gpios = <&gpioa 15 GPIO_ACTIVE_HIGH>, /* red */
<&gpioa 16 GPIO_ACTIVE_HIGH>, /* green */
<&gpioa 17 GPIO_ACTIVE_HIGH>; /* blue */

power-gpios = <&gpiob 1 GPIO_ACTIVE_LOW>;


};

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.

Exchanging data between kernel and user spaces


The Linux operating system prevents a user process from accessing another process and also
prevents processes to access or manipulate directly the kernel data structures and services. This
protection is achieved by separating the whole memory into two logical halves, the user and kernel
space. The system calls are the fundamental interface between an application and the Linux kernel.
The system calls are implemented in kernel space and their respective handlers are called through

[ 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.

MMIO (Memory-Mapped I/O) device access


A peripheral device is controlled by writing and reading its registers. Often, a device has multiple
registers that can be accessed at consecutive addresses either in the memory address space
(MMIO) or in the I/O address space (PIO). See below the main differences between Port I/O and
Memory-Mapped I/O:
1. MMIO
• Same address bus to address memory and I/O devices.
• Access to the I/O devices by using regular instructions.
• Most widely used I/O method across the different architectures supported by Linux.

[ 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.

LAB 5.2: "RGB LED platform device" module


In this lab, you will apply most of the concepts described so far during this chapter. You will
control several LEDs, mapping from physical to virtual several peripheral register addresses of the
SoC. You will create a character device per each LED by using the misc framework and control the

[ 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.

LAB 5.2 hardware description


The BCM2837 SoC has 54 general-purpose I/O (GPIO) lines split into two banks. All the GPIO
pins have at least two alternative functions within the SoC. The alternate functions are usually
peripheral IO and a single peripheral may appear in each bank to allow flexibility on the choice of
IO voltage. The GPIO has 41 registers. All accesses are assumed to be 32-bit.
The function select registers are used to define the operation of the general-purpose I/O pins.
Each of the 54 GPIO pins has at least two alternative functions. The FSEL{n} field determines the
functionality of the nth GPIO pin. All unused alternative function lines are tied to ground and will
output a "0" if selected.
The output set registers are used to set a GPIO pin. The SET{n} field defines the respective GPIO
pin to set, writing a "0" to the field has no effect. If the GPIO pin is being used as in input (by
default) then the value in the SET{n} field is ignored. However, if the pin is subsequently defined
as an output, then the bit will be set according to the last set/clear operation. Separating the set and
clear functions removes the need for read-modify-write operations.
The output clear registers are used to clear a GPIO pin. The CLR{n} field defines the respective
GPIO pin to clear, writing a "0" to the field has no effect. If the GPIO pin is being used as in input
(by default) then the value in the CLR{n} field is ignored. However, if the pin is subsequently
defined as an output, then the bit will be set according to the last set/clear operation. Separating
the set and clear functions removes the need for read-modify-write operations.
For further information about GPIO pins available on BCM2837 SoC, you can see the section 6
General Purpose I/O (GPIO) of the BCM2835 ARM Peripherals guide manual.
You will use three pins of the BCM2837 SoC to control several LEDs. These pins must be
multiplexed as GPIOs in the Device Tree.
To obtain the GPIOs, you will use the Raspberry Pi 40-pin GPIO header, which is shown in the
next figure:

[ 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.

LAB 5.2 Device Tree description


As you have already seen in the previous Pin Controller section of this Chapter 5, the pin controller
allows the processor to share one pad with several functional blocks. The sharing is done by
multiplexing the PAD input/output signals. Since different modules require different PAD settings
(e.g., pull up, keeper), the pin controller controls also the PAD settings parameters. Hardware
modules whose signals are affected by pin configuration are designated "client devices". Again,
each client device must be represented as a node in the Device Tree, just like any other hardware
module. For a client device to operate correctly, certain pin controllers must set up certain specific
pin configurations. Each pin controller must be represented as a node in the Device Tree. The pin
controller of the BCM2837 SoC is declared in arch/arm/boot/dts/bcm283x.dtsi in the kernel source
tree, as you can see in the following code snippet:

[ 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>;

/* Defines common pin muxing groups


*
* While each pin can have its mux selected
* for various functions individually, some
* groups only make sense to switch to a
* particular function together.
*/
dpi_gpio0: dpi_gpio0 {
brcm,pins = <0 1 2 3 4 5 6 7 8 9 10 11
12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27>;
brcm,function = <BCM2835_FSEL_ALT2>;
};
emmc_gpio22: emmc_gpio22 {
brcm,pins = <22 23 24 25 26 27>;
brcm,function = <BCM2835_FSEL_ALT3>;
};

[...]

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

brcm,pins = <7 8 9 10 11>;


brcm,function = <BCM2835_FSEL_ALT0>;
};
spi0_gpio35: spi0_gpio35 {
brcm,pins = <35 36 37 38 39>;
brcm,function = <BCM2835_FSEL_ALT0>;
};

[...]

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

/* To set and clear each individual LED */


#define GPIO_27_INDEX 1 << (GPIO_27 % 32)
#define GPIO_22_INDEX 1 << (GPIO_22 % 32)
#define GPIO_26_INDEX 1 << (GPIO_26 % 32)

/* Select the output function */


#define GPIO_27_FUNC 1 << ((GPIO_27 % 10) * 3)
#define GPIO_22_FUNC 1 << ((GPIO_22 % 10) * 3)
#define GPIO_26_FUNC 1 << ((GPIO_26 % 10) * 3)

[ 121 ]
Platform Drivers Chapter 5

/* Mask the GPIO functions */


/*
* 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. Declare the physical I/O register addresses:


#define BCM2710_PERI_BASE 0x3F000000
#define GPIO_BASE (BCM2710_PERI_BASE + 0x200000)
#define GPFSEL2 GPIO_BASE + 0x08
#define GPSET0 GPIO_BASE + 0x1C
#define GPCLR0 GPIO_BASE + 0x28

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

const char *led_name; /* stores "label" string */


char led_value[8];
};

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

/* Pass file__operations structure to each created misc device */


led_device->led_misc_device.fops = &led_fops;

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);

if(copy_from_user(led_device->led_value, buff, count)) {


pr_info("Bad copied value\n");
return -EFAULT;
}

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;

led_device = container_of(file->private_data, struct led_dev, led_misc_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/

18. Reboot the Raspberry Pi:


root@raspberrypi:/home/pi# reboot

Listing 5-2: ledRGB_rpi3_platform.c


#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>

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 BCM2710_PERI_BASE 0x3F000000


#define GPIO_BASE (BCM2710_PERI_BASE + 0x200000) /* GPIO controller */

#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26

[ 126 ]
Chapter 5 Platform Drivers

/* To switch on/off each LED */


#define GPIO_27_INDEX 1 << (GPIO_27 % 32)
#define GPIO_22_INDEX 1 << (GPIO_22 % 32)
#define GPIO_26_INDEX 1 << (GPIO_26 % 32)

/* Select the output function */


#define GPIO_27_FUNC 1 << ((GPIO_27 % 10) * 3)
#define GPIO_22_FUNC 1 << ((GPIO_22 % 10) * 3)
#define GPIO_26_FUNC 1 << ((GPIO_26 % 10) * 3)

/* Mask the GPIO functions */


#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) */

#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)

#define GPFSEL2 GPIO_BASE + 0x08


#define GPSET0 GPIO_BASE + 0x1C
#define GPCLR0 GPIO_BASE + 0x28

/* Declare __iomem pointers that will keep virtual addresses */


static void __iomem *GPFSEL2_V;
static void __iomem *GPSET0_V;
static void __iomem *GPCLR0_V;

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;

pr_info("led_write() is called.\n");

led_device = container_of(file->private_data, struct led_dev, led_misc_device);

/*
* 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;
}

/* Replace \n with \0 in led_device->led_value */


led_device->led_value[count-1] = '\0';

pr_info("This message received from User Space: %s", led_device->led_value);

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");

led_device = container_of(file->private_data, struct led_dev, led_misc_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);
}

pr_info("led_read() is exit.\n");

return 0;
}

static const struct file_operations led_fops = {


.owner = THIS_MODULE,
.read = led_read,
.write = led_write,
};

static int led_probe(struct platform_device *pdev)


{
struct led_dev *led_device;
int ret_val;
char led_val[8] = "off\n";

pr_info("leds_probe enter\n");

led_device = devm_kzalloc(&pdev->dev, sizeof(struct led_dev), GFP_KERNEL);

of_property_read_string(pdev->dev.of_node, "label", &led_device->led_name);


led_device->led_misc_device.minor = MISC_DYNAMIC_MINOR;

[ 128 ]
Chapter 5 Platform Drivers

led_device->led_misc_device.name = led_device->led_name;
led_device->led_misc_device.fops = &led_fops;

if (strcmp(led_device->led_name,"ledred") == 0) {
led_device->led_mask = GPIO_27_INDEX;
}
else if (strcmp(led_device->led_name,"ledgreen") == 0) {
led_device->led_mask = GPIO_22_INDEX;
}
else if (strcmp(led_device->led_name,"ledblue") == 0) {
led_device->led_mask = GPIO_26_INDEX;
}
else {
pr_info("Bad device tree value\n");
return -EINVAL;
}

/* Initialize the led status to off */


memcpy(led_device->led_value, led_val, sizeof(led_val));

ret_val = misc_register(&led_device->led_misc_device);
if (ret_val) return ret_val; /* misc_register returns 0 if success */

platform_set_drvdata(pdev, led_device);

pr_info("leds_probe exit\n");

return 0;
}

static int led_remove(struct platform_device *pdev)


{
struct led_dev *led_device = platform_get_drvdata(pdev);

pr_info("leds_remove enter\n");

misc_deregister(&led_device->led_misc_device);

pr_info("leds_remove exit\n");

return 0;
}

static const struct of_device_id my_of_ids[] = {


{ .compatible = "arrow,RGBleds"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);

static struct platform_driver led_platform_driver = {


.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "RGBleds",

[ 129 ]
Platform Drivers Chapter 5

.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};

static int led_init(void)


{
int ret_val;
u32 GPFSEL_read, GPFSEL_write;
pr_info("demo_init enter\n");

ret_val = platform_driver_register(&led_platform_driver);
if (ret_val !=0)
{
pr_err("platform value returned %d\n", ret_val);
return ret_val;
}

GPFSEL2_V = ioremap(GPFSEL2, sizeof(u32));


GPSET0_V = ioremap(GPSET0, sizeof(u32));
GPCLR0_V = ioremap(GPCLR0, sizeof(u32));

GPFSEL_read = ioread32(GPFSEL2_V); /* read current value */

/*
* 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);

iowrite32(GPFSEL_write, GPFSEL2_V); /* set GPIOs to output */


iowrite32(GPIO_SET_ALL_LEDS, GPCLR0_V); /* switch off all the leds, output is low */

pr_info("demo_init exit\n");
return 0;
}

static void led_exit(void)


{
pr_info("led driver enter\n");
iowrite32(GPIO_SET_ALL_LEDS, GPCLR0_V); /* switch off all the leds */

iounmap(GPFSEL2_V);
iounmap(GPSET0_V);
iounmap(GPCLR0_V);

platform_driver_unregister(&led_platform_driver);

pr_info("led driver exit\n");


}

[ 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:

root@raspberrypi:/home/pi# echo on > /dev/ledblue


root@raspberrypi:/home/pi# echo on > /dev/ledred
root@raspberrypi:/home/pi# echo on > /dev/ledgreen

root@raspberrypi:/home/pi# echo off > /dev/ledgreen


root@raspberrypi:/home/pi# echo off > /dev/ledred

root@raspberrypi:/home/pi# cat /dev/ledblue


root@raspberrypi:/home/pi# cat /dev/ledgreen
Remove the module. The remove() function is called three times:
root@raspberrypi:/home/pi# rmmod ledRGB_rpi3_platform.ko
led driver enter
leds_remove enter
leds_remove exit
leds_remove enter
leds_remove exit
leds_remove enter
leds_remove exit
led driver exit

[ 131 ]
Platform Drivers Chapter 5

Platform driver resources


Each device managed by a particular driver typically uses different hardware resources (for
example, memory addresses for the I/O registers, DMA channels and IRQ lines).
The platform driver has access to the resources through a kernel API. These kernel functions
automatically read standard platform device parameters from the struct platform_device resource array
(declared in include/linux/platform_device.h in the kernel source tree). This resource array has been filled
with the resource properties (for example, reg, clocks and interrupts) of the DT device nodes. In the
resource-names.txt file, located in Documentation/devicetree/bindings/ in the kernel source tree, you can see
the different resource properties that can be accessed by index. The struct platform_device is declared as
follows:
struct platform_device {
const char *name;
u32 id;
struct device dev;
u32 num_resources;
struct resource *resource;
};

See below the declaration of the resource structure:


struct resource {
resource_size_t start; /* unsigned int (resource_size_t) */
resource_size_t end;
const char *name;
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
};

This is the meaning of each field of the resource structure:


• start/end: This represents where the resource begins/ends. For I/O or memory regions, it
represents where they begin/end. For IRQ lines, buses or DMA channels, start/end must
have the same value.
• flags: This is a mask that characterizes the type of resource, for example
IORESOURCE_MEM.
• name: This identifies or describes the resource.
There are a number of helper functions that get data out of the resource array:
1. platform_get_resource() -- Defined in drivers/base/platform.c in the kernel source tree, it
gets a resource for a device and returns a resource structure filled with the DT values of
the resource so that you can use these values later in the driver code, for example, to map
them in the virtual space by using devm_ioremap() if they are physical memory addresses

[ 132 ]
Chapter 5 Platform Drivers

(specified with IORESOURCE_MEM type). The platform_get_resource() function will


check all the array resources until the type resource is found, then the resource structure is
returned. See below the code of the platform_get_resource() function:
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type,
unsigned int num)
{
int i;
for (i = 0; i < dev->num_resources; i++) {
struct resource *r = &dev->resource[i];
if (type == resource_type(r) && num-- == 0)
return r;
}
return NULL;
}

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);

/* ioremap your memory region */


g_ioremap_addr = devm_ioremap(dev, r->start, resource_size(r));

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.

Linux LED class


The LED class will simplifies the development of drivers that control LEDs. A "class" is both the
implementation of a set of devices and the set of devices themselves. A class can be thought of as a

[ 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

void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness);


/*
* Set LED brightness level immediately - it can block the caller for
* the time required for accessing an LED device register.
*/
int (*brightness_set_blocking)(struct led_classdev *led_cdev,
enum led_brightness brightness);
/* Get LED brightness level */
enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);

/*
* 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;

struct led_trigger *trigger;


struct list_head trig_list;
void *trigger_data;
/* true if activated - deactivate routine uses it to do cleanup */
bool activated;
#endif

/* Ensures consistent access to the LED Flash Class device */


struct mutex led_access;
};

/*
* 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;

dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL);


if (!dr)
return -ENOMEM;

[ 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;
}

LAB 5.3: "RGB LED class" module


In the previous LAB 5.2, you switched on/off several LEDs, creating a char device for each LED
device by using the misc framework and writing to several GPIO registers. You used the write file
operation and the copy_from_user() function to transmit a char array (on/off command) from user to
kernel space.
In this LAB 5.3, you will use the LED subsystem to achieve something very similar to the LAB 5.2,
but this time, you will simplify the code and add more functionalities, such as blinking each LED
with a given period and duty cycle.

LAB 5.3 Device Tree description


In this LAB 5.3, you will keep the same DT GPIO multiplexing of the previous LAB 5.2, as you will
use the same processor pads to control the LEDs. In the LAB 5.2, a DT device node was declared
for each used LED, whereas in this LAB 5.3, you will declare a main LED RGB device node that includes
several sub-nodes, each one representing an individual LED.
You will use in this new driver the for_each_child_of_node() function, which walks through the sub-nodes
of the main node. Only the main node will have the compatible property, so after the matching between
the device and the driver, the probe() function will be called only once, retrieving the info included in all
the sub-nodes. The LED RGB device has a reg property that includes the GPIO register base address and
the size of the address region it is assigned. After the driver and the device are matched, the platform_get_
resource() function returns a resource structure filled with the values of the reg property so that you can
use these values later in the driver code, mapping them in the virtual space by using the devm_ioremap()
function.
Modify the bcm2710-rpi-3-b.dts Device Tree file by adding the following code in bold. The base
address (0x7e200000) of the reg property is the GPFSEL0 register address.
&soc {

[ 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";
};
};

};

LAB 5.3 code description of the "RGB LED class" 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(),
platform_get_resource() */
#include <linux/io.h> /* devm_ioremap(), iowrite32() */
#include <linux/of.h> /* of_property_read_string() */
#include <linux/leds.h> /* misc_register() */

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

/* Offsets added to the base address */


#define GPFSEL2_offset 0x08
#define GPSET0_offset 0x1C
#define GPCLR0_offset 0x28

/* Masks to switch on/off each LED */


#define GPIO_27_INDEX 1 << (GPIO_27 % 32)
#define GPIO_22_INDEX 1 << (GPIO_22 % 32)
#define GPIO_26_INDEX 1 << (GPIO_26 % 32)

/* Masks to select the output function */


#define GPIO_27_FUNC 1 << ((GPIO_27 % 10) * 3)
#define GPIO_22_FUNC 1 << ((GPIO_22 % 10) * 3)
#define GPIO_26_FUNC 1 << ((GPIO_26 % 10) * 3)

/*
* 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");

/* Get our first memory resource from device tree */


r = platform_get_resource(pdev, IORESOURCE_MEM, 0);

/* ioremap our memory region */


g_ioremap_addr = devm_ioremap(dev, r->start, resource_size(r));

[...]

for_each_child_of_node(dev->of_node, child) {

struct led_dev *led_device;


struct led_classdev *cdev;
led_device = devm_kzalloc(dev, sizeof(*led_device), GFP_KERNEL);

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;
}

/* Disable timer trigger until led is on */


led_device->cdev.brightness = LED_OFF;
led_device->cdev.brightness_set = led_control;

ret = devm_led_classdev_register(dev, &led_device->cdev);


}

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);

iowrite32(GPIO_SET_ALL_LEDS, led->base + GPCLR0_offset);

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 */
}

6. Declare a list of devices supported by the driver:


static const struct of_device_id my_of_ids[] = {
{ .compatible = "arrow,RGBclassleds"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);

7. Add a platform_driver structure that will be registered to the platform bus:

[ 140 ]
Chapter 5 Platform Drivers

static struct platform_driver led_platform_driver = {


.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "RGBclassleds",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};

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;
}

9. Create a new ledRGB_rpi3_class_platform.c file in the linux_5.4_rpi3_drivers folder, and add


ledRGB_rpi3_class_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/

11. Reboot the Raspberry Pi:


root@raspberrypi:/home/pi# reboot

Listing 5-3: ledRGB_rpi3_class_platform.c


#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/leds.h>

#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26

#define GPFSEL2_offset 0x08

[ 141 ]
Platform Drivers Chapter 5

#define GPSET0_offset 0x1C


#define GPCLR0_offset 0x28

/* Macros to switch on/off each LED */


#define GPIO_27_INDEX 1 << (GPIO_27 % 32)
#define GPIO_22_INDEX 1 << (GPIO_22 % 32)
#define GPIO_26_INDEX 1 << (GPIO_26 % 32)

/* Select the output function */


#define GPIO_27_FUNC 1 << ((GPIO_27 % 10) * 3)
#define GPIO_22_FUNC 1 << ((GPIO_22 % 10) * 3)
#define GPIO_26_FUNC 1 << ((GPIO_26 % 10) * 3)

#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) */

#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)

struct led_dev
{
u32 led_mask; /* there are different masks if led is R,G or B */
void __iomem *base;
struct led_classdev cdev;
};

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);

iowrite32(GPIO_SET_ALL_LEDS, led->base + GPCLR0_offset);

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 */
}

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");

/* Get the first memory resource from the Device Tree */


r = platform_get_resource(pdev, IORESOURCE_MEM, 0);

[ 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);

/* Map the memory region */


g_ioremap_addr = devm_ioremap(dev, r->start, resource_size(r));
if (!g_ioremap_addr) {
pr_err("ioremap failed \n");
return -ENOMEM;
}

count = of_get_child_count(dev->of_node);
if (!count)
return -EINVAL;

pr_info("there are %d nodes\n", count);

GPFSEL_read = ioread32(g_ioremap_addr + GPFSEL2_offset); /* read actual value */

GPFSEL_write = (GPFSEL_read & ~GPIO_MASK_ALL_LEDS) | (GPIO_SET_FUNCTION_LEDS & GPIO_MASK_


ALL_LEDS);

/* Set the direction of the GPIOs to output */


iowrite32(GPFSEL_write, g_ioremap_addr + GPFSEL2_offset);

/* Switch OFF all the LEDs */


iowrite32(GPIO_SET_ALL_LEDS, g_ioremap_addr + GPCLR0_offset);

for_each_child_of_node(dev->of_node, child) {

struct led_dev *led_device;


struct led_classdev *cdev;
led_device = devm_kzalloc(dev, sizeof(*led_device), GFP_KERNEL);
if (!led_device)
return -ENOMEM;

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;
}

/* Disable the timer trigger */


led_device->cdev.brightness = LED_OFF;
led_device->cdev.brightness_set = led_control;

ret = devm_led_classdev_register(dev, &led_device->cdev);


if (ret) {
dev_err(dev, "failed to register the led %s\n", cdev->name);
of_node_put(child);
return ret;
}
}

pr_info("leds_probe exit\n");

return 0;
}

static int ledclass_remove(struct platform_device *pdev)


{
pr_info("leds_remove enter\n");
pr_info("leds_remove exit\n");

return 0;
}

static const struct of_device_id my_of_ids[] = {


{ .compatible = "arrow,RGBclassleds"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);

static struct platform_driver led_platform_driver = {


.probe = ledclass_probe,
.remove = ledclass_remove,
.driver = {
.name = "RGBclassleds",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};

static int ledRGBclass_init(void)


{
int ret_val;
pr_info("demo_init enter\n");

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;
}

static void ledRGBclass_exit(void)


{
pr_info("led driver enter\n");

platform_driver_unregister(&led_platform_driver);

pr_info("led driver exit\n");


}

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

Remove the module:


root@raspberrypi:/home/pi# rmmod ledRGB_rpi3_class_platform.ko
led driver enter
leds_remove enter
leds_remove exit
led driver exit

Platform device drivers in user space


Device drivers in Linux, traditionally run in kernel space, but can also run in user space. It is not
always necessary to write a device driver, especially when no two applications will require an
exclusive access. The most useful example of this is a memory mapped device, but you can also do
this with devices in the I/O space.
The Linux user space provides several advantages for device drivers, including more robust and
flexible process management, standardized system call interface, simpler resource management,
large number of libraries for XML or other configuration methods and regular expression
parsing, among others. Each call to the kernel (system call) must perform a switch from user
mode to supervisor mode and then back again. This takes time, which can become a performance
bottleneck if the calls are frequent. It also makes applications more straightforward to debug
by providing memory isolation and independent restart. At the same time, while kernel space
applications need to conform to General Public License guidelines, user space applications are not
bound by such restrictions.
On the other hand, user space drivers have their own drawbacks. Interrupt handling is the biggest
challenge for a user space driver. The function handling an interrupt is called in privileged
execution mode, often called supervisor mode. User space drivers have no permission to execute
in privileged execution mode, making it impossible for user space drivers to implement an
interrupt handler. To deal with this problem, you can perform polling or have a small kernel space
driver handling only the interrupt. In the latter case, you can inform to the user space driver of an
interrupt, either by a blocking call, which unblocks when an interrupt occurs, or using a POSIX
signal to preempt the user space driver. If your driver must be accessible to multiple processes at
once and/or manage contention for a resource, then you also need to write a real device driver at
the kernel level, and a user space device driver will not be sufficient or even possible. Allocating
memory that can be used for DMA transfers is also non-trivial for user space drivers. In kernel
space, there are also frameworks that help solve device interdepencies.

[ 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.

User defined I/O: UIO


The Linux kernel provides a framework for developing user space drivers called UIO. This is a
generic kernel driver which allows you to write user space drivers that are able to access device
registers and handle interrupts.
There are two distinct UIO device drivers under drivers/uio/ folder in the kernel source tree:
1. UIO driver (drivers/uio.c):
• The UIO driver creates file attributes in the sysfs. These attributes describe the UIO
device. The UIO driver also maps the device memory into the process address space
using the mmap() function.
• A minimal kernel space driver uio_pdrv_genirq ("UIO platform driver with generic
interrupts") or user provided kernel driver is required to set up the UIO framework.
The uio.c driver contains common support routines that are used by the uio_pdrv_
genirq.c driver.

[ 148 ]
Chapter 5 Platform Drivers

2. UIO platform device driver (drivers/uio_pdev_genirq.c):


• It provides the required kernel space driver for UIO.
• It works with the Device Tree. The Device Tree node needs to set "generic-uio" in its
compatible property.
• The UIO platform device driver is configured from the Device Tree and registers a
UIO device.

[ 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.

How UIO works


Each UIO device is accessed through a device file and several sysfs attribute files. The device
file will be called /dev/uio0 for the first device, and /dev/uio1, /dev/uio2 and so on for subsequent
devices.
The UIO driver creates file attributes in the sys filesystem. The directory /sys/class/uio/ is the root
for all the file attributes. A separate numbered directory structure is created under /sys/class/uio/ for
each UIO device:

[ 150 ]
Chapter 5 Platform Drivers

1. First UIO device: /sys/class/uio/uio0.


2. The /sys/class/uio/uio0/name directory contains the name of the device which correlates to
the name in the struct uio_info structure.
3. The /sys/class/uio/uio0/maps directory has all the memory ranges for the device.
4. Each UIO device can make one or more memory regions available for memory mapping.
Each mapping has its own directory in sysfs, the first mapping appears as /sys/class/uio/
uioX/maps/map0/. Subsequent mappings create directories map1/, map2/ and so on. These
directories will only appear if the size of the mapping is not 0. Each mapX/ directory
contains four read-only files that show attributes of the memory:
• name: A string identifier for this mapping. This is optional, the string can be empty.
Drivers can set this to make it easier for user space to find the correct mapping.
• addr: The address of memory that can be mapped.
• size: The size, in bytes, of the memory pointed to by addr.
• offset: The offset, in bytes, that has to be added to the pointer returned by mmap() to
get to the actual device memory. This is important if the device's memory is not page
aligned. Remember that pointers returned by mmap() are always page aligned, so it is a
good practice to always add this offset.
Interrupts are handled by reading from /dev/uioX. A blocking read() from /dev/uioX will return
as soon as an interrupt occurs. You can also use select() on /dev/uioX to wait for an interrupt. The
integer value read from /dev/uioX represents the total interrupt count. You can use this number to
figure out if you missed some interrupts.
The device memory is mapped into the process address space by calling the mmap() function of the
UIO driver.

Kernel UIO API


The UIO API is small and simple to use:
1. The uio_info structure tells the framework the details of your driver. Some of the members
are required, others are optional. These are some of the uio_info members:
• const char *name: Required. The name of your driver as it will appear in sysfs. It is
recommended to use the name of your module for this.
• const char *version: Required. This string appears in /sys/class/uio/uioX/version.
• struct uio_mem mem[ MAX_UIO_MAPS ]: Required if you have memory that
can be mapped with mmap(). For each mapping you need to fill one of the uio_mem
structures. See the description below for details.
• long irq: Required. If your hardware generates an interrupt, it’s your modules task
to determine the irq number during initialization. If you don’t have a hardware

[ 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

LAB 5.4: "LED UIO platform" module


In this kernel module lab, you will develop a UIO user space driver that controls one of the LEDs
used in the previous lab. The main function of a UIO driver is to expose the hardware registers
to user space and does nothing within kernel space to control them. The LED will be controlled
directly from the UIO user space driver by accessing to the memory mapped registers of the
device. You will also write a kernel driver that obtains the register addresses from the Device Tree
and initializes the uio_info structure with these values. You will also register the UIO device within
the probe() function of the kernel driver.

LAB 5.4 Device Tree description


In this LAB 5.4, you will keep the same DT GPIO multiplexing of the previous LAB 5.3. In the LAB 5.3,
you declared a main LED RGB device node that includes several sub-nodes, each one representing
an individual LED. In this LAB 5.4, you will control only one LED, so you do not have to add any
sub-node under the main node.
Modify the bcm2710-rpi-3-b.dts Device Tree file by adding the following code in bold. The base
address (0x7e200000) of the reg property is the GPFSEL0 register address.
&soc {

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>;
};

};

LAB 5.4 code description of the "LED UIO platform" module


The main code sections of the user provided kernel driver will now be described:
1. Include the function headers:
#include <linux/module.h>

[ 153 ]
Platform Drivers Chapter 5

#include <linux/platform_device.h> /* platform_get_resource() */


#include <linux/io.h> /* devm_ioremap() */
#include <linux/uio_driver.h> /* struct uio_info, uio_register_device() */

2. Declare the uio_info structure:


static struct uio_info the_uio_info;

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;

/* Get your first memory resource from device tree */


r = platform_get_resource(pdev, IORESOURCE_MEM, 0);

/* Map your memory region and get virtual address */


g_ioremap_addr = devm_ioremap(dev, r->start, resource_size(r));

4. Initialize the uio_info structure:


the_uio_info.name = "led_uio";
the_uio_info.version = "1.0";
the_uio_info.mem[0].memtype = UIO_MEM_PHYS;
the_uio_info.mem[0].addr = r->start; /* physical address needed for the kernel user
mapping */
the_uio_info.mem[0].size = resource_size(r);
the_uio_info.mem[0].name = "demo_uio_driver_hw_region";
the_uio_info.mem[0].internal_addr = g_ioremap_addr; /* virtual address for internal
driver use */

5. Register the device to the UIO framework:


uio_register_device(&pdev->dev, &the_uio_info);

6. Declare a list of devices supported by the driver:


static const struct of_device_id my_of_ids[] = {
{ .compatible = "arrow,UIO"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);

7. Add a platform_driver structure that will be registered to the platform bus:


static struct platform_driver my_platform_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "UIO",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,

[ 154 ]
Chapter 5 Platform Drivers

}
};

8. Register your driver with the "Platform Driver" bus:


module_platform_driver(my_platform_driver);

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/

11. Reboot the Raspberry Pi:


root@raspberrypi:/home/pi# reboot

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"

3. Open the UIO device:


open("/dev/uio0", O_RDWR | O_SYNC);

4. Obtain the memory size that is going to be mapped:


FILE *size_fp = fopen(UIO_SIZE, "r");
fscanf(size_fp, "0x%08X", &uio_size);
fclose(size_fp);

5. Perform mapping. A pointer to a virtual address will be returned, that corresponds to


the r->start physical address obtained in the kernel space driver. You can now control the
LED by writing to the virtual register address pointed to by the returned pointer variable.
This user virtual address will be different to the kernel virtual address obtained with dev_
ioremap() and pointed to by the_uio_info.mem[0].internal_addr variable.
6. In the apps folder, you will create the UIO_app.c file and write the Listing 5-5 code on it.
Modify the Makefile file in the apps folder to compile and deploy the new application.

[ 155 ]
Platform Drivers Chapter 5

7. Compile the UIO_app.c application, and deploy it to the Raspberry Pi:


~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy.

Listing 5-4: led_rpi3_UIO_platform.c


#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/uio_driver.h>

static struct uio_info the_uio_info;

static int __init my_probe(struct platform_device *pdev)


{
int ret_val;
struct resource *r;
struct device *dev = &pdev->dev;
void __iomem *g_ioremap_addr;

dev_info(dev, "platform_probe enter\n");

/* Get the first memory resource from the Device Tree */


r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r) {
dev_err(dev, "IORESOURCE_MEM, 0 does not exist\n");
return -EINVAL;
}
dev_info(dev, "r->start = 0x%08lx\n", (long unsigned int)r->start);
dev_info(dev, "r->end = 0x%08lx\n", (long unsigned int)r->end);

/* ioremap the memory region and get the virtual address */


g_ioremap_addr = devm_ioremap(dev, r->start, resource_size(r));
if (!g_ioremap_addr) {
dev_err(dev, "ioremap failed \n");
return -ENOMEM;
}

/* Initialize uio_info struct uio_mem array */


the_uio_info.name = "led_uio";
the_uio_info.version = "1.0";
the_uio_info.mem[0].memtype = UIO_MEM_PHYS;
/* Physical address needed for the kernel user mapping */
the_uio_info.mem[0].addr = r->start;
the_uio_info.mem[0].size = resource_size(r);
the_uio_info.mem[0].name = "demo_uio_driver_hw_region";

/* Virtual address for internal driver use */


the_uio_info.mem[0].internal_addr = g_ioremap_addr;

/* Register the uio device */

[ 156 ]
Chapter 5 Platform Drivers

ret_val = uio_register_device(&pdev->dev, &the_uio_info);


if (ret_val != 0) {
dev_info(dev, "Could not register device \"led_uio\"...");
}
return 0;
}

static int __exit my_remove(struct platform_device *pdev)


{
uio_unregister_device(&the_uio_info);
dev_info(&pdev->dev, "platform_remove exit\n");

return 0;
}

static const struct of_device_id my_of_ids[] = {


{ .compatible = "arrow,UIO"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);

static struct platform_driver my_platform_driver = {


.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "UIO",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
module_platform_driver(my_platform_driver);

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");

Listing 5-5: UIO_app.c


#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>

#define BUFFER_LENGHT 128

#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26

[ 157 ]
Platform Drivers Chapter 5

#define GPFSEL2_offset 0x08


#define GPSET0_offset 0x1C
#define GPCLR0_offset 0x28

/* Masks to switch on/off each LED */


#define GPIO_27_INDEX 1 << (GPIO_27 % 32)
#define GPIO_22_INDEX 1 << (GPIO_22 % 32)
#define GPIO_26_INDEX 1 << (GPIO_26 % 32)

/* Masks to select the output function for each GPIO */


#define GPIO_27_FUNC 1 << ((GPIO_27 % 10) * 3)
#define GPIO_22_FUNC 1 << ((GPIO_22 % 10) * 3)
#define GPIO_26_FUNC 1 << ((GPIO_26 % 10) * 3)

#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) */

#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)

#define UIO_SIZE "/sys/class/uio/uio0/maps/map0/size"

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";

printf("Starting led example\n");

if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {


printf("can't open /dev/mem \n");
exit(-1);
}
printf("opened /dev/mem \n");

devuio_fd = open("/dev/uio0", O_RDWR | O_SYNC);


if (devuio_fd < 0){
perror("Failed to open the device");
exit(EXIT_FAILURE);
}
printf("opened /dev/ui0 \n");

/* Read the size that has to be mapped */

[ 158 ]
Chapter 5 Platform Drivers

FILE *size_fp = fopen(UIO_SIZE, "r");


fscanf(size_fp, "0x%x", &uio_size);
fclose(size_fp);
printf("the value is %d\n", uio_size);

/* 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);
}

GPFSEL_read = *(int *)(demo_driver_map + GPFSEL2_offset);

GPFSEL_write = (GPFSEL_read & ~GPIO_MASK_ALL_LEDS) | (GPIO_SET_FUNCTION_LEDS & GPIO_MASK_


ALL_LEDS);

/* Set GPIO direction to output */


*(int *)(demo_driver_map + GPFSEL2_offset) = GPFSEL_write;
/* Switch off all the LEDs, output is low */
*(int *)(demo_driver_map + GPCLR0_offset) = GPIO_SET_ALL_LEDS;

/* Control the LED */


do {
printf("Enter led value: on, off, or exit :\n");
scanf("%[^\n]%*c", sendstring);

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;
}

} while(strncmp(sendstring, "exit", strlen(sendstring)));

ret = munmap(demo_driver_map, uio_size);


if(ret < 0) {
perror("devuio munmap");
close(devuio_fd);
exit(EXIT_FAILURE);
}

[ 159 ]
Platform Drivers Chapter 5

close(devuio_fd);
printf("Application termined\n");
exit(EXIT_SUCCESS);
}

led_rpi3_UIO_platform.ko with UIO_app demonstration


Load the module:
root@raspberrypi:/home/pi# insmod led_rpi3_UIO_platform.ko
Start the UIO_app application, which turns on/off an LED:
root@raspberrypi:/home/pi# ./UIO_app
Starting led example
Enter led value: on, off, or exit :
on
Enter led value: on, off, or exit :
off
Enter led value: on, off, or exit :
exit
Exit application
Application termined
Remove the module:
root@raspberrypi:/home/pi# rmmod led_rpi3_UIO_platform.ko

[ 160 ]

You might also like