回顾一下我们现在先后都做了几种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的配置设置成蜂鸣器是没问题的,说明驱动的流程正常,还是不知道系统在哪里把引脚电平拉高了。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/aiops/281354.html