1 // SPDX-License-Identifier: GPL-2.0
3 * Driver for LEDs connected to the Intel Cherry Trail Whiskey Cove PMIC
5 * Copyright 2019 Yauhen Kharuzhy <jekhor@gmail.com>
6 * Copyright 2023 Hans de Goede <hansg@kernel.org>
8 * Register info comes from the Lenovo Yoga Book Android opensource code
9 * available from Lenovo. File lenovo_yb1_x90f_l_osc_201803.7z path in the 7z:
10 * YB1_source_code/kernel/cht/drivers/misc/charger_gp_led.c
13 #include <linux/kernel.h>
14 #include <linux/leds.h>
15 #include <linux/mfd/intel_soc_pmic.h>
16 #include <linux/module.h>
17 #include <linux/mod_devicetable.h>
18 #include <linux/platform_device.h>
19 #include <linux/regmap.h>
20 #include <linux/suspend.h>
22 #define CHT_WC_LED1_CTRL 0x5e1f
23 #define CHT_WC_LED1_FSM 0x5e20
24 #define CHT_WC_LED1_PWM 0x5e21
26 #define CHT_WC_LED2_CTRL 0x4fdf
27 #define CHT_WC_LED2_FSM 0x4fe0
28 #define CHT_WC_LED2_PWM 0x4fe1
30 #define CHT_WC_LED1_SWCTL BIT(0) /* HW or SW control of charging led */
31 #define CHT_WC_LED1_ON BIT(1)
33 #define CHT_WC_LED2_ON BIT(0)
34 #define CHT_WC_LED_I_MA2_5 (2 << 2) /* LED current limit */
35 #define CHT_WC_LED_I_MASK GENMASK(3, 2) /* LED current limit mask */
37 #define CHT_WC_LED_F_1_4_HZ (0 << 4)
38 #define CHT_WC_LED_F_1_2_HZ (1 << 4)
39 #define CHT_WC_LED_F_1_HZ (2 << 4)
40 #define CHT_WC_LED_F_2_HZ (3 << 4)
41 #define CHT_WC_LED_F_MASK GENMASK(5, 4)
43 #define CHT_WC_LED_EFF_OFF (0 << 1)
44 #define CHT_WC_LED_EFF_ON (1 << 1)
45 #define CHT_WC_LED_EFF_BLINKING (2 << 1)
46 #define CHT_WC_LED_EFF_BREATHING (3 << 1)
47 #define CHT_WC_LED_EFF_MASK GENMASK(2, 1)
49 #define CHT_WC_LED_COUNT 2
51 struct cht_wc_led_regs
{
52 /* Register addresses */
56 /* Mask + values for turning the LED on/off */
62 struct cht_wc_led_saved_regs
{
69 struct led_classdev cdev
;
70 const struct cht_wc_led_regs
*regs
;
71 struct regmap
*regmap
;
73 struct cht_wc_led_saved_regs saved_regs
;
77 struct cht_wc_led leds
[CHT_WC_LED_COUNT
];
78 /* Saved LED1 initial register values */
79 struct cht_wc_led_saved_regs led1_initial_regs
;
82 static const struct cht_wc_led_regs cht_wc_led_regs
[CHT_WC_LED_COUNT
] = {
84 .ctrl
= CHT_WC_LED1_CTRL
,
85 .fsm
= CHT_WC_LED1_FSM
,
86 .pwm
= CHT_WC_LED1_PWM
,
87 .on_off_mask
= CHT_WC_LED1_SWCTL
| CHT_WC_LED1_ON
,
88 .on_val
= CHT_WC_LED1_SWCTL
| CHT_WC_LED1_ON
,
89 .off_val
= CHT_WC_LED1_SWCTL
,
92 .ctrl
= CHT_WC_LED2_CTRL
,
93 .fsm
= CHT_WC_LED2_FSM
,
94 .pwm
= CHT_WC_LED2_PWM
,
95 .on_off_mask
= CHT_WC_LED2_ON
,
96 .on_val
= CHT_WC_LED2_ON
,
101 static const char * const cht_wc_leds_names
[CHT_WC_LED_COUNT
] = {
102 "platform::" LED_FUNCTION_CHARGING
,
103 "platform::" LED_FUNCTION_INDICATOR
,
106 static int cht_wc_leds_brightness_set(struct led_classdev
*cdev
,
107 enum led_brightness value
)
109 struct cht_wc_led
*led
= container_of(cdev
, struct cht_wc_led
, cdev
);
112 mutex_lock(&led
->mutex
);
115 ret
= regmap_update_bits(led
->regmap
, led
->regs
->ctrl
,
116 led
->regs
->on_off_mask
, led
->regs
->off_val
);
118 dev_err(cdev
->dev
, "Failed to turn off: %d\n", ret
);
122 /* Disable HW blinking */
123 ret
= regmap_update_bits(led
->regmap
, led
->regs
->fsm
,
124 CHT_WC_LED_EFF_MASK
, CHT_WC_LED_EFF_ON
);
126 dev_err(cdev
->dev
, "Failed to update LED FSM reg: %d\n", ret
);
128 ret
= regmap_write(led
->regmap
, led
->regs
->pwm
, value
);
130 dev_err(cdev
->dev
, "Failed to set brightness: %d\n", ret
);
134 ret
= regmap_update_bits(led
->regmap
, led
->regs
->ctrl
,
135 led
->regs
->on_off_mask
, led
->regs
->on_val
);
137 dev_err(cdev
->dev
, "Failed to turn on: %d\n", ret
);
140 mutex_unlock(&led
->mutex
);
144 static enum led_brightness
cht_wc_leds_brightness_get(struct led_classdev
*cdev
)
146 struct cht_wc_led
*led
= container_of(cdev
, struct cht_wc_led
, cdev
);
150 mutex_lock(&led
->mutex
);
152 ret
= regmap_read(led
->regmap
, led
->regs
->ctrl
, &val
);
154 dev_err(cdev
->dev
, "Failed to read LED CTRL reg: %d\n", ret
);
159 val
&= led
->regs
->on_off_mask
;
160 if (val
!= led
->regs
->on_val
) {
165 ret
= regmap_read(led
->regmap
, led
->regs
->pwm
, &val
);
167 dev_err(cdev
->dev
, "Failed to read LED PWM reg: %d\n", ret
);
174 mutex_unlock(&led
->mutex
);
179 /* Return blinking period for given CTRL reg value */
180 static unsigned long cht_wc_leds_get_period(int ctrl
)
182 ctrl
&= CHT_WC_LED_F_MASK
;
185 case CHT_WC_LED_F_1_4_HZ
:
187 case CHT_WC_LED_F_1_2_HZ
:
189 case CHT_WC_LED_F_1_HZ
:
191 case CHT_WC_LED_F_2_HZ
:
199 * Find suitable hardware blink mode for given period.
200 * period < 750 ms - select 2 HZ
201 * 750 ms <= period < 1500 ms - select 1 HZ
202 * 1500 ms <= period < 3000 ms - select 1/2 HZ
203 * 3000 ms <= period < 5000 ms - select 1/4 HZ
204 * 5000 ms <= period - return -1
206 static int cht_wc_leds_find_freq(unsigned long period
)
209 return CHT_WC_LED_F_2_HZ
;
210 else if (period
< 1500)
211 return CHT_WC_LED_F_1_HZ
;
212 else if (period
< 3000)
213 return CHT_WC_LED_F_1_2_HZ
;
214 else if (period
< 5000)
215 return CHT_WC_LED_F_1_4_HZ
;
220 static int cht_wc_leds_set_effect(struct led_classdev
*cdev
,
221 unsigned long *delay_on
,
222 unsigned long *delay_off
,
225 struct cht_wc_led
*led
= container_of(cdev
, struct cht_wc_led
, cdev
);
228 mutex_lock(&led
->mutex
);
230 /* Blink with 1 Hz as default if nothing specified */
231 if (!*delay_on
&& !*delay_off
)
232 *delay_on
= *delay_off
= 500;
234 ctrl
= cht_wc_leds_find_freq(*delay_on
+ *delay_off
);
236 /* Disable HW blinking */
237 ret
= regmap_update_bits(led
->regmap
, led
->regs
->fsm
,
238 CHT_WC_LED_EFF_MASK
, CHT_WC_LED_EFF_ON
);
240 dev_err(cdev
->dev
, "Failed to update LED FSM reg: %d\n", ret
);
242 /* Fallback to software timer */
243 *delay_on
= *delay_off
= 0;
248 ret
= regmap_update_bits(led
->regmap
, led
->regs
->fsm
,
249 CHT_WC_LED_EFF_MASK
, effect
);
251 dev_err(cdev
->dev
, "Failed to update LED FSM reg: %d\n", ret
);
253 /* Set the frequency and make sure the LED is on */
254 ret
= regmap_update_bits(led
->regmap
, led
->regs
->ctrl
,
255 CHT_WC_LED_F_MASK
| led
->regs
->on_off_mask
,
256 ctrl
| led
->regs
->on_val
);
258 dev_err(cdev
->dev
, "Failed to update LED CTRL reg: %d\n", ret
);
260 *delay_off
= *delay_on
= cht_wc_leds_get_period(ctrl
) / 2;
263 mutex_unlock(&led
->mutex
);
268 static int cht_wc_leds_blink_set(struct led_classdev
*cdev
,
269 unsigned long *delay_on
,
270 unsigned long *delay_off
)
272 u8 effect
= CHT_WC_LED_EFF_BLINKING
;
275 * The desired default behavior of LED1 / the charge LED is breathing
276 * while charging and on/solid when full. Since triggers cannot select
277 * breathing, blink_set() gets called when charging. Use slow breathing
278 * when the default "charging-blink-full-solid" trigger is used to
279 * achieve the desired default behavior.
281 if (cdev
->flags
& LED_INIT_DEFAULT_TRIGGER
) {
282 *delay_on
= *delay_off
= 1000;
283 effect
= CHT_WC_LED_EFF_BREATHING
;
286 return cht_wc_leds_set_effect(cdev
, delay_on
, delay_off
, effect
);
289 static int cht_wc_leds_pattern_set(struct led_classdev
*cdev
,
290 struct led_pattern
*pattern
,
293 unsigned long delay_off
, delay_on
;
295 if (repeat
> 0 || len
!= 2 ||
296 pattern
[0].brightness
!= 0 || pattern
[1].brightness
!= 1 ||
297 pattern
[0].delta_t
!= pattern
[1].delta_t
||
298 (pattern
[0].delta_t
!= 250 && pattern
[0].delta_t
!= 500 &&
299 pattern
[0].delta_t
!= 1000 && pattern
[0].delta_t
!= 2000))
302 delay_off
= pattern
[0].delta_t
;
303 delay_on
= pattern
[1].delta_t
;
305 return cht_wc_leds_set_effect(cdev
, &delay_on
, &delay_off
, CHT_WC_LED_EFF_BREATHING
);
308 static int cht_wc_leds_pattern_clear(struct led_classdev
*cdev
)
310 return cht_wc_leds_brightness_set(cdev
, 0);
313 static int cht_wc_led_save_regs(struct cht_wc_led
*led
,
314 struct cht_wc_led_saved_regs
*saved_regs
)
318 ret
= regmap_read(led
->regmap
, led
->regs
->ctrl
, &saved_regs
->ctrl
);
322 ret
= regmap_read(led
->regmap
, led
->regs
->fsm
, &saved_regs
->fsm
);
326 return regmap_read(led
->regmap
, led
->regs
->pwm
, &saved_regs
->pwm
);
329 static void cht_wc_led_restore_regs(struct cht_wc_led
*led
,
330 const struct cht_wc_led_saved_regs
*saved_regs
)
332 regmap_write(led
->regmap
, led
->regs
->ctrl
, saved_regs
->ctrl
);
333 regmap_write(led
->regmap
, led
->regs
->fsm
, saved_regs
->fsm
);
334 regmap_write(led
->regmap
, led
->regs
->pwm
, saved_regs
->pwm
);
337 static int cht_wc_leds_probe(struct platform_device
*pdev
)
339 struct intel_soc_pmic
*pmic
= dev_get_drvdata(pdev
->dev
.parent
);
340 struct cht_wc_leds
*leds
;
345 * On the Lenovo Yoga Tab 3 the LED1 driver output is actually
346 * connected to a haptic feedback motor rather then a LED.
347 * So do not register a LED classdev there (LED2 is unused).
349 if (pmic
->cht_wc_model
== INTEL_CHT_WC_LENOVO_YT3_X90
)
352 leds
= devm_kzalloc(&pdev
->dev
, sizeof(*leds
), GFP_KERNEL
);
357 * LED1 might be in hw-controlled mode when this driver gets loaded; and
358 * since the PMIC is always powered by the battery any changes made are
359 * permanent. Save LED1 regs to restore them on remove() or shutdown().
361 leds
->leds
[0].regs
= &cht_wc_led_regs
[0];
362 leds
->leds
[0].regmap
= pmic
->regmap
;
363 ret
= cht_wc_led_save_regs(&leds
->leds
[0], &leds
->led1_initial_regs
);
367 /* Set LED1 default trigger based on machine model */
368 switch (pmic
->cht_wc_model
) {
369 case INTEL_CHT_WC_GPD_WIN_POCKET
:
370 leds
->leds
[0].cdev
.default_trigger
= "max170xx_battery-charging-blink-full-solid";
372 case INTEL_CHT_WC_XIAOMI_MIPAD2
:
373 leds
->leds
[0].cdev
.default_trigger
= "bq27520-0-charging-blink-full-solid";
375 case INTEL_CHT_WC_LENOVO_YOGABOOK1
:
376 leds
->leds
[0].cdev
.default_trigger
= "bq27542-0-charging-blink-full-solid";
379 dev_warn(&pdev
->dev
, "Unknown model, no default charging trigger\n");
383 for (i
= 0; i
< CHT_WC_LED_COUNT
; i
++) {
384 struct cht_wc_led
*led
= &leds
->leds
[i
];
386 led
->regs
= &cht_wc_led_regs
[i
];
387 led
->regmap
= pmic
->regmap
;
388 mutex_init(&led
->mutex
);
389 led
->cdev
.name
= cht_wc_leds_names
[i
];
390 led
->cdev
.brightness_set_blocking
= cht_wc_leds_brightness_set
;
391 led
->cdev
.brightness_get
= cht_wc_leds_brightness_get
;
392 led
->cdev
.blink_set
= cht_wc_leds_blink_set
;
393 led
->cdev
.pattern_set
= cht_wc_leds_pattern_set
;
394 led
->cdev
.pattern_clear
= cht_wc_leds_pattern_clear
;
395 led
->cdev
.max_brightness
= 255;
397 ret
= led_classdev_register(&pdev
->dev
, &led
->cdev
);
402 platform_set_drvdata(pdev
, leds
);
406 static void cht_wc_leds_remove(struct platform_device
*pdev
)
408 struct cht_wc_leds
*leds
= platform_get_drvdata(pdev
);
411 for (i
= 0; i
< CHT_WC_LED_COUNT
; i
++)
412 led_classdev_unregister(&leds
->leds
[i
].cdev
);
414 /* Restore LED1 regs if hw-control was active else leave LED1 off */
415 if (!(leds
->led1_initial_regs
.ctrl
& CHT_WC_LED1_SWCTL
))
416 cht_wc_led_restore_regs(&leds
->leds
[0], &leds
->led1_initial_regs
);
419 static void cht_wc_leds_disable(struct platform_device
*pdev
)
421 struct cht_wc_leds
*leds
= platform_get_drvdata(pdev
);
424 for (i
= 0; i
< CHT_WC_LED_COUNT
; i
++)
425 cht_wc_leds_brightness_set(&leds
->leds
[i
].cdev
, 0);
427 /* Restore LED1 regs if hw-control was active else leave LED1 off */
428 if (!(leds
->led1_initial_regs
.ctrl
& CHT_WC_LED1_SWCTL
))
429 cht_wc_led_restore_regs(&leds
->leds
[0], &leds
->led1_initial_regs
);
432 /* On suspend save current settings and turn LEDs off */
433 static int cht_wc_leds_suspend(struct device
*dev
)
435 struct cht_wc_leds
*leds
= dev_get_drvdata(dev
);
438 for (i
= 0; i
< CHT_WC_LED_COUNT
; i
++) {
439 ret
= cht_wc_led_save_regs(&leds
->leds
[i
], &leds
->leds
[i
].saved_regs
);
444 cht_wc_leds_disable(to_platform_device(dev
));
448 /* On resume restore the saved settings */
449 static int cht_wc_leds_resume(struct device
*dev
)
451 struct cht_wc_leds
*leds
= dev_get_drvdata(dev
);
454 for (i
= 0; i
< CHT_WC_LED_COUNT
; i
++)
455 cht_wc_led_restore_regs(&leds
->leds
[i
], &leds
->leds
[i
].saved_regs
);
460 static DEFINE_SIMPLE_DEV_PM_OPS(cht_wc_leds_pm
, cht_wc_leds_suspend
, cht_wc_leds_resume
);
462 static struct platform_driver cht_wc_leds_driver
= {
463 .probe
= cht_wc_leds_probe
,
464 .remove
= cht_wc_leds_remove
,
465 .shutdown
= cht_wc_leds_disable
,
467 .name
= "cht_wcove_leds",
468 .pm
= pm_sleep_ptr(&cht_wc_leds_pm
),
471 module_platform_driver(cht_wc_leds_driver
);
473 MODULE_ALIAS("platform:cht_wcove_leds");
474 MODULE_DESCRIPTION("Intel Cherry Trail Whiskey Cove PMIC LEDs driver");
475 MODULE_AUTHOR("Yauhen Kharuzhy <jekhor@gmail.com>");
476 MODULE_LICENSE("GPL");