本文在树莓派上实现了一个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 16 struct 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 subsystem delay_off max_brightness trigger delay_on power uevent |
改变delay_on和delay_off可以改变闪烁的频率,和亮灭比例。默认为各500ms。