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

GPIO / PIN under <tt>RT_USING_DM</tt>

**RT_USING_PIN** with **RT_USING_DM** adds a global GPIO namespace: each platform GPIO controller registers a contiguous virtual pin number range. Application code still uses **rt_pin_mode / rt_pin_write / rt_pin_read** (PIN Device); drivers use **rt_pin_get_named_pin** and optional GPIO IRQ domains.

Sources:

File Role
**dev_pin_dm.c** Global pin table, **pin_api_init**, **pin_pic_init**, DM **rt_pin_ops** shim
**dev_pin_ofw.c** **rt_ofw_get_named_pin** — gpios / gpio properties
**dev_pin.c** Legacy single **gpio** device when no DM controllers
**pin-pl061.c** Reference platform driver (arm,pl061)

Kconfig

Option Role
**RT_USING_PIN** GPIO framework (default y)
**RT_USING_DM** Required for DM path described here
**RT_PIN_PL061** In-tree PL061 driver
**SOC_DM_PIN_DIR** BSP adds SoC GPIO drivers via **osource**

Architecture

DT: gpio-controller node (#gpio-cells, interrupt-controller optional)
|
| platform probe
v
struct rt_device_pin + rt_pin_ops (per controller)
|
+-- pin_api_init(gpio, pin_nr) → global pin_start..pin_start+nr-1
+-- pin_pic_init(gpio, irq) → optional per-line PIC domain
+-- rt_dm_dev_bind_fwdata → rt_ofw_data(np) = gpio
|
v
First controller also: rt_device_pin_register("gpio", pin_api_dm_ops)
|
v
Consumers:
rt_pin_get_named_pin(dev, "foo", index, &mode, &val)
rt_pin_mode(pin, mode); rt_pin_write(pin, val);
Concept Meaning
Local pin Index 0 … pin_nr-1 passed to **rt_pin_ops**
Virtual pin **pin_start + local** — value for **rt_pin_*** and **rt_pin_get_named_pin**
**pin_device_find** Maps virtual pin → owning **struct rt_device_pin**

Controllers are linked on **pin_nodes**; **pin_total_nr** only grows (ranges are not recycled).


Platform driver checklist

  1. **struct rt_device_pin** (or embed as first member of private struct).
  2. Implement **struct rt_pin_ops**: at minimum **pin_mode**, **pin_write**, **pin_read**.
  3. **probe**: iomap, clock, **rt_dm_dev_bind_fwdata(dev, NULL, &gpio->parent)**.
  4. **pin_api_init(&gpio->parent, gpio_count)** — assigns **pin_start**.
  5. If GPIO has a shared IRQ line:
    • **irq = rt_dm_dev_get_irq(dev, 0)**
    • **pin_pic_init(&gpio->parent, irq)**
    • Hardware ISR: loop pending bits → **pin_pic_handle_isr(&gpio->parent, local_pin)**
    • Install **rt_hw_interrupt_install(irq, …)** + **umask**
  6. Optional **pin_parse** for non-default **#gpio-cells** (flags from **dt-bindings/pin/pin.h**).

Reference: **pin-pl061.c** (pl061_probe).


<tt>struct rt_pin_ops</tt> (DM-relevant)

Callback Role
**pin_mode** Input / output / pull / open-drain
**pin_write / pin_read** Level
**pin_attach_irq** Direct per-pin ISR (optional)
**pin_detach_irq** Remove handler
**pin_irq_enable** Enable/disable line interrupt
**pin_irq_mode** Edge/level when using legacy IRQ path
**pin_parse** Decode **#gpio-cells** → local pin + **flags**
**pin_get** Name → virtual pin (optional)
**pin_debounce** Debounce time

If **pin_attach_irq** is NULL, **dev_pin_dm.c** stores handlers in **legacy_isr[]** and relies on **pin_irq_mode** + **pin_pic_handle_isr**.


Device tree: <tt>gpios</tt> properties

**rt_ofw_get_named_pin(np, propname, index, out_mode, out_value)** tries:

  • **{propname}-gpios**, then **{propname}-gpio**
  • If **propname** is **NULL**, tries **gpios** / **gpio**

Flow:

  1. **rt_ofw_parse_phandle_cells(..., "#gpio-cells", …)**
  2. **rt_platform_ofw_request** on GPIO provider if needed
  3. **pin_parse** or default: **args[0]** = local pin
  4. Convert **flags** → **PIN_MODE_***, active level
  5. Return **local + pin_dev->pin_start** (virtual pin)

Wrapper on device:

rt_ssize_t rt_pin_get_named_pin(struct rt_device *dev, const char *propname,
int index, rt_uint8_t *out_mode, rt_uint8_t *out_value);
rt_ssize_t rt_pin_get_named_pin_count(struct rt_device *dev, const char *propname);
Definition: rtdef.h:1369

Typical **probe**:

pin = rt_pin_get_named_pin(dev, "reset", 0, &mode, &active);
if (pin < 0)
return pin;
rt_pin_mode(pin, mode);
rt_pin_write(pin, active); /* or inactive per DT flags */
void rt_pin_write(rt_base_t pin, rt_ssize_t value)
write pin value
void rt_pin_mode(rt_base_t pin, rt_uint8_t mode)
set pin mode

Examples in-tree: **phye-generic-usb.c** (reset), **gpio-restart.c**, **led-gpio.c** (child node), **backlight-gpio.c**.


GPIO IRQ + PIC

**struct rt_pin_irqchip** is embedded in **struct rt_device_pin** (after **parent**).

API Role
**pin_pic_init(gpio, irq)** **rt_pic_linear_irq**, **legacy_isr** array, **pin_dm_ops**
**pin_pic_handle_isr(gpio, local_pin)** Dispatch PIC path and/or legacy_isr callback

Per-line DT interrupt (2 cells: pin + flags) uses **pin_dm_ops.irq_parse** / **irq_map**:

  • Maps hwirq → virtual IRQ
  • **rt_pic_cascade(pirq, gpio->irqchip.irq)** — GPIO block IRQ cascades to controller's **rt_dm_dev_get_irq**

For **rt_pin_attach_irq(virtual_pin, …)** without DT interrupt on that line, legacy path + **pin_irq_enable** on hardware still applies.


Application / driver usage

DM systems should prefer:

/* Not GET_PIN(port, n) from old BSP tables */
pin = rt_pin_get_named_pin(&pdev->parent, "led", 0, &mode, NULL);
rt_pin_mode(pin, mode);

Legacy **GET_PIN** / **drv_gpio.c** pin tables apply to non-DM BSP GPIO only.


Pitfalls

Issue Mitigation
Skip **pin_api_init** **rt_ofw_get_named_pin** adds wrong **pin_start**
**pin_parse missing** Only **args[0]** used; wrong if #gpio-cells > 1 with flags
IRQ without **pin_pic_init** Shared IRQ controllers need **pin_pic_handle_isr**
**pin_attach_irq vs legacy** If **pin_attach_irq NULL**, must enable via **pin_irq_enable** after attach
Provider not probed Ensure **rt_platform_ofw_request** or platform driver registered

See also

  • PIN Device — **rt_pin_*** application API
  • PIC cascade and routing — GPIO PIC cascade
  • components/drivers/include/drivers/dev_pin.h
  • components/drivers/pin/dev_pin_dm.c, dev_pin_ofw.c