From 890363a6fb046b34042de9cce00794b652ca13b2 Mon Sep 17 00:00:00 2001 From: Guy Morand Date: Thu, 30 Mar 2023 11:22:52 +0200 Subject: [PATCH] drivers: led: Add lumissil is31fl3216a driver The IS31FL3216A is a fun light LED controller. The LED current of each channel can be set in 256 steps by adjusting the PWM duty cycle through an I2C interface. Signed-off-by: Guy Morand --- drivers/led/CMakeLists.txt | 1 + drivers/led/Kconfig | 1 + drivers/led/Kconfig.is31fl3216a | 11 ++ drivers/led/is31fl3216a.c | 240 +++++++++++++++++++++++++ dts/bindings/led/issi,is31fl3216a.yaml | 9 + 5 files changed, 262 insertions(+) create mode 100644 drivers/led/Kconfig.is31fl3216a create mode 100644 drivers/led/is31fl3216a.c create mode 100644 dts/bindings/led/issi,is31fl3216a.yaml diff --git a/drivers/led/CMakeLists.txt b/drivers/led/CMakeLists.txt index e018acb0cae..018a2f362d8 100644 --- a/drivers/led/CMakeLists.txt +++ b/drivers/led/CMakeLists.txt @@ -5,6 +5,7 @@ zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/led.h) zephyr_library() zephyr_library_sources_ifdef(CONFIG_HT16K33 ht16k33.c) +zephyr_library_sources_ifdef(CONFIG_IS31FL3216A is31fl3216a.c) zephyr_library_sources_ifdef(CONFIG_LED_GPIO led_gpio.c) zephyr_library_sources_ifdef(CONFIG_LED_PWM led_pwm.c) zephyr_library_sources_ifdef(CONFIG_LED_XEC led_mchp_xec.c) diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig index bf606b6e91b..293d6347946 100644 --- a/drivers/led/Kconfig +++ b/drivers/led/Kconfig @@ -28,6 +28,7 @@ config LED_SHELL source "drivers/led/Kconfig.gpio" source "drivers/led/Kconfig.ht16k33" +source "drivers/led/Kconfig.is31fl3216a" source "drivers/led/Kconfig.lp3943" source "drivers/led/Kconfig.lp503x" source "drivers/led/Kconfig.lp5562" diff --git a/drivers/led/Kconfig.is31fl3216a b/drivers/led/Kconfig.is31fl3216a new file mode 100644 index 00000000000..ef6481bdb80 --- /dev/null +++ b/drivers/led/Kconfig.is31fl3216a @@ -0,0 +1,11 @@ +# Copyright (c) 2023 Endor AG +# SPDX-License-Identifier: Apache-2.0 + +config IS31FL3216A + bool "IS31FL3216A LED driver" + default y + depends on DT_HAS_ISSI_IS31FL3216A_ENABLED + select I2C + help + Enable LED driver for Lumissil Microsystems (a division of ISSI) + IS31FL3216A. This chip supports up to 16 PWM controlled LEDs. diff --git a/drivers/led/is31fl3216a.c b/drivers/led/is31fl3216a.c new file mode 100644 index 00000000000..b5b6ead10e6 --- /dev/null +++ b/drivers/led/is31fl3216a.c @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2023 Endor AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT issi_is31fl3216a + +#include +#include +#include + +#define IS31FL3216A_REG_CONFIG 0x00 +#define IS31FL3216A_REG_CTL_1 0x01 +#define IS31FL3216A_REG_CTL_2 0x02 +#define IS31FL3216A_REG_LIGHT_EFFECT 0x03 +#define IS31FL3216A_REG_CHANNEL_CONFIG 0x04 +#define IS31FL3216A_REG_GPIO_CONFIG 0x05 +#define IS31FL3216A_REG_OUTPUT_PORT 0x06 +#define IS31FL3216A_REG_INT_CONTROL 0x07 +#define IS31FL3216A_REG_ADC_SAMPLE_RATE 0x09 +#define IS31FL3216A_REG_PWM_FIRST 0x10 +#define IS31FL3216A_REG_PWM_LAST 0x1F +#define IS31FL3216A_REG_UPDATE 0xB0 +#define IS31FL3216A_REG_FRAME_DELAY 0xB6 +#define IS31FL3216A_REG_FRAME_START 0xB7 + +#define IS31FL3216A_MAX_LEDS 16 + +LOG_MODULE_REGISTER(is31fl3216a, CONFIG_LED_LOG_LEVEL); + +struct is31fl3216a_cfg { + struct i2c_dt_spec i2c; +}; + +static int is31fl3216a_write_buffer(const struct i2c_dt_spec *i2c, + const uint8_t *buffer, uint32_t num_bytes) +{ + int status; + + status = i2c_write_dt(i2c, buffer, num_bytes); + if (status < 0) { + LOG_ERR("Could not write buffer: %i", status); + return status; + } + + return 0; +} + +static int is31fl3216a_write_reg(const struct i2c_dt_spec *i2c, uint8_t reg, + uint8_t val) +{ + uint8_t buffer[2] = {reg, val}; + + return is31fl3216a_write_buffer(i2c, buffer, sizeof(buffer)); +} + +static int is31fl3216a_update_pwm(const struct i2c_dt_spec *i2c) +{ + return is31fl3216a_write_reg(i2c, IS31FL3216A_REG_UPDATE, 0); +} + +static uint8_t is31fl3216a_brightness_to_pwm(uint8_t brightness) +{ + return (0xFFU * brightness) / 100; +} + +static int is31fl3216a_led_write_channels(const struct device *dev, + uint32_t start_channel, + uint32_t num_channels, + const uint8_t *buf) +{ + const struct is31fl3216a_cfg *config = dev->config; + uint8_t i2c_buffer[IS31FL3216A_MAX_LEDS + 1]; + int status; + int i; + + if (num_channels == 0) { + return 0; + } + + if (start_channel + num_channels > IS31FL3216A_MAX_LEDS) { + return -EINVAL; + } + + /* Invert channels, last register is channel 0 */ + i2c_buffer[0] = IS31FL3216A_REG_PWM_LAST - start_channel - + (num_channels - 1); + for (i = 0; i < num_channels; i++) { + if (buf[num_channels - i - 1] > 100) { + return -EINVAL; + } + i2c_buffer[i + 1] = is31fl3216a_brightness_to_pwm( + buf[num_channels - i - 1]); + } + + status = is31fl3216a_write_buffer(&config->i2c, i2c_buffer, + num_channels + 1); + if (status < 0) { + return status; + } + + return is31fl3216a_update_pwm(&config->i2c); +} + +static int is31fl3216a_led_set_brightness(const struct device *dev, + uint32_t led, uint8_t value) +{ + const struct is31fl3216a_cfg *config = dev->config; + uint8_t pwm_reg = IS31FL3216A_REG_PWM_LAST - led; + int status; + uint8_t pwm_value; + + if (led > IS31FL3216A_MAX_LEDS - 1 || value > 100) { + return -EINVAL; + } + + pwm_value = is31fl3216a_brightness_to_pwm(value); + status = is31fl3216a_write_reg(&config->i2c, pwm_reg, pwm_value); + if (status < 0) { + return status; + } + + return is31fl3216a_update_pwm(&config->i2c); +} + +static int is31fl3216a_led_on(const struct device *dev, uint32_t led) +{ + return is31fl3216a_led_set_brightness(dev, led, 100); +} + +static int is31fl3216a_led_off(const struct device *dev, uint32_t led) +{ + return is31fl3216a_led_set_brightness(dev, led, 0); +} + +static int is31fl3216a_init_registers(const struct i2c_dt_spec *i2c) +{ + int i; + int status; + + status = is31fl3216a_write_reg(i2c, IS31FL3216A_REG_CTL_1, 0xFF); + if (status < 0) { + return status; + } + + status = is31fl3216a_write_reg(i2c, IS31FL3216A_REG_CTL_2, 0xFF); + if (status < 0) { + return status; + } + + status = is31fl3216a_write_reg(i2c, IS31FL3216A_REG_LIGHT_EFFECT, 0x00); + if (status < 0) { + return status; + } + + status = is31fl3216a_write_reg(i2c, IS31FL3216A_REG_CHANNEL_CONFIG, + 0x00); + if (status < 0) { + return status; + } + + status = is31fl3216a_write_reg(i2c, IS31FL3216A_REG_GPIO_CONFIG, 0x00); + if (status < 0) { + return status; + } + + status = is31fl3216a_write_reg(i2c, IS31FL3216A_REG_OUTPUT_PORT, 0x00); + if (status < 0) { + return status; + } + + status = is31fl3216a_write_reg(i2c, IS31FL3216A_REG_INT_CONTROL, 0x00); + if (status < 0) { + return status; + } + + status = is31fl3216a_write_reg(i2c, IS31FL3216A_REG_ADC_SAMPLE_RATE, + 0x00); + if (status < 0) { + return status; + } + + status = is31fl3216a_write_reg(i2c, IS31FL3216A_REG_FRAME_DELAY, 0x00); + if (status < 0) { + return status; + } + + status = is31fl3216a_write_reg(i2c, IS31FL3216A_REG_FRAME_START, 0x00); + if (status < 0) { + return status; + } + + for (i = IS31FL3216A_REG_PWM_FIRST; + i <= IS31FL3216A_REG_PWM_LAST; + i++) { + status = is31fl3216a_write_reg(i2c, i, 0); + if (status < 0) { + return status; + } + } + + status = is31fl3216a_write_reg(i2c, IS31FL3216A_REG_UPDATE, 0); + if (status < 0) { + return status; + } + + return is31fl3216a_write_reg(i2c, IS31FL3216A_REG_CONFIG, 0x00); +} + +static int is31fl3216a_init(const struct device *dev) +{ + const struct is31fl3216a_cfg *config = dev->config; + + LOG_DBG("Initializing @0x%x...", config->i2c.addr); + + if (!i2c_is_ready_dt(&config->i2c)) { + LOG_ERR("I2C device not ready"); + return -ENODEV; + } + + return is31fl3216a_init_registers(&config->i2c); +} + +static const struct led_driver_api is31fl3216a_led_api = { + .set_brightness = is31fl3216a_led_set_brightness, + .on = is31fl3216a_led_on, + .off = is31fl3216a_led_off, + .write_channels = is31fl3216a_led_write_channels +}; + +#define IS31FL3216A_INIT(id) \ + static const struct is31fl3216a_cfg is31fl3216a_##id##_cfg = { \ + .i2c = I2C_DT_SPEC_INST_GET(id), \ + }; \ + DEVICE_DT_INST_DEFINE(id, &is31fl3216a_init, NULL, NULL, \ + &is31fl3216a_##id##_cfg, POST_KERNEL, \ + CONFIG_LED_INIT_PRIORITY, &is31fl3216a_led_api); + +DT_INST_FOREACH_STATUS_OKAY(IS31FL3216A_INIT) diff --git a/dts/bindings/led/issi,is31fl3216a.yaml b/dts/bindings/led/issi,is31fl3216a.yaml new file mode 100644 index 00000000000..c010ae71829 --- /dev/null +++ b/dts/bindings/led/issi,is31fl3216a.yaml @@ -0,0 +1,9 @@ +# Copyright (c) 2023 Endor AG +# SPDX-License-Identifier: Apache-2.0 + +description: | + is31fl3216a LED driver for Lumissil Microsystems (a division of ISSI) + +compatible: "issi,is31fl3216a" + +include: i2c-device.yaml