Linux驱动开发09:【设备树】nanopi的LED驱动

介绍

这节通过在nanopi的设备树中添加LED子节点,来实现一个LED驱动。由于linux内核支持LED驱动框架并且有通用的LED驱动,因此这里只需按照驱动要求添加设备树节点就可以了,不用我们自己重写LED驱动。这一节先在设备树中添加一个LED节点,重新编译设备树测试效果,然后对内核中drivers/leds/leds-gpio.c进行分析,看该LED驱动是如何实现的。

添加设备树节点

我们在sun8i-h3-nanopi-neo-air.dts中添加如下内容:

/ {
    ...
    mygpio-leds {
        compatible = "gpio-leds";        /* 才能被识别为Led */
        pinctrl-names = "default";
        pinctrl-0 = <&test_pin>;
        test {
            label = "nanopi:red:test";
            gpios = <&pio 6 11 GPIO_ACTIVE_HIGH>; /* PG11 */
            default-state = "on";
        };
    };
}

&pio {
    ...
    test_pin: test_pin@0 {
        pins = "PG11";
        function = "gpio_out";
    };
}

该节点分为两部分,第一部分是mygpio-leds子节点,第二部分是test_pin@0子节点。第一个节点是根节点下的一个子节点,根据上一节的分析,该节点将被系统解析为一个platform_device。第二个节点是pio的子节点,该节点表明了对应的GPIO引脚编号和该GPIO的模式,这是pinctrl相关的内容,对于pinctrl不同的平台实现不同,这里nanopi的pinctrl是用字符串来表示引脚号和模式(似乎nanopi的pinctrl只支持设置pin的模式,并不能直接指定该引脚的输出值)。

再看mygpio-leds节点,其中的compatible = "gpio-leds",用于与platform_drivers匹配,pinctrl-0 = <&test_pin>引用了test_pin@0子节点,表明要设置PG11为输出模式,紧接着是一个子节点test,其中label = "nanopi:red:test"表示该LED名称,gpios = <&pio 6 11 GPIO_ACTIVE_HIGH>引用了pio标号,该标号定义在sun8i-h3-h5.dtsi文件中:

pio: pinctrl@01c20800 {
            /* compatible is in per SoC .dtsi file */
            reg = <0x01c20800 0x400>;
            interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>,
                     <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>,
                     <GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&ccu CLK_BUS_PIO>, <&osc24M>, <&osc32k>;
            clock-names = "apb", "hosc", "losc";
            gpio-controller;
            #gpio-cells = <3>;
            interrupt-controller;
            #interrupt-cells = <3>;
            ...
}

注意其中的#gpio-cells = <3>属性,它表明引用该节点时需要三个参数,这在前面有提到,这三个参数的分别表示该GPIO的bank、编号和初始化后默认输出模式。这里bank=6,按照数据手册上的定义,0表示A,1表示B…6就表示G,编号为11,也就是PG11,初始化为高电平。

测试

我们先进行测试,重新编译写好的设备树,然后拷贝到SD卡中,启动nanopi,进入/sys/class/leds目录可以看到我们定义的”nanopi:red:test”文件
这里写图片描述
进入该文件夹的device/of_node目录可以看到我们定义的属性
这里写图片描述
最后退回到/sys/class/leds/nanopi:red:test目录,对brightness文件进行操作,可控制PG11输出高电平和低电平,如果在PG11上接一个LED可以看到该LED的亮灭
这里写图片描述

LED驱动的实现分析

内核中实现LED驱动的文件是drivers/leds/leds-gpio.c,这是一个platform驱动,下面对其进行分析,先看platform_driver的定义。

static const struct of_device_id of_gpio_leds_match[] = {
    { .compatible = "gpio-leds", },
    {},
};

static struct platform_driver gpio_led_driver = {
    .probe      = gpio_led_probe,
    .shutdown   = gpio_led_shutdown,
    .driver     = {
        .name   = "leds-gpio",
        .of_match_table = of_gpio_leds_match,
    },
};

module_platform_driver(gpio_led_driver);

我们定义的mygpio-leds中,compatible = "gpio-leds",因此可以执行gpio_led_probe函数

struct gpio_leds_priv {
    int num_leds;
    struct gpio_led_data leds[];        // 不定长数组,使用时才分配长度
};

static inline int sizeof_gpio_leds_priv(int num_leds)
{
    return sizeof(struct gpio_leds_priv) +
        (sizeof(struct gpio_led_data) * num_leds);
}

 drivers --- leds --- leds-gpio.c --- gpio_led_probe(                 --- struct gpio_led_platform_data *pdata
                                   |    struct platform_device *pdev)  |- pdata = dev_get_platdata(&pdev->dev)
                                   |                                   |- if (pdata && pdata->num_leds) {
                                   |                                   |      ...
                                   |                                   |  } else {
                                   |                                   |      priv = gpio_leds_create(pdev)
                                   |                                   |  }
                                   |                                   |- platform_set_drvdata(pdev, priv)
                                   |- gpio_leds_create(               --- int count
                                   |    struct platform_device *pdev)  |- struct gpio_leds_priv *priv
                                   |                                   |- struct fwnode_handle *child
                                   |                                   |- count = device_get_child_node_count(dev)
                                   |                                   |- priv = devm_kzalloc(dev, 
                                   |                                   |      sizeof_gpio_leds_priv(count), GFP_KERNEL)
                                   |                                   |- device_for_each_child_node(dev, child) {
                                   |                                   |      struct gpio_led_data *led_dat;
                                   |                                   |      struct gpio_led led = {};
                                   |                                   |      struct device_node *np;
                                   |                                   |      np = to_of_node(child); 
                                   |                                   |      led_dat = &priv->leds[priv->num_leds];
                                   |                                   |*     fwnode_property_read_string(child, 
                                   |                                   |              "label", &led.name);
                                   |                                   |*     led.gpiod = devm_fwnode_get_gpiod_from_child(
                                   |                                   |              dev, NULL, child, GPIOD_ASIS, led.name);
                                   |                                   |      // 获取gpio_desc
                                   |                                   |*     create_gpio_led(&led, led_dat, dev, NULL);
                                   |                                   |      // 注册到LED驱动框架
                                   |                                   |      priv->num_leds++;
                                   |                                   |- }
                                   |- create_gpio_led(                  --- led_dat->gpiod = template->gpiod;
                                        const struct gpio_led *template, |- if (!led_dat->gpiod) { // 这里不是空
                                        struct gpio_led_data *led_dat,   |      ...
                                        struct device *parent,           |  }
                                        gpio_blink_set_t blink_set)      |- led_dat->cdev.name = template->name
                                                                         |- 对led_dat的一系列赋值
                                                                         |* gpiod_direction_output(led_dat->gpiod, state);
                                                                         |* devm_led_classdev_register(parent, &led_dat->cdev);

由于我们解析设备树为platform_device时并没有设置platform_data,因此执行gpio_leds_create函数对设备树节点进行解析。首先获取子节点的数目,对gpio_leds_priv进行动态初始化,遍历子节点(这里只有一个子节点),获取每个子节点的label属性,然后通过lable属性获取gpio_desc,然后调用create_gpio_led对LED驱动进行注册,其中对led_dat->cdev进行了设置,然后利用LED驱动框架将其注册到系统

linux的LED框架这里不展开了,该框架与应用层的接口是sysfs,通过读写sysfs的文件,来控制LED的亮灭。

猜你喜欢

转载自blog.csdn.net/Egean/article/details/81806139