RT-Thread RTOS
An open source embedded real-time operating system
LED device model (DM)

LED subsystem under <tt>RT_USING_DM</tt>

**RT_USING_LED** (components/drivers/led/) depends on **RT_USING_DM**. The core **led.c** registers **struct rt_led_device** as char devices **led0**, **led1**, … via **rt_dm_ida**. In-tree OFW drivers bind Linux-style **gpio-leds**, **pwm-leds**, and **register-bit-led** nodes.

Public API and char-device **read/write**: LED subsystem.

Sources:

File Role
**led.c** **rt_led_register**, software blink timer, sysfs-style **read/write**
**led-gpio.c** **gpio-leds** parent + per-child GPIO LED
**led-pwm.c** **pwm-leds** + **pwms** phandle, brightness
**led-syscon.c** **register-bit-led** via System controller (Syscon)

Kconfig

Option Role
**RT_USING_DM** Required parent
**RT_USING_LED** Core **led.c**
**RT_LED_GPIO** **led-gpio.c** — needs **RT_USING_PINCTRL**, **RT_USING_OFW**
**RT_LED_PWM** **led-pwm.c** — needs **RT_USING_PWM**, **RT_USING_OFW**
**RT_LED_SYSCON** **led-syscon.c** — needs **RT_MFD_SYSCON**
**SOC_DM_LED_DIR** BSP adds more LED drivers via **osource**

No rt_bus: each LED is a **RT_PLATFORM_DRIVER_EXPORT** that calls **rt_led_register** from **probe**.


Architecture

DT: gpio-leds / pwm-leds / register-bit-led
|
| platform match (rt_ofw_node_id)
v
probe: per-child or single node
|
| parse gpios / pwms / syscon offset+mask
| fill struct rt_led_ops
| rt_led_register(&led->parent) → "ledN"
v
rt_device (RT_Device_Class_Char, RDWR)
|
+-- rt_led_set_state / set_brightness / set_period
+-- optional software blink (led.c timer)
+-- write "on"|"off"|digits / read state string
Backend Compatible One **rt_led_device per…
GPIO **gpio-leds** Child subnode (like Linux)
PWM **pwm-leds** Child subnode
Syscon **register-bit-led** Platform node (one LED per compatible node)

Core registration (<tt>led.c</tt>)

rt_err_t rt_led_register(struct rt_led_device *led);
rt_err_t rt_led_unregister(struct rt_led_device *led);
Requirement Detail
**led->ops** Must be non-NULL at register
Naming **rt_dm_dev_set_name(..., "led%u", id)**
Blink fallback If **set_state** exists but **set_period** is NULL, **led.c** allocates **blink_timer** in **sysdata** for **RT_LED_S_BLINK**
Unregister Forces **RT_LED_S_OFF**, detaches blink timer

GPIO LEDs (<tt>led-gpio.c</tt>)

Parent node:

/ {
leds {
compatible = "gpio-leds";
pinctrl-0 = <&led_pins>; /* optional: apply once for all children */
status {
gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>;
label = "status";
default-state = "off";
linux,default-trigger = "heartbeat";
};
};
};
Property Role
**gpios** **rt_ofw_get_named_pin** — **active_val** from DT flags
**default-state** **"on"** → **RT_LED_S_ON** at probe; else off
**linux,default-trigger** (fuzzy **default-trigger$**) **heartbeat** or **timer** → **RT_LED_S_BLINK** (software blink in **led.c**)
**pinctrl-0 on parent** Applied once for all children; else per-child **rt_pin_ctrl_confs_apply_by_name**

Ops: **set_state** (off/on/toggle), **get_state** (reads pin level). **RT_LED_S_BLINK** not implemented in GPIO ops — handled by core timer when **set_state** returns **-RT_ENOSYS**.


PWM LEDs (<tt>led-pwm.c</tt>)

pwm-leds {
compatible = "pwm-leds";
backlight {
pwms = <&pwm0 0 1000000>; /* channel, period (ns) */
max-brightness = <255>;
active-low;
default-state = "on";
};
};
Property Role
**pwms** Phandle + **#pwm-cells** — args[0] channel, args[1] period (ns)
**max-brightness** Required; scales **rt_led_set_brightness**
**active-low** Inverts duty calculation
**default-state** **on**, **keep** (preserve existing PWM duty), or off
**default-trigger** Same as GPIO — software blink

Ops: **set_state**, **get_state** (tracks **enabled**), **set_brightness**. Brightness maps to **rt_pwm_set** pulse width.

PWM controller must be probed (**rt_platform_ofw_request** on provider if needed); cookie **rt_ofw_data(pwm_np)** → **struct rt_device_pwm ***.


Syscon bit LEDs (<tt>led-syscon.c</tt>)

syscon: syscon@10000000 {
compatible = "syscon";
reg = <0x10000000 0x1000>;
};
status-led {
compatible = "register-bit-led";
offset = <0x10>;
mask = <0x1>;
default-state = "on";
};
Property Role
**offset** Byte offset into parent syscon register block
**mask** Bit mask for **rt_syscon_update_bits**
Parent **rt_syscon_find_by_ofw_node** on parent OFW node

Ops: **set_state**, **get_state** via **rt_syscon_read**. One platform device per LED node (**pdev->parent.user_data**).


New LED backend checklist

  1. Enable **RT_USING_DM**, **RT_USING_LED**, and backend Kconfig.
  2. Embed **struct rt_led_device parent** first in private struct.
  3. Implement **struct rt_led_ops** — at minimum **set_state**; add **set_brightness** for dimming, **set_period** for hardware blink.
  4. **rt_led_register** after hardware/pinctrl/PWM is ready.
  5. Apply **default-state** / **default-trigger** like in-tree drivers.
  6. Store **rt_ofw_data(child_np) = &led->parent** (GPIO/PWM) or **user_data** (syscon) for **remove**.
  7. **remove**: **rt_led_unregister**, free private data.
  8. **RT_PLATFORM_DRIVER_EXPORT**.

Pitfalls

Issue Mitigation
Double GPIO ownership Do not export the same pin as **RT_USING_PIN** device and LED
PWM not probed Ensure PWM controller **compatible** probes before **pwm-leds** children
Missing max-brightness PWM child probe fails with **-RT_EINVAL**
Syscon parent **register-bit-led** must sit under / reference probed syscon
Blink on GPIO Uses software timer in **led.c** — not hardware PWM blink
Char write brightness Only works when **set_brightness** op exists (PWM path)

See also