xref: /openbmc/linux/drivers/leds/leds-cht-wcove.c (revision dda96847)
1047da762SYauhen Kharuzhy // SPDX-License-Identifier: GPL-2.0
2047da762SYauhen Kharuzhy /*
3047da762SYauhen Kharuzhy  * Driver for LEDs connected to the Intel Cherry Trail Whiskey Cove PMIC
4047da762SYauhen Kharuzhy  *
5047da762SYauhen Kharuzhy  * Copyright 2019 Yauhen Kharuzhy <jekhor@gmail.com>
6047da762SYauhen Kharuzhy  * Copyright 2023 Hans de Goede <hansg@kernel.org>
7047da762SYauhen Kharuzhy  *
8047da762SYauhen Kharuzhy  * Register info comes from the Lenovo Yoga Book Android opensource code
9047da762SYauhen Kharuzhy  * available from Lenovo. File lenovo_yb1_x90f_l_osc_201803.7z path in the 7z:
10047da762SYauhen Kharuzhy  * YB1_source_code/kernel/cht/drivers/misc/charger_gp_led.c
11047da762SYauhen Kharuzhy  */
12047da762SYauhen Kharuzhy 
13047da762SYauhen Kharuzhy #include <linux/kernel.h>
14047da762SYauhen Kharuzhy #include <linux/leds.h>
15047da762SYauhen Kharuzhy #include <linux/mfd/intel_soc_pmic.h>
16047da762SYauhen Kharuzhy #include <linux/module.h>
17047da762SYauhen Kharuzhy #include <linux/mod_devicetable.h>
18047da762SYauhen Kharuzhy #include <linux/platform_device.h>
19047da762SYauhen Kharuzhy #include <linux/regmap.h>
209697e2f0SHans de Goede #include <linux/suspend.h>
21047da762SYauhen Kharuzhy 
22047da762SYauhen Kharuzhy #define CHT_WC_LED1_CTRL		0x5e1f
23047da762SYauhen Kharuzhy #define CHT_WC_LED1_FSM			0x5e20
24047da762SYauhen Kharuzhy #define CHT_WC_LED1_PWM			0x5e21
25047da762SYauhen Kharuzhy 
26047da762SYauhen Kharuzhy #define CHT_WC_LED2_CTRL		0x4fdf
27047da762SYauhen Kharuzhy #define CHT_WC_LED2_FSM			0x4fe0
28047da762SYauhen Kharuzhy #define CHT_WC_LED2_PWM			0x4fe1
29047da762SYauhen Kharuzhy 
30047da762SYauhen Kharuzhy #define CHT_WC_LED1_SWCTL		BIT(0)		/* HW or SW control of charging led */
31047da762SYauhen Kharuzhy #define CHT_WC_LED1_ON			BIT(1)
32047da762SYauhen Kharuzhy 
33047da762SYauhen Kharuzhy #define CHT_WC_LED2_ON			BIT(0)
34047da762SYauhen Kharuzhy #define CHT_WC_LED_I_MA2_5		(2 << 2)	/* LED current limit */
35047da762SYauhen Kharuzhy #define CHT_WC_LED_I_MASK		GENMASK(3, 2)	/* LED current limit mask */
36047da762SYauhen Kharuzhy 
37047da762SYauhen Kharuzhy #define CHT_WC_LED_F_1_4_HZ		(0 << 4)
38047da762SYauhen Kharuzhy #define CHT_WC_LED_F_1_2_HZ		(1 << 4)
39047da762SYauhen Kharuzhy #define CHT_WC_LED_F_1_HZ		(2 << 4)
40047da762SYauhen Kharuzhy #define CHT_WC_LED_F_2_HZ		(3 << 4)
41047da762SYauhen Kharuzhy #define CHT_WC_LED_F_MASK		GENMASK(5, 4)
42047da762SYauhen Kharuzhy 
43047da762SYauhen Kharuzhy #define CHT_WC_LED_EFF_OFF		(0 << 1)
44047da762SYauhen Kharuzhy #define CHT_WC_LED_EFF_ON		(1 << 1)
45047da762SYauhen Kharuzhy #define CHT_WC_LED_EFF_BLINKING		(2 << 1)
46047da762SYauhen Kharuzhy #define CHT_WC_LED_EFF_BREATHING	(3 << 1)
47047da762SYauhen Kharuzhy #define CHT_WC_LED_EFF_MASK		GENMASK(2, 1)
48047da762SYauhen Kharuzhy 
49047da762SYauhen Kharuzhy #define CHT_WC_LED_COUNT		2
50047da762SYauhen Kharuzhy 
51047da762SYauhen Kharuzhy struct cht_wc_led_regs {
52047da762SYauhen Kharuzhy 	/* Register addresses */
53047da762SYauhen Kharuzhy 	u16 ctrl;
54047da762SYauhen Kharuzhy 	u16 fsm;
55047da762SYauhen Kharuzhy 	u16 pwm;
56047da762SYauhen Kharuzhy 	/* Mask + values for turning the LED on/off */
57047da762SYauhen Kharuzhy 	u8 on_off_mask;
58047da762SYauhen Kharuzhy 	u8 on_val;
59047da762SYauhen Kharuzhy 	u8 off_val;
60047da762SYauhen Kharuzhy };
61047da762SYauhen Kharuzhy 
62047da762SYauhen Kharuzhy struct cht_wc_led_saved_regs {
63047da762SYauhen Kharuzhy 	unsigned int ctrl;
64047da762SYauhen Kharuzhy 	unsigned int fsm;
65047da762SYauhen Kharuzhy 	unsigned int pwm;
66047da762SYauhen Kharuzhy };
67047da762SYauhen Kharuzhy 
68047da762SYauhen Kharuzhy struct cht_wc_led {
69047da762SYauhen Kharuzhy 	struct led_classdev cdev;
70047da762SYauhen Kharuzhy 	const struct cht_wc_led_regs *regs;
71047da762SYauhen Kharuzhy 	struct regmap *regmap;
72047da762SYauhen Kharuzhy 	struct mutex mutex;
739697e2f0SHans de Goede 	struct cht_wc_led_saved_regs saved_regs;
74047da762SYauhen Kharuzhy };
75047da762SYauhen Kharuzhy 
76047da762SYauhen Kharuzhy struct cht_wc_leds {
77047da762SYauhen Kharuzhy 	struct cht_wc_led leds[CHT_WC_LED_COUNT];
78047da762SYauhen Kharuzhy 	/* Saved LED1 initial register values */
79047da762SYauhen Kharuzhy 	struct cht_wc_led_saved_regs led1_initial_regs;
80047da762SYauhen Kharuzhy };
81047da762SYauhen Kharuzhy 
82047da762SYauhen Kharuzhy static const struct cht_wc_led_regs cht_wc_led_regs[CHT_WC_LED_COUNT] = {
83047da762SYauhen Kharuzhy 	{
84047da762SYauhen Kharuzhy 		.ctrl		= CHT_WC_LED1_CTRL,
85047da762SYauhen Kharuzhy 		.fsm		= CHT_WC_LED1_FSM,
86047da762SYauhen Kharuzhy 		.pwm		= CHT_WC_LED1_PWM,
87047da762SYauhen Kharuzhy 		.on_off_mask	= CHT_WC_LED1_SWCTL | CHT_WC_LED1_ON,
88047da762SYauhen Kharuzhy 		.on_val		= CHT_WC_LED1_SWCTL | CHT_WC_LED1_ON,
89047da762SYauhen Kharuzhy 		.off_val	= CHT_WC_LED1_SWCTL,
90047da762SYauhen Kharuzhy 	},
91047da762SYauhen Kharuzhy 	{
92047da762SYauhen Kharuzhy 		.ctrl		= CHT_WC_LED2_CTRL,
93047da762SYauhen Kharuzhy 		.fsm		= CHT_WC_LED2_FSM,
94047da762SYauhen Kharuzhy 		.pwm		= CHT_WC_LED2_PWM,
95047da762SYauhen Kharuzhy 		.on_off_mask	= CHT_WC_LED2_ON,
96047da762SYauhen Kharuzhy 		.on_val		= CHT_WC_LED2_ON,
97047da762SYauhen Kharuzhy 		.off_val	= 0,
98047da762SYauhen Kharuzhy 	},
99047da762SYauhen Kharuzhy };
100047da762SYauhen Kharuzhy 
101047da762SYauhen Kharuzhy static const char * const cht_wc_leds_names[CHT_WC_LED_COUNT] = {
102047da762SYauhen Kharuzhy 	"platform::" LED_FUNCTION_CHARGING,
103047da762SYauhen Kharuzhy 	"platform::" LED_FUNCTION_INDICATOR,
104047da762SYauhen Kharuzhy };
105047da762SYauhen Kharuzhy 
cht_wc_leds_brightness_set(struct led_classdev * cdev,enum led_brightness value)106047da762SYauhen Kharuzhy static int cht_wc_leds_brightness_set(struct led_classdev *cdev,
107047da762SYauhen Kharuzhy 				      enum led_brightness value)
108047da762SYauhen Kharuzhy {
109047da762SYauhen Kharuzhy 	struct cht_wc_led *led = container_of(cdev, struct cht_wc_led, cdev);
110047da762SYauhen Kharuzhy 	int ret;
111047da762SYauhen Kharuzhy 
112047da762SYauhen Kharuzhy 	mutex_lock(&led->mutex);
113047da762SYauhen Kharuzhy 
114047da762SYauhen Kharuzhy 	if (!value) {
115047da762SYauhen Kharuzhy 		ret = regmap_update_bits(led->regmap, led->regs->ctrl,
116047da762SYauhen Kharuzhy 					 led->regs->on_off_mask, led->regs->off_val);
117047da762SYauhen Kharuzhy 		if (ret < 0) {
118047da762SYauhen Kharuzhy 			dev_err(cdev->dev, "Failed to turn off: %d\n", ret);
119047da762SYauhen Kharuzhy 			goto out;
120047da762SYauhen Kharuzhy 		}
121047da762SYauhen Kharuzhy 
122047da762SYauhen Kharuzhy 		/* Disable HW blinking */
123047da762SYauhen Kharuzhy 		ret = regmap_update_bits(led->regmap, led->regs->fsm,
124047da762SYauhen Kharuzhy 					 CHT_WC_LED_EFF_MASK, CHT_WC_LED_EFF_ON);
125047da762SYauhen Kharuzhy 		if (ret < 0)
126047da762SYauhen Kharuzhy 			dev_err(cdev->dev, "Failed to update LED FSM reg: %d\n", ret);
127047da762SYauhen Kharuzhy 	} else {
128047da762SYauhen Kharuzhy 		ret = regmap_write(led->regmap, led->regs->pwm, value);
129047da762SYauhen Kharuzhy 		if (ret < 0) {
130047da762SYauhen Kharuzhy 			dev_err(cdev->dev, "Failed to set brightness: %d\n", ret);
131047da762SYauhen Kharuzhy 			goto out;
132047da762SYauhen Kharuzhy 		}
133047da762SYauhen Kharuzhy 
134047da762SYauhen Kharuzhy 		ret = regmap_update_bits(led->regmap, led->regs->ctrl,
135047da762SYauhen Kharuzhy 					 led->regs->on_off_mask, led->regs->on_val);
136047da762SYauhen Kharuzhy 		if (ret < 0)
137047da762SYauhen Kharuzhy 			dev_err(cdev->dev, "Failed to turn on: %d\n", ret);
138047da762SYauhen Kharuzhy 	}
139047da762SYauhen Kharuzhy out:
140047da762SYauhen Kharuzhy 	mutex_unlock(&led->mutex);
141047da762SYauhen Kharuzhy 	return ret;
142047da762SYauhen Kharuzhy }
143047da762SYauhen Kharuzhy 
cht_wc_leds_brightness_get(struct led_classdev * cdev)1443f80ba44SHans de Goede static enum led_brightness cht_wc_leds_brightness_get(struct led_classdev *cdev)
145047da762SYauhen Kharuzhy {
146047da762SYauhen Kharuzhy 	struct cht_wc_led *led = container_of(cdev, struct cht_wc_led, cdev);
147047da762SYauhen Kharuzhy 	unsigned int val;
148047da762SYauhen Kharuzhy 	int ret;
149047da762SYauhen Kharuzhy 
150047da762SYauhen Kharuzhy 	mutex_lock(&led->mutex);
151047da762SYauhen Kharuzhy 
152047da762SYauhen Kharuzhy 	ret = regmap_read(led->regmap, led->regs->ctrl, &val);
153047da762SYauhen Kharuzhy 	if (ret < 0) {
154047da762SYauhen Kharuzhy 		dev_err(cdev->dev, "Failed to read LED CTRL reg: %d\n", ret);
155047da762SYauhen Kharuzhy 		ret = 0;
156047da762SYauhen Kharuzhy 		goto done;
157047da762SYauhen Kharuzhy 	}
158047da762SYauhen Kharuzhy 
159047da762SYauhen Kharuzhy 	val &= led->regs->on_off_mask;
160047da762SYauhen Kharuzhy 	if (val != led->regs->on_val) {
161047da762SYauhen Kharuzhy 		ret = 0;
162047da762SYauhen Kharuzhy 		goto done;
163047da762SYauhen Kharuzhy 	}
164047da762SYauhen Kharuzhy 
165047da762SYauhen Kharuzhy 	ret = regmap_read(led->regmap, led->regs->pwm, &val);
166047da762SYauhen Kharuzhy 	if (ret < 0) {
167047da762SYauhen Kharuzhy 		dev_err(cdev->dev, "Failed to read LED PWM reg: %d\n", ret);
168047da762SYauhen Kharuzhy 		ret = 0;
169047da762SYauhen Kharuzhy 		goto done;
170047da762SYauhen Kharuzhy 	}
171047da762SYauhen Kharuzhy 
172047da762SYauhen Kharuzhy 	ret = val;
173047da762SYauhen Kharuzhy done:
174047da762SYauhen Kharuzhy 	mutex_unlock(&led->mutex);
175047da762SYauhen Kharuzhy 
176047da762SYauhen Kharuzhy 	return ret;
177047da762SYauhen Kharuzhy }
178047da762SYauhen Kharuzhy 
179047da762SYauhen Kharuzhy /* Return blinking period for given CTRL reg value */
cht_wc_leds_get_period(int ctrl)180047da762SYauhen Kharuzhy static unsigned long cht_wc_leds_get_period(int ctrl)
181047da762SYauhen Kharuzhy {
182047da762SYauhen Kharuzhy 	ctrl &= CHT_WC_LED_F_MASK;
183047da762SYauhen Kharuzhy 
184047da762SYauhen Kharuzhy 	switch (ctrl) {
185047da762SYauhen Kharuzhy 	case CHT_WC_LED_F_1_4_HZ:
186047da762SYauhen Kharuzhy 		return 1000 * 4;
187047da762SYauhen Kharuzhy 	case CHT_WC_LED_F_1_2_HZ:
188047da762SYauhen Kharuzhy 		return 1000 * 2;
189047da762SYauhen Kharuzhy 	case CHT_WC_LED_F_1_HZ:
190047da762SYauhen Kharuzhy 		return 1000;
191047da762SYauhen Kharuzhy 	case CHT_WC_LED_F_2_HZ:
192047da762SYauhen Kharuzhy 		return 1000 / 2;
193*dda96847SJiapeng Chong 	}
194047da762SYauhen Kharuzhy 
195047da762SYauhen Kharuzhy 	return 0;
196047da762SYauhen Kharuzhy }
197047da762SYauhen Kharuzhy 
198047da762SYauhen Kharuzhy /*
199047da762SYauhen Kharuzhy  * Find suitable hardware blink mode for given period.
200047da762SYauhen Kharuzhy  * period < 750 ms - select 2 HZ
201047da762SYauhen Kharuzhy  * 750 ms <= period < 1500 ms - select 1 HZ
202047da762SYauhen Kharuzhy  * 1500 ms <= period < 3000 ms - select 1/2 HZ
203047da762SYauhen Kharuzhy  * 3000 ms <= period < 5000 ms - select 1/4 HZ
204047da762SYauhen Kharuzhy  * 5000 ms <= period - return -1
205047da762SYauhen Kharuzhy  */
cht_wc_leds_find_freq(unsigned long period)206047da762SYauhen Kharuzhy static int cht_wc_leds_find_freq(unsigned long period)
207047da762SYauhen Kharuzhy {
208047da762SYauhen Kharuzhy 	if (period < 750)
209047da762SYauhen Kharuzhy 		return CHT_WC_LED_F_2_HZ;
210047da762SYauhen Kharuzhy 	else if (period < 1500)
211047da762SYauhen Kharuzhy 		return CHT_WC_LED_F_1_HZ;
212047da762SYauhen Kharuzhy 	else if (period < 3000)
213047da762SYauhen Kharuzhy 		return CHT_WC_LED_F_1_2_HZ;
214047da762SYauhen Kharuzhy 	else if (period < 5000)
215047da762SYauhen Kharuzhy 		return CHT_WC_LED_F_1_4_HZ;
216047da762SYauhen Kharuzhy 	else
217047da762SYauhen Kharuzhy 		return -1;
218047da762SYauhen Kharuzhy }
219047da762SYauhen Kharuzhy 
cht_wc_leds_set_effect(struct led_classdev * cdev,unsigned long * delay_on,unsigned long * delay_off,u8 effect)2205b916aa7SHans de Goede static int cht_wc_leds_set_effect(struct led_classdev *cdev,
221047da762SYauhen Kharuzhy 				  unsigned long *delay_on,
2225b916aa7SHans de Goede 				  unsigned long *delay_off,
2235b916aa7SHans de Goede 				  u8 effect)
224047da762SYauhen Kharuzhy {
225047da762SYauhen Kharuzhy 	struct cht_wc_led *led = container_of(cdev, struct cht_wc_led, cdev);
226de416a9fSYang Li 	int ctrl, ret;
227047da762SYauhen Kharuzhy 
228047da762SYauhen Kharuzhy 	mutex_lock(&led->mutex);
229047da762SYauhen Kharuzhy 
230047da762SYauhen Kharuzhy 	/* Blink with 1 Hz as default if nothing specified */
231047da762SYauhen Kharuzhy 	if (!*delay_on && !*delay_off)
232047da762SYauhen Kharuzhy 		*delay_on = *delay_off = 500;
233047da762SYauhen Kharuzhy 
234047da762SYauhen Kharuzhy 	ctrl = cht_wc_leds_find_freq(*delay_on + *delay_off);
235047da762SYauhen Kharuzhy 	if (ctrl < 0) {
236047da762SYauhen Kharuzhy 		/* Disable HW blinking */
237047da762SYauhen Kharuzhy 		ret = regmap_update_bits(led->regmap, led->regs->fsm,
238047da762SYauhen Kharuzhy 					 CHT_WC_LED_EFF_MASK, CHT_WC_LED_EFF_ON);
239047da762SYauhen Kharuzhy 		if (ret < 0)
240047da762SYauhen Kharuzhy 			dev_err(cdev->dev, "Failed to update LED FSM reg: %d\n", ret);
241047da762SYauhen Kharuzhy 
242047da762SYauhen Kharuzhy 		/* Fallback to software timer */
243047da762SYauhen Kharuzhy 		*delay_on = *delay_off = 0;
244047da762SYauhen Kharuzhy 		ret = -EINVAL;
245047da762SYauhen Kharuzhy 		goto done;
246047da762SYauhen Kharuzhy 	}
247047da762SYauhen Kharuzhy 
248047da762SYauhen Kharuzhy 	ret = regmap_update_bits(led->regmap, led->regs->fsm,
2495b916aa7SHans de Goede 				 CHT_WC_LED_EFF_MASK, effect);
250047da762SYauhen Kharuzhy 	if (ret < 0)
251047da762SYauhen Kharuzhy 		dev_err(cdev->dev, "Failed to update LED FSM reg: %d\n", ret);
252047da762SYauhen Kharuzhy 
253047da762SYauhen Kharuzhy 	/* Set the frequency and make sure the LED is on */
254047da762SYauhen Kharuzhy 	ret = regmap_update_bits(led->regmap, led->regs->ctrl,
255047da762SYauhen Kharuzhy 				 CHT_WC_LED_F_MASK | led->regs->on_off_mask,
256047da762SYauhen Kharuzhy 				 ctrl | led->regs->on_val);
257047da762SYauhen Kharuzhy 	if (ret < 0)
258047da762SYauhen Kharuzhy 		dev_err(cdev->dev, "Failed to update LED CTRL reg: %d\n", ret);
259047da762SYauhen Kharuzhy 
260047da762SYauhen Kharuzhy 	*delay_off = *delay_on = cht_wc_leds_get_period(ctrl) / 2;
261047da762SYauhen Kharuzhy 
262047da762SYauhen Kharuzhy done:
263047da762SYauhen Kharuzhy 	mutex_unlock(&led->mutex);
264047da762SYauhen Kharuzhy 
265047da762SYauhen Kharuzhy 	return ret;
266047da762SYauhen Kharuzhy }
267047da762SYauhen Kharuzhy 
cht_wc_leds_blink_set(struct led_classdev * cdev,unsigned long * delay_on,unsigned long * delay_off)2685b916aa7SHans de Goede static int cht_wc_leds_blink_set(struct led_classdev *cdev,
2695b916aa7SHans de Goede 				 unsigned long *delay_on,
2705b916aa7SHans de Goede 				 unsigned long *delay_off)
2715b916aa7SHans de Goede {
272ea0c0a85SHans de Goede 	u8 effect = CHT_WC_LED_EFF_BLINKING;
273ea0c0a85SHans de Goede 
274ea0c0a85SHans de Goede 	/*
275ea0c0a85SHans de Goede 	 * The desired default behavior of LED1 / the charge LED is breathing
276ea0c0a85SHans de Goede 	 * while charging and on/solid when full. Since triggers cannot select
277ea0c0a85SHans de Goede 	 * breathing, blink_set() gets called when charging. Use slow breathing
278ea0c0a85SHans de Goede 	 * when the default "charging-blink-full-solid" trigger is used to
279ea0c0a85SHans de Goede 	 * achieve the desired default behavior.
280ea0c0a85SHans de Goede 	 */
281ea0c0a85SHans de Goede 	if (cdev->flags & LED_INIT_DEFAULT_TRIGGER) {
282ea0c0a85SHans de Goede 		*delay_on = *delay_off = 1000;
283ea0c0a85SHans de Goede 		effect = CHT_WC_LED_EFF_BREATHING;
284ea0c0a85SHans de Goede 	}
285ea0c0a85SHans de Goede 
286ea0c0a85SHans de Goede 	return cht_wc_leds_set_effect(cdev, delay_on, delay_off, effect);
2875b916aa7SHans de Goede }
2885b916aa7SHans de Goede 
cht_wc_leds_pattern_set(struct led_classdev * cdev,struct led_pattern * pattern,u32 len,int repeat)2895b916aa7SHans de Goede static int cht_wc_leds_pattern_set(struct led_classdev *cdev,
2905b916aa7SHans de Goede 				   struct led_pattern *pattern,
2915b916aa7SHans de Goede 				   u32 len, int repeat)
2925b916aa7SHans de Goede {
2935b916aa7SHans de Goede 	unsigned long delay_off, delay_on;
2945b916aa7SHans de Goede 
2955b916aa7SHans de Goede 	if (repeat > 0 || len != 2 ||
2965b916aa7SHans de Goede 	    pattern[0].brightness != 0 || pattern[1].brightness != 1 ||
2975b916aa7SHans de Goede 	    pattern[0].delta_t != pattern[1].delta_t ||
2985b916aa7SHans de Goede 	    (pattern[0].delta_t != 250 && pattern[0].delta_t != 500 &&
2995b916aa7SHans de Goede 	     pattern[0].delta_t != 1000 && pattern[0].delta_t != 2000))
3005b916aa7SHans de Goede 		return -EINVAL;
3015b916aa7SHans de Goede 
3025b916aa7SHans de Goede 	delay_off = pattern[0].delta_t;
3035b916aa7SHans de Goede 	delay_on  = pattern[1].delta_t;
3045b916aa7SHans de Goede 
3055b916aa7SHans de Goede 	return cht_wc_leds_set_effect(cdev, &delay_on, &delay_off, CHT_WC_LED_EFF_BREATHING);
3065b916aa7SHans de Goede }
3075b916aa7SHans de Goede 
cht_wc_leds_pattern_clear(struct led_classdev * cdev)3085b916aa7SHans de Goede static int cht_wc_leds_pattern_clear(struct led_classdev *cdev)
3095b916aa7SHans de Goede {
3105b916aa7SHans de Goede 	return cht_wc_leds_brightness_set(cdev, 0);
3115b916aa7SHans de Goede }
3125b916aa7SHans de Goede 
cht_wc_led_save_regs(struct cht_wc_led * led,struct cht_wc_led_saved_regs * saved_regs)313047da762SYauhen Kharuzhy static int cht_wc_led_save_regs(struct cht_wc_led *led,
314047da762SYauhen Kharuzhy 				struct cht_wc_led_saved_regs *saved_regs)
315047da762SYauhen Kharuzhy {
316047da762SYauhen Kharuzhy 	int ret;
317047da762SYauhen Kharuzhy 
318047da762SYauhen Kharuzhy 	ret = regmap_read(led->regmap, led->regs->ctrl, &saved_regs->ctrl);
319047da762SYauhen Kharuzhy 	if (ret < 0)
320047da762SYauhen Kharuzhy 		return ret;
321047da762SYauhen Kharuzhy 
322047da762SYauhen Kharuzhy 	ret = regmap_read(led->regmap, led->regs->fsm, &saved_regs->fsm);
323047da762SYauhen Kharuzhy 	if (ret < 0)
324047da762SYauhen Kharuzhy 		return ret;
325047da762SYauhen Kharuzhy 
326047da762SYauhen Kharuzhy 	return regmap_read(led->regmap, led->regs->pwm, &saved_regs->pwm);
327047da762SYauhen Kharuzhy }
328047da762SYauhen Kharuzhy 
cht_wc_led_restore_regs(struct cht_wc_led * led,const struct cht_wc_led_saved_regs * saved_regs)329047da762SYauhen Kharuzhy static void cht_wc_led_restore_regs(struct cht_wc_led *led,
330047da762SYauhen Kharuzhy 				    const struct cht_wc_led_saved_regs *saved_regs)
331047da762SYauhen Kharuzhy {
332047da762SYauhen Kharuzhy 	regmap_write(led->regmap, led->regs->ctrl, saved_regs->ctrl);
333047da762SYauhen Kharuzhy 	regmap_write(led->regmap, led->regs->fsm, saved_regs->fsm);
334047da762SYauhen Kharuzhy 	regmap_write(led->regmap, led->regs->pwm, saved_regs->pwm);
335047da762SYauhen Kharuzhy }
336047da762SYauhen Kharuzhy 
cht_wc_leds_probe(struct platform_device * pdev)337047da762SYauhen Kharuzhy static int cht_wc_leds_probe(struct platform_device *pdev)
338047da762SYauhen Kharuzhy {
339047da762SYauhen Kharuzhy 	struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
340047da762SYauhen Kharuzhy 	struct cht_wc_leds *leds;
341047da762SYauhen Kharuzhy 	int ret;
342047da762SYauhen Kharuzhy 	int i;
343047da762SYauhen Kharuzhy 
344047da762SYauhen Kharuzhy 	/*
345047da762SYauhen Kharuzhy 	 * On the Lenovo Yoga Tab 3 the LED1 driver output is actually
346047da762SYauhen Kharuzhy 	 * connected to a haptic feedback motor rather then a LED.
347047da762SYauhen Kharuzhy 	 * So do not register a LED classdev there (LED2 is unused).
348047da762SYauhen Kharuzhy 	 */
349047da762SYauhen Kharuzhy 	if (pmic->cht_wc_model == INTEL_CHT_WC_LENOVO_YT3_X90)
350047da762SYauhen Kharuzhy 		return -ENODEV;
351047da762SYauhen Kharuzhy 
352047da762SYauhen Kharuzhy 	leds = devm_kzalloc(&pdev->dev, sizeof(*leds), GFP_KERNEL);
353047da762SYauhen Kharuzhy 	if (!leds)
354047da762SYauhen Kharuzhy 		return -ENOMEM;
355047da762SYauhen Kharuzhy 
356047da762SYauhen Kharuzhy 	/*
357047da762SYauhen Kharuzhy 	 * LED1 might be in hw-controlled mode when this driver gets loaded; and
358047da762SYauhen Kharuzhy 	 * since the PMIC is always powered by the battery any changes made are
359047da762SYauhen Kharuzhy 	 * permanent. Save LED1 regs to restore them on remove() or shutdown().
360047da762SYauhen Kharuzhy 	 */
361047da762SYauhen Kharuzhy 	leds->leds[0].regs = &cht_wc_led_regs[0];
362047da762SYauhen Kharuzhy 	leds->leds[0].regmap = pmic->regmap;
363047da762SYauhen Kharuzhy 	ret = cht_wc_led_save_regs(&leds->leds[0], &leds->led1_initial_regs);
364047da762SYauhen Kharuzhy 	if (ret < 0)
365047da762SYauhen Kharuzhy 		return ret;
366047da762SYauhen Kharuzhy 
367ca3167f2SHans de Goede 	/* Set LED1 default trigger based on machine model */
368ca3167f2SHans de Goede 	switch (pmic->cht_wc_model) {
369ca3167f2SHans de Goede 	case INTEL_CHT_WC_GPD_WIN_POCKET:
370ca3167f2SHans de Goede 		leds->leds[0].cdev.default_trigger = "max170xx_battery-charging-blink-full-solid";
371ca3167f2SHans de Goede 		break;
372ca3167f2SHans de Goede 	case INTEL_CHT_WC_XIAOMI_MIPAD2:
373ca3167f2SHans de Goede 		leds->leds[0].cdev.default_trigger = "bq27520-0-charging-blink-full-solid";
374ca3167f2SHans de Goede 		break;
375ca3167f2SHans de Goede 	case INTEL_CHT_WC_LENOVO_YOGABOOK1:
376ca3167f2SHans de Goede 		leds->leds[0].cdev.default_trigger = "bq27542-0-charging-blink-full-solid";
377ca3167f2SHans de Goede 		break;
378ca3167f2SHans de Goede 	default:
379ca3167f2SHans de Goede 		dev_warn(&pdev->dev, "Unknown model, no default charging trigger\n");
380ca3167f2SHans de Goede 		break;
381ca3167f2SHans de Goede 	}
382ca3167f2SHans de Goede 
383047da762SYauhen Kharuzhy 	for (i = 0; i < CHT_WC_LED_COUNT; i++) {
384047da762SYauhen Kharuzhy 		struct cht_wc_led *led = &leds->leds[i];
385047da762SYauhen Kharuzhy 
386047da762SYauhen Kharuzhy 		led->regs = &cht_wc_led_regs[i];
387047da762SYauhen Kharuzhy 		led->regmap = pmic->regmap;
388047da762SYauhen Kharuzhy 		mutex_init(&led->mutex);
389047da762SYauhen Kharuzhy 		led->cdev.name = cht_wc_leds_names[i];
390047da762SYauhen Kharuzhy 		led->cdev.brightness_set_blocking = cht_wc_leds_brightness_set;
391047da762SYauhen Kharuzhy 		led->cdev.brightness_get = cht_wc_leds_brightness_get;
392047da762SYauhen Kharuzhy 		led->cdev.blink_set = cht_wc_leds_blink_set;
3935b916aa7SHans de Goede 		led->cdev.pattern_set = cht_wc_leds_pattern_set;
3945b916aa7SHans de Goede 		led->cdev.pattern_clear = cht_wc_leds_pattern_clear;
395047da762SYauhen Kharuzhy 		led->cdev.max_brightness = 255;
396047da762SYauhen Kharuzhy 
397047da762SYauhen Kharuzhy 		ret = led_classdev_register(&pdev->dev, &led->cdev);
398047da762SYauhen Kharuzhy 		if (ret < 0)
399047da762SYauhen Kharuzhy 			return ret;
400047da762SYauhen Kharuzhy 	}
401047da762SYauhen Kharuzhy 
402047da762SYauhen Kharuzhy 	platform_set_drvdata(pdev, leds);
403047da762SYauhen Kharuzhy 	return 0;
404047da762SYauhen Kharuzhy }
405047da762SYauhen Kharuzhy 
cht_wc_leds_remove(struct platform_device * pdev)406047da762SYauhen Kharuzhy static void cht_wc_leds_remove(struct platform_device *pdev)
407047da762SYauhen Kharuzhy {
408047da762SYauhen Kharuzhy 	struct cht_wc_leds *leds = platform_get_drvdata(pdev);
409047da762SYauhen Kharuzhy 	int i;
410047da762SYauhen Kharuzhy 
411047da762SYauhen Kharuzhy 	for (i = 0; i < CHT_WC_LED_COUNT; i++)
412047da762SYauhen Kharuzhy 		led_classdev_unregister(&leds->leds[i].cdev);
413047da762SYauhen Kharuzhy 
414047da762SYauhen Kharuzhy 	/* Restore LED1 regs if hw-control was active else leave LED1 off */
415047da762SYauhen Kharuzhy 	if (!(leds->led1_initial_regs.ctrl & CHT_WC_LED1_SWCTL))
416047da762SYauhen Kharuzhy 		cht_wc_led_restore_regs(&leds->leds[0], &leds->led1_initial_regs);
417047da762SYauhen Kharuzhy }
418047da762SYauhen Kharuzhy 
cht_wc_leds_disable(struct platform_device * pdev)419047da762SYauhen Kharuzhy static void cht_wc_leds_disable(struct platform_device *pdev)
420047da762SYauhen Kharuzhy {
421047da762SYauhen Kharuzhy 	struct cht_wc_leds *leds = platform_get_drvdata(pdev);
422047da762SYauhen Kharuzhy 	int i;
423047da762SYauhen Kharuzhy 
424047da762SYauhen Kharuzhy 	for (i = 0; i < CHT_WC_LED_COUNT; i++)
425047da762SYauhen Kharuzhy 		cht_wc_leds_brightness_set(&leds->leds[i].cdev, 0);
426047da762SYauhen Kharuzhy 
427047da762SYauhen Kharuzhy 	/* Restore LED1 regs if hw-control was active else leave LED1 off */
428047da762SYauhen Kharuzhy 	if (!(leds->led1_initial_regs.ctrl & CHT_WC_LED1_SWCTL))
429047da762SYauhen Kharuzhy 		cht_wc_led_restore_regs(&leds->leds[0], &leds->led1_initial_regs);
430047da762SYauhen Kharuzhy }
431047da762SYauhen Kharuzhy 
4329697e2f0SHans de Goede /* On suspend save current settings and turn LEDs off */
cht_wc_leds_suspend(struct device * dev)4339697e2f0SHans de Goede static int cht_wc_leds_suspend(struct device *dev)
4349697e2f0SHans de Goede {
4359697e2f0SHans de Goede 	struct cht_wc_leds *leds = dev_get_drvdata(dev);
4369697e2f0SHans de Goede 	int i, ret;
4379697e2f0SHans de Goede 
4389697e2f0SHans de Goede 	for (i = 0; i < CHT_WC_LED_COUNT; i++) {
4399697e2f0SHans de Goede 		ret = cht_wc_led_save_regs(&leds->leds[i], &leds->leds[i].saved_regs);
4409697e2f0SHans de Goede 		if (ret < 0)
4419697e2f0SHans de Goede 			return ret;
4429697e2f0SHans de Goede 	}
4439697e2f0SHans de Goede 
4449697e2f0SHans de Goede 	cht_wc_leds_disable(to_platform_device(dev));
4459697e2f0SHans de Goede 	return 0;
4469697e2f0SHans de Goede }
4479697e2f0SHans de Goede 
4489697e2f0SHans de Goede /* On resume restore the saved settings */
cht_wc_leds_resume(struct device * dev)4499697e2f0SHans de Goede static int cht_wc_leds_resume(struct device *dev)
4509697e2f0SHans de Goede {
4519697e2f0SHans de Goede 	struct cht_wc_leds *leds = dev_get_drvdata(dev);
4529697e2f0SHans de Goede 	int i;
4539697e2f0SHans de Goede 
4549697e2f0SHans de Goede 	for (i = 0; i < CHT_WC_LED_COUNT; i++)
4559697e2f0SHans de Goede 		cht_wc_led_restore_regs(&leds->leds[i], &leds->leds[i].saved_regs);
4569697e2f0SHans de Goede 
4579697e2f0SHans de Goede 	return 0;
4589697e2f0SHans de Goede }
4599697e2f0SHans de Goede 
4609697e2f0SHans de Goede static DEFINE_SIMPLE_DEV_PM_OPS(cht_wc_leds_pm, cht_wc_leds_suspend, cht_wc_leds_resume);
4619697e2f0SHans de Goede 
462047da762SYauhen Kharuzhy static struct platform_driver cht_wc_leds_driver = {
463047da762SYauhen Kharuzhy 	.probe = cht_wc_leds_probe,
464047da762SYauhen Kharuzhy 	.remove_new = cht_wc_leds_remove,
465047da762SYauhen Kharuzhy 	.shutdown = cht_wc_leds_disable,
466047da762SYauhen Kharuzhy 	.driver = {
467047da762SYauhen Kharuzhy 		.name = "cht_wcove_leds",
4689697e2f0SHans de Goede 		.pm = pm_sleep_ptr(&cht_wc_leds_pm),
469047da762SYauhen Kharuzhy 	},
470047da762SYauhen Kharuzhy };
471047da762SYauhen Kharuzhy module_platform_driver(cht_wc_leds_driver);
472047da762SYauhen Kharuzhy 
473047da762SYauhen Kharuzhy MODULE_ALIAS("platform:cht_wcove_leds");
474047da762SYauhen Kharuzhy MODULE_DESCRIPTION("Intel Cherry Trail Whiskey Cove PMIC LEDs driver");
475047da762SYauhen Kharuzhy MODULE_AUTHOR("Yauhen Kharuzhy <jekhor@gmail.com>");
476047da762SYauhen Kharuzhy MODULE_LICENSE("GPL");
477