天天看點

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

就可以使能核心驅動

Linux驅動開發十四.使用核心自帶的LED驅動

 如果有不明白的地方還可以按?鍵顯示提示資訊

Linux驅動開發十四.使用核心自帶的LED驅動

 選中該選項以後,就可以在核心根目錄下的.config檔案裡看到相對應的選項配置

Linux驅動開發十四.使用核心自帶的LED驅動

 就是光标所在的那一行,配置項對應值為y,重新編譯核心就可以了。

驅動分析

核心中所有的LED驅動都是在/drivers/leds路徑下面,我們可以看看該路徑下的Makefile規則

Linux驅動開發十四.使用核心自帶的LED驅動

因為我們在.config檔案中可以查到配置名稱是CONFIG_LEDS_GPIO,是以可以在Makefile檔案中搜尋一下這個配置項,可以發現規則是leds-gpio.0,對應的驅動檔案應該是leds-gpio.c。我們可以在核心裡查找一下這個檔案(drivers/leds/leds-gpio.c)

把整個驅動代碼放在這裡,想看的話可以看看

Linux驅動開發十四.使用核心自帶的LED驅動
Linux驅動開發十四.使用核心自帶的LED驅動
1 /*
  2  * LEDs driver for GPIOs
  3  *
  4  * Copyright (C) 2007 8D Technologies inc.
  5  * Raphael Assenat <[email protected]>
  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 <[email protected]>, Trent Piepho <[email protected]>");
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裡看到我們在裝置樹裡定義的裝置(注意看下面圖裡的路徑)

Linux驅動開發十四.使用核心自帶的LED驅動

 這個裝置是不會在dev路徑下生成對應節點的。我們要我們的LED就在leds檔案夾下

Linux驅動開發十四.使用核心自帶的LED驅動

 那個red就是我們新建立的裝置。進去看看

Linux驅動開發十四.使用核心自帶的LED驅動

有亮度、觸發模式等如果我們想在使用者态控制裝置,可以用echo加重定向的方式向對應功能的檔案寫入值

echo 1 > brightness         //打開 LED0
echo 0 > brightness         //關閉 LED0      

具體其他的功能可以看下上面說的那個common.txt文檔裡的介紹

PS:這次依舊沒有點亮LED,但是把GPIO的配置設定成蜂鳴器是沒問題的,說明驅動的流程正常,還是不知道系統在哪裡把引腳電平拉高了。