Zhengdian Atomic Embedded Linux Driver Development – Linux comes with LED driver

Previously, I wrote my own LED light driver. In fact, very basic device drivers like LED lights have been integrated into the Linux kernel. The LED light driver of the Linux kernel uses the platform framework, so you only need to add the corresponding LED nodes in the device tree file as required. This chapter will learn how to use the Linux kernel. The built-in LED driver is used to drive the two LED lights LED0 and LED1 on the STM32MP1 development board of Zhengdian Atom.

Linux kernel comes with LED driver enable

In the study of the previous note, I wrote a platform LED light driver based on the device tree. In fact, the Linux kernel already comes with an LED light driver. To use the LED light driver that comes with the Linux kernel, you must first configure the Linux kernel. For the built-in LED light driver, open the Linux configuration menu through “make menuconfig”, and then follow the following path to open the configuration item:

-> Device Drivers
-> LED Support (NEW_LEDS [=y])
-> LED Support for GPIO connected LEDs

Follow the above path, select “LED Support for GPIO connected LEDs” and compile it into the Linux kernel. Press the “Y” key on this option so that the front of this option changes to “<*> “,As shown below:
Enable LED driver
Press the “?” key on “LED Support for GPIO connected LEDs” to open the help information for this option, as shown in the following figure:
Internal LED light driver help information
As can be seen from the above figure, after compiling the LED light driver that comes with Linux into the kernel, CONFIG_LEDS_GPIO will be equal to ‘y’. Linux will choose how to compile the LED light driver based on the value of CONFIG_LEDS_GPIO. If it is ‘y’, it will It is compiled into the Linux kernel.

After configuring the Linux kernel, exit the configuration interface, open the .config file, and you will find the line “CONFIG_LEDS_GPIO=y”.

Recompile the Linux kernel, and then use the newly compiled uImage image to start the development board.

Introduction to the LED driver that comes with the Linux kernel

LED lamp driver framework analysis

The LED light driver file is /drivers/leds/leds-gpio.c. You can open the file /drivers/leds/Makefile and find the following content:
/drivers/leds/Makefile file code snippet
Line 33, if CONFIG_LEDS_GPIO is defined, the file leds-gpio.c will be compiled. In the previous section, we chose to compile the LED driver into the Linux kernel. There will be the line “CONFIG_LEDS_GPIO=y” in the .config file, so The leds-gpio.c driver file will be compiled.

Next, take a look at the driver file leds-gpio.c and find the following content:
leds_gpio.c file code snippet
Lines 203-206, LED driver matching table. This table only has one matching item, the compatible content is “gpio-leds”, so the compatible attribute value of the LED light device node in the device tree must also be “gpio-leds”, otherwise If the device and driver are not successfully matched, the driver will not work.

Lines 316-323, platform_driver driver structure variable, it can be seen that the LED driver that comes with the Linux kernel uses the platform framework. It can be seen from line 317 that the probe function is gpio_led_probe, so when the driver and device are successfully matched, the gpio_led_probe function will be executed. As can be seen from line 320, the driver name is “leds-gpio”, so there will be a file named “leds-gpio” in the /sys/bus/platform/drivers directory, as shown in the following figure:
leds-gpio driver file
Line 326 registers the platform driver gpio_led_driver with the Linux kernel through the module_platform_driver function.

Module_platform_driver function analysis

In the previous section, we know that the LED driver will use the module_platform_driver function to register the platform driver with the Linux kernel. In fact, module_platform_driver is widely used in the Linux kernel to complete the operation of registering the platform driver with the Linux kernel. module_platform_driver is defined in the include/linux/platform_device.h file and is a macro defined as follows:
module_platform_driver function
It can be seen that module_platform_driver depends on module_driver. Module_driver is also a macro and is defined in the include/linux/device.h file. The content is as follows:

Sample code 36.2.2.2 module_driver function
1898 #define module_driver(__driver, __register, __unregister, ...) \
1899 static int __init __driver##_init(void) \
1900 {<!-- --> \
1901 return __register( & amp;(__driver) , ##__VA_ARGS__); \
1902 } \
1903 module_init(__driver##_init); \
1904 static void __exit __driver##_exit(void) \
1905 {<!-- --> \
1906 __unregister( & amp;(__driver) , ##__VA_ARGS__); \
1907 } \
1908 module_exit(__driver##_exit);

With the help of Example Code 36.2.2.1 and Example Code 36.2.2.2, expand the following:

module_platform_driver(gpio_led_driver)

After expansion, you can get:

static int __init gpio_led_driver_init(void)
{<!-- -->
return platform_driver_register ( & amp;(gpio_led_driver));
}
module_init(gpio_led_driver_init);

static void __exit gpio_led_driver_exit(void)
{<!-- -->
platform_driver_unregister ( & amp;(gpio_led_driver) );
}
module_exit(gpio_led_driver_exit);

Is the above code the standard registration and deletion of platform drivers? Therefore, the function of the module_platform_driver function is to complete the registration and deletion of the platform driver.

gpio_led_probe function analysis

When the driver and device match, the gpio_led_probe function will be executed. This function mainly obtains the GPIO information of the LED light from the device tree. The reduced function content is as follows:

Sample code 36.2.3.1 gpio_led_probe function
256 static int gpio_led_probe(struct platform_device *pdev)
257 {<!-- -->
258 struct gpio_led_platform_data *pdata = dev_get_platdata( & amp;pdev->dev);
259 struct gpio_leds_priv *priv;
260 int i, ret = 0;
261
262 if (pdata & amp; & amp; pdata->num_leds) {<!-- --> /* Non-device tree mode */ /* Get platform_device information */
.....
292 } else {<!-- --> /* Use device tree */
293 priv = gpio_leds_create(pdev);
294 if (IS_ERR(priv))
295 return PTR_ERR(priv);
296 }
297
298 platform_set_drvdata(pdev, priv);
299
300 return 0;
301 }

Lines 293-295, if using the device tree, use the gpio_leds_create function to extract device information from the device tree.
The obtained LED light GPIO information is stored in the return value. The content of the gpio_leds_create function is as follows:

Sample code 36.2.3.2 gpio_leds_create function
134 static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
135 {<!-- -->
136 struct device *dev = & amp;pdev->dev;
137 struct fwnode_handle *child;
138 struct gpio_leds_priv *priv;
139 int count, ret;
140
141 count = device_get_child_node_count(dev);
142 if (!count)
143 return ERR_PTR(-ENODEV);
144
145 priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);
146 if (!priv)
147 return ERR_PTR(-ENOMEM);
148
149 device_for_each_child_node(dev, child) {<!-- -->
150 struct gpio_led_data *led_dat = & amp;priv->leds[priv->num_leds];
151 struct gpio_led led = {<!-- -->};
152 const char *state = NULL;
153
154/*
155 * Acquire gpiod from DT with uninitialized label, which
156 * will be updated after LED class device is registered,
157 * Only then the final LED name is known.
158 */
159 led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
160 GPIOD_ASIS,
161 NULL);
162 if (IS_ERR(led.gpiod)) {<!-- -->
163 fwnode_handle_put(child);
164 return ERR_CAST(led.gpiod);
165 }
166
167 led_dat->gpiod = led.gpiod;
168
169 fwnode_property_read_string(child, "linux,default-trigger",
170 &led.default_trigger);
171
172 if (!fwnode_property_read_string(child, "default-state",
173 &state)) {<!-- -->
174 if (!strcmp(state, "keep"))
175 led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
176 else if (!strcmp(state, "on"))
177 led.default_state = LEDS_GPIO_DEFSTATE_ON;
178 else
179 led.default_state = LEDS_GPIO_DEFSTATE_OFF;
180}
181
182 if (fwnode_property_present(child, "retain-state-suspended"))
183 led.retain_state_suspended = 1;
184 if (fwnode_property_present(child, "retain-state-shutdown"))
185 led.retain_state_shutdown = 1;
186 if (fwnode_property_present(child, "panic-indicator"))
187 led.panic_indicator = 1;
188
189 ret = create_gpio_led( & amp;led, led_dat, dev, child, NULL);
190 if (ret < 0) {<!-- -->
191 fwnode_handle_put(child);
192 return ERR_PTR(ret);
193}
194 /* Set gpiod label to match the corresponding LED name. */
195 gpiod_set_consumer_name(led_dat->gpiod,
196 led_dat->cdev.dev->kobj.name);
197 priv->num_leds + + ;
198 }
199
200 return priv;
201 }

Line 141, call the device_get_child_node_count function to count the number of child nodes. Generally, a node is created in the device tree to represent the LED light, and then a child node is created under this node for each LED light. Therefore the number of child nodes is also the number of LED lights.

Line 149, traverse each child node and obtain the information of each child node.

Line 159, obtains the GPIO information used by the LED light.

Lines 169-170, get the “linux, default-trigger” attribute value. You can use this attribute to set the default function of an LED light in the Linux system, such as serving as a system heartbeat indicator, etc.

Lines 172-173, get the “default-state” attribute value, which is the default state attribute of the LED light.

Line 189 calls the create_gpio_led function to create LED-related ios, which actually means setting the ios used by LEDs as output. The create_gpio_led function mainly initializes the gpio_led_data structure type variable led_dat. led_dat saves the LED operating functions and other contents.

Lines 195-196 use the label attribute as the name of the LED. led_dat->cdev.dev->kobj.name points to the label attribute under the LED light node in the device tree.

This is the analysis of the gpio_led_probe function. The main function of the gpio_led_probe function is to obtain the device information of the LED light, and then initialize the corresponding IO based on this information, set it to output, etc.

Device tree node preparation

Open the document Documentation/devicetree/bindings/leds/leds-gpio.txt. This document explains in detail how to write the device tree node corresponding to the Linux built-in driver. When writing the device node, pay attention to the following points:

  1. Create a node to represent the LED light device, such as dtsleds. If there are multiple LED lights on the board, each LED light will be a child node of dtsleds.
  2. The compatible attribute value of the dtsleds node must be “gpio-leds”.
  3. Set the label attribute. This attribute is optional. Each child node has a label attribute. The label attribute generally represents the name of the LED light. For example, if it is distinguished by color, it is red, green, etc.
  4. Each child node must set the gpios attribute value, indicating the GPIO pin used by this LED!
  5. You can set the “linux, default-trigger” attribute value, which is to set the default function of the LED light. Check the Documentation/devicetree/bindings/leds/common.txt document to view the optional functions, such as:
  • backlight: LED light as backlight.
  • default-on: LED light is on.
  • heartbeat: The LED light is used as a heartbeat indicator light and can be used as a system operation reminder light.
  • disk-activity: LED light serves as disk activity indicator light.
  • ide-disk: LED light serves as hard disk activity indicator light.
  • timer: The LED light flashes periodically, driven by a timer, and the flashing frequency can be modified.
  1. You can set the “default-state” attribute value, which can be set to on, off or keep. When it is on, the LED light turns on by default. When it is off, the LED light turns off by default. When it is keep, the LED light keeps the current mode.

There are also some other optional attributes, such as led-sources, color, function and other attributes. The usage of these attributes is explained in detail in Documentation/devicetree/bindings/leds/common.txt, you can check it yourself.

The experiment in this section uses both LED lights on the STM32MP1 development board. LED0 (red) is connected to the PI0 pin, and LED1 (green) is connected to PF3. First, create the pinctrl nodes corresponding to these two LED lights:

Sample code 36.3.1 pinctrl child node
1 led_pins_a: gpioled-0 {<!-- -->
2 pins {<!-- -->
3 pinmux = <STM32_PINMUX('I', 0, GPIO)>, /* LED0 */
4 <STM32_PINMUX('F', 3, GPIO)>; /* LED1 */
5 drive-push-pull;
6 bias-pull-up;
7 output-high;
8 slew-rate = <0>;
9     }; 
10};

Finally, add the LED device subnode according to the previous binding document requirements, open stm32mp157d-atk.dts, and add the following LED light device subnode under the root node:

Sample code 36.3.2 dtsleds device node
1 dtsleds {<!-- -->
2 compatible = "gpio-leds";
3 pinctrl-0 = < & amp;led_pins_a>;
4
5 led0 {<!-- -->
6 label = "red";
7 gpios = < & amp;gpioi 0 GPIO_ACTIVE_LOW>;
8 default-state = "off";
9      }; 
10
11 led1 {<!-- -->
12 label = "green";
13 gpios = < & amp;gpiof 3 GPIO_ACTIVE_LOW>;
14 default-state = "off";
15};
16};

In line 3, set the LED pinctrl node to led_pins_a, which is sample code 36.3.1. Lines 4-8 are LED0 on the development board, and lines 10-13 are LED1 on the development board. After the modification is completed, save and recompile the device tree, and then start the development board with the new device tree.

Run the test

Start the development board with the new uImage and stm32mp157d-atk.dtb. After starting, check whether the /sys/bus/platform/devices/dtsleds directory exists, as shown in the following figure:
dtsleds directory
Enter the dtsleds/leds directory. The contents of this directory are as shown below:
leds directory content
As can be seen from the picture above, there are two subdirectories in the leds directory, namely: green and red, where green is LED1 and red is LED0. The names of these two subdirectories are the 5th and 5th lines in the sample code 36.3.2. The label attribute value set in line 11.

Whether the setting is useful or not can only be known through testing. First, check whether there are two files “sys/class/leds/red/brightness” and “sys/class/leds/green/brightness” in the system. These two files correspond to LED0 and LED1 respectively. By operating these two files, LED0 and LED1 can be turned on and off.

If so, you can enter the following command to turn on two LED lights:

echo 1 > /sys/class/leds/red/brightness //Turn on LED0
echo 1 > /sys/class/leds/green/brightness //Turn on LED1

The shutdown command is as follows:

echo 0 > /sys/class/leds/red/brightness //Turn off LED0
echo 0 > /sys/class/leds/green/brightness //Turn off LED1

If the two LED lights can be turned on and off normally, it means that the LED light driver that comes with the Linux kernel is working properly. Generally, an LED light is used as the system indicator. If the system is running normally, the LED indicator will flash on and off. Here, LED0 is set as the system indicator light. Add the “linux, default-trigger” attribute information to the device node dtsleds/led0. The attribute value is “heartbeat”. The content of the dtsleds node after modification as follows:

Sample code 36.4.1 dtsleds device node
1 dtsleds {<!-- -->
2 compatible = "gpio-leds";
3 pinctrl-0 = < & amp;led_pins_a>;
4
5 led0 {<!-- -->
6 label = "red";
7 gpios = < & amp;gpioi 0 GPIO_ACTIVE_LOW>;
8 linux,default-trigger = "heartbeat";
9 default-state = "on";
10};
11
12 led1 {<!-- -->
13 label = "green";
14 gpios = < & amp;gpiof 3 GPIO_ACTIVE_LOW>;
15 default-state = "off";
16};
17};

Line 8, set LED0 to heartbeat.

Line 9, LED0 is turned on by default.

Recompile the device tree and use the new device tree to start the Linux system. After startup, LED0 will flash as the system heartbeat indicator, indicating that the system is running.

Summary

In this chapter, we directly use the LED driver that comes with the Linux kernel. There is no need to write a driver by yourself, just configure the device tree directly.

First, enable the LED driver in the following path:

-> Device Drivers
-> LED Support (NEW_LEDS [=y])
-> LED Support for GPIO connected LEDs

Then add the electrical properties of the LED in the stm32mp15-pinctrl.dtsi file; then add the device subnode of the LED light in the device tree stm32mp157d-atk.dts.