blob: 0fd0f128d37bcc9972350ff19e17a2e82f62a4fd [file] [log] [blame]
/*
* Copyright (c) 2023 NXP Semiconductors
* Copyright (c) 2023 Cognipilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT onnn_ncp5623
/**
* @file
* @brief NCP5623 LED driver
*
* The NCP5623 is a 3-channel LED driver that communicates over I2C.
*/
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/led.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(ncp5623, CONFIG_LED_LOG_LEVEL);
#define NCP5623_LED_CURRENT 0x20
#define NCP5623_LED_PWM0 0x40
#define NCP5623_LED_PWM1 0x60
#define NCP5623_LED_PWM2 0x80
#define NCP5623_CHANNEL_COUNT 3
/* Brightness limits */
#define NCP5623_MIN_BRIGHTNESS 0
#define NCP5623_MAX_BRIGHTNESS 0x1f
static const uint8_t led_channels[] = {NCP5623_LED_PWM0, NCP5623_LED_PWM1, NCP5623_LED_PWM2};
struct ncp5623_config {
struct i2c_dt_spec bus;
uint8_t num_leds;
const struct led_info *leds_info;
};
static const struct led_info *ncp5623_led_to_info(const struct ncp5623_config *config, uint32_t led)
{
if (led < config->num_leds) {
return &config->leds_info[led];
}
return NULL;
}
static int ncp5623_get_info(const struct device *dev, uint32_t led, const struct led_info **info)
{
const struct ncp5623_config *config = dev->config;
const struct led_info *led_info = ncp5623_led_to_info(config, led);
if (!led_info) {
return -EINVAL;
}
*info = led_info;
return 0;
}
static int ncp5623_set_color(const struct device *dev, uint32_t led, uint8_t num_colors,
const uint8_t *color)
{
const struct ncp5623_config *config = dev->config;
const struct led_info *led_info = ncp5623_led_to_info(config, led);
uint8_t buf[6] = {0x70, NCP5623_LED_PWM0, 0x70, NCP5623_LED_PWM1, 0x70, NCP5623_LED_PWM2};
if (!led_info) {
return -ENODEV;
}
if (led_info->num_colors != 3) {
return -ENOTSUP;
}
if (num_colors != 3) {
return -EINVAL;
}
buf[1] = buf[1] | color[0] / 8;
buf[3] = buf[3] | color[1] / 8;
buf[5] = buf[5] | color[2] / 8;
return i2c_burst_write_dt(&config->bus, NCP5623_LED_CURRENT | NCP5623_MAX_BRIGHTNESS, buf,
sizeof(buf));
}
static int ncp5623_set_brightness(const struct device *dev, uint32_t led, uint8_t value)
{
const struct ncp5623_config *config = dev->config;
const struct led_info *led_info = ncp5623_led_to_info(config, led);
int ret = 0;
if (!led_info) {
return -ENODEV;
}
if (value > 100) {
return -EINVAL;
}
if (led_info->num_colors != 1) {
return -ENOTSUP;
}
/* Rescale 0..100 to 0..31 */
value = value * NCP5623_MAX_BRIGHTNESS / 100;
ret = i2c_reg_write_byte_dt(&config->bus, led_channels[led] | value, 0x70);
if (ret < 0) {
LOG_ERR("%s: LED write failed", dev->name);
}
return ret;
}
static inline int ncp5623_led_on(const struct device *dev, uint32_t led)
{
return ncp5623_set_brightness(dev, led, 100);
}
static inline int ncp5623_led_off(const struct device *dev, uint32_t led)
{
return ncp5623_set_brightness(dev, led, 0);
}
static int ncp5623_led_init(const struct device *dev)
{
const struct ncp5623_config *config = dev->config;
const struct led_info *led_info = NULL;
int i;
uint8_t buf[6] = {0x70, NCP5623_LED_PWM0, 0x70, NCP5623_LED_PWM1, 0x70, NCP5623_LED_PWM2};
if (!i2c_is_ready_dt(&config->bus)) {
LOG_ERR("%s: I2C device not ready", dev->name);
return -ENODEV;
}
if (config->num_leds == 1) { /* one three-channel (RGB) LED */
led_info = ncp5623_led_to_info(config, 0);
if (!led_info) {
return -ENODEV;
}
if (led_info->num_colors != NCP5623_CHANNEL_COUNT) {
LOG_ERR("%s: invalid number of colors %d (must be %d with a single LED)",
dev->name, led_info->num_colors, NCP5623_CHANNEL_COUNT);
return -EINVAL;
}
} else if (config->num_leds <= 3) { /* three single-channel LEDs */
for (i = 0; i < config->num_leds; i++) {
led_info = ncp5623_led_to_info(config, 0);
if (!led_info) {
return -ENODEV;
}
if (led_info->num_colors > 1) {
LOG_ERR("%s: invalid number of colors %d (must be 1 when defining "
"multiple leds)",
dev->name, led_info->num_colors);
return -EINVAL;
}
}
} else {
LOG_ERR("%s: invalid number of leds %d (max %d)", dev->name, config->num_leds,
NCP5623_CHANNEL_COUNT);
return -EINVAL;
}
if (i2c_burst_write_dt(&config->bus, NCP5623_LED_CURRENT | NCP5623_MAX_BRIGHTNESS, buf,
6)) {
LOG_ERR("%s: LED write failed", dev->name);
return -EIO;
}
return 0;
}
static const struct led_driver_api ncp5623_led_api = {
.set_brightness = ncp5623_set_brightness,
.on = ncp5623_led_on,
.off = ncp5623_led_off,
.get_info = ncp5623_get_info,
.set_color = ncp5623_set_color,
};
#define COLOR_MAPPING(led_node_id) \
static const uint8_t color_mapping_##led_node_id[] = DT_PROP(led_node_id, color_mapping);
#define LED_INFO(led_node_id) \
{ \
.label = DT_PROP(led_node_id, label), \
.index = DT_PROP(led_node_id, index), \
.num_colors = DT_PROP_LEN(led_node_id, color_mapping), \
.color_mapping = color_mapping_##led_node_id, \
},
#define NCP5623_DEFINE(id) \
\
DT_INST_FOREACH_CHILD(id, COLOR_MAPPING) \
\
static const struct led_info ncp5623_leds_##id[] = {DT_INST_FOREACH_CHILD(id, LED_INFO)}; \
\
static const struct ncp5623_config ncp5623_config_##id = { \
.bus = I2C_DT_SPEC_INST_GET(id), \
.num_leds = ARRAY_SIZE(ncp5623_leds_##id), \
.leds_info = ncp5623_leds_##id, \
}; \
DEVICE_DT_INST_DEFINE(id, &ncp5623_led_init, NULL, NULL, &ncp5623_config_##id, \
POST_KERNEL, CONFIG_LED_INIT_PRIORITY, &ncp5623_led_api);
DT_INST_FOREACH_STATUS_OKAY(NCP5623_DEFINE)