diff --git a/app/drivers/CMakeLists.txt b/app/drivers/CMakeLists.txt index 44d69ac3..2af9054d 100644 --- a/app/drivers/CMakeLists.txt +++ b/app/drivers/CMakeLists.txt @@ -5,3 +5,4 @@ add_subdirectory_ifdef(CONFIG_ZMK_DRIVERS_GPIO gpio) add_subdirectory(kscan) add_subdirectory(sensor) add_subdirectory(display) +add_subdirectory(led_strip) diff --git a/app/drivers/Kconfig b/app/drivers/Kconfig index c57ed334..5c0d6437 100644 --- a/app/drivers/Kconfig +++ b/app/drivers/Kconfig @@ -5,3 +5,4 @@ rsource "gpio/Kconfig" rsource "kscan/Kconfig" rsource "sensor/Kconfig" rsource "display/Kconfig" +rsource "led_strip/Kconfig" diff --git a/app/drivers/led_strip/CMakeLists.txt b/app/drivers/led_strip/CMakeLists.txt new file mode 100644 index 00000000..bb131953 --- /dev/null +++ b/app/drivers/led_strip/CMakeLists.txt @@ -0,0 +1,4 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +zephyr_sources_ifdef(CONFIG_CKLED2001 ckled2001.c) diff --git a/app/drivers/led_strip/Kconfig b/app/drivers/led_strip/Kconfig new file mode 100644 index 00000000..12c7e4a5 --- /dev/null +++ b/app/drivers/led_strip/Kconfig @@ -0,0 +1,4 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +rsource "Kconfig.ckled2001" diff --git a/app/drivers/led_strip/Kconfig.ckled2001 b/app/drivers/led_strip/Kconfig.ckled2001 new file mode 100644 index 00000000..55797b2c --- /dev/null +++ b/app/drivers/led_strip/Kconfig.ckled2001 @@ -0,0 +1,12 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +DT_COMPAT_ZMK_CKLED2001 := zmk,ckled2001 + +config CKLED2001 + bool "CKLED2001 compatible LED strip controller driver" + default $(dt_compat_enabled,$(DT_COMPAT_ZMK_CKLED2001)) + depends on I2C + depends on LED_STRIP + help + Enable driver for CKLED2001 compatible LED strip controller. diff --git a/app/drivers/led_strip/ckled2001.c b/app/drivers/led_strip/ckled2001.c new file mode 100644 index 00000000..5c25ed30 --- /dev/null +++ b/app/drivers/led_strip/ckled2001.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_ckled2001 + +#include + +#define LOG_LEVEL CONFIG_LED_STRIP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(ckled2001); + +#include +#include +#include + +#define REG_SET_CMD_PAGE 0xFD +#define LED_CONTROL_PAGE 0x00 +#define LED_PWM_PAGE 0x01 +#define FUNCTION_PAGE 0x03 +#define CURRENT_TUNE_PAGE 0x04 + +#define REG_CONFIGRATION 0x00 +#define MSKSW_SHUTDOWN_MODE 0x0 +#define MSKSW_NORMAL_MODE 0x1 + +#define REG_PDU 0x13 +#define MSKSET_CA_CB_CHANNEL 0xAA +#define MSKCLR_CA_CB_CHANNEL 0x00 + +#define REG_SCAN_PHASE 0x14 +#define MSKPHASE_CHANNELS(cnt) (12 - cnt) + +#define REG_SLEW_RATE_CONTROL_MODE1 0x15 +#define MSKPWM_DELAY_PHASE_ENABLE 0x04 +#define MSKPWM_DELAY_PHASE_DISABLE 0x00 + +#define REG_SLEW_RATE_CONTROL_MODE2 0x16 +#define MSKDRIVING_SINKING_CHHANNEL_SLEWRATE_ENABLE 0xC0 +#define MSKDRIVING_SINKING_CHHANNEL_SLEWRATE_DISABLE 0x00 + +#define REG_SOFTWARE_SLEEP 0x1A +#define MSKSLEEP_ENABLE 0x02 +#define MSKSLEEP_DISABLE 0x00 + +#define COUNT_BETWEEN(a, b) ((b - a) + 1) + +#define LED_CONTROL_CNT 24 +#define LED_PWM_CNT 192 +#define CURRENT_TUNE_CNT 12 + +struct ckled2001_channel_map { + uint8_t ch_r; + uint8_t ch_g; + uint8_t ch_b; +}; + +struct ckled2001_config { + struct i2c_dt_spec bus; + uint8_t scan_phase_channels; + struct ckled2001_channel_map *map; + uint32_t map_cnt; + uint8_t *pwm_buffer; +}; + +static inline int ckled2001_write_reg(const struct device *dev, uint8_t reg, uint8_t value) { + const struct ckled2001_config *config = dev->config; + return i2c_burst_write_dt(&config->bus, reg, &value, 1); +} + +static inline int ckled2001_set_control(const struct device *dev, uint8_t value) { + ckled2001_write_reg(dev, REG_SET_CMD_PAGE, LED_CONTROL_PAGE); + for (int i = 0; i < LED_CONTROL_CNT; i++) { + ckled2001_write_reg(dev, i, value); + } + return 0; +} + +static inline int ckled2001_flush_pwm_buffer(const struct device *dev) { + const struct ckled2001_config *config = dev->config; + ckled2001_write_reg(dev, REG_SET_CMD_PAGE, LED_PWM_PAGE); + return i2c_burst_write_dt(&config->bus, 0, (const uint8_t *)config->pwm_buffer, LED_PWM_CNT); +} + +static int ckled2001_update_rgb(const struct device *dev, struct led_rgb *pixels, + size_t num_pixels) { + const struct ckled2001_config *config = dev->config; + + if (num_pixels > config->map_cnt) { + num_pixels = config->map_cnt; + } + for (size_t i = 0; i < num_pixels; i++) { + config->pwm_buffer[config->map[i].ch_r] = pixels[i].r; + config->pwm_buffer[config->map[i].ch_g] = pixels[i].g; + config->pwm_buffer[config->map[i].ch_b] = pixels[i].b; + } + + return ckled2001_flush_pwm_buffer(dev); +} + +static int ckled2001_update_channels(const struct device *dev, uint8_t *channels, + size_t num_channels) { + return 0; +} + +static int ckled2001_init(const struct device *dev) { + const struct ckled2001_config *config = dev->config; + + LOG_INF("Loaded %d channel mappings", config->map_cnt); + + if (!device_is_ready(config->bus.bus)) { + LOG_ERR("I2C bus not ready: %s", config->bus.bus->name); + return -ENODEV; + } + + // Set functions + ckled2001_write_reg(dev, REG_SET_CMD_PAGE, FUNCTION_PAGE); + ckled2001_write_reg(dev, REG_CONFIGRATION, MSKSW_SHUTDOWN_MODE); + ckled2001_write_reg(dev, REG_PDU, MSKSET_CA_CB_CHANNEL); + ckled2001_write_reg(dev, REG_SCAN_PHASE, MSKPHASE_CHANNELS(config->scan_phase_channels)); + ckled2001_write_reg(dev, REG_SLEW_RATE_CONTROL_MODE1, MSKPWM_DELAY_PHASE_ENABLE); + ckled2001_write_reg(dev, REG_SLEW_RATE_CONTROL_MODE2, + MSKDRIVING_SINKING_CHHANNEL_SLEWRATE_ENABLE); + ckled2001_write_reg(dev, REG_SOFTWARE_SLEEP, MSKSLEEP_DISABLE); + + // Turn off all LEDs + ckled2001_set_control(dev, 0x00); + + // Init PWM page + memset(config->pwm_buffer, 0x00, LED_PWM_CNT); + ckled2001_flush_pwm_buffer(dev); + + // Init current page + ckled2001_write_reg(dev, REG_SET_CMD_PAGE, CURRENT_TUNE_PAGE); + for (int i = 0; i < CURRENT_TUNE_CNT; i++) { + switch (i) { + case 2: + case 5: + case 8: + case 11: + ckled2001_write_reg(dev, i, 0xA0); + break; + default: + ckled2001_write_reg(dev, i, 0xFF); + break; + } + } + + // Turn on all LEDs + ckled2001_set_control(dev, 0xFF); + + // Set to normal mode + ckled2001_write_reg(dev, REG_SET_CMD_PAGE, FUNCTION_PAGE); + ckled2001_write_reg(dev, REG_CONFIGRATION, MSKSW_NORMAL_MODE); + + return 0; +} + +static const struct led_strip_driver_api ckled2001_api = { + .update_rgb = ckled2001_update_rgb, + .update_channels = ckled2001_update_channels, +}; + +#define CKLED2001_INIT(n) \ + static uint8_t ckled2001_channel_map##n[] = DT_INST_PROP(n, map); \ + \ + static uint8_t ckled2001_pwm_buffer_##n[LED_PWM_CNT]; \ + \ + static const struct ckled2001_config ckled2001_config_##n = { \ + .bus = I2C_DT_SPEC_INST_GET(n), \ + .scan_phase_channels = DT_INST_PROP_OR(n, scan_phase_channels, 12), \ + .map = (struct ckled2001_channel_map *)ckled2001_channel_map##n, \ + .map_cnt = DT_INST_PROP_LEN(n, map) / 3, \ + .pwm_buffer = ckled2001_pwm_buffer_##n, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, &ckled2001_init, NULL, NULL, &ckled2001_config_##n, POST_KERNEL, \ + CONFIG_LED_STRIP_INIT_PRIORITY, &ckled2001_api); + +DT_INST_FOREACH_STATUS_OKAY(CKLED2001_INIT); diff --git a/app/drivers/zephyr/dts/bindings/led_strip/zmk,ckled2001.yaml b/app/drivers/zephyr/dts/bindings/led_strip/zmk,ckled2001.yaml new file mode 100644 index 00000000..353740c0 --- /dev/null +++ b/app/drivers/zephyr/dts/bindings/led_strip/zmk,ckled2001.yaml @@ -0,0 +1,38 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: CKLED2001 LED strip controller + +compatible: "zmk,ckled2001" + +include: [i2c-device.yaml] + +properties: + label: + type: string + required: true + + chain-length: + type: int + required: true + + scan-phase-channels: + type: int + default: 12 + enum: + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - 8 + - 9 + - 10 + - 11 + - 12 + + map: + type: array + required: true diff --git a/app/include/dt-bindings/zmk/ckled2001.h b/app/include/dt-bindings/zmk/ckled2001.h new file mode 100644 index 00000000..5c2050fb --- /dev/null +++ b/app/include/dt-bindings/zmk/ckled2001.h @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#define CK(ch, idx) ((ch << 4) | idx) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 7b649174..b706b7f9 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -335,15 +335,15 @@ int zmk_rgb_underglow_off() { } #endif + k_timer_stop(&underglow_tick); + state.on = false; + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; } led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); - k_timer_stop(&underglow_tick); - state.on = false; - return zmk_rgb_underglow_save_state(); }