mirror of
https://source.denx.de/u-boot/u-boot.git
synced 2025-08-14 19:26:58 +02:00
The LM3532 is a 500-kHz fixed frequency asynchronous boost converter which provides the power for 3 high-voltage, low-side current sinks. The device is programmable over an I2C-compatible interface and has independent current control for all three channels. Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
381 lines
9.9 KiB
C
381 lines
9.9 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* TI LM3532 LED driver
|
|
*
|
|
* Copyright (c) 2019 Texas Instruments Incorporated
|
|
* Copyright (c) 2025 Svyatoslav Ryhel <clamor95@gmail.com>
|
|
*/
|
|
|
|
#define LOG_CATEGORY UCLASS_PANEL_BACKLIGHT
|
|
|
|
#include <backlight.h>
|
|
#include <dm.h>
|
|
#include <dm/ofnode.h>
|
|
#include <i2c.h>
|
|
#include <log.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <asm/gpio.h>
|
|
#include <power/regulator.h>
|
|
|
|
#define LM3532_BL_MODE_MANUAL 0x00
|
|
#define LM3532_BL_MODE_ALS 0x01
|
|
|
|
#define LM3532_REG_OUTPUT_CFG 0x10
|
|
#define LM3532_REG_STARTSHUT_RAMP 0x11
|
|
#define LM3532_REG_RT_RAMP 0x12
|
|
#define LM3532_REG_PWM_A_CFG 0x13
|
|
#define LM3532_REG_PWM_B_CFG 0x14
|
|
#define LM3532_REG_PWM_C_CFG 0x15
|
|
#define LM3532_REG_ZONE_CFG_A 0x16
|
|
#define LM3532_REG_CTRL_A_FS_CURR 0x17
|
|
#define LM3532_REG_ZONE_CFG_B 0x18
|
|
#define LM3532_REG_CTRL_B_FS_CURR 0x19
|
|
#define LM3532_REG_ZONE_CFG_C 0x1a
|
|
#define LM3532_REG_CTRL_C_FS_CURR 0x1b
|
|
#define LM3532_REG_ENABLE 0x1d
|
|
#define LM3532_ALS_CONFIG 0x23
|
|
#define LM3532_REG_ZN_0_HI 0x60
|
|
#define LM3532_REG_ZN_0_LO 0x61
|
|
#define LM3532_REG_ZN_1_HI 0x62
|
|
#define LM3532_REG_ZN_1_LO 0x63
|
|
#define LM3532_REG_ZN_2_HI 0x64
|
|
#define LM3532_REG_ZN_2_LO 0x65
|
|
#define LM3532_REG_ZN_3_HI 0x66
|
|
#define LM3532_REG_ZN_3_LO 0x67
|
|
#define LM3532_REG_ZONE_TRGT_A 0x70
|
|
#define LM3532_REG_ZONE_TRGT_B 0x75
|
|
#define LM3532_REG_ZONE_TRGT_C 0x7a
|
|
#define LM3532_REG_MAX 0x7e
|
|
|
|
/* Control Enable */
|
|
#define LM3532_CTRL_A_ENABLE BIT(0)
|
|
#define LM3532_CTRL_B_ENABLE BIT(1)
|
|
#define LM3532_CTRL_C_ENABLE BIT(2)
|
|
|
|
/* PWM Zone Control */
|
|
#define LM3532_PWM_ZONE_MASK 0x7c
|
|
#define LM3532_PWM_ZONE_0_EN BIT(2)
|
|
#define LM3532_PWM_ZONE_1_EN BIT(3)
|
|
#define LM3532_PWM_ZONE_2_EN BIT(4)
|
|
#define LM3532_PWM_ZONE_3_EN BIT(5)
|
|
#define LM3532_PWM_ZONE_4_EN BIT(6)
|
|
|
|
/* Brightness Configuration */
|
|
#define LM3532_I2C_CTRL BIT(0)
|
|
#define LM3532_ALS_CTRL 0
|
|
#define LM3532_LINEAR_MAP BIT(1)
|
|
#define LM3532_ZONE_MASK (BIT(2) | BIT(3) | BIT(4))
|
|
#define LM3532_ZONE_0 0
|
|
#define LM3532_ZONE_1 BIT(2)
|
|
#define LM3532_ZONE_2 BIT(3)
|
|
#define LM3532_ZONE_3 (BIT(2) | BIT(3))
|
|
#define LM3532_ZONE_4 BIT(4)
|
|
|
|
#define LM3532_ENABLE_ALS BIT(3)
|
|
#define LM3532_ALS_SEL_SHIFT 6
|
|
|
|
/* Zone Boundary Register */
|
|
#define LM3532_ALS_WINDOW_mV 2000
|
|
#define LM3532_ALS_ZB_MAX 4
|
|
#define LM3532_ALS_OFFSET_mV 2
|
|
|
|
#define LM3532_CONTROL_A 0
|
|
#define LM3532_CONTROL_B 1
|
|
#define LM3532_CONTROL_C 2
|
|
#define LM3532_MAX_CONTROL_BANKS 3
|
|
#define LM3532_MAX_LED_STRINGS 3
|
|
|
|
#define LM3532_OUTPUT_CFG_MASK 0x3
|
|
#define LM3532_BRT_VAL_ADJUST 8
|
|
#define LM3532_RAMP_DOWN_SHIFT 3
|
|
|
|
#define LM3532_NUM_RAMP_VALS 8
|
|
#define LM3532_NUM_AVG_VALS 8
|
|
#define LM3532_NUM_IMP_VALS 32
|
|
|
|
#define LM3532_FS_CURR_MIN 5000
|
|
#define LM3532_FS_CURR_MAX 29800
|
|
#define LM3532_FS_CURR_STEP 800
|
|
|
|
struct lm3532_bank_data {
|
|
int control_bank;
|
|
int mode;
|
|
int ctrl_brt_pointer;
|
|
int num_leds;
|
|
int full_scale_current;
|
|
u32 present:1;
|
|
u32 led_strings[LM3532_MAX_CONTROL_BANKS];
|
|
};
|
|
|
|
struct lm3532_backlight_priv {
|
|
struct gpio_desc enable_gpio;
|
|
struct udevice *regulator;
|
|
|
|
u32 runtime_ramp_up;
|
|
u32 runtime_ramp_down;
|
|
|
|
struct lm3532_bank_data bank[LM3532_MAX_CONTROL_BANKS];
|
|
};
|
|
|
|
/* This device does not like i2c md so use this instead */
|
|
static void __maybe_unused dump_i2c(struct udevice *dev)
|
|
{
|
|
int i, c;
|
|
|
|
for (i = 0; i < 0x10; i++) {
|
|
printf("00%02x: %02x", i * 0x10, dm_i2c_reg_read(dev, i * 0x10));
|
|
for (c = 1; c < 0xf; c++)
|
|
printf(" %02x", dm_i2c_reg_read(dev, i * 0x10 + c));
|
|
printf(" %02x\n", dm_i2c_reg_read(dev, i * 0x10 + 0xf));
|
|
}
|
|
}
|
|
|
|
static int lm3532_backlight_enable(struct udevice *dev)
|
|
{
|
|
struct lm3532_backlight_priv *priv = dev_get_priv(dev);
|
|
int ret, i;
|
|
|
|
for (i = 0; i < LM3532_MAX_CONTROL_BANKS; i++) {
|
|
if (priv->bank[i].present) {
|
|
u32 ctrl_en_val = BIT(priv->bank[i].control_bank);
|
|
|
|
ret = dm_i2c_reg_clrset(dev, LM3532_REG_ENABLE,
|
|
ctrl_en_val, ctrl_en_val);
|
|
if (ret) {
|
|
log_debug("%s: failed to set ctrl: %d\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
regulator_set_enable_if_allowed(priv->regulator, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lm3532_backlight_set_brightness(struct udevice *dev, int percent)
|
|
{
|
|
struct lm3532_backlight_priv *priv = dev_get_priv(dev);
|
|
struct lm3532_bank_data *bank;
|
|
int ret, i;
|
|
|
|
for (i = 0; i < LM3532_MAX_CONTROL_BANKS; i++) {
|
|
if (priv->bank[i].present) {
|
|
bank = &priv->bank[i];
|
|
u32 brightness_reg = LM3532_REG_ZONE_TRGT_A +
|
|
bank->control_bank * 5 +
|
|
(bank->ctrl_brt_pointer >> 2);
|
|
|
|
ret = dm_i2c_reg_write(dev, brightness_reg, percent);
|
|
if (ret) {
|
|
log_debug("%s: failed to set brightness: %d\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const int ramp_table[LM3532_NUM_RAMP_VALS] = { 8, 1024, 2048, 4096, 8192,
|
|
16384, 32768, 65536 };
|
|
static int lm3532_get_ramp_index(int ramp_time)
|
|
{
|
|
int i;
|
|
|
|
if (ramp_time <= ramp_table[0])
|
|
return 0;
|
|
|
|
if (ramp_time > ramp_table[LM3532_NUM_RAMP_VALS - 1])
|
|
return LM3532_NUM_RAMP_VALS - 1;
|
|
|
|
for (i = 1; i < LM3532_NUM_RAMP_VALS; i++) {
|
|
if (ramp_time == ramp_table[i])
|
|
return i;
|
|
|
|
/* Find an approximate index by looking up the table */
|
|
if (ramp_time > ramp_table[i - 1] &&
|
|
ramp_time < ramp_table[i]) {
|
|
if (ramp_time - ramp_table[i - 1] < ramp_table[i] - ramp_time)
|
|
return i - 1;
|
|
else
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lm3532_backlight_of_to_plat(struct udevice *dev)
|
|
{
|
|
struct lm3532_backlight_priv *priv = dev_get_priv(dev);
|
|
u32 ramp_time, reg;
|
|
ofnode child;
|
|
int ret;
|
|
|
|
ret = gpio_request_by_name(dev, "enable-gpios", 0,
|
|
&priv->enable_gpio, GPIOD_IS_OUT);
|
|
if (ret) {
|
|
log_debug("%s: could not decode enable-gpios (%d)\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = device_get_supply_regulator(dev, "vin-supply", &priv->regulator);
|
|
if (ret) {
|
|
log_debug("%s: vin regulator not defined: %d\n", __func__, ret);
|
|
if (ret != -ENOENT)
|
|
return log_ret(ret);
|
|
}
|
|
|
|
ramp_time = dev_read_u32_default(dev, "ramp-up-us", 0);
|
|
priv->runtime_ramp_up = lm3532_get_ramp_index(ramp_time);
|
|
|
|
ramp_time = dev_read_u32_default(dev, "ramp-down-us", 0);
|
|
priv->runtime_ramp_down = lm3532_get_ramp_index(ramp_time);
|
|
|
|
/* Backlight is one of children but has no dedicated driver */
|
|
ofnode_for_each_subnode(child, dev_ofnode(dev)) {
|
|
ret = ofnode_read_u32(child, "reg", ®);
|
|
if (ret || reg > LM3532_CONTROL_C) {
|
|
log_debug("%s: control bank invalid %d\n", __func__, reg);
|
|
continue;
|
|
}
|
|
|
|
struct lm3532_bank_data *bank = &priv->bank[reg];
|
|
|
|
bank->control_bank = reg;
|
|
bank->present = 1;
|
|
bank->mode = ofnode_read_u32_default(child, "ti,led-mode",
|
|
LM3532_BL_MODE_MANUAL);
|
|
bank->mode = LM3532_BL_MODE_ALS ? LM3532_ALS_CTRL : LM3532_I2C_CTRL;
|
|
|
|
if (ofnode_read_bool(child, "ti,linear-mapping-mode"))
|
|
bank->mode |= LM3532_LINEAR_MAP;
|
|
|
|
bank->num_leds = ofnode_read_size(child, "led-sources");
|
|
bank->num_leds /= sizeof(u32);
|
|
if (bank->num_leds > LM3532_MAX_LED_STRINGS) {
|
|
log_debug("%s: too many LED string defined %d\n",
|
|
__func__, bank->num_leds);
|
|
continue;
|
|
}
|
|
|
|
ret = ofnode_read_u32_array(child, "led-sources",
|
|
bank->led_strings,
|
|
bank->num_leds);
|
|
if (ret) {
|
|
log_debug("%s: led-sources property missing %d\n",
|
|
__func__, ret);
|
|
continue;
|
|
}
|
|
|
|
ret = ofnode_read_u32(child, "led-max-microamp",
|
|
&bank->full_scale_current);
|
|
if (ret)
|
|
log_debug("%s: failed getting led-max-microamp %d\n",
|
|
__func__, ret);
|
|
else
|
|
bank->full_scale_current = min(bank->full_scale_current,
|
|
LM3532_FS_CURR_MAX);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lm3532_backlight_init_registers(struct udevice *dev,
|
|
struct lm3532_bank_data *bank)
|
|
{
|
|
struct lm3532_backlight_priv *priv = dev_get_priv(dev);
|
|
u32 brightness_config_val, runtime_ramp_val;
|
|
u32 output_cfg_val = 0, output_cfg_shift = 0, output_cfg_mask = 0;
|
|
int fs_current_reg, fs_current_val;
|
|
int ret, i;
|
|
|
|
if (!bank->present)
|
|
return 0;
|
|
|
|
u32 brightness_config_reg = LM3532_REG_ZONE_CFG_A + bank->control_bank * 2;
|
|
/*
|
|
* This could be hard coded to the default value but the control
|
|
* brightness register may have changed during boot.
|
|
*/
|
|
ret = dm_i2c_reg_read(dev, brightness_config_reg);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
bank->ctrl_brt_pointer = ret & ~LM3532_ZONE_MASK;
|
|
brightness_config_val = bank->ctrl_brt_pointer | bank->mode;
|
|
|
|
ret = dm_i2c_reg_write(dev, brightness_config_reg, brightness_config_val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (bank->full_scale_current) {
|
|
fs_current_reg = LM3532_REG_CTRL_A_FS_CURR + bank->control_bank * 2;
|
|
fs_current_val = (bank->full_scale_current - LM3532_FS_CURR_MIN) /
|
|
LM3532_FS_CURR_STEP;
|
|
|
|
ret = dm_i2c_reg_write(dev, fs_current_reg, fs_current_val);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < bank->num_leds; i++) {
|
|
output_cfg_shift = bank->led_strings[i] * 2;
|
|
output_cfg_val |= (bank->control_bank << output_cfg_shift);
|
|
output_cfg_mask |= LM3532_OUTPUT_CFG_MASK << output_cfg_shift;
|
|
}
|
|
|
|
ret = dm_i2c_reg_clrset(dev, LM3532_REG_OUTPUT_CFG, output_cfg_mask,
|
|
output_cfg_val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
runtime_ramp_val = priv->runtime_ramp_up |
|
|
(priv->runtime_ramp_down << LM3532_RAMP_DOWN_SHIFT);
|
|
|
|
return dm_i2c_reg_write(dev, LM3532_REG_RT_RAMP, runtime_ramp_val);
|
|
}
|
|
|
|
static int lm3532_backlight_probe(struct udevice *dev)
|
|
{
|
|
struct lm3532_backlight_priv *priv = dev_get_priv(dev);
|
|
int ret, i;
|
|
|
|
if (device_get_uclass_id(dev->parent) != UCLASS_I2C)
|
|
return -EPROTONOSUPPORT;
|
|
|
|
dm_gpio_set_value(&priv->enable_gpio, 1);
|
|
|
|
for (i = 0; i < LM3532_MAX_CONTROL_BANKS; i++) {
|
|
ret = lm3532_backlight_init_registers(dev, &priv->bank[i]);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct backlight_ops lm3532_backlight_ops = {
|
|
.enable = lm3532_backlight_enable,
|
|
.set_brightness = lm3532_backlight_set_brightness,
|
|
};
|
|
|
|
static const struct udevice_id lm3532_backlight_ids[] = {
|
|
{ .compatible = "ti,lm3532" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(lm3532_backlight) = {
|
|
.name = "lm3532_backlight",
|
|
.id = UCLASS_PANEL_BACKLIGHT,
|
|
.of_match = lm3532_backlight_ids,
|
|
.of_to_plat = lm3532_backlight_of_to_plat,
|
|
.probe = lm3532_backlight_probe,
|
|
.ops = &lm3532_backlight_ops,
|
|
.priv_auto = sizeof(struct lm3532_backlight_priv),
|
|
};
|