diff --git a/app/drivers/CMakeLists.txt b/app/drivers/CMakeLists.txt index b47e87a2..006aaa80 100644 --- a/app/drivers/CMakeLists.txt +++ b/app/drivers/CMakeLists.txt @@ -2,4 +2,5 @@ # SPDX-License-Identifier: MIT add_subdirectory(kscan) -add_subdirectory(sensor) \ No newline at end of file +add_subdirectory(led_strip) +add_subdirectory(sensor) diff --git a/app/drivers/Kconfig b/app/drivers/Kconfig index 7ad76992..8452f361 100644 --- a/app/drivers/Kconfig +++ b/app/drivers/Kconfig @@ -2,4 +2,5 @@ # SPDX-License-Identifier: MIT rsource "kscan/Kconfig" +rsource "led_strip/Kconfig" rsource "sensor/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..b5fb26b6 --- /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_IS31FL3743A is31fl3743a) diff --git a/app/drivers/led_strip/Kconfig b/app/drivers/led_strip/Kconfig new file mode 100644 index 00000000..e357d56a --- /dev/null +++ b/app/drivers/led_strip/Kconfig @@ -0,0 +1,10 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +rsource "is31fl3743a/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/is31fl3743a/CMakeLists.txt b/app/drivers/led_strip/is31fl3743a/CMakeLists.txt new file mode 100644 index 00000000..b9e8b950 --- /dev/null +++ b/app/drivers/led_strip/is31fl3743a/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +zephyr_library() + +zephyr_library_sources(is31fl3743a.c) diff --git a/app/drivers/led_strip/is31fl3743a/Kconfig b/app/drivers/led_strip/is31fl3743a/Kconfig new file mode 100644 index 00000000..a90f5b31 --- /dev/null +++ b/app/drivers/led_strip/is31fl3743a/Kconfig @@ -0,0 +1,20 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +config IS31FL3743A + bool "IS31FL3743A LED Matrix driver" + depends on I2C + 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. + +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/is31fl3743a/is31fl3743a.c b/app/drivers/led_strip/is31fl3743a/is31fl3743a.c new file mode 100644 index 00000000..2ddcf179 --- /dev/null +++ b/app/drivers/led_strip/is31fl3743a/is31fl3743a.c @@ -0,0 +1,264 @@ +/* + * 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_CS_PINS 18 +#define IS31FL3743A_SW_PINS 11 + +#define IS31FL3743A_PSR (0xfd) +#define IS31FL3743A_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 *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; + uint8_t sync; + uint8_t *cs_map; +}; + +struct is31fl3743a_data { + const struct device *i2c; + const struct device *gpio; +}; + +static int is31fl3743a_reg_write(const struct device *dev, uint8_t addr, uint8_t value) +{ + const struct is31fl3743a_data *data = dev->data; + const struct is31fl3743a_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 is31fl3743a_reg_burst_write(const struct device *dev, + uint8_t start_addr, + const uint8_t *buffer, + size_t num_bytes) +{ + const struct is31fl3743a_data *data = dev->data; + const struct is31fl3743a_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 is31fl3743a_set_page(const struct device *dev, uint8_t page_addr) +{ + if (is31fl3743a_reg_write(dev, IS31FL3743A_PSWL, IS31FL3743A_PSWL_ENABLE)) { + return -EIO; + } + + if (is31fl3743a_reg_write(dev, IS31FL3743A_PSR, 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 the RGB LED matrix using cs-order devicetree property + * to assign correct R,G,B channels. + */ +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; + + uint8_t *px_buffer = config->px_buffer; + uint8_t *cs_map = config->cs_map; + + size_t sw_offset = 0; + size_t cs = 0; + + if (!num_pixels_ok(config, num_pixels)) { + return -ENOMEM; + } + + for (size_t i = 0; i < num_pixels; ++i) { + px_buffer[sw_offset + cs_map[cs++]] = pixels[i].r; + px_buffer[sw_offset + cs_map[cs++]] = pixels[i].g; + px_buffer[sw_offset + cs_map[cs++]] = pixels[i].b; + + if (IS31FL3743A_CS_PINS <= cs) { + cs = 0; + sw_offset += IS31FL3743A_CS_PINS; + } + } + + if (is31fl3743a_set_page(dev, IS31FL3743A_PAGE_PWM)) { + LOG_ERR("Failed to set PWM page on %s", config->label); + return -EIO; + } + + return is31fl3743a_reg_burst_write(dev, 0x01, px_buffer, 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; + } + + return is31fl3743a_reg_burst_write(dev, 0x01, channels, num_channels); +} + +/* + * Initiates a driver instance for IS31FL3743A. + * + * 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) +{ + struct is31fl3743a_data *data = dev->data; + const struct is31fl3743a_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 (is31fl3743a_set_page(dev, IS31FL3743A_PAGE_FUNCTION)) { + LOG_ERR("Couldn't switch to function registers on %s", config->label); + return -EIO; + } + + is31fl3743a_reg_write(dev, 0x2f, 0xae); // Reset + is31fl3743a_reg_write(dev, 0x00, (config->sws << 4) | (0x01 << 3) | 0x01 ); // SWS, H logic, Normal operation + is31fl3743a_reg_write(dev, 0x01, config->gcc); // Set GCC + is31fl3743a_reg_write(dev, 0x24, 0x08); // Thermal shutoff at 100*C, put into DT + is31fl3743a_reg_write(dev, 0x25, (config->sync << 6)); // Set SYNC setting + + // Set scaling registers, default to 0xff + if (is31fl3743a_set_page(dev, IS31FL3743A_PAGE_SCALING)) { + LOG_ERR("Couldn't switch to scaling registers on %s", config->label); + } + + uint8_t scaling_buffer[config->px_buffer_size]; + + for (size_t i = 0; i < config->px_buffer_size; ++i) { + scaling_buffer[i] = 0xff; + } + + is31fl3743a_reg_burst_write(dev, 0x01, scaling_buffer, config->px_buffer_size); + + 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, riset) * DT_INST_PROP(idx, led_max_current) * 256 * 256) / (343 * 255) + +#define IS31FL3743A_DEVICE(idx) \ + \ + static struct is31fl3743a_data is31fl3743a_##idx##_data; \ + \ + static uint8_t is31fl3743a_##idx##_px_buffer[IS31FL3743A_BUFFER_SIZE(idx)]; \ + \ + static uint8_t is31fl3743a_##idx##_cs_map[] = DT_INST_PROP(idx, cs_order); \ + \ + static const struct is31fl3743a_config is31fl3743a_##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 = is31fl3743a_##idx##_px_buffer, \ + .px_buffer_size = IS31FL3743A_BUFFER_SIZE(idx), \ + .gcc = IS31FL3743A_GCC(idx), \ + .sws = DT_INST_PROP(idx, sw_setting), \ + .sync = DT_INST_PROP(idx, sync), \ + .cs_map = is31fl3743a_##idx##_cs_map, \ + }; \ + \ + DEVICE_AND_API_INIT(is31fl3743a_##idx, \ + DT_INST_LABEL(idx), \ + &is31fl3743a_init, \ + &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/drivers/led_strip/is31fl3743a/issi,is31fl3743a.yaml b/app/drivers/led_strip/is31fl3743a/issi,is31fl3743a.yaml new file mode 100644 index 00000000..4a861734 --- /dev/null +++ b/app/drivers/led_strip/is31fl3743a/issi,is31fl3743a.yaml @@ -0,0 +1,6 @@ +description: | + Driver for the IS31FL3743A LED matrix driver + +compatible: "issi,issi_is31fl3743a" + +include: i2c-device.yaml 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..e068d41f --- /dev/null +++ b/app/dts/bindings/led_strip/issi,is31fl3743a.yaml @@ -0,0 +1,65 @@ +# 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. + + cs-order: + type: uint8-array + required: false + default: [0x02, 0x01, 0x00, 0x05, 0x04, 0x03, 0x08, 0x07, 0x06, 0x0B, 0x0A, 0x09, 0x0E, 0x0D, 0x0C, 0x11, 0x10, 0x0F] + description: | + Stores the configuration of CS* pins. + Each 3 consecutive numbers must be R, G and B pins for a single LED. + Matches the reference design from the datasheet by default. + + riset: + type: int + required: true + description: Riset 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 34. + + 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. + + sdb-gpios: + type: phandle-array + required: true + description: | + GPIO pin for hardware shutdown functionality.