回顧一下我們現在先後都做了幾種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 <[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裡看到我們在裝置樹裡定義的裝置(注意看下面圖裡的路徑)
這個裝置是不會在dev路徑下生成對應節點的。我們要我們的LED就在leds檔案夾下
那個red就是我們新建立的裝置。進去看看
有亮度、觸發模式等如果我們想在使用者态控制裝置,可以用echo加重定向的方式向對應功能的檔案寫入值
echo 1 > brightness //打開 LED0
echo 0 > brightness //關閉 LED0
具體其他的功能可以看下上面說的那個common.txt文檔裡的介紹
PS:這次依舊沒有點亮LED,但是把GPIO的配置設定成蜂鳴器是沒問題的,說明驅動的流程正常,還是不知道系統在哪裡把引腳電平拉高了。