diff --git a/drivers/led/CMakeLists.txt b/drivers/led/CMakeLists.txt index b38ecbf5841..2560b038c13 100644 --- a/drivers/led/CMakeLists.txt +++ b/drivers/led/CMakeLists.txt @@ -16,6 +16,7 @@ zephyr_library_sources_ifdef(CONFIG_LP5562 lp5562.c) zephyr_library_sources_ifdef(CONFIG_LP5569 lp5569.c) zephyr_library_sources_ifdef(CONFIG_PCA9633 pca9633.c) zephyr_library_sources_ifdef(CONFIG_TLC59108 tlc59108.c) +zephyr_library_sources_ifdef(CONFIG_IS31FL3733 is31fl3733.c) zephyr_library_sources_ifdef(CONFIG_LED_SHELL led_shell.c) diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig index 91cb28544b9..65d2449ee1a 100644 --- a/drivers/led/Kconfig +++ b/drivers/led/Kconfig @@ -38,5 +38,6 @@ source "drivers/led/Kconfig.pca9633" source "drivers/led/Kconfig.pwm" source "drivers/led/Kconfig.tlc59108" source "drivers/led/Kconfig.xec" +source "drivers/led/Kconfig.is31fl3733" endif # LED diff --git a/drivers/led/Kconfig.is31fl3733 b/drivers/led/Kconfig.is31fl3733 new file mode 100644 index 00000000000..20eaf3d269c --- /dev/null +++ b/drivers/led/Kconfig.is31fl3733 @@ -0,0 +1,12 @@ +# Copyright 2023 Daniel DeGrasse +# SPDX-License-Identifier: Apache-2.0 + +config IS31FL3733 + bool "IS31FL3733 LED driver" + default y + depends on DT_HAS_ISSI_IS31FL3733_ENABLED + select I2C + help + Enable LED driver for IS31FL3733. + IS31FL3733 is a matrix LED driver, capable of a maximum of 3.29 mA + per LED, or 42 mA total across all LEDs in the 12x16 dot matrix. diff --git a/drivers/led/is31fl3733.c b/drivers/led/is31fl3733.c new file mode 100644 index 00000000000..3298bbc9b7c --- /dev/null +++ b/drivers/led/is31fl3733.c @@ -0,0 +1,301 @@ +/* + * Copyright 2022-2023 Daniel DeGrasse + * + * SPDX-License-Identifier: Apache-2.0 + */ +#define DT_DRV_COMPAT issi_is31fl3733 + +#include +#include +#include +#include + +#include + +#include +LOG_MODULE_REGISTER(is31fl3733, CONFIG_LED_LOG_LEVEL); + +/* IS31FL3733 register definitions */ +#define CMD_SEL_REG 0xFD /* Command/page selection reg */ +#define CMD_SEL_LED 0x0 /* LED configuration page */ +#define CMD_SEL_PWM 0x1 /* PWM configuration page */ +#define CMD_SEL_FUNC 0x3 /* Function configuration page */ + +#define CMD_LOCK_REG 0xFE /* Command selection lock reg */ +#define CMD_LOCK_UNLOCK 0xC5 /* Command sel unlock value */ + +/* IS31FL3733 page specific register definitions */ + +/* Function configuration page */ +#define CONF_REG 0x0 /* configuration register */ +#define CONF_REG_SSD_MASK 0x1 /* Software shutdown mask */ +#define CONF_REG_SSD_SHIFT 0x0 /* Software shutdown shift */ +#define CONF_REG_SYNC_SHIFT 0x6 /* Sync mode shift */ +#define CONF_REG_SYNC_MASK 0xC /* Sync mode mask */ + +#define GLOBAL_CURRENT_CTRL_REG 0x1 /* global current control register */ + +#define RESET_REG 0x11 /* Reset all registers to POR state */ + +/* Matrix Layout definitions */ +#define IS31FL3733_ROW_COUNT 12 +#define IS31FL3733_COL_COUNT 16 +#define IS31FL3733_MAX_LED (IS31FL3733_ROW_COUNT * IS31FL3733_COL_COUNT) + +/* Max brightness */ +#define IS31FL3733_MAX_BRIGHTNESS 100 + +struct is31fl3733_config { + struct i2c_dt_spec bus; + struct gpio_dt_spec sdb; + uint8_t current_limit; + uint8_t sync; +}; + +struct is31fl3733_data { + /* Active configuration page */ + uint32_t selected_page; + /* Scratch buffer, used for bulk controller writes */ + uint8_t scratch_buf[IS31FL3733_MAX_LED + 1]; + /* LED config reg state, IS31FL3733 conf reg is write only */ + uint8_t conf_reg; +}; + +/* Selects target register page for IS31FL3733. After setting the + * target page, all I2C writes will use the selected page until the selected + * page is changed. + */ +static int is31fl3733_select_page(const struct device *dev, uint8_t page) +{ + const struct is31fl3733_config *config = dev->config; + struct is31fl3733_data *data = dev->data; + int ret = 0U; + + if (data->selected_page == page) { + /* No change necessary */ + return 0; + } + + /* Unlock page selection register */ + ret = i2c_reg_write_byte_dt(&config->bus, CMD_LOCK_REG, CMD_LOCK_UNLOCK); + if (ret < 0) { + LOG_ERR("Could not unlock page selection register"); + return ret; + } + + /* Write to function select to select active page */ + ret = i2c_reg_write_byte_dt(&config->bus, CMD_SEL_REG, page); + if (ret < 0) { + LOG_ERR("Could not select active page"); + return ret; + } + data->selected_page = page; + + return ret; +} + +static int is31fl3733_led_set_brightness(const struct device *dev, uint32_t led, uint8_t value) +{ + const struct is31fl3733_config *config = dev->config; + int ret; + uint8_t led_brightness = (uint8_t)(((uint32_t)value * 255) / 100); + + if (led >= IS31FL3733_MAX_LED) { + return -EINVAL; + } + + /* Configure PWM mode */ + ret = is31fl3733_select_page(dev, CMD_SEL_PWM); + if (ret < 0) { + return ret; + } + + return i2c_reg_write_byte_dt(&config->bus, led, led_brightness); +} + +static int is31fl3733_led_on(const struct device *dev, uint32_t led) +{ + return is31fl3733_led_set_brightness(dev, led, IS31FL3733_MAX_BRIGHTNESS); +} + +static int is31fl3733_led_off(const struct device *dev, uint32_t led) +{ + return is31fl3733_led_set_brightness(dev, led, 0); +} + +static int is31fl3733_led_write_channels(const struct device *dev, uint32_t start_channel, + uint32_t num_channels, const uint8_t *buf) +{ + const struct is31fl3733_config *config = dev->config; + struct is31fl3733_data *data = dev->data; + int ret = 0U; + uint8_t *pwm_start; + + if ((start_channel + num_channels) > IS31FL3733_MAX_LED) { + return -EINVAL; + } + pwm_start = data->scratch_buf + start_channel; + /* Set PWM and LED target registers as first byte of each transfer */ + *pwm_start = start_channel; + memcpy((pwm_start + 1), buf, num_channels); + + /* Write LED PWM states */ + ret = is31fl3733_select_page(dev, CMD_SEL_PWM); + if (ret < 0) { + return ret; + } + LOG_HEXDUMP_DBG(pwm_start, (num_channels + 1), "PWM states"); + + return i2c_write_dt(&config->bus, pwm_start, num_channels + 1); +} + +static int is31fl3733_init(const struct device *dev) +{ + const struct is31fl3733_config *config = dev->config; + struct is31fl3733_data *data = dev->data; + int ret = 0U; + uint8_t dummy; + + if (!i2c_is_ready_dt(&config->bus)) { + LOG_ERR("I2C device not ready"); + return -ENODEV; + } + if (config->sdb.port != NULL) { + if (!gpio_is_ready_dt(&config->sdb)) { + LOG_ERR("GPIO SDB pin not ready"); + return -ENODEV; + } + /* Set SDB pin high to exit hardware shutdown */ + ret = gpio_pin_configure_dt(&config->sdb, GPIO_OUTPUT_ACTIVE); + if (ret < 0) { + return ret; + } + } + + ret = is31fl3733_select_page(dev, CMD_SEL_FUNC); + if (ret < 0) { + return ret; + } + /* + * read reset reg to reset all registers to POR state, + * in case we are booting from a warm reset. + */ + ret = i2c_reg_read_byte_dt(&config->bus, RESET_REG, &dummy); + if (ret < 0) { + return ret; + } + + /* Select function page after LED controller reset */ + ret = is31fl3733_select_page(dev, CMD_SEL_FUNC); + if (ret < 0) { + return ret; + } + /* Set global current control register based off devicetree value */ + ret = i2c_reg_write_byte_dt(&config->bus, GLOBAL_CURRENT_CTRL_REG, + config->current_limit); + if (ret < 0) { + return ret; + } + /* As a final step, we exit software shutdown, disabling display + * blanking. We also set the LED controller sync mode here. + */ + data->conf_reg = (config->sync << CONF_REG_SYNC_SHIFT) | CONF_REG_SSD_MASK; + ret = i2c_reg_write_byte_dt(&config->bus, CONF_REG, data->conf_reg); + if (ret < 0) { + return ret; + } + + /* Enable all LEDs. We only control LED brightness in this driver. */ + data->scratch_buf[0] = 0x0; + memset(data->scratch_buf + 1, 0xFF, (IS31FL3733_MAX_LED / 8)); + ret = is31fl3733_select_page(dev, CMD_SEL_LED); + if (ret < 0) { + return ret; + } + + return i2c_write_dt(&config->bus, data->scratch_buf, + (IS31FL3733_MAX_LED / 8) + 1); +} + +/* Custom IS31FL3733 specific APIs */ + +/** + * @brief Blanks IS31FL3733 LED display. + * + * When blank_en is set, the LED display will be disabled. This can be used for + * flicker-free display updates, or power saving. + * + * @param dev: LED device structure + * @param blank_en: should blanking be enabled + * @return 0 on success, or negative value on error. + */ +int is31fl3733_blank(const struct device *dev, bool blank_en) +{ + const struct is31fl3733_config *config = dev->config; + struct is31fl3733_data *data = dev->data; + int ret; + + ret = is31fl3733_select_page(dev, CMD_SEL_FUNC); + if (ret < 0) { + return ret; + } + + if (blank_en) { + data->conf_reg &= ~CONF_REG_SSD_MASK; + } else { + data->conf_reg |= CONF_REG_SSD_MASK; + } + + return i2c_reg_write_byte_dt(&config->bus, CONF_REG, data->conf_reg); +} + +/** + * @brief Sets led current limit + * + * Sets the current limit for the LED driver. This is a separate value + * from per-led brightness, and applies to all LEDs. + * This value sets the output current limit according + * to the following formula: (840/R_ISET) * (limit/256) + * See table 14 of the datasheet for additional details. + * @param dev: LED device structure + * @param limit: current limit to apply + * @return 0 on success, or negative value on error. + */ +int is31fl3733_current_limit(const struct device *dev, uint8_t limit) +{ + const struct is31fl3733_config *config = dev->config; + int ret; + + ret = is31fl3733_select_page(dev, CMD_SEL_FUNC); + if (ret < 0) { + return ret; + } + + /* Set global current control register */ + return i2c_reg_write_byte_dt(&config->bus, GLOBAL_CURRENT_CTRL_REG, limit); +} + +static const struct led_driver_api is31fl3733_api = { + .on = is31fl3733_led_on, + .off = is31fl3733_led_off, + .set_brightness = is31fl3733_led_set_brightness, + .write_channels = is31fl3733_led_write_channels, +}; + +#define IS31FL3733_DEVICE(n) \ + static const struct is31fl3733_config is31fl3733_config_##n = { \ + .bus = I2C_DT_SPEC_INST_GET(n), \ + .sdb = GPIO_DT_SPEC_INST_GET_OR(n, sdb_gpios, {}), \ + .current_limit = DT_INST_PROP(n, current_limit), \ + .sync = DT_INST_ENUM_IDX(n, sync_mode), \ + }; \ + \ + static struct is31fl3733_data is31fl3733_data_##n = { \ + .selected_page = CMD_SEL_LED, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, &is31fl3733_init, NULL, &is31fl3733_data_##n, \ + &is31fl3733_config_##n, POST_KERNEL, CONFIG_LED_INIT_PRIORITY, \ + &is31fl3733_api); + +DT_INST_FOREACH_STATUS_OKAY(IS31FL3733_DEVICE) diff --git a/dts/bindings/led/issi,is31fl3733.yaml b/dts/bindings/led/issi,is31fl3733.yaml new file mode 100644 index 00000000000..365dfece42e --- /dev/null +++ b/dts/bindings/led/issi,is31fl3733.yaml @@ -0,0 +1,38 @@ +# Copyright 2023 Daniel DeGrasse +# SPDX-License-Identifier: Apache-2.0 +description: ISSI IS31FL3733 LED Matrix Driver + +compatible: "issi,is31fl3733" + +include: "i2c-device.yaml" + +properties: + sdb-gpios: + type: phandle-array + description: | + Hardware shutdown pin. If routed on the board, this property must be + present. Set to a logical 1 at boot to exit the device from hardware + shutdown. + + current-limit: + type: int + default: 0xFF + description: | + Global current limit. Sets the global current control register of LED + driver (set table 14). Limits global current based on the following + formula: (840/R_ISET) * (current-limit/256). Defaults to max value + of 0xFF, so led output will still be enabled if property is + not provided. + + sync-mode: + type: string + default: "none" + enum: + - "none" + - "master" + - "slave" + description: | + This property configures the LED controller as a master or slave + clock device. This can be used to synchronize the output of multiple + LED controllers. See SYNC bits in led configuration register for more + information. diff --git a/include/zephyr/drivers/led/is31fl3733.h b/include/zephyr/drivers/led/is31fl3733.h new file mode 100644 index 00000000000..33f18a06d64 --- /dev/null +++ b/include/zephyr/drivers/led/is31fl3733.h @@ -0,0 +1,36 @@ +/* + * Copyright 2023 Daniel DeGrasse + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_LED_IS31FL3733_H_ +#define ZEPHYR_INCLUDE_DRIVERS_LED_IS31FL3733_H_ + +/** + * @brief Blanks IS31FL3733 LED display. + * + * When blank_en is set, the LED display will be disabled. This can be used for + * flicker-free display updates, or power saving. + * + * @param dev: LED device structure + * @param blank_en: should blanking be enabled + * @return 0 on success, or negative value on error. + */ +int is31fl3733_blank(const struct device *dev, bool blank_en); + +/** + * @brief Sets led current limit + * + * Sets the current limit for the LED driver. This is a separate value + * from per-led brightness, and applies to all LEDs. + * This value sets the output current limit according + * to the following formula: (840/R_ISET) * (limit/256) + * See table 14 of the datasheet for additional details. + * @param dev: LED device structure + * @param limit: current limit to apply + * @return 0 on success, or negative value on error. + */ +int is31fl3733_current_limit(const struct device *dev, uint8_t limit); + +#endif /* ZEPHYR_INCLUDE_DRIVERS_LED_IS31FL3733_H_ */