键会认了。今天看四颗用户灯为什么会一起灭——写在同一个节点里,有一个脚申请失败,整组都不会出现。

ls /sys/class/leds/ 只有 work,没有 user-led0user-led1user-led2user-led3。四颗用户灯全灭。work 还在心跳,说明 leds-gpio 这个驱动本身没坏,内核也没把 LED 子系统编掉。

四颗用户灯写在同一个 gpio-leds 节点里。驱动遍历子节点,对每一颗 devm_fwnode_get_gpiod_from_child()任意一颗申请 GPIO 失败,整个节点 probe 返回错误,已经登记的也会跟着没了。于是你看见的是「四颗一起没有」,不是「坏了两颗」。

失败的那两颗脚,被公版 dtsi 里两个 regulator 先占了:

  • vcc3v3_vga 用 GPIO0_D5 当使能,正是 user-led2
  • pcie30_3v3 用 GPIO0_D4 当使能,正是 user-led1

这块板没有 VGA 座,PCIe 3.3 V 已经走另一路固定电源。两个节点在 dtb 里没有真正的消费者,却把脚占死。leds-gpio 对 D4/D5 拿到 -EBUSY,整节点退出。

work 是公版另一个 gpio-leds 节点,脚是 GPIO0_C0,独占,所以还在闪。不要用 work 亮着证明用户灯驱动通了,也不要把 work 当成 user-led0。

产品 rk3568_evb

device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dts

官方这张图:申请、配置方向、写电平、释放。gpio-leds 在内核里替你做了前三步,用户态只写 brightness。脚被别人申请走了,这张图在 probe 第一格就停。

【截图 26-leds-on】 四颗 user-led 点亮,旁边 work 心跳,能分清哪四颗是用户灯。 文件:assets/shots/26-leds-on.jpg


1. 四颗灯的脚,高电平亮

sysfs 名linux 编号默认 trigger
user-led0GPIO0_D327heartbeat(可改 none)
user-led1GPIO0_D428none
user-led2GPIO0_D529none
user-led3GPIO0_D630none
workGPIO0_C016heartbeat
编号 = bank × 32 + 组内偏移 + 位号
D=24
GPIO0_D3 = 0×32 + 24 + 3 = 27
GPIO0_D4 = 28
GPIO0_D5 = 29
GPIO0_D6 = 30
GPIO0_C0 = 0×32 + 16 + 0 = 16

硬件是高电平亮。设备树必须 GPIO_ACTIVE_HIGH。写成 ACTIVE_LOW 时,brightness=1 灯灭、0 灯亮,看起来像极性反了。说明书写高电平,就按高电平。

不要和扩展座上的「流水灯」模块混。那是移位寄存器或一串 GPIO3,走电机那组脚,应用里另一张卡片。板载四颗在核心板/底板丝印 D3~D6 附近,sysfs 名带 user-led


2. 驱动为什么整节点失败

drivers/leds/leds-gpio.c 里创建子灯:

led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
                                             GPIOD_ASIS,
                                             NULL);
if (IS_ERR(led.gpiod)) {
    fwnode_handle_put(child);
    return ERR_CAST(led.gpiod);
}

没有「跳过失败的那颗、继续登记其它」。D4 被 regulator-gpio 占用,这里返回 -EBUSYgpio_led_create_of 失败,gpio_led_probe 失败。dmesg 类似:

leds-gpio: probe of leds failed with error -16

或者节点名是你板级起的 gpio-leds / board-leds

leds-gpio: probe of board-leds failed with error -16

-16 是 -EBUSY-22 是参数。没有这行、也没有 user-led:节点没编译进 dtb,或 compatible 不是 gpio-leds

work 能亮,只说明另一份 gpio-leds 节点成功了。两份节点互不影响。

公版占用长这样(rk3568-evb 公版 dtsi,不要在板级再抄一份 gpio):

vcc3v3_vga: vcc3v3-vga {
    compatible = "regulator-fixed";
    regulator-name = "vcc3v3_vga";
    regulator-always-on;
    gpio = <&gpio0 RK_PD5 GPIO_ACTIVE_HIGH>;
    enable-active-high;
};

pcie30_3v3: gpio-regulator {
    compatible = "regulator-gpio";
    regulator-name = "pcie30_3v3";
    gpios = <&gpio0 RK_PD4 GPIO_ACTIVE_HIGH>;
    states = <100000 0x0
              3300000 0x1>;
};

本板无 VGA。PCIe 供电板级改成了不占 GPIO 的 fixed regulator。这两个节点在本板 dtb 里搜不到消费者,纯占脚。


3. 设备树:用户灯节点 + 关掉两个 regulator

board_leds: gpio-leds {
    compatible = "gpio-leds";

    led0 {
        label = "user-led0";
        gpios = <&gpio0 RK_PD3 GPIO_ACTIVE_HIGH>;
        linux,default-trigger = "none";
    };
    led1 {
        label = "user-led1";
        gpios = <&gpio0 RK_PD4 GPIO_ACTIVE_HIGH>;
        linux,default-trigger = "none";
    };
    led2 {
        label = "user-led2";
        gpios = <&gpio0 RK_PD5 GPIO_ACTIVE_HIGH>;
        linux,default-trigger = "none";
    };
    led3 {
        label = "user-led3";
        gpios = <&gpio0 RK_PD6 GPIO_ACTIVE_HIGH>;
        linux,default-trigger = "none";
    };
};

&vcc3v3_vga {
    status = "disabled";
};

&pcie30_3v3 {
    status = "disabled";
};

user-led0 如果默认 heartbeat,开机就会闪,演示「点灯」时先:

echo none > /sys/class/leds/user-led0/trigger

否则你写 brightness 会被 trigger 抢回去。

内核:

CONFIG_LEDS_CLASS=y
CONFIG_LEDS_GPIO=y
CONFIG_LEDS_TRIGGERS=y

改 DTS 之后刷 resource + boot_linux。只刷 p5 不够。

hdc shell "cat /proc/partitions"
hdc shell "dd if=/data/resource.img of=/dev/block/mmcblk1p4 bs=4M conv=fsync"
hdc shell "dd if=/data/boot_linux.img of=/dev/block/mmcblk1p5 bs=4M conv=fsync"

4. 加节点之前,先解包查谁占脚

dtc -I dtb -O dts -o /tmp/live.dts /sys/firmware/fdt
grep -n "RK_PD4\|RK_PD5\|gpio0 28\|gpio0 29\|PD4\|PD5" /tmp/live.dts

或者在编译机对 resource 里的 rk-kernel.dtb 解包。搜到 vcc3v3_vga / pcie30_3v3 仍 okay,就是还没 disable。

运行时:

hdc shell "ls /sys/class/leds/"
hdc shell "cat /sys/kernel/debug/gpio" | grep -E "gpio-(16|27|28|29|30)"

修好之后期望:

user-led0
user-led1
user-led2
user-led3
work

debugfs 里 27~30 的 consumer 是 user-ledN 或 leds。16 是 work。若 28 仍写着 pcie30_3v3、29 写着 vcc3v3_vga,disable 没进生效 DTB。

点灯:

hdc shell "echo none > /sys/class/leds/user-led0/trigger"
hdc shell "echo 1 > /sys/class/leds/user-led0/brightness"
hdc shell "echo 1 > /sys/class/leds/user-led1/brightness"
hdc shell "echo 1 > /sys/class/leds/user-led2/brightness"
hdc shell "echo 1 > /sys/class/leds/user-led3/brightness"

brightness 取值 0 / 1(GPIO 灯没有硬件 PWM 调光,写 255 也是亮)。max_brightness 读出来通常是 1。

循环:

hdc shell 'for n in 0 1 2 3; do
  echo 1 > /sys/class/leds/user-led$n/brightness
  sleep 0.2
  echo 0 > /sys/class/leds/user-led$n/brightness
done'

四颗会依次亮。只有 user-led0 会、1/2 不会:D4/D5 仍被占,但那会整节点失败,按理 0 也不会出现。若出现「只有两颗」,你把四颗拆成了两个 gpio-leds 节点,那是另一份 dts,按节点各自看 probe。


5. 完整 C

/* leds_walk.c
 * armv7-unknown-linux-ohos-clang --target=arm-linux-ohos \
 *   --sysroot=${OHOS_NATIVE}/sysroot \
 *   -o leds_walk leds_walk.c \
 *   -Wl,--dynamic-linker=/system/lib/ld-musl-arm.so.1
 */
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>

static int wr(const char *path, const char *s)
{
    int fd = open(path, O_WRONLY);
    if (fd < 0) {
        fprintf(stderr, "open %s: %s\n", path, strerror(errno));
        return -1;
    }
    int n = (int)write(fd, s, strlen(s));
    close(fd);
    return n < 0 ? -1 : 0;
}

static int led_set(const char *name, int on)
{
    char p[128];
    snprintf(p, sizeof(p), "/sys/class/leds/%s/trigger", name);
    wr(p, "none");
    snprintf(p, sizeof(p), "/sys/class/leds/%s/brightness", name);
    return wr(p, on ? "1" : "0");
}

int main(void)
{
    DIR *d = opendir("/sys/class/leds");
    struct dirent *e;
    int found = 0;
    if (!d) {
        perror("leds");
        return 1;
    }
    printf("leds:");
    while ((e = readdir(d)) != NULL) {
        if (e->d_name[0] == '.') {
            continue;
        }
        printf(" %s", e->d_name);
        if (strncmp(e->d_name, "user-led", 8) == 0) {
            found++;
        }
    }
    printf("\n");
    closedir(d);

    if (found == 0) {
        fprintf(stderr,
                "没有 user-led*。work 在不算数。\n"
                "查 vcc3v3_vga / pcie30_3v3 是否还 okay,"
                "dmesg 搜 leds-gpio probe failed\n");
        return 2;
    }
    if (found != 4) {
        fprintf(stderr, "user-led 只有 %d 颗,期望 4。\n", found);
    }

    int i, k;
    for (k = 0; k < 3; k++) {
        for (i = 0; i < 4; i++) {
            char name[32];
            snprintf(name, sizeof(name), "user-led%d", i);
            led_set(name, 1);
            usleep(150000);
            led_set(name, 0);
        }
    }
    return 0;
}

HAP 写 sysfs 需要 chmod。开机脚本:

for n in 0 1 2 3; do
    chmod 0666 /sys/class/leds/user-led$n/brightness
    chmod 0666 /sys/class/leds/user-led$n/trigger
done

不要 chmod work 的 brightness 给应用乱写,心跳没了你会以为内核死了。


6. ArkTS

import evbio from 'libevbio.so';

@Entry
@Component
struct UserLeds {
  @State on: boolean[] = [false, false, false, false];
  @State log: string = '';

  build() {
    Column() {
      Text('user-led0..3').fontSize(22).fontWeight(FontWeight.Bold)
      Text('不是 work,也不是扩展座流水灯').fontSize(14).margin({ bottom: 12 })
      Row() {
        ForEach([0, 1, 2, 3], (i: number) => {
          Button('LED' + i.toString())
            .backgroundColor(this.on[i] ? '#C62828' : '#9E9E9E')
            .onClick(() => { this.toggle(i); })
            .margin(6)
        })
      }
      Button('跑马').onClick(() => { this.walk(); }).margin({ top: 12 })
      Text(this.log).margin({ top: 8 }).fontSize(14)
    }.padding(20)
  }

  path(i: number, leaf: string): string {
    return '/sys/class/leds/user-led' + i.toString() + '/' + leaf;
  }

  toggle(i: number) {
    evbio.writeText(this.path(i, 'trigger'), 'none');
    this.on[i] = !this.on[i];
    let r: string = evbio.writeText(this.path(i, 'brightness'),
      this.on[i] ? '1' : '0');
    this.log = 'led' + i.toString() + ' -> ' + r;
  }

  walk() {
    for (let i = 0; i < 4; i++) {
      evbio.writeText(this.path(i, 'trigger'), 'none');
      evbio.writeText(this.path(i, 'brightness'), '0');
      this.on[i] = false;
    }
    this.log = '开始跑马';
    let i: number = 0;
    let t: number = setInterval(() => {
      let prev: number = (i + 3) % 4;
      evbio.writeText(this.path(prev, 'brightness'), '0');
      this.on[prev] = false;
      evbio.writeText(this.path(i, 'brightness'), '1');
      this.on[i] = true;
      i++;
      if (i >= 12) {
        clearInterval(t);
        evbio.writeText(this.path(3, 'brightness'), '0');
        this.on[3] = false;
      }
    }, 200);
  }

  aboutToAppear() {
    let n: string = evbio.readText('/sys/class/leds/user-led0/brightness');
    if (n.length === 0) {
      this.log = '没有 user-led0。四颗一起没 = 整节点 probe 失败,查 VGA/PCIe regulator';
    }
  }
}

读 user-led0/brightness 空,不要去点 work 凑合演示。那会把心跳关掉,且验收对不上四颗用户灯。


7. 现象对照

现象原因处理
四颗全无,work 在D4/D5 被 regulator 占,整节点失败disable `vcc3v3_vga`、`pcie30_3v3`
有节点,写 1 灯灭ACTIVE_LOW 写反了改 HIGH
写 1 马上灭trigger 还是 heartbeatecho none
只有 work 能闪看错灯user-led 在 D3~D6
应用流水灯在亮、板载不亮那是扩展模块看 `/sys/class/leds/user-led*`
dmesg -16GPIO busydtc 搜 PD4/PD5 谁先 claim
改 dts 无效只刷了 boot_linux刷 resource p4
flowchart TD
  A[四颗用户灯全灭] --> B{ls leds 有 user-led0?}
  B -->|有 work 无 user-led| C{dmesg leds-gpio -16?}
  C -->|是| D[dtc 搜 PD4/PD5]
  D --> E[disable vcc3v3_vga 和 pcie30_3v3]
  B -->|四颗都在| F{写 brightness=1?}
  F -->|灭| G[极性 / trigger]
  F -->|亮| H[通]
  B -->|一个都没有含 work| I[LEDS_GPIO 没编进内核]

四颗写在一个 gpio-leds 节点里,一颗脚 busy,四颗都不登记。公版 VGA / PCIe 电源占了 D5/D4,关掉。work 是另一颗灯,GPIO0_C0,别拿它充数。


系列第 26 篇 · 芯片:瑞芯微 RK3568 · OpenHarmony 4.1(API 11) · Linux 5.10

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐