本文在树莓派上实现了一个LED驱动,通过device tree和platform driver为驱动模型,注册led到内核led class中。
首先,device tree添加led节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | hy,led@0 { compatible = "hy,gpio-led"; gpios = <&gpio 21 0x0>, <&gpio 26 0x0>;};gpio: gpio@7e200000 { compatible = "brcm,bcm2835-gpio"; reg = <0x7e200000 0xb4>; interrupts = <0x2 0x11 0x2 0x12>; gpio-controller; #gpio-cells = <0x2>; interrupt-controller; #interrupt-cells = <0x2>; phandle = <0x10>; |
如上,主要是 gpios属性,指定使用哪些GPIO控制LED,指定了两个GPIO,21和26。
内核LED选项
CONFIG_NEW_LEDS=y
CONFIG_LEDS_CLASS=y
CONFIG_LEDS_CLASS_FLASH=y
CONFIG_LEDS_USER=y
CONFIG_LEDS_TRIGGERS=y
CONFIG_LEDS_TRIGGER_TIMER=y
CONFIG_LEDS_TRIGGER_ONESHOT=y
CONFIG_LEDS_TRIGGER_HEARTBEAT=y
CONFIG_LEDS_TRIGGER_BACKLIGHT=y
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | #include <linux/module.h>#include <linux/platform_device.h>#include <linux/of_gpio.h>#include <linux/device.h>#include <linux/leds.h>#include <linux/list.h>#define MAX_LED_NAME 16struct hy_led{ char name[MAX_LED_NAME]; int gpionum; struct led_classdev cdev; struct list_head list;};struct hy_leds{ struct device *dev; struct list_head head;};static void hy_led_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness){ struct hy_led *led = container_of(led_cdev, struct hy_led, cdev); gpio_set_value(led->gpionum, !!brightness);}static int led_probe(struct platform_device *pdev){ struct device *dev = & pdev->dev; struct device_node *np = dev->of_node; int gpionum; int index = 0; int ret; struct hy_leds *leds; struct hy_led *led; leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL); if (!leds) { return -ENOMEM; } leds->dev = dev; INIT_LIST_HEAD(&leds->head); platform_set_drvdata(pdev, leds); do { gpionum = of_get_gpio(np, index); if (gpionum < 0) break; if (!gpio_is_valid(gpionum)) { dev_err(dev, "invalid gpio %d\n", gpionum); return -ENODEV; } led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); if (!led) { return -ENOMEM; } list_add(&led->list, &leds->head); ret = devm_gpio_request(dev, gpionum, "hy,gpio-led"); if (ret < 0) { dev_err(dev, "request gpio failed: %d\n", ret); return ret; } ret = gpio_direction_output(gpionum, 0); if (ret) { dev_err(dev, "set gpio %d to output and value 1 failed: %d\n", gpionum, ret); return ret; } led->gpionum = gpionum; snprintf(led->name, sizeof(led->name)-1, "led%d", gpionum); led->cdev.name = led->name; led->cdev.brightness = LED_OFF; led->cdev.max_brightness = LED_ON; led->cdev.brightness_set = hy_led_brightness_set; devm_led_classdev_register(dev, &led->cdev); dev_info(dev, "register led %s\n", led->name); index++; } while (1); if (!index) { dev_err(dev, "no gpio led found\n"); return -ENODEV; } return 0;}struct of_device_id of_table[] = { { .compatible = "hy,gpio-led", }, {},};MODULE_DEVICE_TABLE(of, of_table);static struct platform_driver leddrv = { .probe = led_probe, .driver = { .name = "hy,led", .of_match_table = of_table, },};static int __init mod_init(void){ return platform_driver_register(&leddrv);}static void __exit mod_exit(void){ platform_driver_unregister(&leddrv);}module_init(mod_init);module_exit(mod_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Yuan Jianpeng"); |
platform的部分在之前的文章中已经讲了,主要讲一下probe函数。由于内核资源全部使用devm_*函数,因此不需要remove函数,内核在device deattch时,会自动释放devm申请的资源,并且按照申请的顺序,逆序释放。
定义两个结构体:
struct hy_leds,dev存储device设备的指针,head存储hy_led结构体的链表。
struct hy_led,表示一个LED。name存储led的名字,gpionum存储gpio id,cdev存储led_classdev结构体,list存储链表头。
结构体hy_leds动态分配,并通过接口platform_set_drvdata()存储在dev的driver_data字段。
通过of_get_gpio()获取所有的GPIO Led,获得其gpionum,每个led都分配一个hy_led实例,并添加到链表hy_led.head中,
以上设计,通过platform_device的dev的driver_data可以找到hy_led,通过hy_leds.head,可以遍历所有的hy_led,如果没有使用devm_*自动释放资源,此设计在remove函数中,就发挥作用了。
使用devm_gpio_request()向GPIO subsystem请求一个GPIO,使用gpio_direction_output()将GPIO设置为输出模式,并设置输出电压为0。
最后初始化led_classdev,设置name, brightness以及brightness_set回调函数。调用devm_led_classdev_register()注册led class。
hy_led_brightness_set() 回调用来设置LED亮灭,它的第一个参数是led_classdev结构体的指针,由于cdev是定义在hy_led中的,因此可以通过container_of来获取hy_led,进而得到gpionum,此时就可以通过gpio_set_value()接口来设置该LED的亮灭了。
编译内核、dts、驱动。tftp boot启动树莓派,将驱动上传到/tmp目录,并安装:
1 2 3 4 | /tmp # tftp -gr led.ko 192.168.2.20/tmp # insmod led.ko[ 9310.652762] hy,led soc:hy,led@0: register led led21[ 9310.658155] hy,led soc:hy,led@0: register led led26 |
查看/sys/class/leds下面是否有对应的条目:
1 2 | /tmp # ls /sys/class/leds/led21 led26 |
将led接到GPIO26,另一只脚接一个电阻,电阻接到地,使用下面的命令点亮:
1 | # echo 1 > /sys/class/leds/led26/brightness |
使用timer trigger让led闪烁,首先查看支持的trigger:
1 2 3 4 5 | # cat /sys/class/leds/led26/trigger [none] kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer oneshot heartbeat backlight |
使用timer trigger:
1 | # echo timer > /sys/class/leds/led26/trigger |
此时led开始闪烁,并且/sys/class/leds/led26出现新的文件delay_off和delay_on,用来控制闪烁的频率:
1 2 3 4 | # ls /sys/class/leds/led26/brightness device subsystemdelay_off max_brightness triggerdelay_on power uevent |
改变delay_on和delay_off可以改变闪烁的频率,和亮灭比例。默认为各500ms。