| /* |
| * Copyright (c) 2018 Linaro Ltd. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT ti_lp3943 |
| |
| /** |
| * @file |
| * @brief LP3943 LED driver |
| * |
| * Limitations: |
| * - Blink period and brightness value are controlled by two sets of PSCx/PWMx |
| * registers. This driver partitions the available LEDs into two groups as |
| * 0 to 7 and 8 to 15 and assigns PSC0/PWM0 to LEDs from 0 to 7 and PSC1/PWM1 |
| * to LEDs from 8 to 15. So, it is not possible to set unique blink period |
| * and brightness value for LEDs in a group, changing either of these |
| * values for a LED will affect other LEDs also. |
| */ |
| |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/drivers/led.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/kernel.h> |
| |
| #define LOG_LEVEL CONFIG_LED_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(lp3943); |
| |
| #include "led_context.h" |
| |
| /* LP3943 Registers */ |
| #define LP3943_INPUT_1 0x00 |
| #define LP3943_INPUT_2 0x01 |
| #define LP3943_PSC0 0x02 |
| #define LP3943_PWM0 0x03 |
| #define LP3943_PSC1 0x04 |
| #define LP3943_PWM1 0x05 |
| #define LP3943_LS0 0x06 |
| #define LP3943_LS1 0x07 |
| #define LP3943_LS2 0x08 |
| #define LP3943_LS3 0x09 |
| |
| #define LP3943_MASK 0x03 |
| |
| enum lp3943_modes { |
| LP3943_OFF, |
| LP3943_ON, |
| LP3943_DIM0, |
| LP3943_DIM1, |
| }; |
| |
| struct lp3943_config { |
| struct i2c_dt_spec bus; |
| }; |
| |
| struct lp3943_data { |
| struct led_data dev_data; |
| }; |
| |
| static int lp3943_get_led_reg(uint32_t *led, uint8_t *reg) |
| { |
| switch (*led) { |
| case 0: |
| case 1: |
| case 2: |
| __fallthrough; |
| case 3: |
| *reg = LP3943_LS0; |
| break; |
| case 4: |
| case 5: |
| case 6: |
| __fallthrough; |
| case 7: |
| *reg = LP3943_LS1; |
| *led -= 4U; |
| break; |
| case 8: |
| case 9: |
| case 10: |
| __fallthrough; |
| case 11: |
| *reg = LP3943_LS2; |
| *led -= 8U; |
| break; |
| case 12: |
| case 13: |
| case 14: |
| __fallthrough; |
| case 15: |
| *reg = LP3943_LS3; |
| *led -= 12U; |
| break; |
| default: |
| LOG_ERR("Invalid LED specified"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int lp3943_set_dim_states(const struct lp3943_config *config, |
| uint32_t led, uint8_t mode) |
| { |
| int ret; |
| uint8_t reg; |
| |
| ret = lp3943_get_led_reg(&led, ®); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Set DIMx states for the LEDs */ |
| if (i2c_reg_update_byte_dt(&config->bus, reg, LP3943_MASK << (led << 1), |
| mode << (led << 1))) { |
| LOG_ERR("LED reg update failed"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int lp3943_led_blink(const struct device *dev, uint32_t led, |
| uint32_t delay_on, uint32_t delay_off) |
| { |
| const struct lp3943_config *config = dev->config; |
| struct lp3943_data *data = dev->data; |
| struct led_data *dev_data = &data->dev_data; |
| int ret; |
| uint16_t period; |
| uint8_t reg, val, mode; |
| |
| period = delay_on + delay_off; |
| |
| if (period < dev_data->min_period || period > dev_data->max_period) { |
| return -EINVAL; |
| } |
| |
| /* Use DIM0 for LEDs 0 to 7 and DIM1 for LEDs 8 to 15 */ |
| if (led < 8) { |
| mode = LP3943_DIM0; |
| } else { |
| mode = LP3943_DIM1; |
| } |
| |
| if (mode == LP3943_DIM0) { |
| reg = LP3943_PSC0; |
| } else { |
| reg = LP3943_PSC1; |
| } |
| |
| val = (period * 255U) / dev_data->max_period; |
| if (i2c_reg_write_byte_dt(&config->bus, reg, val)) { |
| LOG_ERR("LED write failed"); |
| return -EIO; |
| } |
| |
| ret = lp3943_set_dim_states(config, led, mode); |
| if (ret) { |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int lp3943_led_set_brightness(const struct device *dev, uint32_t led, |
| uint8_t value) |
| { |
| const struct lp3943_config *config = dev->config; |
| struct lp3943_data *data = dev->data; |
| struct led_data *dev_data = &data->dev_data; |
| int ret; |
| uint8_t reg, val, mode; |
| |
| if (value < dev_data->min_brightness || |
| value > dev_data->max_brightness) { |
| return -EINVAL; |
| } |
| |
| /* Use DIM0 for LEDs 0 to 7 and DIM1 for LEDs 8 to 15 */ |
| if (led < 8) { |
| mode = LP3943_DIM0; |
| } else { |
| mode = LP3943_DIM1; |
| } |
| |
| if (mode == LP3943_DIM0) { |
| reg = LP3943_PWM0; |
| } else { |
| reg = LP3943_PWM1; |
| } |
| |
| val = (value * 255U) / dev_data->max_brightness; |
| if (i2c_reg_write_byte_dt(&config->bus, reg, val)) { |
| LOG_ERR("LED write failed"); |
| return -EIO; |
| } |
| |
| ret = lp3943_set_dim_states(config, led, mode); |
| if (ret) { |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static inline int lp3943_led_on(const struct device *dev, uint32_t led) |
| { |
| const struct lp3943_config *config = dev->config; |
| int ret; |
| uint8_t reg, mode; |
| |
| ret = lp3943_get_led_reg(&led, ®); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Set LED state to ON */ |
| mode = LP3943_ON; |
| if (i2c_reg_update_byte_dt(&config->bus, reg, LP3943_MASK << (led << 1), |
| mode << (led << 1))) { |
| LOG_ERR("LED reg update failed"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static inline int lp3943_led_off(const struct device *dev, uint32_t led) |
| { |
| const struct lp3943_config *config = dev->config; |
| int ret; |
| uint8_t reg; |
| |
| ret = lp3943_get_led_reg(&led, ®); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Set LED state to OFF */ |
| if (i2c_reg_update_byte_dt(&config->bus, reg, LP3943_MASK << (led << 1), |
| 0)) { |
| LOG_ERR("LED reg update failed"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int lp3943_led_init(const struct device *dev) |
| { |
| const struct lp3943_config *config = dev->config; |
| struct lp3943_data *data = dev->data; |
| struct led_data *dev_data = &data->dev_data; |
| |
| if (!device_is_ready(config->bus.bus)) { |
| LOG_ERR("I2C device not ready"); |
| return -ENODEV; |
| } |
| |
| /* Hardware specific limits */ |
| dev_data->min_period = 0U; |
| dev_data->max_period = 1600U; |
| dev_data->min_brightness = 0U; |
| dev_data->max_brightness = 100U; |
| |
| return 0; |
| } |
| |
| static struct lp3943_data lp3943_led_data; |
| |
| static const struct lp3943_config lp3943_led_config = { |
| .bus = I2C_DT_SPEC_INST_GET(0), |
| }; |
| |
| static const struct led_driver_api lp3943_led_api = { |
| .blink = lp3943_led_blink, |
| .set_brightness = lp3943_led_set_brightness, |
| .on = lp3943_led_on, |
| .off = lp3943_led_off, |
| }; |
| |
| DEVICE_DT_INST_DEFINE(0, &lp3943_led_init, NULL, &lp3943_led_data, |
| &lp3943_led_config, POST_KERNEL, CONFIG_LED_INIT_PRIORITY, |
| &lp3943_led_api); |