feat(led_strip): Add driver for CKLED2001 LED strip controller

Signed-off-by: XiNGRZ <hi@xingrz.me>
This commit is contained in:
XiNGRZ 2022-08-11 13:50:16 +08:00
parent 1c862c5b94
commit 5e6342b1cb
8 changed files with 249 additions and 0 deletions

View file

@ -5,3 +5,4 @@ add_subdirectory_ifdef(CONFIG_ZMK_DRIVERS_GPIO gpio)
add_subdirectory(kscan) add_subdirectory(kscan)
add_subdirectory(sensor) add_subdirectory(sensor)
add_subdirectory(display) add_subdirectory(display)
add_subdirectory(led_strip)

View file

@ -5,3 +5,4 @@ rsource "gpio/Kconfig"
rsource "kscan/Kconfig" rsource "kscan/Kconfig"
rsource "sensor/Kconfig" rsource "sensor/Kconfig"
rsource "display/Kconfig" rsource "display/Kconfig"
rsource "led_strip/Kconfig"

View file

@ -0,0 +1,4 @@
# Copyright (c) 2022 The ZMK Contributors
# SPDX-License-Identifier: MIT
zephyr_sources_ifdef(CONFIG_CKLED2001 ckled2001.c)

View file

@ -0,0 +1,4 @@
# Copyright (c) 2022 The ZMK Contributors
# SPDX-License-Identifier: MIT
rsource "Kconfig.ckled2001"

View file

@ -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.

View file

@ -0,0 +1,181 @@
/*
* Copyright (c) 2022 The ZMK Contributors
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_ckled2001
#include <drivers/led_strip.h>
#define LOG_LEVEL CONFIG_LED_STRIP_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(ckled2001);
#include <zephyr.h>
#include <device.h>
#include <drivers/i2c.h>
#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);

View file

@ -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

View file

@ -0,0 +1,8 @@
/*
* Copyright (c) 2022 The ZMK Contributors
* SPDX-License-Identifier: MIT
*/
#pragma once
#define CK(ch, idx) ((ch << 4) | idx)