From 8d979fbbc3a168b68280956e5bfabbbd47047534 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Sun, 4 Jul 2021 20:41:00 +0200 Subject: [PATCH] Implement a driver for IS31FL3741 LED matrix driver IC --- app/drivers/CMakeLists.txt | 1 + app/drivers/Kconfig | 3 +- app/drivers/led_strip/CMakeLists.txt | 4 + app/drivers/led_strip/Kconfig | 10 + .../led_strip/is31fl3741/CMakeLists.txt | 6 + app/drivers/led_strip/is31fl3741/Kconfig | 20 ++ app/drivers/led_strip/is31fl3741/is31fl3741.c | 267 ++++++++++++++++++ .../led_strip/is31fl3741/issi,is31fl3741.yaml | 6 + .../bindings/led_strip/issi,is31fl3741.yaml | 52 ++++ app/include/dt-bindings/zmk/issi_transform.h | 18 ++ 10 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 app/drivers/led_strip/CMakeLists.txt create mode 100644 app/drivers/led_strip/Kconfig create mode 100644 app/drivers/led_strip/is31fl3741/CMakeLists.txt create mode 100644 app/drivers/led_strip/is31fl3741/Kconfig create mode 100644 app/drivers/led_strip/is31fl3741/is31fl3741.c create mode 100644 app/drivers/led_strip/is31fl3741/issi,is31fl3741.yaml create mode 100644 app/dts/bindings/led_strip/issi,is31fl3741.yaml create mode 100644 app/include/dt-bindings/zmk/issi_transform.h diff --git a/app/drivers/CMakeLists.txt b/app/drivers/CMakeLists.txt index 44d69ac3..aec0b187 100644 --- a/app/drivers/CMakeLists.txt +++ b/app/drivers/CMakeLists.txt @@ -3,5 +3,6 @@ add_subdirectory_ifdef(CONFIG_ZMK_DRIVERS_GPIO gpio) add_subdirectory(kscan) +add_subdirectory(led_strip) add_subdirectory(sensor) add_subdirectory(display) diff --git a/app/drivers/Kconfig b/app/drivers/Kconfig index c57ed334..dc437468 100644 --- a/app/drivers/Kconfig +++ b/app/drivers/Kconfig @@ -3,5 +3,6 @@ rsource "gpio/Kconfig" rsource "kscan/Kconfig" +rsource "led_strip/Kconfig" rsource "sensor/Kconfig" -rsource "display/Kconfig" +rsource "display/Kconfig" \ No newline at end of file diff --git a/app/drivers/led_strip/CMakeLists.txt b/app/drivers/led_strip/CMakeLists.txt new file mode 100644 index 00000000..beb82ae5 --- /dev/null +++ b/app/drivers/led_strip/CMakeLists.txt @@ -0,0 +1,4 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +add_subdirectory_ifdef(CONFIG_IS31FL3741 is31fl3741) diff --git a/app/drivers/led_strip/Kconfig b/app/drivers/led_strip/Kconfig new file mode 100644 index 00000000..22f77819 --- /dev/null +++ b/app/drivers/led_strip/Kconfig @@ -0,0 +1,10 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +rsource "is31fl3741/Kconfig" + +config LEG_STRIP_INIT_PRIORITY + int "LED strip initialization priority" + default 90 + help + System initialization priority for LED strip drivers. diff --git a/app/drivers/led_strip/is31fl3741/CMakeLists.txt b/app/drivers/led_strip/is31fl3741/CMakeLists.txt new file mode 100644 index 00000000..91bea8e1 --- /dev/null +++ b/app/drivers/led_strip/is31fl3741/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +zephyr_library() + +zephyr_library_sources(is31fl3741.c) diff --git a/app/drivers/led_strip/is31fl3741/Kconfig b/app/drivers/led_strip/is31fl3741/Kconfig new file mode 100644 index 00000000..0db07f07 --- /dev/null +++ b/app/drivers/led_strip/is31fl3741/Kconfig @@ -0,0 +1,20 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +config IS31FL3741 + bool "IS31FL3741 LED Matrix driver" + depends on I2C + help + Enable an IS31FL3741 LED Matrix driver. + + The IS31FL3741 is a general purpose 39x9 LED Matrix programmed + via an I2C compatible interface. Each LED can be dimmed + individually with 8-bit PWM data and 8-bit scaling data which + allows 256 steps of linear PWM dimming and 256 steps of DC current + adjustable level. + +config LED_STRIP_INIT_PRIORITY + int "LED strip initialization priority" + default 90 + help + System initialization priority for LED strip drivers. diff --git a/app/drivers/led_strip/is31fl3741/is31fl3741.c b/app/drivers/led_strip/is31fl3741/is31fl3741.c new file mode 100644 index 00000000..590874a6 --- /dev/null +++ b/app/drivers/led_strip/is31fl3741/is31fl3741.c @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT issi_is31fl3741 + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#define IS31FL3741_BUFFER_SIZE (39 * 9) +#define IS31FL3741_BUFFER_PAGE_BREAK (0xb4) + +#define IS31FL3741_REG_PS (0xfd) +#define IS31FL3741_REG_PSWL (0xfe) + +#define IS31FL3741_PSWL_ENABLE (0xc5) +#define IS31FL3741_PSWL_DISABLE (0x00) + +#define IS31FL3741_PAGE_PWM_A (0x00) +#define IS31FL3741_PAGE_PWM_B (0x01) +#define IS31FL3741_PAGE_SCALING_A (0x02) +#define IS31FL3741_PAGE_SCALING_B (0x03) +#define IS31FL3741_PAGE_FUNCTION (0x04) + +struct is31fl3741_config { + char *bus; + int reg; + char *label; + char *sdb_port; + gpio_pin_t sdb_pin; + gpio_dt_flags_t sdb_flags; + uint8_t *px_buffer; + size_t px_buffer_size; + uint8_t gcc; + uint8_t sws; + uint16_t *rgb_map; +}; + +struct is31fl3741_data { + const struct device *i2c; + const struct device *gpio; +}; + +static int is31fl3741_reg_write(const struct device *dev, uint8_t addr, uint8_t value) { + const struct is31fl3741_data *data = dev->data; + const struct is31fl3741_config *config = dev->config; + + if (i2c_reg_write_byte(data->i2c, config->reg, addr, value)) { + LOG_ERR("Failed writing value %x to register address %x on device %x.", value, addr, + config->reg); + return -EIO; + } + + return 0; +} + +static int is31fl3741_reg_burst_write(const struct device *dev, uint8_t start_addr, + const uint8_t *buffer, size_t num_bytes) { + const struct is31fl3741_data *data = dev->data; + const struct is31fl3741_config *config = dev->config; + + if (i2c_burst_write(data->i2c, config->reg, start_addr, buffer, num_bytes)) { + LOG_ERR("Failed burst write with starting address %x", start_addr); + return -EIO; + } + + return 0; +} + +static int is31fl3741_set_page(const struct device *dev, uint8_t page_addr) { + if (is31fl3741_reg_write(dev, IS31FL3741_REG_PSWL, IS31FL3741_PSWL_ENABLE)) { + return -EIO; + } + + if (is31fl3741_reg_write(dev, IS31FL3741_REG_PS, page_addr)) { + return -EIO; + } + + return 0; +} + +static inline bool num_pixels_ok(const struct is31fl3741_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 is31fl3741_strip_update_channels(const struct device *dev, uint8_t *channels, + size_t num_channels) { + const struct is31fl3741_config *config = dev->config; + + if (config->px_buffer_size < num_channels) { + return -ENOMEM; + } + + is31fl3741_set_page(dev, IS31FL3741_PAGE_PWM_A); + + int result; + + result = is31fl3741_reg_burst_write( + dev, + 0x00, + channels, + (num_channels <= IS31FL3741_BUFFER_PAGE_BREAK) ? num_channels : IS31FL3741_BUFFER_PAGE_BREAK); + + if (result || num_channels <= IS31FL3741_BUFFER_PAGE_BREAK) { + return result; + } + + is31fl3741_set_page(dev, IS31FL3741_PAGE_PWM_B); + + return is31fl3741_reg_burst_write(dev, 0x00, channels + 0xb4, num_channels - 0xb4); +} + +/* + * Updates the RGB LED matrix using cs-order devicetree property + * to assign correct R,G,B channels. + */ +static int is31fl3741_strip_update_rgb(const struct device *dev, struct led_rgb *pixels, + size_t num_pixels) { + const struct is31fl3741_config *config = dev->config; + + uint8_t *px_buffer = config->px_buffer; + uint16_t *rgb_map = config->rgb_map; + + size_t i = 0; + size_t j = 0; + + if (!num_pixels_ok(config, num_pixels)) { + return -ENOMEM; + } + + while (i < num_pixels) { + px_buffer[rgb_map[j++]] = pixels[i].r; + px_buffer[rgb_map[j++]] = pixels[i].g; + px_buffer[rgb_map[j++]] = pixels[i].b; + + ++i; + } + + return is31fl3741_strip_update_channels(dev, px_buffer, config->px_buffer_size); +} + +/* + * Initiates a driver instance for IS31FL3741. + * + * 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 is31fl3741_init(const struct device *dev) { + struct is31fl3741_data *data = dev->data; + const struct is31fl3741_config *config = dev->config; + + data->i2c = device_get_binding(config->bus); + + if (data->i2c == NULL) { + LOG_ERR("I2C device %s not found", config->bus); + return -ENODEV; + } + + data->gpio = device_get_binding(config->sdb_port); + + if (data->gpio == NULL) { + LOG_ERR("GPIO device %s not found", config->sdb_port); + return -ENODEV; + } + + gpio_pin_configure(data->gpio, config->sdb_pin, (GPIO_OUTPUT | config->sdb_flags)); + + if (gpio_pin_set(data->gpio, config->sdb_pin, 1)) { + LOG_ERR("SDB pin for %s cannot be pulled high", config->label); + return -EIO; + } + + // Set configuration registers + if (is31fl3741_set_page(dev, IS31FL3741_PAGE_FUNCTION)) { + LOG_ERR("Couldn't switch to function registers on %s", config->label); + return -EIO; + } + + // Reset + is31fl3741_reg_write(dev, 0x3f, 0xae); // Reset + + // Re-set configuration registers + if (is31fl3741_set_page(dev, IS31FL3741_PAGE_FUNCTION)) { + LOG_ERR("Couldn't switch to function registers on %s", config->label); + return -EIO; + } + + // Configure LED driver operation mode + is31fl3741_reg_write( + dev, 0x00, (config->sws << 4) | (0x01 << 3) | 0x01); // SWS, H logic, Normal operation + is31fl3741_reg_write(dev, 0x01, config->gcc); // Set GCC + + // Set all scaling registers to 0xff, brightness is controlled using PWM + uint8_t scaling_buffer[0xb4]; + for (size_t i = 0; i < 0xb4; ++i) { + scaling_buffer[i] = 0xff; + } + + if (is31fl3741_set_page(dev, IS31FL3741_PAGE_SCALING_A)) { + LOG_ERR("Couldn't switch to scaling register A on %s", config->label); + } + + is31fl3741_reg_burst_write(dev, 0x00, scaling_buffer, 0xb4); + + if (is31fl3741_set_page(dev, IS31FL3741_PAGE_SCALING_B)) { + LOG_ERR("Couldn't switch to scaling register B on %s", config->label); + } + + is31fl3741_reg_burst_write(dev, 0x00, scaling_buffer, 0xab); + + return 0; +} + +static const struct led_strip_driver_api is31fl3741_api = { + .update_rgb = is31fl3741_strip_update_rgb, + .update_channels = is31fl3741_strip_update_channels, +}; + +#define IS31FL3741_GCC(idx) \ + (DT_INST_PROP(idx, r_ext) * DT_INST_PROP(idx, led_max_current) * 256 * 256) / (383 * 255) + +#define IS31FL3741_DEVICE(idx) \ + \ + static struct is31fl3741_data is31fl3741_##idx##_data; \ + \ + static uint8_t is31fl3741_##idx##_px_buffer[IS31FL3741_BUFFER_SIZE]; \ + \ + static uint16_t is31fl3741_##idx##_rgb_map[IS31FL3741_BUFFER_SIZE] = DT_INST_PROP(idx, map); \ + \ + static const struct is31fl3741_config is31fl3741_##idx##_config = { \ + .bus = DT_INST_BUS_LABEL(idx), \ + .reg = DT_INST_REG_ADDR(idx), \ + .label = DT_INST_LABEL(idx), \ + .sdb_port = DT_INST_GPIO_LABEL(idx, sdb_gpios), \ + .sdb_pin = DT_INST_GPIO_PIN(idx, sdb_gpios), \ + .sdb_flags = DT_INST_GPIO_FLAGS(idx, sdb_gpios), \ + .px_buffer = is31fl3741_##idx##_px_buffer, \ + .px_buffer_size = IS31FL3741_BUFFER_SIZE, \ + .gcc = IS31FL3741_GCC(idx), \ + .sws = DT_INST_PROP(idx, sw_setting), \ + .rgb_map = is31fl3741_##idx##_rgb_map, \ + }; \ + \ + DEVICE_AND_API_INIT(is31fl3741_##idx, DT_INST_LABEL(idx), &is31fl3741_init, \ + &is31fl3741_##idx##_data, &is31fl3741_##idx##_config, POST_KERNEL, \ + CONFIG_LED_STRIP_INIT_PRIORITY, &is31fl3741_api); + +DT_INST_FOREACH_STATUS_OKAY(IS31FL3741_DEVICE); diff --git a/app/drivers/led_strip/is31fl3741/issi,is31fl3741.yaml b/app/drivers/led_strip/is31fl3741/issi,is31fl3741.yaml new file mode 100644 index 00000000..53d27936 --- /dev/null +++ b/app/drivers/led_strip/is31fl3741/issi,is31fl3741.yaml @@ -0,0 +1,6 @@ +description: | + Driver for the IS31FL3741 LED matrix driver + +compatible: "issi,issi_is31fl3741" + +include: i2c-device.yaml diff --git a/app/dts/bindings/led_strip/issi,is31fl3741.yaml b/app/dts/bindings/led_strip/issi,is31fl3741.yaml new file mode 100644 index 00000000..9b321ee8 --- /dev/null +++ b/app/dts/bindings/led_strip/issi,is31fl3741.yaml @@ -0,0 +1,52 @@ +# Copyright (c) 2020, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: ISSI IS31FL3741 LED Matrix driver + +compatible: "issi,is31fl3741" + +include: i2c-device.yaml + +properties: + sw-setting: + type: int + required: false + enum: + - 0x00 + - 0x01 + - 0x02 + - 0x03 + - 0x04 + - 0x05 + - 0x06 + - 0x07 + - 0x08 + 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 allowed LED current in mAh. The maximum allowed by the chip is 38. + + sdb-gpios: + type: phandle-array + required: true + description: | + GPIO pin for hardware shutdown functionality. + + chain-length: + type: int diff --git a/app/include/dt-bindings/zmk/issi_transform.h b/app/include/dt-bindings/zmk/issi_transform.h new file mode 100644 index 00000000..fef1137d --- /dev/null +++ b/app/include/dt-bindings/zmk/issi_transform.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/** + * Maps a 39x9 matrix cell index to the appropriate IS31FL3741 register indexes. + */ +#define PIXEL(n) ( \ + ((0 <= ((n) % 39)) && (((n) % 39) < 30)) \ + ? ((((n) / 39) * 30) + ((n) % 39)) \ + : (210 + (((n) / 39) * 9) + (((n) % 39) - 31))) + +#define RGB(com, r, g, b) PIXEL(com + r) PIXEL(com + g) PIXEL(com + b) + +#define SW(n) ((n - 1) * 39) +#define CS(n) (n - 1)