diff --git a/app/dts/bindings/led_strip/issi,is31fl3743a.yaml b/app/dts/bindings/led_strip/issi,is31fl3743a.yaml new file mode 100644 index 00000000..43a58855 --- /dev/null +++ b/app/dts/bindings/led_strip/issi,is31fl3743a.yaml @@ -0,0 +1,363 @@ +# Copyright (c) 2020, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: ISSI IS31FL3743A LED Matrix driver + +compatible: "issi,is31fl3743a" + +include: i2c-device.yaml + +properties: + sw-setting: + type: int + required: false + enum: + - 0x00 + - 0x01 + - 0x02 + - 0x03 + - 0x04 + - 0x05 + - 0x06 + - 0x07 + - 0x08 + - 0x09 + - 0x0a + default: 0x00 + description: | + SW setting used to control the matrix size by turning off SWx pins + as described in the datasheet. + + map: + type: array + required: true + description: | + Configure how RGB pixels are mapped to individual pixel registers. + + r-ext: + type: int + required: true + description: Rext resistor value in kiloohms. + + led-max-current: + type: int + required: true + description: Maximum current for each LED in mA. The chip supports up to 34mA. + + sync: + type: int + enum: + - 0x00 + - 0x10 + - 0x11 + required: false + default: 0x00 + description: | + Controls the SYNC function. Set to 0x11 to set the device as main or 0x10 for secondary. 0x00 for 'off'. + + sdb-gpios: + type: phandle-array + required: false + description: | + GPIO pin for hardware shutdown functionality. + + chain-length: + type: int + required: true + description: | + How many RGB LEDs are driven by the IC. + + scaling-red: + type: int + required: false + default: 255 + description: | + Current scaling factor for red channel LEDs on a scale of 0-255. + This setting adjusts the brightness of red pixels relative to other channels by scaling + the amount of current flowing through them. 255 equals led-max-current. + + scaling-green: + type: int + required: false + default: 255 + description: | + Current scaling factor for green channel LEDs on a scale of 0-255. + This setting adjusts the brightness of green pixels relative to other channels by scaling + the amount of current flowing through them. 255 equals led-max-current. + + scaling-blue: + type: int + required: false + default: 255 + description: | + Current scaling factor for blue channel LEDs on a scale of 0-255. + This setting adjusts the brightness of blue pixels relative to other channels by scaling + the amount of current flowing through them. 255 equals led-max-current. + + gamma: + type: array + required: false + description: | + Gamma correction lookup values. + The gamma values make the LED brightness seem more linear to human eyes. + Default values match the recommendation from the IC datasheet but note that this may + or may not apply for your particular LEDs. + default: + [ + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 6, + 6, + 6, + 6, + 7, + 7, + 7, + 7, + 8, + 8, + 8, + 8, + 10, + 10, + 10, + 10, + 12, + 12, + 12, + 12, + 14, + 14, + 14, + 14, + 16, + 16, + 16, + 16, + 18, + 18, + 18, + 18, + 20, + 20, + 20, + 20, + 22, + 22, + 22, + 22, + 24, + 24, + 24, + 24, + 26, + 26, + 26, + 26, + 29, + 29, + 29, + 29, + 32, + 32, + 32, + 32, + 35, + 35, + 35, + 35, + 38, + 38, + 38, + 38, + 41, + 41, + 41, + 41, + 44, + 44, + 44, + 44, + 47, + 47, + 47, + 47, + 50, + 50, + 50, + 50, + 53, + 53, + 53, + 53, + 57, + 57, + 57, + 57, + 61, + 61, + 61, + 61, + 65, + 65, + 65, + 65, + 69, + 69, + 69, + 69, + 73, + 73, + 73, + 73, + 77, + 77, + 77, + 77, + 81, + 81, + 81, + 81, + 85, + 85, + 85, + 85, + 89, + 89, + 89, + 89, + 94, + 94, + 94, + 94, + 99, + 99, + 99, + 99, + 104, + 104, + 104, + 104, + 109, + 109, + 109, + 109, + 114, + 114, + 114, + 114, + 119, + 119, + 119, + 119, + 124, + 124, + 124, + 124, + 129, + 129, + 129, + 129, + 134, + 134, + 134, + 134, + 140, + 140, + 140, + 140, + 146, + 146, + 146, + 146, + 152, + 152, + 152, + 152, + 158, + 158, + 158, + 158, + 164, + 164, + 164, + 164, + 170, + 170, + 170, + 170, + 176, + 176, + 176, + 176, + 182, + 182, + 182, + 182, + 188, + 188, + 188, + 188, + 195, + 195, + 195, + 195, + 202, + 202, + 202, + 202, + 209, + 209, + 209, + 209, + 216, + 216, + 216, + 216, + 223, + 223, + 223, + 223, + 230, + 230, + 230, + 230, + 237, + 237, + 237, + 237, + 244, + 244, + 244, + 244, + 251, + 251, + 251, + 251, + 255, + 255, + 255, + 255, + ] diff --git a/app/include/dt-bindings/zmk/is31fl3743_transform.h b/app/include/dt-bindings/zmk/is31fl3743_transform.h new file mode 100644 index 00000000..b64f584d --- /dev/null +++ b/app/include/dt-bindings/zmk/is31fl3743_transform.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define RGB(com, r, g, b) (com + r)(com + g)(com + b) + +#define SW(n) ((n - 1) * 18) +#define CS(n) (n - 1) diff --git a/app/module/drivers/CMakeLists.txt b/app/module/drivers/CMakeLists.txt index 5281c3dc..27dccfc8 100644 --- a/app/module/drivers/CMakeLists.txt +++ b/app/module/drivers/CMakeLists.txt @@ -5,3 +5,4 @@ add_subdirectory_ifdef(CONFIG_GPIO gpio) add_subdirectory_ifdef(CONFIG_KSCAN kscan) add_subdirectory_ifdef(CONFIG_SENSOR sensor) add_subdirectory_ifdef(CONFIG_DISPLAY display) +add_subdirectory_ifdef(CONFIG_LED_STRIP led_strip) diff --git a/app/module/drivers/Kconfig b/app/module/drivers/Kconfig index c57ed334..d23f8f26 100644 --- a/app/module/drivers/Kconfig +++ b/app/module/drivers/Kconfig @@ -3,5 +3,6 @@ rsource "gpio/Kconfig" rsource "kscan/Kconfig" +rsource "led_strip/Kconfig" rsource "sensor/Kconfig" rsource "display/Kconfig" diff --git a/app/module/drivers/led_strip/CMakeLists.txt b/app/module/drivers/led_strip/CMakeLists.txt new file mode 100644 index 00000000..17111538 --- /dev/null +++ b/app/module/drivers/led_strip/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +zephyr_library_amend() + +zephyr_library_sources_ifdef(CONFIG_IS31FL3743A is31fl3743a.c) diff --git a/app/module/drivers/led_strip/Kconfig b/app/module/drivers/led_strip/Kconfig new file mode 100644 index 00000000..44f93906 --- /dev/null +++ b/app/module/drivers/led_strip/Kconfig @@ -0,0 +1,8 @@ +# Copyright (c) 2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +if LED_STRIP + +rsource "Kconfig.is31fl3743a" + +endif # LED_STRIP diff --git a/app/module/drivers/led_strip/Kconfig.is31fl3743a b/app/module/drivers/led_strip/Kconfig.is31fl3743a new file mode 100644 index 00000000..85e1c201 --- /dev/null +++ b/app/module/drivers/led_strip/Kconfig.is31fl3743a @@ -0,0 +1,15 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +config IS31FL3743A + bool "IS31FL3743A LED matrix driver" + depends on I2C + depends on LED_STRIP + help + Enable an IS31FL3743A LED matrix driver. + + The IS31FL3743A is a general purpose 18xn(n=1-11) LED matrix + programmed via 1MHz I2C compatible interface. Each LED can be + dimmed individually with 8-bit PWM data and 8-bit DC scaling data + which allows 256 steps of linear PWM dimming and 256 steps of DC + current adjustable levels. diff --git a/app/module/drivers/led_strip/is31fl3743a.c b/app/module/drivers/led_strip/is31fl3743a.c new file mode 100644 index 00000000..8dc5fcf5 --- /dev/null +++ b/app/module/drivers/led_strip/is31fl3743a.c @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT issi_is31fl3743a + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#define IS31FL3743A_REG_PS (0xfd) +#define IS31FL3743A_REG_PSWL (0xfe) + +#define IS31FL3743A_PSWL_ENABLE (0xc5) +#define IS31FL3743A_PSWL_DISABLE (0x00) + +#define IS31FL3743A_PAGE_PWM (0x00) +#define IS31FL3743A_PAGE_SCALING (0x01) +#define IS31FL3743A_PAGE_FUNCTION (0x02) + +struct is31fl3743a_config { + char *label; + struct i2c_dt_spec i2c; + struct gpio_dt_spec gpio; + size_t px_buffer_size; + uint8_t gcc; + uint8_t sws; + uint8_t sync; + uint8_t *rgb_map; + uint8_t *gamma; + uint8_t scaling_red; + uint8_t scaling_green; + uint8_t scaling_blue; +}; + +struct is31fl3743a_data { + uint8_t *px_buffer; +}; + +static int is31fl3743a_reg_write(const struct device *dev, uint8_t addr, uint8_t value) { + const struct is31fl3743a_config *config = dev->config; + + if (i2c_reg_write_byte_dt(&config->i2c, addr, value)) { + LOG_ERR("Failed writing value %x to register address %x on device %x.", value, addr, + config->i2c.addr); + return -EIO; + } + + return 0; +} + +static int is31fl3743a_reg_burst_write(const struct device *dev, uint8_t start_addr, + const uint8_t *buffer, size_t num_bytes) { + const struct is31fl3743a_config *config = dev->config; + + if (i2c_burst_write_dt(&config->i2c, start_addr, buffer, num_bytes)) { + LOG_ERR("Failed burst write with starting address %x.", start_addr); + return -EIO; + } + + return 0; +} + +static int is31fl3743a_set_page(const struct device *dev, uint8_t page_addr) { + if (is31fl3743a_reg_write(dev, IS31FL3743A_REG_PSWL, IS31FL3743A_PSWL_ENABLE)) { + return -EIO; + } + + if (is31fl3743a_reg_write(dev, IS31FL3743A_REG_PS, page_addr)) { + return -EIO; + } + + return 0; +} + +static inline bool num_pixels_ok(const struct is31fl3743a_config *config, size_t num_pixels) { + size_t num_bytes; + + const bool overflow = size_mul_overflow(num_pixels, 3, &num_bytes); + + return !overflow && (num_bytes <= config->px_buffer_size); +} + +/** + * Updates individual LED channels without an RGB interpretation. + */ +static int is31fl3743a_strip_update_channels(const struct device *dev, uint8_t *channels, + size_t num_channels) { + const struct is31fl3743a_config *config = dev->config; + + if (config->px_buffer_size < num_channels) { + return -ENOMEM; + } + + is31fl3743a_set_page(dev, IS31FL3743A_PAGE_PWM); + + return is31fl3743a_reg_burst_write(dev, 0x01, channels, num_channels); +} + +/* + * Updates the RGB LED matrix according to devicetree's map property. + */ +static int is31fl3743a_strip_update_rgb(const struct device *dev, struct led_rgb *pixels, + size_t num_pixels) { + const struct is31fl3743a_config *config = dev->config; + const struct is31fl3743a_data *data = dev->data; + + size_t pixel_idx = 0; + size_t led_idx = 0; + + if (!num_pixels_ok(config, num_pixels)) { + return -ENOMEM; + } + + while (pixel_idx < num_pixels) { + data->px_buffer[config->rgb_map[led_idx++]] = config->gamma[pixels[pixel_idx].r]; + data->px_buffer[config->rgb_map[led_idx++]] = config->gamma[pixels[pixel_idx].g]; + data->px_buffer[config->rgb_map[led_idx++]] = config->gamma[pixels[pixel_idx].b]; + + ++pixel_idx; + } + + return is31fl3743a_strip_update_channels(dev, data->px_buffer, config->px_buffer_size); +} + +/* + * Initiates a driver instance for IS31FL3743A. + * + * If available, SDB is pulled high to enable chip operation, + * followed by a reset to clear out all previous values. + * + * Function and scaling registers are then pre-configured based on devicetree settings. + */ +int static is31fl3743a_init(const struct device *dev) { + const struct is31fl3743a_config *config = dev->config; + const struct is31fl3743a_data *data = dev->data; + + if (!device_is_ready(config->i2c.bus)) { + LOG_ERR("I2C device %s is not ready", config->i2c.bus->name); + return -ENODEV; + } + + if (!device_is_ready(config->gpio.port)) { + LOG_ERR("GPIO device for %s is not ready", config->label); + return -ENODEV; + } + + if (gpio_pin_configure_dt(&config->gpio, GPIO_OUTPUT)) { + LOG_ERR("SDB pin for %s cannot be configured", config->label); + return -EIO; + } + + if (gpio_pin_set_dt(&config->gpio, 1)) { + LOG_ERR("SDB pin for %s cannot be pulled high", config->label); + return -EIO; + } + + // Reset settings + is31fl3743a_set_page(dev, IS31FL3743A_PAGE_FUNCTION); + is31fl3743a_reg_write(dev, 0x2f, 0xae); + + // Set configuration & GCC registers + is31fl3743a_set_page(dev, IS31FL3743A_PAGE_FUNCTION); + is31fl3743a_reg_write(dev, 0x00, (config->sws << 4) | (0x01 << 3) | 0x01); // Configuration + is31fl3743a_reg_write(dev, 0x01, config->gcc); // GCC + + // Set scaling registers + uint8_t *px_buffer = data->px_buffer; + uint8_t *rgb_map = config->rgb_map; + + for (size_t i = 0; i < config->px_buffer_size; i += 3) { + px_buffer[rgb_map[i]] = config->scaling_red; + px_buffer[rgb_map[i + 1]] = config->scaling_green; + px_buffer[rgb_map[i + 2]] = config->scaling_blue; + } + + is31fl3743a_set_page(dev, IS31FL3743A_PAGE_SCALING); + is31fl3743a_reg_burst_write(dev, 0x01, px_buffer, num_pixels); + + // Re-initialize px_buffer to prevent any scaling values from sticking around + // when updating PWN registers during normal operation. + for (size_t i = 0; i < config->px_buffer_size; ++i) { + px_buffer[i] = 0; + } + + return 0; +} + +static const struct led_strip_driver_api is31fl3743a_api = { + .update_rgb = is31fl3743a_strip_update_rgb, + .update_channels = is31fl3743a_strip_update_channels, +}; + +#define IS31FL3743A_BUFFER_SIZE(idx) \ + IS31FL3743A_CS_PINS *(IS31FL3743A_SW_PINS - DT_INST_PROP(idx, sw_setting)) + +#define IS31FL3743A_GCC(idx) \ + (DT_INST_PROP(idx, r_ext) * DT_INST_PROP(idx, led_max_current) * 256 * 256) / (343 * 255) + +#define IS31FL3743A_DEVICE(idx) \ + \ + static uint8_t is31fl3743a_##idx##_px_buffer[IS31FL3743A_BUFFER_SIZE(idx)]; \ + \ + static struct is31fl3743a_data is31fl3743a_##idx##_data = { \ + .px_buffer = is31fl3743a_##idx##_px_buffer, \ + }; \ + \ + static uint8_t is31fl3743a_##idx##_rgb_map[IS31FL3743A_BUFFER_SIZE(idx)] = \ + DT_INST_PROP(idx, map); \ + \ + static uint8_t is31fl3743a_##idx##_gamma[] = DT_INST_PROP(idx, gamma); \ + \ + static const struct is31fl3743a_config is31fl3743a_##idx##_config = { \ + .label = DT_INST_LABEL(idx), \ + .i2c = I2C_DT_SPEC_INST_GET(idx), \ + .gpio = GPIO_DT_SPEC_INST_GET(idx, sdb_gpios), \ + .px_buffer_size = IS31FL3743A_BUFFER_SIZE(idx), \ + .gcc = IS31FL3743A_GCC(idx), \ + .sws = DT_INST_PROP(idx, sw_setting), \ + .sync = DT_INST_PROP(idx, sync), \ + .rgb_map = is31fl3743a_##idx##_rgb_map, \ + .gamma = is31fl3743a_##idx##_gamma, \ + .scaling_red = DT_INST_PROP(idx, scaling_red), \ + .scaling_green = DT_INST_PROP(idx, scaling_green), \ + .scaling_blue = DT_INST_PROP(idx, scaling_blue), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(idx, &is31fl3743a_init, NULL, &is31fl3743a_##idx##_data, \ + &is31fl3743a_##idx##_config, POST_KERNEL, \ + CONFIG_LED_STRIP_INIT_PRIORITY, &is31fl3743a_api); + +DT_INST_FOREACH_STATUS_OKAY(IS31FL3743A_DEVICE); diff --git a/app/module/dts/bindings/led_strip/issi,is31fl3743a.yaml b/app/module/dts/bindings/led_strip/issi,is31fl3743a.yaml new file mode 100644 index 00000000..4a861734 --- /dev/null +++ b/app/module/dts/bindings/led_strip/issi,is31fl3743a.yaml @@ -0,0 +1,6 @@ +description: | + Driver for the IS31FL3743A LED matrix driver + +compatible: "issi,issi_is31fl3743a" + +include: i2c-device.yaml