302 lines
9.1 KiB
C
302 lines
9.1 KiB
C
/*
|
|
* Copyright 2022-2023 Daniel DeGrasse <daniel@degrasse.com>
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#define DT_DRV_COMPAT issi_is31fl3733
|
|
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/drivers/led.h>
|
|
#include <zephyr/kernel.h>
|
|
|
|
#include <zephyr/drivers/led/is31fl3733.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
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)
|