Skip to content

v2: refcounted shared power-rail/pin ownership + sleep-mode rail handling #952

Description

@tyeth-ai-assisted

Context

#936 added a panel power-enable rail to the display Add (ws.display.Add.power, a prerequisite ws.digitalio.Add — see adafruit/Wippersnapper_Protobuf#208). The PR implements deliberately two-party arbitration, per review:

  • pin free at Add → the display driver owns the rail (enable level before init, released to disable level in its destructor, i.e. display Remove/replace);
  • pin already registered with the digitalio controller → pass through only if it's an OUTPUT already at the enable level, else the Add fails ("Power-enable pin unavailable").

That's correct for the direct display↔digitalio conflict, but it does not generalize. This issue captures the gaps so the design discussion has a home.

Failure scenarios (current behavior, verified against the code)

Assume one rail (e.g. GPIO15) powers the display and I²C sensors and NeoPixels, with components added/removed in arbitrary order at check-in or runtime:

Order of events What happens today
digitalio Add (OUTPUT@enable) → display Add ✅ pass-through works
display Add → digitalio Add, same pin ❌ digitalio never queries display ownership — it reconfigures the pin per its own Add (possibly INPUT or LOW) and kills the panel
display owns rail → display Remove ❌ driver dtor drives the rail to disable — I²C sensors / NeoPixels on the same rail are power-cut silently
passed-through digitalio pin → digitalio Remove DigitalIOHardware dtor does LOW + pinMode(INPUT) — the rail floats and the panel dies with no notification path
two displays sharing one rail ❌ ownership is private to each driver instance; the second display sees the pin as "free", both own it, first Remove powers down the second
I²C bus init when PIN_I2C_POWER == the rail i2c/hardware.cpp drives the macro pin directly — it even flips it through INPUT to sniff rest polarity (momentary float/brownout), invisible to any check
NeoPixel power pin v2 pixels doesn't model a power pin at all

Root cause: ownership is pairwise, one-directional, and implicit — there is no shared registry, no refcount, and time-of-Add-only checking.

Deep sleep / light sleep (v2 sleep component)

The sleep controller configures wakeups and sleeps; it does not tear down component hardware and never calls gpio_hold_en:

  • Deep sleep: the digital GPIO matrix powers down → all non-RTC pins float. An active-high rail sags off (accidentally the right power-saving outcome, by luck). An is_inverted rail may float ON through sleep (or oscillate, per board pulls) — burning the battery budget the sleep exists to save. Correct handling needs gpio_hold_en + gpio_deep_sleep_hold_en at the disable level, or an RTC GPIO.
  • Wake from deep sleep = reset → check-in replay → componentAdds re-run → the add-order nondeterminism above repeats on every wake cycle.
  • Light sleep: GPIO state is retained and execution resumes — the panel stays lit between sensor reads, so no display power saving unless something deliberately drops the rail (policy question).

Proposed direction

A central refcounted pin/rail registry (in the digitalio controller, or a small PinOwnershipRegistry):

  • acquire(pin, level [, flags]) / release(pin): same-level acquires bump a refcount (add-order independence falls out for free); a conflicting-level acquire fails the component Add deterministically; the rail is only driven to its disable level when the last holder releases.
  • Symmetric: digitalio Add/Remove, display power/backlight, PIN_I2C_POWER, and a future pixels power pin all go through it — no component can stomp or power-cut another's rail.
  • Sleep hook: a pre-sleep pass over held rails — gpio_hold the appropriate level for deep sleep (critical for inverted rails); a future proto policy flag (e.g. keep_powered_during_sleep) covers wake-on-display use cases; light sleep policy (blank/power-down the panel?) decided per component.

Scope

Touches digitalio, display, i2c, pixels, and sleep — hence split out of #936 rather than grown inside a board-support PR.

Refs: #936 (review discussion), adafruit/Wippersnapper_Protobuf#208 ("prerequisite components" thread).

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions