Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions data/mappings/info_config.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@
"WS2812_I2C_ADDRESS": {"info_key": "ws2812.i2c_address", "value_type": "hex"},
"WS2812_I2C_TIMEOUT": {"info_key": "ws2812.i2c_timeout", "value_type": "int"},
"WS2812_RGBW": {"info_key": "ws2812.rgbw", "value_type": "flag"},
"WS2812_VCC_ENABLE_PINS": {"info_key": "ws2812.vcc_enable_pins", "value_type": "array"},
"WS2812_VCC_ACTIVE_LOW": {"info_key": "ws2812.vcc_active_low", "value_type": "flag"},

"LAYOUTS": {"info_key": "layout_aliases", "value_type": "mapping"},

Expand Down
8 changes: 7 additions & 1 deletion data/schemas/keyboard.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -1075,7 +1075,13 @@
"pin": {"$ref": "./definitions.jsonschema#/mcu_pin"},
"rgbw": {"type": "boolean"},
"i2c_address": {"$ref": "./definitions.jsonschema#/hex_number_2d"},
"i2c_timeout": {"$ref": "./definitions.jsonschema#/unsigned_int"}
"i2c_timeout": {"$ref": "./definitions.jsonschema#/unsigned_int"},
"vcc_enable_pins": {
"type": "array",
"items": {"$ref": "./definitions.jsonschema#/mcu_pin"},
"minItems": 1
},
"vcc_active_low": {"type": "boolean"}
}
}
}
Expand Down
19 changes: 19 additions & 0 deletions docs/drivers/ws2812.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ To enable RGBW conversion, add the following to your `config.h`:
#define WS2812_RGBW
```

### VCC Enable Pin {#vcc-enable-pin}

WS2812 chips draw idle current (~1 mA per LED) even when displaying black. At long chain lengths this dominates standby drain on wireless keyboards. If your board has a GPIO that gates the chain's 5 V rail (e.g. through a load switch or FET), declare the pin(s) and QMK will toggle the rail with the RGB Matrix / RGBLight enable state, USB suspend, and (for RGB Matrix) the idle timeout.

In `keyboard.json`:

```json
"ws2812": {
"vcc_enable_pins": ["A8"]
}
```

|Key |Required|Description |
|------------------|--------|---------------------------------------------------------------------------------------------|
|`vcc_enable_pins` |Yes |One or more pins driven together. Use a list when multiple stages need to toggle in lockstep (e.g., main rail + boost converter).|
|`vcc_active_low` |No |Set to `true` when the pin is asserted low to enable. Defaults to active-high; omit otherwise.|

The pins are driven as outputs lazily on the first transition; boards needing an explicit initial direction can `gpio_set_pin_output` in `keyboard_pre_init_kb`. The corresponding C-side macros are `WS2812_VCC_ENABLE_PINS` and `WS2812_VCC_ACTIVE_LOW`.

## Driver Configuration {#driver-configuration}

Driver selection can be configured in `rules.mk` as `WS2812_DRIVER`, or in `info.json` as `ws2812.driver`. Valid values are `bitbang` (default), `i2c`, `spi`, `pwm`, `vendor`, or `custom`. See below for information on individual drivers.
Expand Down
35 changes: 35 additions & 0 deletions drivers/led/ws2812.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later

#include "ws2812.h"
#include "gpio.h"

#if defined(WS2812_RGBW)
void ws2812_rgb_to_rgbw(ws2812_led_t *led) {
Expand All @@ -13,3 +14,37 @@ void ws2812_rgb_to_rgbw(ws2812_led_t *led) {
led->b -= led->w;
}
#endif

#if defined(WS2812_VCC_ENABLE_PINS)

static const pin_t ws2812_vcc_enable_pins[] = WS2812_VCC_ENABLE_PINS;

void ws2812_set_power(bool on) {
static bool initialized = false;
static bool last_on = false;

if (initialized && on == last_on) {
return;
}

bool pin_value = on;
#if defined(WS2812_VCC_ACTIVE_LOW)
pin_value = !pin_value;
#endif

for (uint8_t i = 0; i < ARRAY_SIZE(ws2812_vcc_enable_pins); i++) {
gpio_write_pin(ws2812_vcc_enable_pins[i], pin_value);
}

if (!initialized) {
/* Latch-then-output order avoids active-low glitch on first call. */
for (uint8_t i = 0; i < ARRAY_SIZE(ws2812_vcc_enable_pins); i++) {
gpio_set_pin_output(ws2812_vcc_enable_pins[i]);
}
initialized = true;
}

last_on = on;
}

#endif
6 changes: 6 additions & 0 deletions drivers/led/ws2812.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

#pragma once

#include <stdbool.h>

#include "util.h"

/*
Expand Down Expand Up @@ -95,3 +97,7 @@ void ws2812_set_color_all(uint8_t red, uint8_t green, uint8_t blue);
void ws2812_flush(void);

void ws2812_rgb_to_rgbw(ws2812_led_t *led);

#if defined(WS2812_VCC_ENABLE_PINS)
void ws2812_set_power(bool on);
#endif
4 changes: 2 additions & 2 deletions quantum/quantum.c
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ void suspend_power_down_quantum(void) {
# endif

// Turn off underglow
# if defined(RGBLIGHT_SLEEP) && defined(RGBLIGHT_ENABLE)
# if defined(RGBLIGHT_ENABLE)
rgblight_suspend();
# endif

Expand Down Expand Up @@ -541,7 +541,7 @@ __attribute__((weak)) void suspend_wakeup_init_quantum(void) {
led_wakeup();

// Wake up underglow
#if defined(RGBLIGHT_SLEEP) && defined(RGBLIGHT_ENABLE)
#if defined(RGBLIGHT_ENABLE)
rgblight_wakeup();
#endif

Expand Down
34 changes: 29 additions & 5 deletions quantum/rgb_matrix/rgb_matrix.c
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,18 @@ static void rgb_task_start(void) {
(last_input_activity_elapsed() > (uint32_t)RGB_MATRIX_TIMEOUT) ||
#endif // RGB_MATRIX_TIMEOUT > 0
false;
bool off_state = suspend_backlight || !rgb_matrix_config.enable;

if (rgb_matrix_driver.set_power != NULL) {
rgb_matrix_driver.set_power(!off_state);
if (off_state) {
rgb_task_state = SYNCING;
return;
}
}

// Set effect to be renedered
rgb_current_effect = suspend_backlight || !rgb_matrix_config.enable ? 0 : rgb_matrix_config.mode;
rgb_current_effect = off_state ? 0 : rgb_matrix_config.mode;

// next task
rgb_task_state = RENDERING;
Expand Down Expand Up @@ -524,16 +533,31 @@ void rgb_matrix_init(void) {
eeconfig_update_rgb_matrix_default();
}
eeconfig_debug_rgb_matrix(); // display current eeprom values

if (rgb_matrix_driver.set_power != NULL) {
rgb_matrix_driver.set_power(rgb_matrix_config.enable);
}
}

void rgb_matrix_set_suspend_state(bool state) {
#ifdef RGB_MATRIX_SLEEP
bool gate_available = (rgb_matrix_driver.set_power != NULL);

#ifndef RGB_MATRIX_SLEEP
/* Gate opts in independently of RGB_MATRIX_SLEEP. */
if (!gate_available) {
return;
}
#endif

if (state && !suspend_state) { // only run if turning off, and only once
rgb_task_render(0); // turn off all LEDs when suspending
rgb_task_flush(0); // and actually flash led state to LEDs
if (gate_available) {
rgb_matrix_driver.set_power(false);
} else {
rgb_task_render(0); // turn off all LEDs when suspending
rgb_task_flush(0); // and actually flash led state to LEDs
}
}
suspend_state = state;
#endif
}

bool rgb_matrix_get_suspend_state(void) {
Expand Down
3 changes: 3 additions & 0 deletions quantum/rgb_matrix/rgb_matrix_drivers.c
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ const rgb_matrix_driver_t rgb_matrix_driver = {
.flush = ws2812_flush,
.set_color = ws2812_set_color,
.set_color_all = ws2812_set_color_all,
#if defined(WS2812_VCC_ENABLE_PINS)
.set_power = ws2812_set_power,
#endif
};

#endif
3 changes: 3 additions & 0 deletions quantum/rgb_matrix/rgb_matrix_drivers.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#pragma once

#include <stdbool.h>
#include <stdint.h>

#if defined(RGB_MATRIX_AW20216S)
Expand Down Expand Up @@ -46,6 +47,8 @@ typedef struct {
void (*set_color_all)(uint8_t r, uint8_t g, uint8_t b);
/* Flush any buffered changes to the hardware. */
void (*flush)(void);
/* Optional: toggle the chain's VCC-enable rail. NULL when not supported. */
void (*set_power)(bool on);
} rgb_matrix_driver_t;

extern const rgb_matrix_driver_t rgb_matrix_driver;
34 changes: 30 additions & 4 deletions quantum/rgblight/rgblight.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ rgblight_config_t rgblight_config;
rgblight_status_t rgblight_status = {.timer_enabled = false};
bool is_rgblight_initialized = false;

static inline void rgblight_set_power(bool on) {
if (rgblight_driver.set_power != NULL) {
rgblight_driver.set_power(on);
}
}

#ifdef RGBLIGHT_SLEEP
static bool is_suspended;
static bool pre_suspend_enabled;
Expand Down Expand Up @@ -225,6 +231,7 @@ void rgblight_init(void) {
rgblight_timer_init(); // setup the timer

rgblight_driver.init();
rgblight_set_power(rgblight_config.enable);

if (rgblight_config.enable) {
rgblight_mode_noeeprom(rgblight_config.mode);
Expand Down Expand Up @@ -371,12 +378,14 @@ void rgblight_enable(void) {
// No need to update EEPROM here. rgblight_mode() will do that, actually
// eeconfig_update_rgblight(&rgblight_config);
dprintf("rgblight enable [EEPROM]: rgblight_config.enable = %u\n", rgblight_config.enable);
rgblight_set_power(true);
rgblight_mode(rgblight_config.mode);
}

void rgblight_enable_noeeprom(void) {
rgblight_config.enable = 1;
dprintf("rgblight enable [NOEEPROM]: rgblight_config.enable = %u\n", rgblight_config.enable);
rgblight_set_power(true);
rgblight_mode_noeeprom(rgblight_config.mode);
}

Expand Down Expand Up @@ -822,9 +831,15 @@ void rgblight_blink_layer_repeat_helper(void) {

#endif

#ifdef RGBLIGHT_SLEEP

void rgblight_suspend(void) {
#ifndef RGBLIGHT_SLEEP
/* Gate opts in independently of RGBLIGHT_SLEEP. */
if (rgblight_driver.set_power == NULL) {
return;
}
rgblight_set_power(false);
return;
#else
rgblight_timer_disable();
if (!is_suspended) {
is_suspended = true;
Expand All @@ -838,9 +853,17 @@ void rgblight_suspend(void) {

rgblight_disable_noeeprom();
}
#endif
}

void rgblight_wakeup(void) {
#ifndef RGBLIGHT_SLEEP
if (rgblight_driver.set_power == NULL) {
return;
}
rgblight_set_power(rgblight_config.enable);
return;
#else
is_suspended = false;

if (pre_suspend_enabled) {
Expand All @@ -854,12 +877,15 @@ void rgblight_wakeup(void) {
# endif

rgblight_timer_enable();
}

#endif
}

void rgblight_set(void) {
if (!rgblight_config.enable) {
if (rgblight_driver.set_power != NULL) {
rgblight_set_power(false);
return;
}
for (uint8_t i = rgblight_ranges.effect_start_pos; i < rgblight_ranges.effect_end_pos; i++) {
rgblight_driver.set_color(rgblight_led_index(i), 0, 0, 0);
}
Expand Down
3 changes: 3 additions & 0 deletions quantum/rgblight/rgblight_drivers.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const rgblight_driver_t rgblight_driver = {
.set_color = ws2812_set_color,
.set_color_all = ws2812_set_color_all,
.flush = ws2812_flush,
#if defined(WS2812_VCC_ENABLE_PINS)
.set_power = ws2812_set_power,
#endif
};

#elif defined(RGBLIGHT_APA102)
Expand Down
3 changes: 3 additions & 0 deletions quantum/rgblight/rgblight_drivers.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@

#pragma once

#include <stdbool.h>
#include <stdint.h>

typedef struct {
void (*init)(void);
void (*set_color)(int index, uint8_t red, uint8_t green, uint8_t blue);
void (*set_color_all)(uint8_t red, uint8_t green, uint8_t blue);
void (*flush)(void);
/* Optional: toggle the chain's VCC-enable rail. NULL when not supported. */
void (*set_power)(bool on);
} rgblight_driver_t;

extern const rgblight_driver_t rgblight_driver;