/* * 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)