Linux驱动开发十四.使用内核自带的LED驱动

回顾一下我们现在先后都做了几种LED的点亮试验:

  • 裸机点亮LED
  • 使用汇编语言读写寄存器点亮LED
  • 使用C语言读写寄存器点亮LED
  • 在系统下直接操作寄存器映射点亮LED
  • 在设备树下完成LED相关设备信息后在系统中调用设备树信息点亮LED
  • 使用gpio和pinctrl子系统点亮LED
  • 使用platform驱动架构点亮LED(这种方式也是基于设备树或在文件中指定要读写的寄存器地址)

上面几种方法有个共通的特点,就是需要我们自己去写驱动。不管是字符设备的驱动架构还是platform结构的,都要我们自己去写驱动代码。可是Linux是一种很完善的操作系统,而像LED这种简单的基础类型设备驱动,Linux内核已经为我们继承了。今天我们就借由这个内核为我们提供的LED驱动来看一下这种现成的简单驱动可以证明去使用。

内核配置

如果想使用内核为我们提供的驱动,我们需要在配置内核的时候就将对应的选项选中。在make的时候使用make menuconfig进入菜单配置,然后依次进入并选中下面的配置菜单

->Device drivers
  ->LED Support
    ->LED Support for GPIO connected LEDs
就可以使能内核驱动

 

 如果有不明白的地方还可以按?键显示提示信息

 

 选中该选项以后,就可以在内核根目录下的.config文件里看到相对应的选项配置

 

 就是光标所在的那一行,配置项对应值为y,重新编译内核就可以了。

驱动分析

内核中所有的LED驱动都是在/drivers/leds路径下面,我们可以看看该路径下的Makefile规则

因为我们在.config文件中可以查到配置名称是CONFIG_LEDS_GPIO,所以可以在Makefile文件中搜索一下这个配置项,可以发现规则是leds-gpio.0,对应的驱动文件应该是leds-gpio.c。我们可以在内核里查找一下这个文件(drivers/leds/leds-gpio.c)

把整个驱动代码放在这里,想看的话可以看看

  1 /*
  2  * LEDs driver for GPIOs
  3  *
  4  * Copyright (C) 2007 8D Technologies inc.
  5  * Raphael Assenat <raph@8d.com>
  6  * Copyright (C) 2008 Freescale Semiconductor, Inc.
  7  *
  8  * This program is free software; you can redistribute it and/or modify
  9  * it under the terms of the GNU General Public License version 2 as
 10  * published by the Free Software Foundation.
 11  *
 12  */
 13 #include <linux/err.h>
 14 #include <linux/gpio.h>
 15 #include <linux/gpio/consumer.h>
 16 #include <linux/kernel.h>
 17 #include <linux/leds.h>
 18 #include <linux/module.h>
 19 #include <linux/platform_device.h>
 20 #include <linux/property.h>
 21 #include <linux/slab.h>
 22 #include <linux/workqueue.h>
 23 
 24 struct gpio_led_data {
 25     struct led_classdev cdev;
 26     struct gpio_desc *gpiod;
 27     struct work_struct work;
 28     u8 new_level;
 29     u8 can_sleep;
 30     u8 blinking;
 31     int (*platform_gpio_blink_set)(struct gpio_desc *desc, int state,
 32             unsigned long *delay_on, unsigned long *delay_off);
 33 };
 34 
 35 static void gpio_led_work(struct work_struct *work)
 36 {
 37     struct gpio_led_data *led_dat =
 38         container_of(work, struct gpio_led_data, work);
 39 
 40     if (led_dat->blinking) {
 41         led_dat->platform_gpio_blink_set(led_dat->gpiod,
 42                     led_dat->new_level, NULL, NULL);
 43         led_dat->blinking = 0;
 44     } else
 45         gpiod_set_value_cansleep(led_dat->gpiod, led_dat->new_level);
 46 }
 47 
 48 static void gpio_led_set(struct led_classdev *led_cdev,
 49     enum led_brightness value)
 50 {
 51     struct gpio_led_data *led_dat =
 52         container_of(led_cdev, struct gpio_led_data, cdev);
 53     int level;
 54 
 55     if (value == LED_OFF)
 56         level = 0;
 57     else
 58         level = 1;
 59 
 60     /* Setting GPIOs with I2C/etc requires a task context, and we don't
 61      * seem to have a reliable way to know if we're already in one; so
 62      * let's just assume the worst.
 63      */
 64     if (led_dat->can_sleep) {
 65         led_dat->new_level = level;
 66         schedule_work(&led_dat->work);
 67     } else {
 68         if (led_dat->blinking) {
 69             led_dat->platform_gpio_blink_set(led_dat->gpiod, level,
 70                              NULL, NULL);
 71             led_dat->blinking = 0;
 72         } else
 73             gpiod_set_value(led_dat->gpiod, level);
 74     }
 75 }
 76 
 77 static int gpio_blink_set(struct led_classdev *led_cdev,
 78     unsigned long *delay_on, unsigned long *delay_off)
 79 {
 80     struct gpio_led_data *led_dat =
 81         container_of(led_cdev, struct gpio_led_data, cdev);
 82 
 83     led_dat->blinking = 1;
 84     return led_dat->platform_gpio_blink_set(led_dat->gpiod, GPIO_LED_BLINK,
 85                         delay_on, delay_off);
 86 }
 87 
 88 static int create_gpio_led(const struct gpio_led *template,
 89     struct gpio_led_data *led_dat, struct device *parent,
 90     int (*blink_set)(struct gpio_desc *, int, unsigned long *,
 91              unsigned long *))
 92 {
 93     int ret, state;
 94 
 95     led_dat->gpiod = template->gpiod;
 96     if (!led_dat->gpiod) {
 97         /*
 98          * This is the legacy code path for platform code that
 99          * still uses GPIO numbers. Ultimately we would like to get
100          * rid of this block completely.
101          */
102         unsigned long flags = 0;
103 
104         /* skip leds that aren't available */
105         if (!gpio_is_valid(template->gpio)) {
106             dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n",
107                     template->gpio, template->name);
108             return 0;
109         }
110 
111         if (template->active_low)
112             flags |= GPIOF_ACTIVE_LOW;
113 
114         ret = devm_gpio_request_one(parent, template->gpio, flags,
115                         template->name);
116         if (ret < 0)
117             return ret;
118 
119         led_dat->gpiod = gpio_to_desc(template->gpio);
120         if (IS_ERR(led_dat->gpiod))
121             return PTR_ERR(led_dat->gpiod);
122     }
123 
124     led_dat->cdev.name = template->name;
125     led_dat->cdev.default_trigger = template->default_trigger;
126     led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod);
127     led_dat->blinking = 0;
128     if (blink_set) {
129         led_dat->platform_gpio_blink_set = blink_set;
130         led_dat->cdev.blink_set = gpio_blink_set;
131     }
132     led_dat->cdev.brightness_set = gpio_led_set;
133     if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP)
134         state = !!gpiod_get_value_cansleep(led_dat->gpiod);
135     else
136         state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
137     led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
138     if (!template->retain_state_suspended)
139         led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
140 
141     ret = gpiod_direction_output(led_dat->gpiod, state);
142     if (ret < 0)
143         return ret;
144 
145     INIT_WORK(&led_dat->work, gpio_led_work);
146 
147     return led_classdev_register(parent, &led_dat->cdev);
148 }
149 
150 static void delete_gpio_led(struct gpio_led_data *led)
151 {
152     led_classdev_unregister(&led->cdev);
153     cancel_work_sync(&led->work);
154 }
155 
156 struct gpio_leds_priv {
157     int num_leds;
158     struct gpio_led_data leds[];
159 };
160 
161 static inline int sizeof_gpio_leds_priv(int num_leds)
162 {
163     return sizeof(struct gpio_leds_priv) +
164         (sizeof(struct gpio_led_data) * num_leds);
165 }
166 
167 static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
168 {
169     struct device *dev = &pdev->dev;
170     struct fwnode_handle *child;
171     struct gpio_leds_priv *priv;
172     int count, ret;
173     struct device_node *np;
174 
175     count = device_get_child_node_count(dev);
176     if (!count)
177         return ERR_PTR(-ENODEV);
178 
179     priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);
180     if (!priv)
181         return ERR_PTR(-ENOMEM);
182 
183     device_for_each_child_node(dev, child) {
184         struct gpio_led led = {};
185         const char *state = NULL;
186 
187         led.gpiod = devm_get_gpiod_from_child(dev, NULL, child);
188         if (IS_ERR(led.gpiod)) {
189             fwnode_handle_put(child);
190             ret = PTR_ERR(led.gpiod);
191             goto err;
192         }
193 
194         np = of_node(child);
195 
196         if (fwnode_property_present(child, "label")) {
197             fwnode_property_read_string(child, "label", &led.name);
198         } else {
199             if (IS_ENABLED(CONFIG_OF) && !led.name && np)
200                 led.name = np->name;
201             if (!led.name)
202                 return ERR_PTR(-EINVAL);
203         }
204         fwnode_property_read_string(child, "linux,default-trigger",
205                         &led.default_trigger);
206 
207         if (!fwnode_property_read_string(child, "default-state",
208                          &state)) {
209             if (!strcmp(state, "keep"))
210                 led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
211             else if (!strcmp(state, "on"))
212                 led.default_state = LEDS_GPIO_DEFSTATE_ON;
213             else
214                 led.default_state = LEDS_GPIO_DEFSTATE_OFF;
215         }
216 
217         if (fwnode_property_present(child, "retain-state-suspended"))
218             led.retain_state_suspended = 1;
219 
220         ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],
221                       dev, NULL);
222         if (ret < 0) {
223             fwnode_handle_put(child);
224             goto err;
225         }
226     }
227 
228     return priv;
229 
230 err:
231     for (count = priv->num_leds - 2; count >= 0; count--)
232         delete_gpio_led(&priv->leds[count]);
233     return ERR_PTR(ret);
234 }
235 
236 static const struct of_device_id of_gpio_leds_match[] = {
237     { .compatible = "gpio-leds", },
238     {},
239 };
240 
241 MODULE_DEVICE_TABLE(of, of_gpio_leds_match);
242 
243 static int gpio_led_probe(struct platform_device *pdev)
244 {
245     struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
246     struct gpio_leds_priv *priv;
247     int i, ret = 0;
248 
249     if (pdata && pdata->num_leds) {
250         priv = devm_kzalloc(&pdev->dev,
251                 sizeof_gpio_leds_priv(pdata->num_leds),
252                     GFP_KERNEL);
253         if (!priv)
254             return -ENOMEM;
255 
256         priv->num_leds = pdata->num_leds;
257         for (i = 0; i < priv->num_leds; i++) {
258             ret = create_gpio_led(&pdata->leds[i],
259                           &priv->leds[i],
260                           &pdev->dev, pdata->gpio_blink_set);
261             if (ret < 0) {
262                 /* On failure: unwind the led creations */
263                 for (i = i - 1; i >= 0; i--)
264                     delete_gpio_led(&priv->leds[i]);
265                 return ret;
266             }
267         }
268     } else {
269         priv = gpio_leds_create(pdev);
270         if (IS_ERR(priv))
271             return PTR_ERR(priv);
272     }
273 
274     platform_set_drvdata(pdev, priv);
275 
276     return 0;
277 }
278 
279 static int gpio_led_remove(struct platform_device *pdev)
280 {
281     struct gpio_leds_priv *priv = platform_get_drvdata(pdev);
282     int i;
283 
284     for (i = 0; i < priv->num_leds; i++)
285         delete_gpio_led(&priv->leds[i]);
286 
287     return 0;
288 }
289 
290 static struct platform_driver gpio_led_driver = {
291     .probe        = gpio_led_probe,
292     .remove        = gpio_led_remove,
293     .driver        = {
294         .name    = "leds-gpio",
295         .of_match_table = of_gpio_leds_match,
296     },
297 };
298 
299 module_platform_driver(gpio_led_driver);
300 
301 MODULE_AUTHOR("Raphael Assenat <raph@8d.com>, Trent Piepho <tpiepho@freescale.com>");
302 MODULE_DESCRIPTION("GPIO LED driver");
303 MODULE_LICENSE("GPL");
304 MODULE_ALIAS("platform:leds-gpio");
leds-gpio.c

下面我们大致分析一下这个内核提供的驱动的实现思路。

驱动加载

首先可以发现,内核提供的这个驱动是基于platform框架下的,驱动的加载并没有直接使用platform_driver_register这种方式,而是在最后用了1行代码完成了驱动的加载和卸载

module_platform_driver(gpio_led_driver);

 可以点开这个函数看一下思路

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

可以发现这个module_platform_driver是一个宏,里面调用了module_driver,再展开看一下

 1 **
 2  * module_driver() - Helper macro for drivers that don't do anything
 3  * special in module init/exit. This eliminates a lot of boilerplate.
 4  * Each module may only use this macro once, and calling it replaces
 5  * module_init() and module_exit().
 6  *
 7  * @__driver: driver name
 8  * @__register: register function for this driver type
 9  * @__unregister: unregister function for this driver type
10  * @...: Additional arguments to be passed to __register and __unregister.
11  *
12  * Use this macro to construct bus specific macros for registering
13  * drivers, and do not use it on its own.
14  */
15 #define module_driver(__driver, __register, __unregister, ...) \
16 static int __init __driver##_init(void) \
17 { \
18     return __register(&(__driver) , ##__VA_ARGS__); \
19 } \
20 module_init(__driver##_init); \
21 static void __exit __driver##_exit(void) \
22 { \
23     __unregister(&(__driver) , ##__VA_ARGS__); \
24 } \
25 module_exit(__driver##_exit);
26 
27 #endif /* _DEVICE_H_ */

说白了就是使用一个宏来完成模块的加载和卸载,因为每个模块都需要加载和卸载,内核就把这个功能拿出来做了个模块,在每个驱动模块下面直接调用,就简化了代码。

platform_driver结构

看一下驱动结构体的定义

1 static struct platform_driver gpio_led_driver = {
2     .probe        = gpio_led_probe,
3     .remove        = gpio_led_remove,
4     .driver        = {
5         .name    = "leds-gpio",
6         .of_match_table = of_gpio_leds_match,
7     },
8 };

是不是很简单,和我们前面讲在设备树下面进行platform架构的驱动构建是一样的内容。回想一下怎么匹配设备树节点?就是那个of_match_table成员变量。展开变量值of_gpio_leds_match看看内容

1 static const struct of_device_id of_gpio_leds_match[] = {
2     { .compatible = "gpio-leds", },
3     {},
4 };

里面只有一个,就是compatible。也就是说我们要在后面完善设备树的时候将compatible的值也设置为gpio-leds。

gpio_led_probe函数

在设备和驱动完成匹配以后,gpio_led_probe函数就会执行,这个函数主要作用就是从设备树中获取LED的相关信息,并且整个驱动中利用几个结构体来保存LED的相关信息。然后调用一系列的函数完成相关节点的创建。整个过程有兴趣的小伙伴们可以看一看,想研究的话不是很复杂,对应的函数和变量从命名上就能看出来大致的作用。

LED的描述

在驱动代码一开始的地方,内核定义了一个结构体gpio_led_data去描述LED,但是具体的工作状态(亮度、点亮方式)是由成员led_classdev决定的。有兴趣的可以看看里面的内容。

设备构建

这个内核自带的驱动使用跟前面点亮LED的过程不同,是先有驱动,然后根据驱动的模式去构建设备或设备树。

从LED驱动的名称可以发现,这个驱动是基于GPIO系统去实现的。所以我们需要在设备树下面新建一个节点,并且按照GPIO的使用流程定义好设备相关属性。设备节点的创建在Documentation/devicetree/bindings/leds下面有个文档可以参考

 1 LEDs connected to GPIO lines
 2 
 3 Required properties:
 4 - compatible : should be "gpio-leds".
 5 
 6 Each LED is represented as a sub-node of the gpio-leds device.  Each
 7 node's name represents the name of the corresponding LED.
 8 
 9 LED sub-node properties:
10 - gpios :  Should specify the LED's GPIO, see "gpios property" in
11   Documentation/devicetree/bindings/gpio/gpio.txt.  Active low LEDs should be
12   indicated using flags in the GPIO specifier.
13 - label :  (optional)
14   see Documentation/devicetree/bindings/leds/common.txt
15 - linux,default-trigger :  (optional)
16   see Documentation/devicetree/bindings/leds/common.txt
17 - default-state:  (optional) The initial state of the LED.  Valid
18   values are "on", "off", and "keep".  If the LED is already on or off
19   and the default-state property is set the to same value, then no
20   glitch should be produced where the LED momentarily turns off (or
21   on).  The "keep" setting will keep the LED at whatever its current
22   state is, without producing a glitch.  The default is off if this
23   property is not present.
24 - retain-state-suspended: (optional) The suspend state can be retained.Such
25   as charge-led gpio.
26 
27 Examples:
28 
29 #include <dt-bindings/gpio/gpio.h>
30 
31 leds {
32     compatible = "gpio-leds";
33     hdd {
34         label = "IDE Activity";
35         gpios = <&mcu_pio 0 GPIO_ACTIVE_LOW>;
36         linux,default-trigger = "ide-disk";
37     };
38 
39     fault {
40         gpios = <&mcu_pio 1 GPIO_ACTIVE_HIGH>;
41         /* Keep LED on if BIOS detected hardware fault */
42         default-state = "keep";
43     };
44 };
45 
46 run-control {
47     compatible = "gpio-leds";
48     red {
49         gpios = <&mpc8572 6 GPIO_ACTIVE_HIGH>;
50         default-state = "off";
51     };
52     green {
53         gpios = <&mpc8572 7 GPIO_ACTIVE_HIGH>;
54         default-state = "on";
55     };
56 };
57 
58 leds {
59     compatible = "gpio-leds";
60 
61     charger-led {
62         gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;
63         linux,default-trigger = "max8903-charger-charging";
64         retain-state-suspended;
65     };
66 };

文档里写的很清楚,并且还给出了相应的范例,我们按照范例在根节点下创建一个新的LED设备

/*内核LED驱动*/
dtsleds {
    compatible = "gpio-leds";
    led0 {
        label = "red";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_gpioled>;            
        gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
        default-state = "on";
    };
};

设备名称就是dtsleds,里面的属性必须要有个值为gpio-leds的compatib,用来和驱动做匹配。然后每个单独的LED都分别作为一个子节点(参考46-56行内容),我就做了一个led0,表示开发板上的LED0。

label属性是可选项,用来描述具体哪个LED的名称,pinctrl-names和pinctrl-0是用来配置pinctrl系统辅助GPIO的,哪个pinctrl-o的值如果有疑问可以看看前面讲GPIO子系统时候的设置方法(点击查看)。最主要的就是gpios属性,用来描述LED设备接在了哪个GPIO引脚上。因为驱动的名称是LEDS_GPIO,所以驱动是基于GPIO来实现的。必须要有这个gpio节点属性。(也就是说这个LED驱动这个不光可以驱动LED,还可以用来驱动各种类似蜂鸣器这种简单的GPIO设备)。default-state表示初始状态,on或者off表示启动或停止。

范例里第36行有个linux,default-trigger可以单独拿出来说一下,这个属性表示设备的工作状态,在Documentation/devicetree/bindings/leds/common.txt里面定义了多重工作模式供我们直接使用

设备驱动

重新编译设备树,启动系统,如果设备加载完成后可以在sys/bus/platform/devices里看到我们在设备树里定义的设备(注意看下面图里的路径)

 这个设备是不会在dev路径下生成对应节点的。我们要我们的LED就在leds文件夹下

 那个red就是我们新创建的设备。进去看看

有亮度、触发模式等如果我们想在用户态控制设备,可以用echo加重定向的方式向对应功能的文件写入值

echo 1 > brightness         //打开 LED0
echo 0 > brightness         //关闭 LED0

具体其他的功能可以看下上面说的那个common.txt文档里的介绍

PS:这次依旧没有点亮LED,但是把GPIO的配置设置成蜂鸣器是没问题的,说明驱动的流程正常,还是不知道系统在哪里把引脚电平拉高了。

posted @ 2022-08-20 19:20  银色的音色  阅读(536)  评论(0编辑  收藏  举报