ILD

使用platform driver和device tree开发树莓派led驱动
作者:YUAN JIANPENG 邮箱:yuanjp@hust.edu.cn
发布时间:2018-7-27 站点:Inside Linux Development

本文在树莓派上实现了一个LED驱动,通过device tree和platform driver为驱动模型,注册led到内核led class中。


1 device tree

首先,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。


2 kernel config

内核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


3 驱动实现

代码如下:

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的亮灭了。


4 测试

编译内核、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。

Copyright © insidelinuxdev.net 2017-2021. Some Rights Reserved.