zmkfirmware / zmk

ZMK Firmware Repository
https://zmk.dev/
MIT License
2.78k stars 2.81k forks source link

Discussion: More flexible/nuanced peripheral/device power management #1317

Open petejohanson opened 2 years ago

petejohanson commented 2 years ago

Good (but late) brainstorming session w/ Kim and Joel on Discord: https://discord.com/channels/719497620560543766/719544230497878116/979238459157934100 around better device/peripheral power management for ZMK (which ultimate came up as Kim has been working to solve our long standing "re-init OLEDs when power is restored" bug for our display feature). We would like to align on all the personas, hardware configurations, and use cases here, to fully agree on that, then we can discuss technical solution.

General

Various peripheral hardware found on keyboards can draw either small or large amounts of current during use, impacting battery life. Having ZMK have a cohesive, configurable system for managing how and when those devices are active, and when individual peripherals or whole collections of peripherals are on/off will improve the end user experience on a variety of hardware targets. Ideally we would have a system that allows both:

Hardware

The following hardware features are tied into this:

Related: I'm iterating on https://github.com/petejohanson/zmk-uno (to be moved soon) that includes a main power domain, and sub-domain for just the addressable RGB. This will hopefully help us test some of these scenarios as we iterate on this.

Existing Functionality

ZMK currently has some basic implementations that satisfy some of the functionality we are talking about:

Example Hardware

Baselines

Pro Micro Kit (PMK)

Onboard Custom (OC)

Variants

RGB Variant (-RGB suffix)

OLED Variant (-OLED suffix)

Pointer Variant (-P suffix)

Big Battery Variant (-BB suffix)

Example

Something like a Corne w/ RGB and OLEDs installed with be PMK-RGB-OLED. That same keyboard, but with a giant battery hidden in the case would be PMK-RGB-OLED-BB. A custom keyboard with a pointer and RGB on custom external power cutoffs would be OC-P-RGB.

Use Cases

TBD

joelspadin commented 2 years ago

I think a reasonable design would be to split things into two separate concerns:

  1. Code which decides which devices (or groups of devices) should be on/off.
  2. Code which implements power control to make those devices be on/off.

For part 1, we can use any rules we want to decide which devices should be powered. Think of it as a black box where inputs are variables such as USB power state, idle state, and user settings, the output is an on/off state for each device for which we want to control power.

An idea I had for how to implement the inside of that box is that we could provide a way to configure some sort of "power policy" per device (or group of devices), which is just a function that returns whether the device should be on given the current keyboard state. Some examples of potential power policies:

On = Example use cases
true e-paper display, trackpad
not_idle OLED displays (always want to blank on idle to avoid burn-in)
usb OR not_idle LCD displays (no burn-in concerns, so always on when on USB power)
usb AND not_idle Simple backlighting (only on if powered, but no reason to waste power if idle)
user_setting AND not_idle Power user backlighting (use a key to manually toggle, but still turn off if idle)

The author for a board/shield could set default policies for each device, and then power users could override that if for example they have a large battery and want things to stay on when battery powered. Example DT code purely to illustrate how you could set sane defaults and then have power users override them (this doesn't handle things like mapping user settings to control devices, etc.):

// foo.overlay
/ {
  power_control {
    compatible = "zmk,something";
    pwr_display: display {
      policy = "not_idle";
      devices = <&display>;
    };
    pwr_backlight: backlight {
      policy = "powered_and_not_idle";
      devices = <&backlight &underglow>;
    };
  };
};

// foo.keymap
&pwr_display {
  policy = "always_on"; // I'm using an e-paper display, not an OLED
};

For part 2, device drivers can implement their own power state handling, plus they could be attached to a toggleable power source (I believe Zephyr calls this a "regulator"?) which controls whether the device is supplied with power. Multiple devices can share a power source, making it impossible to cut power to one while powering another. In this case, I think the most reasonable behavior is to supply power whenever at least one device should be on. There is no need to have any sort of user settings or overrides at this level, because if you want a way to force the power source to be off, you can just adjust the logic in part 1 to turn off all the devices that are powered by it, and then the power source will turn off.

I think it is also important that our design considers that using a GPIO to enable/disable a power supply is not the only way to control power. For example, a matrix RGB LED IC could be sent a command over I2C to put it into shutdown mode where it uses minimal power until instructed to turn back on. The Zephyr driver for this can handle the power control on its own.