1bc1b8492SDan Murphy // SPDX-License-Identifier: GPL-2.0 2bc1b8492SDan Murphy // TI LM3532 LED driver 3c5437338SAlexander A. Klimov // Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com/ 4c5437338SAlexander A. Klimov // https://www.ti.com/lit/ds/symlink/lm3532.pdf 5bc1b8492SDan Murphy 6bc1b8492SDan Murphy #include <linux/i2c.h> 7bc1b8492SDan Murphy #include <linux/leds.h> 8bc1b8492SDan Murphy #include <linux/slab.h> 9bc1b8492SDan Murphy #include <linux/regmap.h> 10bc1b8492SDan Murphy #include <linux/types.h> 11bc1b8492SDan Murphy #include <linux/regulator/consumer.h> 12bc1b8492SDan Murphy #include <linux/module.h> 13bc1b8492SDan Murphy #include <uapi/linux/uleds.h> 14bc1b8492SDan Murphy #include <linux/gpio/consumer.h> 15bc1b8492SDan Murphy 16bc1b8492SDan Murphy #define LM3532_NAME "lm3532-led" 17bc1b8492SDan Murphy #define LM3532_BL_MODE_MANUAL 0x00 18bc1b8492SDan Murphy #define LM3532_BL_MODE_ALS 0x01 19bc1b8492SDan Murphy 20bc1b8492SDan Murphy #define LM3532_REG_OUTPUT_CFG 0x10 21bc1b8492SDan Murphy #define LM3532_REG_STARTSHUT_RAMP 0x11 22bc1b8492SDan Murphy #define LM3532_REG_RT_RAMP 0x12 23bc1b8492SDan Murphy #define LM3532_REG_PWM_A_CFG 0x13 24bc1b8492SDan Murphy #define LM3532_REG_PWM_B_CFG 0x14 25bc1b8492SDan Murphy #define LM3532_REG_PWM_C_CFG 0x15 26bc1b8492SDan Murphy #define LM3532_REG_ZONE_CFG_A 0x16 274c905450SDan Murphy #define LM3532_REG_CTRL_A_FS_CURR 0x17 28bc1b8492SDan Murphy #define LM3532_REG_ZONE_CFG_B 0x18 294c905450SDan Murphy #define LM3532_REG_CTRL_B_FS_CURR 0x19 30bc1b8492SDan Murphy #define LM3532_REG_ZONE_CFG_C 0x1a 314c905450SDan Murphy #define LM3532_REG_CTRL_C_FS_CURR 0x1b 32bc1b8492SDan Murphy #define LM3532_REG_ENABLE 0x1d 33bc1b8492SDan Murphy #define LM3532_ALS_CONFIG 0x23 34bc1b8492SDan Murphy #define LM3532_REG_ZN_0_HI 0x60 35bc1b8492SDan Murphy #define LM3532_REG_ZN_0_LO 0x61 36bc1b8492SDan Murphy #define LM3532_REG_ZN_1_HI 0x62 37bc1b8492SDan Murphy #define LM3532_REG_ZN_1_LO 0x63 38bc1b8492SDan Murphy #define LM3532_REG_ZN_2_HI 0x64 39bc1b8492SDan Murphy #define LM3532_REG_ZN_2_LO 0x65 40bc1b8492SDan Murphy #define LM3532_REG_ZN_3_HI 0x66 41bc1b8492SDan Murphy #define LM3532_REG_ZN_3_LO 0x67 4213123940SDan Murphy #define LM3532_REG_ZONE_TRGT_A 0x70 4313123940SDan Murphy #define LM3532_REG_ZONE_TRGT_B 0x75 4413123940SDan Murphy #define LM3532_REG_ZONE_TRGT_C 0x7a 45bc1b8492SDan Murphy #define LM3532_REG_MAX 0x7e 46bc1b8492SDan Murphy 476559ac32SDan Murphy /* Control Enable */ 48bc1b8492SDan Murphy #define LM3532_CTRL_A_ENABLE BIT(0) 49bc1b8492SDan Murphy #define LM3532_CTRL_B_ENABLE BIT(1) 50bc1b8492SDan Murphy #define LM3532_CTRL_C_ENABLE BIT(2) 51bc1b8492SDan Murphy 52bc1b8492SDan Murphy /* PWM Zone Control */ 53bc1b8492SDan Murphy #define LM3532_PWM_ZONE_MASK 0x7c 54bc1b8492SDan Murphy #define LM3532_PWM_ZONE_0_EN BIT(2) 55bc1b8492SDan Murphy #define LM3532_PWM_ZONE_1_EN BIT(3) 56bc1b8492SDan Murphy #define LM3532_PWM_ZONE_2_EN BIT(4) 57bc1b8492SDan Murphy #define LM3532_PWM_ZONE_3_EN BIT(5) 58bc1b8492SDan Murphy #define LM3532_PWM_ZONE_4_EN BIT(6) 59bc1b8492SDan Murphy 60bc1b8492SDan Murphy /* Brightness Configuration */ 61bc1b8492SDan Murphy #define LM3532_I2C_CTRL BIT(0) 62bc1b8492SDan Murphy #define LM3532_ALS_CTRL 0 63bc1b8492SDan Murphy #define LM3532_LINEAR_MAP BIT(1) 64bc1b8492SDan Murphy #define LM3532_ZONE_MASK (BIT(2) | BIT(3) | BIT(4)) 65bc1b8492SDan Murphy #define LM3532_ZONE_0 0 66bc1b8492SDan Murphy #define LM3532_ZONE_1 BIT(2) 67bc1b8492SDan Murphy #define LM3532_ZONE_2 BIT(3) 68bc1b8492SDan Murphy #define LM3532_ZONE_3 (BIT(2) | BIT(3)) 69bc1b8492SDan Murphy #define LM3532_ZONE_4 BIT(4) 70bc1b8492SDan Murphy 71bc1b8492SDan Murphy #define LM3532_ENABLE_ALS BIT(3) 72bc1b8492SDan Murphy #define LM3532_ALS_SEL_SHIFT 6 73bc1b8492SDan Murphy 74bc1b8492SDan Murphy /* Zone Boundary Register */ 75bc1b8492SDan Murphy #define LM3532_ALS_WINDOW_mV 2000 76bc1b8492SDan Murphy #define LM3532_ALS_ZB_MAX 4 77bc1b8492SDan Murphy #define LM3532_ALS_OFFSET_mV 2 78bc1b8492SDan Murphy 79bc1b8492SDan Murphy #define LM3532_CONTROL_A 0 80bc1b8492SDan Murphy #define LM3532_CONTROL_B 1 81bc1b8492SDan Murphy #define LM3532_CONTROL_C 2 82bc1b8492SDan Murphy #define LM3532_MAX_CONTROL_BANKS 3 83bc1b8492SDan Murphy #define LM3532_MAX_LED_STRINGS 3 84bc1b8492SDan Murphy 85bc1b8492SDan Murphy #define LM3532_OUTPUT_CFG_MASK 0x3 86bc1b8492SDan Murphy #define LM3532_BRT_VAL_ADJUST 8 87bc1b8492SDan Murphy #define LM3532_RAMP_DOWN_SHIFT 3 88bc1b8492SDan Murphy 89bc1b8492SDan Murphy #define LM3532_NUM_RAMP_VALS 8 90bc1b8492SDan Murphy #define LM3532_NUM_AVG_VALS 8 91bc1b8492SDan Murphy #define LM3532_NUM_IMP_VALS 32 92bc1b8492SDan Murphy 93517ea49aSDan Murphy #define LM3532_FS_CURR_MIN 5000 94517ea49aSDan Murphy #define LM3532_FS_CURR_MAX 29800 95517ea49aSDan Murphy #define LM3532_FS_CURR_STEP 800 96517ea49aSDan Murphy 97bc1b8492SDan Murphy /* 98bc1b8492SDan Murphy * struct lm3532_als_data 999e955a42SDan Murphy * @config: value of ALS configuration register 1009e955a42SDan Murphy * @als1_imp_sel: value of ALS1 resistor select register 1019e955a42SDan Murphy * @als2_imp_sel: value of ALS2 resistor select register 1029e955a42SDan Murphy * @als_avrg_time: ALS averaging time 1039e955a42SDan Murphy * @als_input_mode: ALS input mode for brightness control 1049e955a42SDan Murphy * @als_vmin: Minimum ALS voltage 1059e955a42SDan Murphy * @als_vmax: Maximum ALS voltage 1069e955a42SDan Murphy * @zone_lo: values of ALS lo ZB(Zone Boundary) registers 1079e955a42SDan Murphy * @zone_hi: values of ALS hi ZB(Zone Boundary) registers 108bc1b8492SDan Murphy */ 109bc1b8492SDan Murphy struct lm3532_als_data { 110bc1b8492SDan Murphy u8 config; 111bc1b8492SDan Murphy u8 als1_imp_sel; 112bc1b8492SDan Murphy u8 als2_imp_sel; 113bc1b8492SDan Murphy u8 als_avrg_time; 114bc1b8492SDan Murphy u8 als_input_mode; 115bc1b8492SDan Murphy u32 als_vmin; 116bc1b8492SDan Murphy u32 als_vmax; 117bc1b8492SDan Murphy u8 zones_lo[LM3532_ALS_ZB_MAX]; 118bc1b8492SDan Murphy u8 zones_hi[LM3532_ALS_ZB_MAX]; 119bc1b8492SDan Murphy }; 120bc1b8492SDan Murphy 121bc1b8492SDan Murphy /** 122bc1b8492SDan Murphy * struct lm3532_led 123bc1b8492SDan Murphy * @led_dev: led class device 1249e955a42SDan Murphy * @priv: Pointer the device data structure 1259e955a42SDan Murphy * @control_bank: Control bank the LED is associated to 1269e955a42SDan Murphy * @mode: Mode of the LED string 1279e955a42SDan Murphy * @ctrl_brt_pointer: Zone target register that controls the sink 1289e955a42SDan Murphy * @num_leds: Number of LED strings are supported in this array 1299e955a42SDan Murphy * @full_scale_current: The full-scale current setting for the current sink. 1309e955a42SDan Murphy * @led_strings: The LED strings supported in this array 1319e955a42SDan Murphy * @enabled: Enabled status 132bc1b8492SDan Murphy */ 133bc1b8492SDan Murphy struct lm3532_led { 134bc1b8492SDan Murphy struct led_classdev led_dev; 135bc1b8492SDan Murphy struct lm3532_data *priv; 136bc1b8492SDan Murphy 137bc1b8492SDan Murphy int control_bank; 138bc1b8492SDan Murphy int mode; 13913123940SDan Murphy int ctrl_brt_pointer; 140bc1b8492SDan Murphy int num_leds; 141517ea49aSDan Murphy int full_scale_current; 14228799272SColin Ian King unsigned int enabled:1; 143bc1b8492SDan Murphy u32 led_strings[LM3532_MAX_CONTROL_BANKS]; 144bc1b8492SDan Murphy }; 145bc1b8492SDan Murphy 146bc1b8492SDan Murphy /** 147bc1b8492SDan Murphy * struct lm3532_data 1489e955a42SDan Murphy * @enable_gpio: Hardware enable gpio 149bc1b8492SDan Murphy * @regulator: regulator 150bc1b8492SDan Murphy * @client: i2c client 1519e955a42SDan Murphy * @regmap: Devices register map 1529e955a42SDan Murphy * @dev: Pointer to the devices device struct 1539e955a42SDan Murphy * @lock: Lock for reading/writing the device 1549e955a42SDan Murphy * @als_data: Pointer to the als data struct 1559e955a42SDan Murphy * @runtime_ramp_up: Runtime ramp up setting 1569e955a42SDan Murphy * @runtime_ramp_down: Runtime ramp down setting 1579e955a42SDan Murphy * @leds: Array of LED strings 158bc1b8492SDan Murphy */ 159bc1b8492SDan Murphy struct lm3532_data { 160bc1b8492SDan Murphy struct gpio_desc *enable_gpio; 161bc1b8492SDan Murphy struct regulator *regulator; 162bc1b8492SDan Murphy struct i2c_client *client; 163bc1b8492SDan Murphy struct regmap *regmap; 164bc1b8492SDan Murphy struct device *dev; 165bc1b8492SDan Murphy struct mutex lock; 166bc1b8492SDan Murphy 167bc1b8492SDan Murphy struct lm3532_als_data *als_data; 168bc1b8492SDan Murphy 169bc1b8492SDan Murphy u32 runtime_ramp_up; 170bc1b8492SDan Murphy u32 runtime_ramp_down; 171bc1b8492SDan Murphy 172bc1b8492SDan Murphy struct lm3532_led leds[]; 173bc1b8492SDan Murphy }; 174bc1b8492SDan Murphy 175bc1b8492SDan Murphy static const struct reg_default lm3532_reg_defs[] = { 176bc1b8492SDan Murphy {LM3532_REG_OUTPUT_CFG, 0xe4}, 177bc1b8492SDan Murphy {LM3532_REG_STARTSHUT_RAMP, 0xc0}, 178bc1b8492SDan Murphy {LM3532_REG_RT_RAMP, 0xc0}, 179bc1b8492SDan Murphy {LM3532_REG_PWM_A_CFG, 0x82}, 180bc1b8492SDan Murphy {LM3532_REG_PWM_B_CFG, 0x82}, 181bc1b8492SDan Murphy {LM3532_REG_PWM_C_CFG, 0x82}, 182bc1b8492SDan Murphy {LM3532_REG_ZONE_CFG_A, 0xf1}, 1834c905450SDan Murphy {LM3532_REG_CTRL_A_FS_CURR, 0xf3}, 184bc1b8492SDan Murphy {LM3532_REG_ZONE_CFG_B, 0xf1}, 1854c905450SDan Murphy {LM3532_REG_CTRL_B_FS_CURR, 0xf3}, 186bc1b8492SDan Murphy {LM3532_REG_ZONE_CFG_C, 0xf1}, 1874c905450SDan Murphy {LM3532_REG_CTRL_C_FS_CURR, 0xf3}, 188bc1b8492SDan Murphy {LM3532_REG_ENABLE, 0xf8}, 189bc1b8492SDan Murphy {LM3532_ALS_CONFIG, 0x44}, 190bc1b8492SDan Murphy {LM3532_REG_ZN_0_HI, 0x35}, 191bc1b8492SDan Murphy {LM3532_REG_ZN_0_LO, 0x33}, 192bc1b8492SDan Murphy {LM3532_REG_ZN_1_HI, 0x6a}, 193bc1b8492SDan Murphy {LM3532_REG_ZN_1_LO, 0x66}, 194bc1b8492SDan Murphy {LM3532_REG_ZN_2_HI, 0xa1}, 195bc1b8492SDan Murphy {LM3532_REG_ZN_2_LO, 0x99}, 196bc1b8492SDan Murphy {LM3532_REG_ZN_3_HI, 0xdc}, 197bc1b8492SDan Murphy {LM3532_REG_ZN_3_LO, 0xcc}, 198bc1b8492SDan Murphy }; 199bc1b8492SDan Murphy 200bc1b8492SDan Murphy static const struct regmap_config lm3532_regmap_config = { 201bc1b8492SDan Murphy .reg_bits = 8, 202bc1b8492SDan Murphy .val_bits = 8, 203bc1b8492SDan Murphy 204bc1b8492SDan Murphy .max_register = LM3532_REG_MAX, 205bc1b8492SDan Murphy .reg_defaults = lm3532_reg_defs, 206bc1b8492SDan Murphy .num_reg_defaults = ARRAY_SIZE(lm3532_reg_defs), 207bc1b8492SDan Murphy .cache_type = REGCACHE_FLAT, 208bc1b8492SDan Murphy }; 209bc1b8492SDan Murphy 210536129ccSKrzysztof Wilczynski static const int als_imp_table[LM3532_NUM_IMP_VALS] = {37000, 18500, 12330, 211bc1b8492SDan Murphy 92500, 7400, 6170, 5290, 212bc1b8492SDan Murphy 4630, 4110, 3700, 3360, 213bc1b8492SDan Murphy 3080, 2850, 2640, 2440, 214bc1b8492SDan Murphy 2310, 2180, 2060, 1950, 215bc1b8492SDan Murphy 1850, 1760, 1680, 1610, 216bc1b8492SDan Murphy 1540, 1480, 1420, 1370, 217bc1b8492SDan Murphy 1320, 1280, 1230, 1190}; 218bc1b8492SDan Murphy static int lm3532_get_als_imp_index(int als_imped) 219bc1b8492SDan Murphy { 220bc1b8492SDan Murphy int i; 221bc1b8492SDan Murphy 222bc1b8492SDan Murphy if (als_imped > als_imp_table[1]) 223bc1b8492SDan Murphy return 0; 224bc1b8492SDan Murphy 225bc1b8492SDan Murphy if (als_imped < als_imp_table[LM3532_NUM_IMP_VALS - 1]) 226bc1b8492SDan Murphy return LM3532_NUM_IMP_VALS - 1; 227bc1b8492SDan Murphy 228bc1b8492SDan Murphy for (i = 1; i < LM3532_NUM_IMP_VALS; i++) { 229bc1b8492SDan Murphy if (als_imped == als_imp_table[i]) 230bc1b8492SDan Murphy return i; 231bc1b8492SDan Murphy 232bc1b8492SDan Murphy /* Find an approximate index by looking up the table */ 233bc1b8492SDan Murphy if (als_imped < als_imp_table[i - 1] && 234bc1b8492SDan Murphy als_imped > als_imp_table[i]) { 235bc1b8492SDan Murphy if (als_imped - als_imp_table[i - 1] < 236bc1b8492SDan Murphy als_imp_table[i] - als_imped) 237bc1b8492SDan Murphy return i + 1; 238bc1b8492SDan Murphy else 239bc1b8492SDan Murphy return i; 240bc1b8492SDan Murphy } 241bc1b8492SDan Murphy } 242bc1b8492SDan Murphy 243bc1b8492SDan Murphy return -EINVAL; 244bc1b8492SDan Murphy } 245bc1b8492SDan Murphy 246bc1b8492SDan Murphy static int lm3532_get_index(const int table[], int size, int value) 247bc1b8492SDan Murphy { 248bc1b8492SDan Murphy int i; 249bc1b8492SDan Murphy 250bc1b8492SDan Murphy for (i = 1; i < size; i++) { 251bc1b8492SDan Murphy if (value == table[i]) 252bc1b8492SDan Murphy return i; 253bc1b8492SDan Murphy 254bc1b8492SDan Murphy /* Find an approximate index by looking up the table */ 255bc1b8492SDan Murphy if (value > table[i - 1] && 256bc1b8492SDan Murphy value < table[i]) { 257bc1b8492SDan Murphy if (value - table[i - 1] < table[i] - value) 258bc1b8492SDan Murphy return i - 1; 259bc1b8492SDan Murphy else 260bc1b8492SDan Murphy return i; 261bc1b8492SDan Murphy } 262bc1b8492SDan Murphy } 263bc1b8492SDan Murphy 264bc1b8492SDan Murphy return -EINVAL; 265bc1b8492SDan Murphy } 266bc1b8492SDan Murphy 267536129ccSKrzysztof Wilczynski static const int als_avrg_table[LM3532_NUM_AVG_VALS] = {17920, 35840, 71680, 268bc1b8492SDan Murphy 1433360, 286720, 573440, 269bc1b8492SDan Murphy 1146880, 2293760}; 270bc1b8492SDan Murphy static int lm3532_get_als_avg_index(int avg_time) 271bc1b8492SDan Murphy { 272bc1b8492SDan Murphy if (avg_time <= als_avrg_table[0]) 273bc1b8492SDan Murphy return 0; 274bc1b8492SDan Murphy 275bc1b8492SDan Murphy if (avg_time > als_avrg_table[LM3532_NUM_AVG_VALS - 1]) 276bc1b8492SDan Murphy return LM3532_NUM_AVG_VALS - 1; 277bc1b8492SDan Murphy 278bc1b8492SDan Murphy return lm3532_get_index(&als_avrg_table[0], LM3532_NUM_AVG_VALS, 279bc1b8492SDan Murphy avg_time); 280bc1b8492SDan Murphy } 281bc1b8492SDan Murphy 282536129ccSKrzysztof Wilczynski static const int ramp_table[LM3532_NUM_RAMP_VALS] = { 8, 1024, 2048, 4096, 8192, 283bc1b8492SDan Murphy 16384, 32768, 65536}; 284bc1b8492SDan Murphy static int lm3532_get_ramp_index(int ramp_time) 285bc1b8492SDan Murphy { 286bc1b8492SDan Murphy if (ramp_time <= ramp_table[0]) 287bc1b8492SDan Murphy return 0; 288bc1b8492SDan Murphy 289bc1b8492SDan Murphy if (ramp_time > ramp_table[LM3532_NUM_RAMP_VALS - 1]) 290bc1b8492SDan Murphy return LM3532_NUM_RAMP_VALS - 1; 291bc1b8492SDan Murphy 292bc1b8492SDan Murphy return lm3532_get_index(&ramp_table[0], LM3532_NUM_RAMP_VALS, 293bc1b8492SDan Murphy ramp_time); 294bc1b8492SDan Murphy } 295bc1b8492SDan Murphy 296070a0eedSTony Lindgren /* Caller must take care of locking */ 297bc1b8492SDan Murphy static int lm3532_led_enable(struct lm3532_led *led_data) 298bc1b8492SDan Murphy { 299bc1b8492SDan Murphy int ctrl_en_val = BIT(led_data->control_bank); 300bc1b8492SDan Murphy int ret; 301bc1b8492SDan Murphy 302070a0eedSTony Lindgren if (led_data->enabled) 303070a0eedSTony Lindgren return 0; 304070a0eedSTony Lindgren 305bc1b8492SDan Murphy ret = regmap_update_bits(led_data->priv->regmap, LM3532_REG_ENABLE, 306bc1b8492SDan Murphy ctrl_en_val, ctrl_en_val); 307bc1b8492SDan Murphy if (ret) { 308bc1b8492SDan Murphy dev_err(led_data->priv->dev, "Failed to set ctrl:%d\n", ret); 309bc1b8492SDan Murphy return ret; 310bc1b8492SDan Murphy } 311bc1b8492SDan Murphy 312070a0eedSTony Lindgren ret = regulator_enable(led_data->priv->regulator); 313070a0eedSTony Lindgren if (ret < 0) 314070a0eedSTony Lindgren return ret; 315070a0eedSTony Lindgren 316070a0eedSTony Lindgren led_data->enabled = 1; 317070a0eedSTony Lindgren 318070a0eedSTony Lindgren return 0; 319bc1b8492SDan Murphy } 320bc1b8492SDan Murphy 321070a0eedSTony Lindgren /* Caller must take care of locking */ 322bc1b8492SDan Murphy static int lm3532_led_disable(struct lm3532_led *led_data) 323bc1b8492SDan Murphy { 324bc1b8492SDan Murphy int ctrl_en_val = BIT(led_data->control_bank); 325bc1b8492SDan Murphy int ret; 326bc1b8492SDan Murphy 327070a0eedSTony Lindgren if (!led_data->enabled) 328070a0eedSTony Lindgren return 0; 329070a0eedSTony Lindgren 330bc1b8492SDan Murphy ret = regmap_update_bits(led_data->priv->regmap, LM3532_REG_ENABLE, 3316559ac32SDan Murphy ctrl_en_val, 0); 332bc1b8492SDan Murphy if (ret) { 333bc1b8492SDan Murphy dev_err(led_data->priv->dev, "Failed to set ctrl:%d\n", ret); 334bc1b8492SDan Murphy return ret; 335bc1b8492SDan Murphy } 336bc1b8492SDan Murphy 337070a0eedSTony Lindgren ret = regulator_disable(led_data->priv->regulator); 338070a0eedSTony Lindgren if (ret < 0) 339070a0eedSTony Lindgren return ret; 340070a0eedSTony Lindgren 341070a0eedSTony Lindgren led_data->enabled = 0; 342070a0eedSTony Lindgren 343070a0eedSTony Lindgren return 0; 344bc1b8492SDan Murphy } 345bc1b8492SDan Murphy 346bc1b8492SDan Murphy static int lm3532_brightness_set(struct led_classdev *led_cdev, 347bc1b8492SDan Murphy enum led_brightness brt_val) 348bc1b8492SDan Murphy { 349bc1b8492SDan Murphy struct lm3532_led *led = 350bc1b8492SDan Murphy container_of(led_cdev, struct lm3532_led, led_dev); 351bc1b8492SDan Murphy u8 brightness_reg; 352bc1b8492SDan Murphy int ret; 353bc1b8492SDan Murphy 354bc1b8492SDan Murphy mutex_lock(&led->priv->lock); 355bc1b8492SDan Murphy 3566559ac32SDan Murphy if (led->mode == LM3532_ALS_CTRL) { 357bc1b8492SDan Murphy if (brt_val > LED_OFF) 358bc1b8492SDan Murphy ret = lm3532_led_enable(led); 359bc1b8492SDan Murphy else 360bc1b8492SDan Murphy ret = lm3532_led_disable(led); 361bc1b8492SDan Murphy 362bc1b8492SDan Murphy goto unlock; 363bc1b8492SDan Murphy } 364bc1b8492SDan Murphy 365bc1b8492SDan Murphy if (brt_val == LED_OFF) { 366bc1b8492SDan Murphy ret = lm3532_led_disable(led); 367bc1b8492SDan Murphy goto unlock; 368bc1b8492SDan Murphy } 369bc1b8492SDan Murphy 370bc1b8492SDan Murphy ret = lm3532_led_enable(led); 371bc1b8492SDan Murphy if (ret) 372bc1b8492SDan Murphy goto unlock; 373bc1b8492SDan Murphy 37413123940SDan Murphy brightness_reg = LM3532_REG_ZONE_TRGT_A + led->control_bank * 5 + 37513123940SDan Murphy (led->ctrl_brt_pointer >> 2); 376bc1b8492SDan Murphy 377bc1b8492SDan Murphy ret = regmap_write(led->priv->regmap, brightness_reg, brt_val); 378bc1b8492SDan Murphy 379bc1b8492SDan Murphy unlock: 380bc1b8492SDan Murphy mutex_unlock(&led->priv->lock); 381bc1b8492SDan Murphy return ret; 382bc1b8492SDan Murphy } 383bc1b8492SDan Murphy 384bc1b8492SDan Murphy static int lm3532_init_registers(struct lm3532_led *led) 385bc1b8492SDan Murphy { 386bc1b8492SDan Murphy struct lm3532_data *drvdata = led->priv; 387bc1b8492SDan Murphy unsigned int runtime_ramp_val; 388bc1b8492SDan Murphy unsigned int output_cfg_val = 0; 389bc1b8492SDan Murphy unsigned int output_cfg_shift = 0; 390bc1b8492SDan Murphy unsigned int output_cfg_mask = 0; 39113123940SDan Murphy unsigned int brightness_config_reg; 39213123940SDan Murphy unsigned int brightness_config_val; 393517ea49aSDan Murphy int fs_current_reg; 394517ea49aSDan Murphy int fs_current_val; 395bc1b8492SDan Murphy int ret, i; 396bc1b8492SDan Murphy 39713123940SDan Murphy if (drvdata->enable_gpio) 39813123940SDan Murphy gpiod_direction_output(drvdata->enable_gpio, 1); 39913123940SDan Murphy 40013123940SDan Murphy brightness_config_reg = LM3532_REG_ZONE_CFG_A + led->control_bank * 2; 40113123940SDan Murphy /* 40213123940SDan Murphy * This could be hard coded to the default value but the control 40313123940SDan Murphy * brightness register may have changed during boot. 40413123940SDan Murphy */ 40513123940SDan Murphy ret = regmap_read(drvdata->regmap, brightness_config_reg, 40613123940SDan Murphy &led->ctrl_brt_pointer); 40713123940SDan Murphy if (ret) 40813123940SDan Murphy return ret; 40913123940SDan Murphy 41013123940SDan Murphy led->ctrl_brt_pointer &= LM3532_ZONE_MASK; 41113123940SDan Murphy brightness_config_val = led->ctrl_brt_pointer | led->mode; 41213123940SDan Murphy ret = regmap_write(drvdata->regmap, brightness_config_reg, 41313123940SDan Murphy brightness_config_val); 41413123940SDan Murphy if (ret) 41513123940SDan Murphy return ret; 41613123940SDan Murphy 417517ea49aSDan Murphy if (led->full_scale_current) { 418517ea49aSDan Murphy fs_current_reg = LM3532_REG_CTRL_A_FS_CURR + led->control_bank * 2; 419517ea49aSDan Murphy fs_current_val = (led->full_scale_current - LM3532_FS_CURR_MIN) / 420517ea49aSDan Murphy LM3532_FS_CURR_STEP; 421517ea49aSDan Murphy 422517ea49aSDan Murphy ret = regmap_write(drvdata->regmap, fs_current_reg, 423517ea49aSDan Murphy fs_current_val); 424517ea49aSDan Murphy if (ret) 425517ea49aSDan Murphy return ret; 426517ea49aSDan Murphy } 427517ea49aSDan Murphy 428bc1b8492SDan Murphy for (i = 0; i < led->num_leds; i++) { 429bc1b8492SDan Murphy output_cfg_shift = led->led_strings[i] * 2; 430bc1b8492SDan Murphy output_cfg_val |= (led->control_bank << output_cfg_shift); 431bc1b8492SDan Murphy output_cfg_mask |= LM3532_OUTPUT_CFG_MASK << output_cfg_shift; 432bc1b8492SDan Murphy } 433bc1b8492SDan Murphy 434bc1b8492SDan Murphy ret = regmap_update_bits(drvdata->regmap, LM3532_REG_OUTPUT_CFG, 435bc1b8492SDan Murphy output_cfg_mask, output_cfg_val); 436bc1b8492SDan Murphy if (ret) 437bc1b8492SDan Murphy return ret; 438bc1b8492SDan Murphy 439bc1b8492SDan Murphy runtime_ramp_val = drvdata->runtime_ramp_up | 440bc1b8492SDan Murphy (drvdata->runtime_ramp_down << LM3532_RAMP_DOWN_SHIFT); 441bc1b8492SDan Murphy 442bc1b8492SDan Murphy return regmap_write(drvdata->regmap, LM3532_REG_RT_RAMP, 443bc1b8492SDan Murphy runtime_ramp_val); 444bc1b8492SDan Murphy } 445bc1b8492SDan Murphy 446bc1b8492SDan Murphy static int lm3532_als_configure(struct lm3532_data *priv, 447bc1b8492SDan Murphy struct lm3532_led *led) 448bc1b8492SDan Murphy { 449bc1b8492SDan Murphy struct lm3532_als_data *als = priv->als_data; 450bc1b8492SDan Murphy u32 als_vmin, als_vmax, als_vstep; 451bc1b8492SDan Murphy int zone_reg = LM3532_REG_ZN_0_HI; 452bc1b8492SDan Murphy int ret; 453bc1b8492SDan Murphy int i; 454bc1b8492SDan Murphy 455bc1b8492SDan Murphy als_vmin = als->als_vmin; 456bc1b8492SDan Murphy als_vmax = als->als_vmax; 457bc1b8492SDan Murphy 458bc1b8492SDan Murphy als_vstep = (als_vmax - als_vmin) / ((LM3532_ALS_ZB_MAX + 1) * 2); 459bc1b8492SDan Murphy 460bc1b8492SDan Murphy for (i = 0; i < LM3532_ALS_ZB_MAX; i++) { 461bc1b8492SDan Murphy als->zones_lo[i] = ((als_vmin + als_vstep + (i * als_vstep)) * 462bc1b8492SDan Murphy LED_FULL) / 1000; 463bc1b8492SDan Murphy als->zones_hi[i] = ((als_vmin + LM3532_ALS_OFFSET_mV + 464bc1b8492SDan Murphy als_vstep + (i * als_vstep)) * LED_FULL) / 1000; 465bc1b8492SDan Murphy 466bc1b8492SDan Murphy zone_reg = LM3532_REG_ZN_0_HI + i * 2; 467bc1b8492SDan Murphy ret = regmap_write(priv->regmap, zone_reg, als->zones_lo[i]); 468bc1b8492SDan Murphy if (ret) 469bc1b8492SDan Murphy return ret; 470bc1b8492SDan Murphy 471bc1b8492SDan Murphy zone_reg += 1; 472bc1b8492SDan Murphy ret = regmap_write(priv->regmap, zone_reg, als->zones_hi[i]); 473bc1b8492SDan Murphy if (ret) 474bc1b8492SDan Murphy return ret; 475bc1b8492SDan Murphy } 476bc1b8492SDan Murphy 477bc1b8492SDan Murphy als->config = (als->als_avrg_time | (LM3532_ENABLE_ALS) | 478bc1b8492SDan Murphy (als->als_input_mode << LM3532_ALS_SEL_SHIFT)); 479bc1b8492SDan Murphy 48013123940SDan Murphy return regmap_write(priv->regmap, LM3532_ALS_CONFIG, als->config); 481bc1b8492SDan Murphy } 482bc1b8492SDan Murphy 483bc1b8492SDan Murphy static int lm3532_parse_als(struct lm3532_data *priv) 484bc1b8492SDan Murphy { 485bc1b8492SDan Murphy struct lm3532_als_data *als; 486bc1b8492SDan Murphy int als_avg_time; 487bc1b8492SDan Murphy int als_impedance; 488bc1b8492SDan Murphy int ret; 489bc1b8492SDan Murphy 490bc1b8492SDan Murphy als = devm_kzalloc(priv->dev, sizeof(*als), GFP_KERNEL); 491bc1b8492SDan Murphy if (als == NULL) 492bc1b8492SDan Murphy return -ENOMEM; 493bc1b8492SDan Murphy 494bc1b8492SDan Murphy ret = device_property_read_u32(&priv->client->dev, "ti,als-vmin", 495bc1b8492SDan Murphy &als->als_vmin); 496bc1b8492SDan Murphy if (ret) 497bc1b8492SDan Murphy als->als_vmin = 0; 498bc1b8492SDan Murphy 499bc1b8492SDan Murphy ret = device_property_read_u32(&priv->client->dev, "ti,als-vmax", 500bc1b8492SDan Murphy &als->als_vmax); 501bc1b8492SDan Murphy if (ret) 502bc1b8492SDan Murphy als->als_vmax = LM3532_ALS_WINDOW_mV; 503bc1b8492SDan Murphy 504bc1b8492SDan Murphy if (als->als_vmax > LM3532_ALS_WINDOW_mV) { 505bc1b8492SDan Murphy ret = -EINVAL; 506bc1b8492SDan Murphy return ret; 507bc1b8492SDan Murphy } 508bc1b8492SDan Murphy 509bc1b8492SDan Murphy ret = device_property_read_u32(&priv->client->dev, "ti,als1-imp-sel", 510bc1b8492SDan Murphy &als_impedance); 511bc1b8492SDan Murphy if (ret) 512bc1b8492SDan Murphy als->als1_imp_sel = 0; 513bc1b8492SDan Murphy else 514bc1b8492SDan Murphy als->als1_imp_sel = lm3532_get_als_imp_index(als_impedance); 515bc1b8492SDan Murphy 516bc1b8492SDan Murphy ret = device_property_read_u32(&priv->client->dev, "ti,als2-imp-sel", 517bc1b8492SDan Murphy &als_impedance); 518bc1b8492SDan Murphy if (ret) 519bc1b8492SDan Murphy als->als2_imp_sel = 0; 520bc1b8492SDan Murphy else 521bc1b8492SDan Murphy als->als2_imp_sel = lm3532_get_als_imp_index(als_impedance); 522bc1b8492SDan Murphy 523bc1b8492SDan Murphy ret = device_property_read_u32(&priv->client->dev, "ti,als-avrg-time-us", 524bc1b8492SDan Murphy &als_avg_time); 525bc1b8492SDan Murphy if (ret) 526bc1b8492SDan Murphy als->als_avrg_time = 0; 527bc1b8492SDan Murphy else 528bc1b8492SDan Murphy als->als_avrg_time = lm3532_get_als_avg_index(als_avg_time); 529bc1b8492SDan Murphy 530bc1b8492SDan Murphy ret = device_property_read_u8(&priv->client->dev, "ti,als-input-mode", 531bc1b8492SDan Murphy &als->als_input_mode); 532bc1b8492SDan Murphy if (ret) 533bc1b8492SDan Murphy als->als_input_mode = 0; 534bc1b8492SDan Murphy 535bc1b8492SDan Murphy if (als->als_input_mode > LM3532_BL_MODE_ALS) { 536bc1b8492SDan Murphy ret = -EINVAL; 537bc1b8492SDan Murphy return ret; 538bc1b8492SDan Murphy } 539bc1b8492SDan Murphy 540bc1b8492SDan Murphy priv->als_data = als; 541bc1b8492SDan Murphy 542bc1b8492SDan Murphy return ret; 543bc1b8492SDan Murphy } 544bc1b8492SDan Murphy 545bc1b8492SDan Murphy static int lm3532_parse_node(struct lm3532_data *priv) 546bc1b8492SDan Murphy { 547bc1b8492SDan Murphy struct fwnode_handle *child = NULL; 548bc1b8492SDan Murphy struct lm3532_led *led; 549bc1b8492SDan Murphy int control_bank; 550bc1b8492SDan Murphy u32 ramp_time; 551bc1b8492SDan Murphy size_t i = 0; 552bc1b8492SDan Murphy int ret; 553bc1b8492SDan Murphy 554bc1b8492SDan Murphy priv->enable_gpio = devm_gpiod_get_optional(&priv->client->dev, 555bc1b8492SDan Murphy "enable", GPIOD_OUT_LOW); 556bc1b8492SDan Murphy if (IS_ERR(priv->enable_gpio)) 557bc1b8492SDan Murphy priv->enable_gpio = NULL; 558bc1b8492SDan Murphy 559bc1b8492SDan Murphy priv->regulator = devm_regulator_get(&priv->client->dev, "vin"); 560bc1b8492SDan Murphy if (IS_ERR(priv->regulator)) 561bc1b8492SDan Murphy priv->regulator = NULL; 562bc1b8492SDan Murphy 563bc1b8492SDan Murphy ret = device_property_read_u32(&priv->client->dev, "ramp-up-us", 564bc1b8492SDan Murphy &ramp_time); 565bc1b8492SDan Murphy if (ret) 566bc1b8492SDan Murphy dev_info(&priv->client->dev, "ramp-up-ms property missing\n"); 567bc1b8492SDan Murphy else 568bc1b8492SDan Murphy priv->runtime_ramp_up = lm3532_get_ramp_index(ramp_time); 569bc1b8492SDan Murphy 570bc1b8492SDan Murphy ret = device_property_read_u32(&priv->client->dev, "ramp-down-us", 571bc1b8492SDan Murphy &ramp_time); 572bc1b8492SDan Murphy if (ret) 573bc1b8492SDan Murphy dev_info(&priv->client->dev, "ramp-down-ms property missing\n"); 574bc1b8492SDan Murphy else 575bc1b8492SDan Murphy priv->runtime_ramp_down = lm3532_get_ramp_index(ramp_time); 576bc1b8492SDan Murphy 577bc1b8492SDan Murphy device_for_each_child_node(priv->dev, child) { 578cf6eb52fSPavel struct led_init_data idata = { 579cf6eb52fSPavel .fwnode = child, 580cf6eb52fSPavel .default_label = ":", 581cf6eb52fSPavel .devicename = priv->client->name, 582cf6eb52fSPavel }; 583cf6eb52fSPavel 584bc1b8492SDan Murphy led = &priv->leds[i]; 585bc1b8492SDan Murphy 586bc1b8492SDan Murphy ret = fwnode_property_read_u32(child, "reg", &control_bank); 587bc1b8492SDan Murphy if (ret) { 588bc1b8492SDan Murphy dev_err(&priv->client->dev, "reg property missing\n"); 589bc1b8492SDan Murphy goto child_out; 590bc1b8492SDan Murphy } 591bc1b8492SDan Murphy 592bc1b8492SDan Murphy if (control_bank > LM3532_CONTROL_C) { 593bc1b8492SDan Murphy dev_err(&priv->client->dev, "Control bank invalid\n"); 594bc1b8492SDan Murphy continue; 595bc1b8492SDan Murphy } 596bc1b8492SDan Murphy 597bc1b8492SDan Murphy led->control_bank = control_bank; 598bc1b8492SDan Murphy 599bc1b8492SDan Murphy ret = fwnode_property_read_u32(child, "ti,led-mode", 600bc1b8492SDan Murphy &led->mode); 601bc1b8492SDan Murphy if (ret) { 602bc1b8492SDan Murphy dev_err(&priv->client->dev, "ti,led-mode property missing\n"); 603bc1b8492SDan Murphy goto child_out; 604bc1b8492SDan Murphy } 605bc1b8492SDan Murphy 6066d4faf3bSDan Murphy if (fwnode_property_present(child, "led-max-microamp") && 6076d4faf3bSDan Murphy fwnode_property_read_u32(child, "led-max-microamp", 6086d4faf3bSDan Murphy &led->full_scale_current)) 6096d4faf3bSDan Murphy dev_err(&priv->client->dev, 6106d4faf3bSDan Murphy "Failed getting led-max-microamp\n"); 6116d4faf3bSDan Murphy else 6126d4faf3bSDan Murphy led->full_scale_current = min(led->full_scale_current, 6136d4faf3bSDan Murphy LM3532_FS_CURR_MAX); 614517ea49aSDan Murphy 615bc1b8492SDan Murphy if (led->mode == LM3532_BL_MODE_ALS) { 6166559ac32SDan Murphy led->mode = LM3532_ALS_CTRL; 617bc1b8492SDan Murphy ret = lm3532_parse_als(priv); 618bc1b8492SDan Murphy if (ret) 619bc1b8492SDan Murphy dev_err(&priv->client->dev, "Failed to parse als\n"); 620bc1b8492SDan Murphy else 621bc1b8492SDan Murphy lm3532_als_configure(priv, led); 6226559ac32SDan Murphy } else { 6236559ac32SDan Murphy led->mode = LM3532_I2C_CTRL; 624bc1b8492SDan Murphy } 625bc1b8492SDan Murphy 626cc93c863SAndy Shevchenko led->num_leds = fwnode_property_count_u32(child, "led-sources"); 627bc1b8492SDan Murphy if (led->num_leds > LM3532_MAX_LED_STRINGS) { 62831e065c4SPavel dev_err(&priv->client->dev, "Too many LED string defined\n"); 629bc1b8492SDan Murphy continue; 630bc1b8492SDan Murphy } 631bc1b8492SDan Murphy 632bc1b8492SDan Murphy ret = fwnode_property_read_u32_array(child, "led-sources", 633bc1b8492SDan Murphy led->led_strings, 634bc1b8492SDan Murphy led->num_leds); 635bc1b8492SDan Murphy if (ret) { 636bc1b8492SDan Murphy dev_err(&priv->client->dev, "led-sources property missing\n"); 637bc1b8492SDan Murphy goto child_out; 638bc1b8492SDan Murphy } 639bc1b8492SDan Murphy 640bc1b8492SDan Murphy led->priv = priv; 641bc1b8492SDan Murphy led->led_dev.brightness_set_blocking = lm3532_brightness_set; 642bc1b8492SDan Murphy 643cf6eb52fSPavel ret = devm_led_classdev_register_ext(priv->dev, &led->led_dev, &idata); 644bc1b8492SDan Murphy if (ret) { 645bc1b8492SDan Murphy dev_err(&priv->client->dev, "led register err: %d\n", 646bc1b8492SDan Murphy ret); 647bc1b8492SDan Murphy goto child_out; 648bc1b8492SDan Murphy } 649bc1b8492SDan Murphy 6506559ac32SDan Murphy ret = lm3532_init_registers(led); 6516559ac32SDan Murphy if (ret) { 6526559ac32SDan Murphy dev_err(&priv->client->dev, "register init err: %d\n", 6536559ac32SDan Murphy ret); 6546559ac32SDan Murphy goto child_out; 6556559ac32SDan Murphy } 656bc1b8492SDan Murphy 657bc1b8492SDan Murphy i++; 658bc1b8492SDan Murphy } 6592f39f68cSAndy Shevchenko return 0; 660bc1b8492SDan Murphy 661bc1b8492SDan Murphy child_out: 6622f39f68cSAndy Shevchenko fwnode_handle_put(child); 663bc1b8492SDan Murphy return ret; 664bc1b8492SDan Murphy } 665bc1b8492SDan Murphy 666*912bcc8aSUwe Kleine-König static int lm3532_probe(struct i2c_client *client) 667bc1b8492SDan Murphy { 668bc1b8492SDan Murphy struct lm3532_data *drvdata; 669bc1b8492SDan Murphy int ret = 0; 670bc1b8492SDan Murphy int count; 671bc1b8492SDan Murphy 672bc1b8492SDan Murphy count = device_get_child_node_count(&client->dev); 673bc1b8492SDan Murphy if (!count) { 674bc1b8492SDan Murphy dev_err(&client->dev, "LEDs are not defined in device tree!"); 675bc1b8492SDan Murphy return -ENODEV; 676bc1b8492SDan Murphy } 677bc1b8492SDan Murphy 678bc1b8492SDan Murphy drvdata = devm_kzalloc(&client->dev, struct_size(drvdata, leds, count), 679bc1b8492SDan Murphy GFP_KERNEL); 680bc1b8492SDan Murphy if (drvdata == NULL) 681bc1b8492SDan Murphy return -ENOMEM; 682bc1b8492SDan Murphy 683bc1b8492SDan Murphy drvdata->client = client; 684bc1b8492SDan Murphy drvdata->dev = &client->dev; 685bc1b8492SDan Murphy 686bc1b8492SDan Murphy drvdata->regmap = devm_regmap_init_i2c(client, &lm3532_regmap_config); 687bc1b8492SDan Murphy if (IS_ERR(drvdata->regmap)) { 688bc1b8492SDan Murphy ret = PTR_ERR(drvdata->regmap); 689bc1b8492SDan Murphy dev_err(&client->dev, "Failed to allocate register map: %d\n", 690bc1b8492SDan Murphy ret); 691bc1b8492SDan Murphy return ret; 692bc1b8492SDan Murphy } 693bc1b8492SDan Murphy 694bc1b8492SDan Murphy mutex_init(&drvdata->lock); 695bc1b8492SDan Murphy i2c_set_clientdata(client, drvdata); 696bc1b8492SDan Murphy 697bc1b8492SDan Murphy ret = lm3532_parse_node(drvdata); 698bc1b8492SDan Murphy if (ret) { 699bc1b8492SDan Murphy dev_err(&client->dev, "Failed to parse node\n"); 700bc1b8492SDan Murphy return ret; 701bc1b8492SDan Murphy } 702bc1b8492SDan Murphy 703bc1b8492SDan Murphy return ret; 704bc1b8492SDan Murphy } 705bc1b8492SDan Murphy 706ed5c2f5fSUwe Kleine-König static void lm3532_remove(struct i2c_client *client) 707bc1b8492SDan Murphy { 708bc1b8492SDan Murphy struct lm3532_data *drvdata = i2c_get_clientdata(client); 709bc1b8492SDan Murphy 710bc1b8492SDan Murphy mutex_destroy(&drvdata->lock); 711bc1b8492SDan Murphy 712bc1b8492SDan Murphy if (drvdata->enable_gpio) 713bc1b8492SDan Murphy gpiod_direction_output(drvdata->enable_gpio, 0); 714bc1b8492SDan Murphy } 715bc1b8492SDan Murphy 716bc1b8492SDan Murphy static const struct of_device_id of_lm3532_leds_match[] = { 717bc1b8492SDan Murphy { .compatible = "ti,lm3532", }, 718bc1b8492SDan Murphy {}, 719bc1b8492SDan Murphy }; 720bc1b8492SDan Murphy MODULE_DEVICE_TABLE(of, of_lm3532_leds_match); 721bc1b8492SDan Murphy 722bc1b8492SDan Murphy static const struct i2c_device_id lm3532_id[] = { 723bc1b8492SDan Murphy {LM3532_NAME, 0}, 724bc1b8492SDan Murphy {} 725bc1b8492SDan Murphy }; 726bc1b8492SDan Murphy MODULE_DEVICE_TABLE(i2c, lm3532_id); 727bc1b8492SDan Murphy 728bc1b8492SDan Murphy static struct i2c_driver lm3532_i2c_driver = { 729*912bcc8aSUwe Kleine-König .probe_new = lm3532_probe, 730bc1b8492SDan Murphy .remove = lm3532_remove, 731bc1b8492SDan Murphy .id_table = lm3532_id, 732bc1b8492SDan Murphy .driver = { 733bc1b8492SDan Murphy .name = LM3532_NAME, 734bc1b8492SDan Murphy .of_match_table = of_lm3532_leds_match, 735bc1b8492SDan Murphy }, 736bc1b8492SDan Murphy }; 737bc1b8492SDan Murphy module_i2c_driver(lm3532_i2c_driver); 738bc1b8492SDan Murphy 739bc1b8492SDan Murphy MODULE_DESCRIPTION("Back Light driver for LM3532"); 740bc1b8492SDan Murphy MODULE_LICENSE("GPL v2"); 741bc1b8492SDan Murphy MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>"); 742