Doug Zobel | f9dc67d | 2023-11-17 12:38:11 +0100 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * Copyright (C) 2018 Doug Zobel <douglas.zobel@climate.com> |
| 4 | * |
| 5 | * Driver for TI lp5562 4 channel LED driver. There are only 3 |
| 6 | * engines available for the 4 LEDs, so white and blue LEDs share |
| 7 | * the same engine. This means that the blink period is shared |
| 8 | * between them. Changing the period of blue blink will affect |
| 9 | * the white period (and vice-versa). Blue and white On/Off |
| 10 | * states remain independent (as would PWM brightness if that's |
| 11 | * ever added to the LED core). |
| 12 | */ |
| 13 | |
| 14 | #include <dm.h> |
| 15 | #include <errno.h> |
| 16 | #include <led.h> |
| 17 | #include <i2c.h> |
| 18 | #include <asm/gpio.h> |
| 19 | #include <linux/delay.h> |
| 20 | |
| 21 | #define DEFAULT_CURRENT 100 /* 10 mA */ |
| 22 | #define MIN_BLINK_PERIOD 32 /* ms */ |
| 23 | #define MAX_BLINK_PERIOD 2248 /* ms */ |
| 24 | |
| 25 | /* Register Map */ |
| 26 | #define REG_ENABLE 0x00 |
| 27 | #define REG_OP_MODE 0x01 |
| 28 | #define REG_B_PWM 0x02 |
| 29 | #define REG_G_PWM 0x03 |
| 30 | #define REG_R_PWM 0x04 |
| 31 | #define REG_B_CUR 0x05 |
| 32 | #define REG_G_CUR 0x06 |
| 33 | #define REG_R_CUR 0x07 |
| 34 | #define REG_CONFIG 0x08 |
| 35 | #define REG_ENG1_PC 0x09 |
| 36 | #define REG_ENG2_PC 0x0A |
| 37 | #define REG_ENG3_PC 0x0B |
| 38 | #define REG_STATUS 0x0C |
| 39 | #define REG_RESET 0x0D |
| 40 | #define REG_W_PWM 0x0E |
| 41 | #define REG_W_CUR 0x0F |
| 42 | #define REG_ENG1_MEM_BEGIN 0x10 |
| 43 | #define REG_ENG2_MEM_BEGIN 0x30 |
| 44 | #define REG_ENG3_MEM_BEGIN 0x50 |
| 45 | #define REG_LED_MAP 0x70 |
| 46 | |
| 47 | /* LED Register Values */ |
| 48 | /* 0x00 ENABLE */ |
| 49 | #define REG_ENABLE_CHIP_ENABLE (0x1 << 6) |
| 50 | #define REG_ENABLE_ENG_EXEC_HOLD 0x0 |
| 51 | #define REG_ENABLE_ENG_EXEC_RUN 0x2 |
| 52 | #define REG_ENABLE_ENG_EXEC_MASK 0x3 |
| 53 | |
| 54 | /* 0x01 OP MODE */ |
| 55 | #define REG_OP_MODE_DISABLED 0x0 |
| 56 | #define REG_OP_MODE_LOAD_SRAM 0x1 |
| 57 | #define REG_OP_MODE_RUN 0x2 |
| 58 | #define REG_OP_MODE_MASK 0x3 |
| 59 | |
| 60 | /* 0x02, 0x03, 0x04, 0x0E PWM */ |
| 61 | #define REG_PWM_MIN_VALUE 0 |
| 62 | #define REG_PWM_MAX_VALUE 0xFF |
| 63 | |
| 64 | /* 0x08 CONFIG */ |
| 65 | #define REG_CONFIG_EXT_CLK 0x0 |
| 66 | #define REG_CONFIG_INT_CLK 0x1 |
| 67 | #define REG_CONFIG_AUTO_CLK 0x2 |
| 68 | #define REG_CONFIG_CLK_MASK 0x3 |
| 69 | |
| 70 | /* 0x0D RESET */ |
| 71 | #define REG_RESET_RESET 0xFF |
| 72 | |
| 73 | /* 0x70 LED MAP */ |
| 74 | #define REG_LED_MAP_ENG_MASK 0x03 |
| 75 | #define REG_LED_MAP_W_ENG_SHIFT 6 |
| 76 | #define REG_LED_MAP_R_ENG_SHIFT 4 |
| 77 | #define REG_LED_MAP_G_ENG_SHIFT 2 |
| 78 | #define REG_LED_MAP_B_ENG_SHIFT 0 |
| 79 | |
| 80 | /* Engine program related */ |
| 81 | #define REG_ENGINE_MEM_SIZE 0x20 |
| 82 | #define LED_PGRM_RAMP_INCREMENT_SHIFT 0 |
| 83 | #define LED_PGRM_RAMP_SIGN_SHIFT 7 |
| 84 | #define LED_PGRM_RAMP_STEP_SHIFT 8 |
| 85 | #define LED_PGRM_RAMP_PRESCALE_SHIFT 14 |
| 86 | |
| 87 | struct lp5562_led_wrap_priv { |
| 88 | struct gpio_desc enable_gpio; |
| 89 | }; |
| 90 | |
| 91 | struct lp5562_led_priv { |
| 92 | u8 reg_pwm; |
| 93 | u8 reg_current; |
| 94 | u8 map_shift; |
| 95 | u8 enginenum; |
| 96 | }; |
| 97 | |
| 98 | /* enum values map to LED_MAP (0x70) values */ |
| 99 | enum lp5562_led_ctl_mode { |
| 100 | I2C = 0x0, |
| 101 | #ifdef CONFIG_LED_BLINK |
| 102 | ENGINE1 = 0x1, |
| 103 | ENGINE2 = 0x2, |
| 104 | ENGINE3 = 0x3 |
| 105 | #endif |
| 106 | }; |
| 107 | |
| 108 | /* |
| 109 | * Update a register value |
| 110 | * dev - I2C udevice (parent of led) |
| 111 | * regnum - register number to update |
| 112 | * value - value to write to register |
| 113 | * mask - mask of bits that should be changed |
| 114 | */ |
| 115 | static int lp5562_led_reg_update(struct udevice *dev, int regnum, |
| 116 | u8 value, u8 mask) |
| 117 | { |
| 118 | int ret; |
| 119 | |
| 120 | if (mask == 0xFF) |
| 121 | ret = dm_i2c_reg_write(dev, regnum, value); |
| 122 | else |
| 123 | ret = dm_i2c_reg_clrset(dev, regnum, mask, value); |
| 124 | |
| 125 | |
| 126 | /* |
| 127 | * Data sheet says "Delay between consecutive I2C writes to |
| 128 | * ENABLE register (00h) need to be longer than 488 μs |
| 129 | * (typical)." and "Delay between consecutive I2C writes to |
| 130 | * OP_MODE register need to be longer than 153 μs (typ)." |
| 131 | * |
| 132 | * The linux driver does usleep_range(500, 600) and |
| 133 | * usleep_range(200, 300), respectively. |
| 134 | */ |
| 135 | switch (regnum) { |
| 136 | case REG_ENABLE: |
| 137 | udelay(600); |
| 138 | break; |
| 139 | case REG_OP_MODE: |
| 140 | udelay(300); |
| 141 | break; |
| 142 | } |
| 143 | |
| 144 | return ret; |
| 145 | } |
| 146 | |
| 147 | #ifdef CONFIG_LED_BLINK |
| 148 | /* |
| 149 | * Program the lp5562 engine |
| 150 | * dev - I2C udevice (parent of led) |
| 151 | * program - array of commands |
| 152 | * size - number of commands in program array (1-16) |
| 153 | * engine - engine number (1-3) |
| 154 | */ |
| 155 | static int lp5562_led_program_engine(struct udevice *dev, u16 *program, |
| 156 | u8 size, u8 engine) |
| 157 | { |
| 158 | int ret, cmd; |
| 159 | u8 engine_reg = REG_ENG1_MEM_BEGIN + |
| 160 | ((engine - 1) * REG_ENGINE_MEM_SIZE); |
| 161 | u8 shift = (3 - engine) * 2; |
| 162 | __be16 prog_be[16]; |
| 163 | |
| 164 | if (size < 1 || size > 16 || engine < 1 || engine > 3) |
| 165 | return -EINVAL; |
| 166 | |
| 167 | for (cmd = 0; cmd < size; cmd++) |
| 168 | prog_be[cmd] = cpu_to_be16(program[cmd]); |
| 169 | |
| 170 | /* set engine mode to 'disabled' */ |
| 171 | ret = lp5562_led_reg_update(dev, REG_OP_MODE, |
| 172 | REG_OP_MODE_DISABLED << shift, |
| 173 | REG_OP_MODE_MASK << shift); |
| 174 | if (ret != 0) |
| 175 | goto done; |
| 176 | |
| 177 | /* set exec mode to 'hold' */ |
| 178 | ret = lp5562_led_reg_update(dev, REG_ENABLE, |
| 179 | REG_ENABLE_ENG_EXEC_HOLD << shift, |
| 180 | REG_ENABLE_ENG_EXEC_MASK << shift); |
| 181 | if (ret != 0) |
| 182 | goto done; |
| 183 | |
| 184 | /* set engine mode to 'load SRAM' */ |
| 185 | ret = lp5562_led_reg_update(dev, REG_OP_MODE, |
| 186 | REG_OP_MODE_LOAD_SRAM << shift, |
| 187 | REG_OP_MODE_MASK << shift); |
| 188 | if (ret != 0) |
| 189 | goto done; |
| 190 | |
| 191 | /* send the re-ordered program sequence */ |
| 192 | ret = dm_i2c_write(dev, engine_reg, (uchar *)prog_be, sizeof(u16) * size); |
| 193 | if (ret != 0) |
| 194 | goto done; |
| 195 | |
| 196 | /* set engine mode to 'run' */ |
| 197 | ret = lp5562_led_reg_update(dev, REG_OP_MODE, |
| 198 | REG_OP_MODE_RUN << shift, |
| 199 | REG_OP_MODE_MASK << shift); |
| 200 | if (ret != 0) |
| 201 | goto done; |
| 202 | |
| 203 | /* set engine exec to 'run' */ |
| 204 | ret = lp5562_led_reg_update(dev, REG_ENABLE, |
| 205 | REG_ENABLE_ENG_EXEC_RUN << shift, |
| 206 | REG_ENABLE_ENG_EXEC_MASK << shift); |
| 207 | |
| 208 | done: |
| 209 | return ret; |
| 210 | } |
| 211 | |
| 212 | /* |
| 213 | * Get the LED's current control mode (I2C or ENGINE[1-3]) |
| 214 | * dev - led udevice (child udevice) |
| 215 | */ |
| 216 | static enum lp5562_led_ctl_mode lp5562_led_get_control_mode(struct udevice *dev) |
| 217 | { |
| 218 | struct lp5562_led_priv *priv = dev_get_priv(dev); |
| 219 | u8 data; |
| 220 | enum lp5562_led_ctl_mode mode = I2C; |
| 221 | |
| 222 | if (dm_i2c_read(dev_get_parent(dev), REG_LED_MAP, &data, 1) == 0) |
| 223 | mode = (data & (REG_LED_MAP_ENG_MASK << priv->map_shift)) |
| 224 | >> priv->map_shift; |
| 225 | |
| 226 | return mode; |
| 227 | } |
| 228 | #endif |
| 229 | |
| 230 | /* |
| 231 | * Set the LED's control mode to I2C or ENGINE[1-3] |
| 232 | * dev - led udevice (child udevice) |
| 233 | * mode - mode to change to |
| 234 | */ |
| 235 | static int lp5562_led_set_control_mode(struct udevice *dev, |
| 236 | enum lp5562_led_ctl_mode mode) |
| 237 | { |
| 238 | struct lp5562_led_priv *priv = dev_get_priv(dev); |
| 239 | |
| 240 | return (lp5562_led_reg_update(dev_get_parent(dev), REG_LED_MAP, |
| 241 | mode << priv->map_shift, |
| 242 | REG_LED_MAP_ENG_MASK << priv->map_shift)); |
| 243 | } |
| 244 | |
| 245 | /* |
| 246 | * Return the LED's PWM value; If LED is in BLINK state, then it is |
| 247 | * under engine control mode which doesn't use this PWM value. |
| 248 | * dev - led udevice (child udevice) |
| 249 | */ |
| 250 | static int lp5562_led_get_pwm(struct udevice *dev) |
| 251 | { |
| 252 | struct lp5562_led_priv *priv = dev_get_priv(dev); |
| 253 | u8 data; |
| 254 | |
| 255 | if (dm_i2c_read(dev_get_parent(dev), priv->reg_pwm, &data, 1) != 0) |
| 256 | return -EINVAL; |
| 257 | |
| 258 | return data; |
| 259 | } |
| 260 | |
| 261 | /* |
| 262 | * Set the LED's PWM value and configure it to use this (I2C mode). |
| 263 | * dev - led udevice (child udevice) |
| 264 | * value - PWM value (0 - 255) |
| 265 | */ |
| 266 | static int lp5562_led_set_pwm(struct udevice *dev, u8 value) |
| 267 | { |
| 268 | struct lp5562_led_priv *priv = dev_get_priv(dev); |
| 269 | |
| 270 | if (lp5562_led_reg_update(dev_get_parent(dev), priv->reg_pwm, |
| 271 | value, 0xff) != 0) |
| 272 | return -EINVAL; |
| 273 | |
| 274 | /* set LED to I2C register mode */ |
| 275 | return lp5562_led_set_control_mode(dev, I2C); |
| 276 | } |
| 277 | |
| 278 | /* |
| 279 | * Return the led's current state |
| 280 | * dev - led udevice (child udevice) |
| 281 | * |
| 282 | */ |
| 283 | static enum led_state_t lp5562_led_get_state(struct udevice *dev) |
| 284 | { |
| 285 | enum led_state_t state = LEDST_ON; |
| 286 | |
| 287 | if (lp5562_led_get_pwm(dev) == REG_PWM_MIN_VALUE) |
| 288 | state = LEDST_OFF; |
| 289 | |
| 290 | #ifdef CONFIG_LED_BLINK |
| 291 | if (lp5562_led_get_control_mode(dev) != I2C) |
| 292 | state = LEDST_BLINK; |
| 293 | #endif |
| 294 | |
| 295 | return state; |
| 296 | } |
| 297 | |
| 298 | /* |
| 299 | * Set the led state |
| 300 | * dev - led udevice (child udevice) |
| 301 | * state - State to set the LED to |
| 302 | */ |
| 303 | static int lp5562_led_set_state(struct udevice *dev, enum led_state_t state) |
| 304 | { |
| 305 | #ifdef CONFIG_LED_BLINK |
| 306 | struct lp5562_led_priv *priv = dev_get_priv(dev); |
| 307 | #endif |
| 308 | |
| 309 | switch (state) { |
| 310 | case LEDST_OFF: |
| 311 | return lp5562_led_set_pwm(dev, REG_PWM_MIN_VALUE); |
| 312 | case LEDST_ON: |
| 313 | return lp5562_led_set_pwm(dev, REG_PWM_MAX_VALUE); |
| 314 | #ifdef CONFIG_LED_BLINK |
| 315 | case LEDST_BLINK: |
| 316 | return lp5562_led_set_control_mode(dev, priv->enginenum); |
| 317 | #endif |
| 318 | case LEDST_TOGGLE: |
| 319 | if (lp5562_led_get_state(dev) == LEDST_OFF) |
| 320 | return lp5562_led_set_state(dev, LEDST_ON); |
| 321 | else |
| 322 | return lp5562_led_set_state(dev, LEDST_OFF); |
| 323 | break; |
| 324 | default: |
| 325 | return -EINVAL; |
| 326 | } |
| 327 | |
| 328 | return 0; |
| 329 | } |
| 330 | |
| 331 | #ifdef CONFIG_LED_BLINK |
| 332 | /* |
| 333 | * Set the blink period of an LED; note blue and white share the same |
| 334 | * engine so changing the period of one affects the other. |
| 335 | * dev - led udevice (child udevice) |
| 336 | * period_ms - blink period in ms |
| 337 | */ |
| 338 | static int lp5562_led_set_period(struct udevice *dev, int period_ms) |
| 339 | { |
| 340 | struct lp5562_led_priv *priv = dev_get_priv(dev); |
| 341 | u8 opcode = 0; |
| 342 | u16 program[7]; |
| 343 | u16 wait_time; |
| 344 | |
| 345 | /* Blink is implemented as an engine program. Simple on/off |
| 346 | * for short periods, or fade in/fade out for longer periods: |
| 347 | * |
| 348 | * if (period_ms < 500): |
| 349 | * set PWM to 100% |
| 350 | * pause for period / 2 |
| 351 | * set PWM to 0% |
| 352 | * pause for period / 2 |
| 353 | * goto start |
| 354 | * |
| 355 | * else |
| 356 | * raise PWM 0% -> 50% in 62.7 ms |
| 357 | * raise PWM 50% -> 100% in 62.7 ms |
| 358 | * pause for (period - 4 * 62.7) / 2 |
| 359 | * lower PWM 100% -> 50% in 62.7 ms |
| 360 | * lower PWM 50% -> 0% in 62.7 ms |
| 361 | * pause for (period - 4 * 62.7) / 2 |
| 362 | * goto start |
| 363 | */ |
| 364 | |
| 365 | if (period_ms < MIN_BLINK_PERIOD) |
| 366 | period_ms = MIN_BLINK_PERIOD; |
| 367 | else if (period_ms > MAX_BLINK_PERIOD) |
| 368 | period_ms = MAX_BLINK_PERIOD; |
| 369 | |
| 370 | if (period_ms < 500) { |
| 371 | /* Simple on/off blink */ |
| 372 | wait_time = period_ms / 2; |
| 373 | |
| 374 | /* 1st command is full brightness */ |
| 375 | program[opcode++] = |
| 376 | (1 << LED_PGRM_RAMP_PRESCALE_SHIFT) | |
| 377 | REG_PWM_MAX_VALUE; |
| 378 | |
| 379 | /* 2nd command is wait (period / 2) using 15.6ms steps */ |
| 380 | program[opcode++] = |
| 381 | (1 << LED_PGRM_RAMP_PRESCALE_SHIFT) | |
| 382 | (((wait_time * 10) / 156) << LED_PGRM_RAMP_STEP_SHIFT) | |
| 383 | (0 << LED_PGRM_RAMP_INCREMENT_SHIFT); |
| 384 | |
| 385 | /* 3rd command is 0% brightness */ |
| 386 | program[opcode++] = |
| 387 | (1 << LED_PGRM_RAMP_PRESCALE_SHIFT); |
| 388 | |
| 389 | /* 4th command is wait (period / 2) using 15.6ms steps */ |
| 390 | program[opcode++] = |
| 391 | (1 << LED_PGRM_RAMP_PRESCALE_SHIFT) | |
| 392 | (((wait_time * 10) / 156) << LED_PGRM_RAMP_STEP_SHIFT) | |
| 393 | (0 << LED_PGRM_RAMP_INCREMENT_SHIFT); |
| 394 | |
| 395 | /* 5th command: repeat */ |
| 396 | program[opcode++] = 0x00; |
| 397 | } else { |
| 398 | /* fade-in / fade-out blink */ |
| 399 | wait_time = ((period_ms - 251) / 2); |
| 400 | |
| 401 | /* ramp up time is 256 * 0.49ms (125.4ms) done in 2 steps */ |
| 402 | /* 1st command is ramp up 1/2 way */ |
| 403 | program[opcode++] = |
| 404 | (0 << LED_PGRM_RAMP_PRESCALE_SHIFT) | |
| 405 | (1 << LED_PGRM_RAMP_STEP_SHIFT) | |
| 406 | (127 << LED_PGRM_RAMP_INCREMENT_SHIFT); |
| 407 | |
| 408 | /* 2nd command is ramp up rest of the way */ |
| 409 | program[opcode++] = |
| 410 | (0 << LED_PGRM_RAMP_PRESCALE_SHIFT) | |
| 411 | (1 << LED_PGRM_RAMP_STEP_SHIFT) | |
| 412 | (127 << LED_PGRM_RAMP_INCREMENT_SHIFT); |
| 413 | |
| 414 | /* 3rd: wait ((period - 2 * ramp_time) / 2) (15.6ms steps) */ |
| 415 | program[opcode++] = |
| 416 | (1 << LED_PGRM_RAMP_PRESCALE_SHIFT) | |
| 417 | (((wait_time * 10) / 156) << LED_PGRM_RAMP_STEP_SHIFT) | |
| 418 | (0 << LED_PGRM_RAMP_INCREMENT_SHIFT); |
| 419 | |
| 420 | /* ramp down is same as ramp up with sign bit set */ |
| 421 | /* 4th command is ramp down 1/2 way */ |
| 422 | program[opcode++] = |
| 423 | (0 << LED_PGRM_RAMP_PRESCALE_SHIFT) | |
| 424 | (1 << LED_PGRM_RAMP_STEP_SHIFT) | |
| 425 | (1 << LED_PGRM_RAMP_SIGN_SHIFT) | |
| 426 | (127 << LED_PGRM_RAMP_INCREMENT_SHIFT); |
| 427 | |
| 428 | /* 5th command is ramp down rest of the way */ |
| 429 | program[opcode++] = |
| 430 | (0 << LED_PGRM_RAMP_PRESCALE_SHIFT) | |
| 431 | (1 << LED_PGRM_RAMP_STEP_SHIFT) | |
| 432 | (1 << LED_PGRM_RAMP_SIGN_SHIFT) | |
| 433 | (127 << LED_PGRM_RAMP_INCREMENT_SHIFT); |
| 434 | |
| 435 | /* 6th: wait ((period - 2 * ramp_time) / 2) (15.6ms steps) */ |
| 436 | program[opcode++] = |
| 437 | (1 << LED_PGRM_RAMP_PRESCALE_SHIFT) | |
| 438 | (((wait_time * 10) / 156) << LED_PGRM_RAMP_STEP_SHIFT) | |
| 439 | (0 << LED_PGRM_RAMP_INCREMENT_SHIFT); |
| 440 | |
| 441 | /* 7th command: repeat */ |
| 442 | program[opcode++] = 0x00; |
| 443 | } |
| 444 | |
| 445 | return lp5562_led_program_engine(dev_get_parent(dev), program, |
| 446 | opcode, priv->enginenum); |
| 447 | } |
| 448 | #endif |
| 449 | |
| 450 | static const struct led_ops lp5562_led_ops = { |
| 451 | .get_state = lp5562_led_get_state, |
| 452 | .set_state = lp5562_led_set_state, |
| 453 | #ifdef CONFIG_LED_BLINK |
| 454 | .set_period = lp5562_led_set_period, |
| 455 | #endif |
| 456 | }; |
| 457 | |
| 458 | static int lp5562_led_probe(struct udevice *dev) |
| 459 | { |
| 460 | struct lp5562_led_priv *priv = dev_get_priv(dev); |
| 461 | u8 current; |
| 462 | int ret = 0; |
| 463 | |
| 464 | /* Child LED nodes */ |
| 465 | switch (dev_read_addr(dev)) { |
| 466 | case 0: |
| 467 | priv->reg_current = REG_R_CUR; |
| 468 | priv->reg_pwm = REG_R_PWM; |
| 469 | priv->map_shift = REG_LED_MAP_R_ENG_SHIFT; |
| 470 | priv->enginenum = 1; |
| 471 | break; |
| 472 | case 1: |
| 473 | priv->reg_current = REG_G_CUR; |
| 474 | priv->reg_pwm = REG_G_PWM; |
| 475 | priv->map_shift = REG_LED_MAP_G_ENG_SHIFT; |
| 476 | priv->enginenum = 2; |
| 477 | break; |
| 478 | case 2: |
| 479 | priv->reg_current = REG_B_CUR; |
| 480 | priv->reg_pwm = REG_B_PWM; |
| 481 | priv->map_shift = REG_LED_MAP_B_ENG_SHIFT; |
| 482 | priv->enginenum = 3; /* shared with white */ |
| 483 | break; |
| 484 | case 3: |
| 485 | priv->reg_current = REG_W_CUR; |
| 486 | priv->map_shift = REG_LED_MAP_W_ENG_SHIFT; |
| 487 | priv->enginenum = 3; /* shared with blue */ |
| 488 | break; |
| 489 | default: |
| 490 | return -EINVAL; |
| 491 | } |
| 492 | |
| 493 | current = dev_read_u8_default(dev, "max-cur", DEFAULT_CURRENT); |
| 494 | |
| 495 | ret = lp5562_led_reg_update(dev_get_parent(dev), priv->reg_current, |
| 496 | current, 0xff); |
| 497 | |
| 498 | return ret; |
| 499 | } |
| 500 | |
| 501 | static int lp5562_led_bind(struct udevice *dev) |
| 502 | { |
| 503 | struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev); |
| 504 | |
| 505 | /* |
| 506 | * For the child nodes, parse a "chan-name" property, since |
| 507 | * the DT bindings for this device use that instead of |
| 508 | * "label". |
| 509 | */ |
| 510 | uc_plat->label = dev_read_string(dev, "chan-name"); |
| 511 | |
| 512 | return 0; |
| 513 | } |
| 514 | |
| 515 | U_BOOT_DRIVER(lp5562_led) = { |
| 516 | .name = "lp5562-led", |
| 517 | .id = UCLASS_LED, |
| 518 | .bind = lp5562_led_bind, |
| 519 | .probe = lp5562_led_probe, |
| 520 | .priv_auto = sizeof(struct lp5562_led_priv), |
| 521 | .ops = &lp5562_led_ops, |
| 522 | }; |
| 523 | |
| 524 | |
| 525 | static int lp5562_led_wrap_probe(struct udevice *dev) |
| 526 | { |
| 527 | struct lp5562_led_wrap_priv *priv = dev_get_priv(dev); |
| 528 | u8 clock_mode; |
| 529 | int ret; |
| 530 | |
| 531 | /* Enable gpio if needed */ |
| 532 | if (gpio_request_by_name(dev, "enabled-gpios", 0, |
| 533 | &priv->enable_gpio, GPIOD_IS_OUT) == 0) { |
| 534 | dm_gpio_set_value(&priv->enable_gpio, 1); |
| 535 | udelay(1000); |
| 536 | } |
| 537 | |
| 538 | /* Ensure all registers have default values. */ |
| 539 | ret = lp5562_led_reg_update(dev, REG_RESET, REG_RESET_RESET, 0xff); |
| 540 | if (ret) |
| 541 | return ret; |
| 542 | udelay(10000); |
| 543 | |
| 544 | /* Enable the chip */ |
| 545 | ret = lp5562_led_reg_update(dev, REG_ENABLE, REG_ENABLE_CHIP_ENABLE, 0xff); |
| 546 | if (ret) |
| 547 | return ret; |
| 548 | |
| 549 | /* |
| 550 | * The DT bindings say 0=auto, 1=internal, 2=external, while |
| 551 | * the register[0:1] values are 0=external, 1=internal, |
| 552 | * 2=auto. |
| 553 | */ |
| 554 | clock_mode = dev_read_u8_default(dev, "clock-mode", 0); |
| 555 | ret = lp5562_led_reg_update(dev, REG_CONFIG, 2 - clock_mode, REG_CONFIG_CLK_MASK); |
| 556 | |
| 557 | return ret; |
| 558 | } |
| 559 | |
| 560 | static int lp5562_led_wrap_bind(struct udevice *dev) |
| 561 | { |
| 562 | return led_bind_generic(dev, "lp5562-led"); |
| 563 | } |
| 564 | |
| 565 | static const struct udevice_id lp5562_led_ids[] = { |
| 566 | { .compatible = "ti,lp5562" }, |
| 567 | { /* sentinel */ } |
| 568 | }; |
| 569 | |
| 570 | U_BOOT_DRIVER(lp5562_led_wrap) = { |
| 571 | .name = "lp5562-led-wrap", |
| 572 | .id = UCLASS_NOP, |
| 573 | .of_match = lp5562_led_ids, |
| 574 | .bind = lp5562_led_wrap_bind, |
| 575 | .probe = lp5562_led_wrap_probe, |
| 576 | .priv_auto = sizeof(struct lp5562_led_wrap_priv), |
| 577 | }; |