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..2277a777 --- /dev/null +++ b/app/drivers/led_strip/Kconfig @@ -0,0 +1,4 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +rsource "is31fl3741/Kconfig" 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..ba0dc630 --- /dev/null +++ b/app/drivers/led_strip/is31fl3741/Kconfig @@ -0,0 +1,15 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +config IS31FL3741 + bool "IS31FL3741 LED Matrix driver" + depends on I2C + depends on LED_STRIP + 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. diff --git a/app/drivers/led_strip/is31fl3741/is31fl3741.c b/app/drivers/led_strip/is31fl3741/is31fl3741.c new file mode 100644 index 00000000..9efc0b08 --- /dev/null +++ b/app/drivers/led_strip/is31fl3741/is31fl3741.c @@ -0,0 +1,269 @@ +/* + * 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 *label; + struct i2c_dt_spec i2c; + struct gpio_dt_spec gpio; + size_t px_buffer_size; + uint8_t gcc; + uint8_t sws; + uint16_t *rgb_map; + uint8_t *gamma; + uint8_t scaling_red; + uint8_t scaling_blue; + uint8_t scaling_green; +}; + +struct is31fl3741_data { + uint8_t *px_buffer; +}; + +static int is31fl3741_reg_write(const struct device *dev, uint8_t addr, uint8_t value) { + const struct is31fl3741_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 is31fl3741_reg_burst_write(const struct device *dev, uint8_t start_addr, + const uint8_t *buffer, size_t num_bytes) { + const struct is31fl3741_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 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; + + LOG_ERR("UPDATING CHANNELS!"); + + 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 according to devicetree's map property. + */ +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; + struct is31fl3741_data *data = dev->data; + + size_t i = 0; + size_t j = 0; + + if (!num_pixels_ok(config, num_pixels)) { + return -ENOMEM; + } + + while (i < num_pixels) { + data->px_buffer[config->rgb_map[j++]] = config->gamma[pixels[i].r]; + data->px_buffer[config->rgb_map[j++]] = config->gamma[pixels[i].g]; + data->px_buffer[config->rgb_map[j++]] = config->gamma[pixels[i].b]; + + ++i; + } + + return is31fl3741_strip_update_channels(dev, data->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) { + const struct is31fl3741_config *config = dev->config; + const struct is31fl3741_data *data = dev->data; + + if (!device_is_ready(config->i2c.bus)) { + LOG_ERR("I2C device %s 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; + } + + // 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 scaling registers + uint8_t *px_buffer = data->px_buffer; + uint16_t *rgb_map = config->rgb_map; + + for (int 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; + } + + is31fl3741_set_page(dev, IS31FL3741_PAGE_SCALING_A); + is31fl3741_reg_burst_write(dev, 0x00, px_buffer, 0xb4); + + is31fl3741_set_page(dev, IS31FL3741_PAGE_SCALING_B); + is31fl3741_reg_burst_write(dev, 0x00, px_buffer + 0xb4, 0xab); + + // Re-initialize px_buffer to prevent any scaling values from being sent + // to PWM 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 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 uint8_t is31fl3741_##idx##_px_buffer[IS31FL3741_BUFFER_SIZE]; \ + \ + static struct is31fl3741_data is31fl3741_##idx##_data = { \ + .px_buffer = is31fl3741_##idx##_px_buffer, \ + }; \ + \ + static uint16_t is31fl3741_##idx##_rgb_map[IS31FL3741_BUFFER_SIZE] = DT_INST_PROP(idx, map); \ + \ + static uint8_t is31fl3741_##idx##_gamma[] = DT_INST_PROP(idx, gamma); \ + \ + static const struct is31fl3741_config is31fl3741_##idx##_config = { \ + .label = DT_INST_PROP(idx, label), \ + .i2c = I2C_DT_SPEC_INST_GET(idx), \ + .gpio = GPIO_DT_SPEC_INST_GET(idx, sdb_gpios), \ + .px_buffer_size = IS31FL3741_BUFFER_SIZE, \ + .gcc = IS31FL3741_GCC(idx), \ + .sws = DT_INST_PROP(idx, sw_setting), \ + .rgb_map = is31fl3741_##idx##_rgb_map, \ + .gamma = is31fl3741_##idx##_gamma, \ + .scaling_red = DT_INST_PROP(idx, red_scaling), \ + .scaling_green = DT_INST_PROP(idx, green_scaling), \ + .scaling_blue = DT_INST_PROP(idx, blue_scaling), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(idx, &is31fl3741_init, NULL, &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/dts/bindings/led_strip/issi,is31fl3741.yaml b/app/dts/bindings/led_strip/issi,is31fl3741.yaml new file mode 100644 index 00000000..69b63403 --- /dev/null +++ b/app/dts/bindings/led_strip/issi,is31fl3741.yaml @@ -0,0 +1,350 @@ +# 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 + required: true + description: | + How many RGB LEDs are driven by the IC. + + red-scaling: + type: int + required: false + default: 255 + description: | + Red channel scaling factor 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 being equivalent to the value of led-max-current. + + green-scaling: + type: int + required: false + default: 255 + description: | + Green channel scaling factor 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 being equivalent to the value of led-max-current. + + blue-scaling: + type: int + required: false + default: 255 + description: | + Blue channel scaling factor 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 being equivalent to the value of 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 this may + or may not work 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/issi_transform.h b/app/include/dt-bindings/zmk/issi_transform.h new file mode 100644 index 00000000..8816fb8f --- /dev/null +++ b/app/include/dt-bindings/zmk/issi_transform.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/** + * Maps a 39x9 matrix cell index to a 1D array matching IS31FL3741's PWN registers order. + */ +#define PIXEL(n) \ + ((((n) % 39) < 30) ? ((((n) / 39) * 30) + ((n) % 39)) \ + : (270 + (((n) / 39) * 9) + ((n) % 39) - 30)) + +#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)