summaryrefslogtreecommitdiff
path: root/documentation/6.components/device-driver/led/dm.md
blob: fad46c05ad3d0a2c258b382f4e8e5d7fad36289e (plain)
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
@page page_device_led_dm LED device model (DM)

# LED subsystem under `RT_USING_DM`

**`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`**: @ref page_device_led.

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 @ref page_device_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 (`led.c`)

```c
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 (`led-gpio.c`)

**Parent** node:

```dts
/ {
    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 (`led-pwm.c`)

```dts
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 (`led-syscon.c`)

```dts
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

- @ref page_device_input
- @ref page_device_syscon
- @ref page_device_pwm
- @ref page_device_pinctrl
- `components/drivers/include/drivers/led.h`
- `components/drivers/led/Kconfig`