xref: /openbmc/linux/drivers/leds/leds-cht-wcove.c (revision 047da762)
1*047da762SYauhen Kharuzhy // SPDX-License-Identifier: GPL-2.0
2*047da762SYauhen Kharuzhy /*
3*047da762SYauhen Kharuzhy  * Driver for LEDs connected to the Intel Cherry Trail Whiskey Cove PMIC
4*047da762SYauhen Kharuzhy  *
5*047da762SYauhen Kharuzhy  * Copyright 2019 Yauhen Kharuzhy <jekhor@gmail.com>
6*047da762SYauhen Kharuzhy  * Copyright 2023 Hans de Goede <hansg@kernel.org>
7*047da762SYauhen Kharuzhy  *
8*047da762SYauhen Kharuzhy  * Register info comes from the Lenovo Yoga Book Android opensource code
9*047da762SYauhen Kharuzhy  * available from Lenovo. File lenovo_yb1_x90f_l_osc_201803.7z path in the 7z:
10*047da762SYauhen Kharuzhy  * YB1_source_code/kernel/cht/drivers/misc/charger_gp_led.c
11*047da762SYauhen Kharuzhy  */
12*047da762SYauhen Kharuzhy 
13*047da762SYauhen Kharuzhy #include <linux/kernel.h>
14*047da762SYauhen Kharuzhy #include <linux/leds.h>
15*047da762SYauhen Kharuzhy #include <linux/mfd/intel_soc_pmic.h>
16*047da762SYauhen Kharuzhy #include <linux/module.h>
17*047da762SYauhen Kharuzhy #include <linux/mod_devicetable.h>
18*047da762SYauhen Kharuzhy #include <linux/platform_device.h>
19*047da762SYauhen Kharuzhy #include <linux/regmap.h>
20*047da762SYauhen Kharuzhy 
21*047da762SYauhen Kharuzhy #define CHT_WC_LED1_CTRL		0x5e1f
22*047da762SYauhen Kharuzhy #define CHT_WC_LED1_FSM			0x5e20
23*047da762SYauhen Kharuzhy #define CHT_WC_LED1_PWM			0x5e21
24*047da762SYauhen Kharuzhy 
25*047da762SYauhen Kharuzhy #define CHT_WC_LED2_CTRL		0x4fdf
26*047da762SYauhen Kharuzhy #define CHT_WC_LED2_FSM			0x4fe0
27*047da762SYauhen Kharuzhy #define CHT_WC_LED2_PWM			0x4fe1
28*047da762SYauhen Kharuzhy 
29*047da762SYauhen Kharuzhy #define CHT_WC_LED1_SWCTL		BIT(0)		/* HW or SW control of charging led */
30*047da762SYauhen Kharuzhy #define CHT_WC_LED1_ON			BIT(1)
31*047da762SYauhen Kharuzhy 
32*047da762SYauhen Kharuzhy #define CHT_WC_LED2_ON			BIT(0)
33*047da762SYauhen Kharuzhy #define CHT_WC_LED_I_MA2_5		(2 << 2)	/* LED current limit */
34*047da762SYauhen Kharuzhy #define CHT_WC_LED_I_MASK		GENMASK(3, 2)	/* LED current limit mask */
35*047da762SYauhen Kharuzhy 
36*047da762SYauhen Kharuzhy #define CHT_WC_LED_F_1_4_HZ		(0 << 4)
37*047da762SYauhen Kharuzhy #define CHT_WC_LED_F_1_2_HZ		(1 << 4)
38*047da762SYauhen Kharuzhy #define CHT_WC_LED_F_1_HZ		(2 << 4)
39*047da762SYauhen Kharuzhy #define CHT_WC_LED_F_2_HZ		(3 << 4)
40*047da762SYauhen Kharuzhy #define CHT_WC_LED_F_MASK		GENMASK(5, 4)
41*047da762SYauhen Kharuzhy 
42*047da762SYauhen Kharuzhy #define CHT_WC_LED_EFF_OFF		(0 << 1)
43*047da762SYauhen Kharuzhy #define CHT_WC_LED_EFF_ON		(1 << 1)
44*047da762SYauhen Kharuzhy #define CHT_WC_LED_EFF_BLINKING		(2 << 1)
45*047da762SYauhen Kharuzhy #define CHT_WC_LED_EFF_BREATHING	(3 << 1)
46*047da762SYauhen Kharuzhy #define CHT_WC_LED_EFF_MASK		GENMASK(2, 1)
47*047da762SYauhen Kharuzhy 
48*047da762SYauhen Kharuzhy #define CHT_WC_LED_COUNT		2
49*047da762SYauhen Kharuzhy 
50*047da762SYauhen Kharuzhy struct cht_wc_led_regs {
51*047da762SYauhen Kharuzhy 	/* Register addresses */
52*047da762SYauhen Kharuzhy 	u16 ctrl;
53*047da762SYauhen Kharuzhy 	u16 fsm;
54*047da762SYauhen Kharuzhy 	u16 pwm;
55*047da762SYauhen Kharuzhy 	/* Mask + values for turning the LED on/off */
56*047da762SYauhen Kharuzhy 	u8 on_off_mask;
57*047da762SYauhen Kharuzhy 	u8 on_val;
58*047da762SYauhen Kharuzhy 	u8 off_val;
59*047da762SYauhen Kharuzhy };
60*047da762SYauhen Kharuzhy 
61*047da762SYauhen Kharuzhy struct cht_wc_led_saved_regs {
62*047da762SYauhen Kharuzhy 	unsigned int ctrl;
63*047da762SYauhen Kharuzhy 	unsigned int fsm;
64*047da762SYauhen Kharuzhy 	unsigned int pwm;
65*047da762SYauhen Kharuzhy };
66*047da762SYauhen Kharuzhy 
67*047da762SYauhen Kharuzhy struct cht_wc_led {
68*047da762SYauhen Kharuzhy 	struct led_classdev cdev;
69*047da762SYauhen Kharuzhy 	const struct cht_wc_led_regs *regs;
70*047da762SYauhen Kharuzhy 	struct regmap *regmap;
71*047da762SYauhen Kharuzhy 	struct mutex mutex;
72*047da762SYauhen Kharuzhy };
73*047da762SYauhen Kharuzhy 
74*047da762SYauhen Kharuzhy struct cht_wc_leds {
75*047da762SYauhen Kharuzhy 	struct cht_wc_led leds[CHT_WC_LED_COUNT];
76*047da762SYauhen Kharuzhy 	/* Saved LED1 initial register values */
77*047da762SYauhen Kharuzhy 	struct cht_wc_led_saved_regs led1_initial_regs;
78*047da762SYauhen Kharuzhy };
79*047da762SYauhen Kharuzhy 
80*047da762SYauhen Kharuzhy static const struct cht_wc_led_regs cht_wc_led_regs[CHT_WC_LED_COUNT] = {
81*047da762SYauhen Kharuzhy 	{
82*047da762SYauhen Kharuzhy 		.ctrl		= CHT_WC_LED1_CTRL,
83*047da762SYauhen Kharuzhy 		.fsm		= CHT_WC_LED1_FSM,
84*047da762SYauhen Kharuzhy 		.pwm		= CHT_WC_LED1_PWM,
85*047da762SYauhen Kharuzhy 		.on_off_mask	= CHT_WC_LED1_SWCTL | CHT_WC_LED1_ON,
86*047da762SYauhen Kharuzhy 		.on_val		= CHT_WC_LED1_SWCTL | CHT_WC_LED1_ON,
87*047da762SYauhen Kharuzhy 		.off_val	= CHT_WC_LED1_SWCTL,
88*047da762SYauhen Kharuzhy 	},
89*047da762SYauhen Kharuzhy 	{
90*047da762SYauhen Kharuzhy 		.ctrl		= CHT_WC_LED2_CTRL,
91*047da762SYauhen Kharuzhy 		.fsm		= CHT_WC_LED2_FSM,
92*047da762SYauhen Kharuzhy 		.pwm		= CHT_WC_LED2_PWM,
93*047da762SYauhen Kharuzhy 		.on_off_mask	= CHT_WC_LED2_ON,
94*047da762SYauhen Kharuzhy 		.on_val		= CHT_WC_LED2_ON,
95*047da762SYauhen Kharuzhy 		.off_val	= 0,
96*047da762SYauhen Kharuzhy 	},
97*047da762SYauhen Kharuzhy };
98*047da762SYauhen Kharuzhy 
99*047da762SYauhen Kharuzhy static const char * const cht_wc_leds_names[CHT_WC_LED_COUNT] = {
100*047da762SYauhen Kharuzhy 	"platform::" LED_FUNCTION_CHARGING,
101*047da762SYauhen Kharuzhy 	"platform::" LED_FUNCTION_INDICATOR,
102*047da762SYauhen Kharuzhy };
103*047da762SYauhen Kharuzhy 
104*047da762SYauhen Kharuzhy static int cht_wc_leds_brightness_set(struct led_classdev *cdev,
105*047da762SYauhen Kharuzhy 				      enum led_brightness value)
106*047da762SYauhen Kharuzhy {
107*047da762SYauhen Kharuzhy 	struct cht_wc_led *led = container_of(cdev, struct cht_wc_led, cdev);
108*047da762SYauhen Kharuzhy 	int ret;
109*047da762SYauhen Kharuzhy 
110*047da762SYauhen Kharuzhy 	mutex_lock(&led->mutex);
111*047da762SYauhen Kharuzhy 
112*047da762SYauhen Kharuzhy 	if (!value) {
113*047da762SYauhen Kharuzhy 		ret = regmap_update_bits(led->regmap, led->regs->ctrl,
114*047da762SYauhen Kharuzhy 					 led->regs->on_off_mask, led->regs->off_val);
115*047da762SYauhen Kharuzhy 		if (ret < 0) {
116*047da762SYauhen Kharuzhy 			dev_err(cdev->dev, "Failed to turn off: %d\n", ret);
117*047da762SYauhen Kharuzhy 			goto out;
118*047da762SYauhen Kharuzhy 		}
119*047da762SYauhen Kharuzhy 
120*047da762SYauhen Kharuzhy 		/* Disable HW blinking */
121*047da762SYauhen Kharuzhy 		ret = regmap_update_bits(led->regmap, led->regs->fsm,
122*047da762SYauhen Kharuzhy 					 CHT_WC_LED_EFF_MASK, CHT_WC_LED_EFF_ON);
123*047da762SYauhen Kharuzhy 		if (ret < 0)
124*047da762SYauhen Kharuzhy 			dev_err(cdev->dev, "Failed to update LED FSM reg: %d\n", ret);
125*047da762SYauhen Kharuzhy 	} else {
126*047da762SYauhen Kharuzhy 		ret = regmap_write(led->regmap, led->regs->pwm, value);
127*047da762SYauhen Kharuzhy 		if (ret < 0) {
128*047da762SYauhen Kharuzhy 			dev_err(cdev->dev, "Failed to set brightness: %d\n", ret);
129*047da762SYauhen Kharuzhy 			goto out;
130*047da762SYauhen Kharuzhy 		}
131*047da762SYauhen Kharuzhy 
132*047da762SYauhen Kharuzhy 		ret = regmap_update_bits(led->regmap, led->regs->ctrl,
133*047da762SYauhen Kharuzhy 					 led->regs->on_off_mask, led->regs->on_val);
134*047da762SYauhen Kharuzhy 		if (ret < 0)
135*047da762SYauhen Kharuzhy 			dev_err(cdev->dev, "Failed to turn on: %d\n", ret);
136*047da762SYauhen Kharuzhy 	}
137*047da762SYauhen Kharuzhy out:
138*047da762SYauhen Kharuzhy 	mutex_unlock(&led->mutex);
139*047da762SYauhen Kharuzhy 	return ret;
140*047da762SYauhen Kharuzhy }
141*047da762SYauhen Kharuzhy 
142*047da762SYauhen Kharuzhy enum led_brightness cht_wc_leds_brightness_get(struct led_classdev *cdev)
143*047da762SYauhen Kharuzhy {
144*047da762SYauhen Kharuzhy 	struct cht_wc_led *led = container_of(cdev, struct cht_wc_led, cdev);
145*047da762SYauhen Kharuzhy 	unsigned int val;
146*047da762SYauhen Kharuzhy 	int ret;
147*047da762SYauhen Kharuzhy 
148*047da762SYauhen Kharuzhy 	mutex_lock(&led->mutex);
149*047da762SYauhen Kharuzhy 
150*047da762SYauhen Kharuzhy 	ret = regmap_read(led->regmap, led->regs->ctrl, &val);
151*047da762SYauhen Kharuzhy 	if (ret < 0) {
152*047da762SYauhen Kharuzhy 		dev_err(cdev->dev, "Failed to read LED CTRL reg: %d\n", ret);
153*047da762SYauhen Kharuzhy 		ret = 0;
154*047da762SYauhen Kharuzhy 		goto done;
155*047da762SYauhen Kharuzhy 	}
156*047da762SYauhen Kharuzhy 
157*047da762SYauhen Kharuzhy 	val &= led->regs->on_off_mask;
158*047da762SYauhen Kharuzhy 	if (val != led->regs->on_val) {
159*047da762SYauhen Kharuzhy 		ret = 0;
160*047da762SYauhen Kharuzhy 		goto done;
161*047da762SYauhen Kharuzhy 	}
162*047da762SYauhen Kharuzhy 
163*047da762SYauhen Kharuzhy 	ret = regmap_read(led->regmap, led->regs->pwm, &val);
164*047da762SYauhen Kharuzhy 	if (ret < 0) {
165*047da762SYauhen Kharuzhy 		dev_err(cdev->dev, "Failed to read LED PWM reg: %d\n", ret);
166*047da762SYauhen Kharuzhy 		ret = 0;
167*047da762SYauhen Kharuzhy 		goto done;
168*047da762SYauhen Kharuzhy 	}
169*047da762SYauhen Kharuzhy 
170*047da762SYauhen Kharuzhy 	ret = val;
171*047da762SYauhen Kharuzhy done:
172*047da762SYauhen Kharuzhy 	mutex_unlock(&led->mutex);
173*047da762SYauhen Kharuzhy 
174*047da762SYauhen Kharuzhy 	return ret;
175*047da762SYauhen Kharuzhy }
176*047da762SYauhen Kharuzhy 
177*047da762SYauhen Kharuzhy /* Return blinking period for given CTRL reg value */
178*047da762SYauhen Kharuzhy static unsigned long cht_wc_leds_get_period(int ctrl)
179*047da762SYauhen Kharuzhy {
180*047da762SYauhen Kharuzhy 	ctrl &= CHT_WC_LED_F_MASK;
181*047da762SYauhen Kharuzhy 
182*047da762SYauhen Kharuzhy 	switch (ctrl) {
183*047da762SYauhen Kharuzhy 	case CHT_WC_LED_F_1_4_HZ:
184*047da762SYauhen Kharuzhy 		return 1000 * 4;
185*047da762SYauhen Kharuzhy 	case CHT_WC_LED_F_1_2_HZ:
186*047da762SYauhen Kharuzhy 		return 1000 * 2;
187*047da762SYauhen Kharuzhy 	case CHT_WC_LED_F_1_HZ:
188*047da762SYauhen Kharuzhy 		return 1000;
189*047da762SYauhen Kharuzhy 	case CHT_WC_LED_F_2_HZ:
190*047da762SYauhen Kharuzhy 		return 1000 / 2;
191*047da762SYauhen Kharuzhy 	};
192*047da762SYauhen Kharuzhy 
193*047da762SYauhen Kharuzhy 	return 0;
194*047da762SYauhen Kharuzhy }
195*047da762SYauhen Kharuzhy 
196*047da762SYauhen Kharuzhy /*
197*047da762SYauhen Kharuzhy  * Find suitable hardware blink mode for given period.
198*047da762SYauhen Kharuzhy  * period < 750 ms - select 2 HZ
199*047da762SYauhen Kharuzhy  * 750 ms <= period < 1500 ms - select 1 HZ
200*047da762SYauhen Kharuzhy  * 1500 ms <= period < 3000 ms - select 1/2 HZ
201*047da762SYauhen Kharuzhy  * 3000 ms <= period < 5000 ms - select 1/4 HZ
202*047da762SYauhen Kharuzhy  * 5000 ms <= period - return -1
203*047da762SYauhen Kharuzhy  */
204*047da762SYauhen Kharuzhy static int cht_wc_leds_find_freq(unsigned long period)
205*047da762SYauhen Kharuzhy {
206*047da762SYauhen Kharuzhy 	if (period < 750)
207*047da762SYauhen Kharuzhy 		return CHT_WC_LED_F_2_HZ;
208*047da762SYauhen Kharuzhy 	else if (period < 1500)
209*047da762SYauhen Kharuzhy 		return CHT_WC_LED_F_1_HZ;
210*047da762SYauhen Kharuzhy 	else if (period < 3000)
211*047da762SYauhen Kharuzhy 		return CHT_WC_LED_F_1_2_HZ;
212*047da762SYauhen Kharuzhy 	else if (period < 5000)
213*047da762SYauhen Kharuzhy 		return CHT_WC_LED_F_1_4_HZ;
214*047da762SYauhen Kharuzhy 	else
215*047da762SYauhen Kharuzhy 		return -1;
216*047da762SYauhen Kharuzhy }
217*047da762SYauhen Kharuzhy 
218*047da762SYauhen Kharuzhy static int cht_wc_leds_blink_set(struct led_classdev *cdev,
219*047da762SYauhen Kharuzhy 				 unsigned long *delay_on,
220*047da762SYauhen Kharuzhy 				 unsigned long *delay_off)
221*047da762SYauhen Kharuzhy {
222*047da762SYauhen Kharuzhy 	struct cht_wc_led *led = container_of(cdev, struct cht_wc_led, cdev);
223*047da762SYauhen Kharuzhy 	unsigned int ctrl;
224*047da762SYauhen Kharuzhy 	int ret;
225*047da762SYauhen Kharuzhy 
226*047da762SYauhen Kharuzhy 	mutex_lock(&led->mutex);
227*047da762SYauhen Kharuzhy 
228*047da762SYauhen Kharuzhy 	/* Blink with 1 Hz as default if nothing specified */
229*047da762SYauhen Kharuzhy 	if (!*delay_on && !*delay_off)
230*047da762SYauhen Kharuzhy 		*delay_on = *delay_off = 500;
231*047da762SYauhen Kharuzhy 
232*047da762SYauhen Kharuzhy 	ctrl = cht_wc_leds_find_freq(*delay_on + *delay_off);
233*047da762SYauhen Kharuzhy 	if (ctrl < 0) {
234*047da762SYauhen Kharuzhy 		/* Disable HW blinking */
235*047da762SYauhen Kharuzhy 		ret = regmap_update_bits(led->regmap, led->regs->fsm,
236*047da762SYauhen Kharuzhy 					 CHT_WC_LED_EFF_MASK, CHT_WC_LED_EFF_ON);
237*047da762SYauhen Kharuzhy 		if (ret < 0)
238*047da762SYauhen Kharuzhy 			dev_err(cdev->dev, "Failed to update LED FSM reg: %d\n", ret);
239*047da762SYauhen Kharuzhy 
240*047da762SYauhen Kharuzhy 		/* Fallback to software timer */
241*047da762SYauhen Kharuzhy 		*delay_on = *delay_off = 0;
242*047da762SYauhen Kharuzhy 		ret = -EINVAL;
243*047da762SYauhen Kharuzhy 		goto done;
244*047da762SYauhen Kharuzhy 	}
245*047da762SYauhen Kharuzhy 
246*047da762SYauhen Kharuzhy 	ret = regmap_update_bits(led->regmap, led->regs->fsm,
247*047da762SYauhen Kharuzhy 				 CHT_WC_LED_EFF_MASK, CHT_WC_LED_EFF_BLINKING);
248*047da762SYauhen Kharuzhy 	if (ret < 0)
249*047da762SYauhen Kharuzhy 		dev_err(cdev->dev, "Failed to update LED FSM reg: %d\n", ret);
250*047da762SYauhen Kharuzhy 
251*047da762SYauhen Kharuzhy 	/* Set the frequency and make sure the LED is on */
252*047da762SYauhen Kharuzhy 	ret = regmap_update_bits(led->regmap, led->regs->ctrl,
253*047da762SYauhen Kharuzhy 				 CHT_WC_LED_F_MASK | led->regs->on_off_mask,
254*047da762SYauhen Kharuzhy 				 ctrl | led->regs->on_val);
255*047da762SYauhen Kharuzhy 	if (ret < 0)
256*047da762SYauhen Kharuzhy 		dev_err(cdev->dev, "Failed to update LED CTRL reg: %d\n", ret);
257*047da762SYauhen Kharuzhy 
258*047da762SYauhen Kharuzhy 	*delay_off = *delay_on = cht_wc_leds_get_period(ctrl) / 2;
259*047da762SYauhen Kharuzhy 
260*047da762SYauhen Kharuzhy done:
261*047da762SYauhen Kharuzhy 	mutex_unlock(&led->mutex);
262*047da762SYauhen Kharuzhy 
263*047da762SYauhen Kharuzhy 	return ret;
264*047da762SYauhen Kharuzhy }
265*047da762SYauhen Kharuzhy 
266*047da762SYauhen Kharuzhy static int cht_wc_led_save_regs(struct cht_wc_led *led,
267*047da762SYauhen Kharuzhy 				struct cht_wc_led_saved_regs *saved_regs)
268*047da762SYauhen Kharuzhy {
269*047da762SYauhen Kharuzhy 	int ret;
270*047da762SYauhen Kharuzhy 
271*047da762SYauhen Kharuzhy 	ret = regmap_read(led->regmap, led->regs->ctrl, &saved_regs->ctrl);
272*047da762SYauhen Kharuzhy 	if (ret < 0)
273*047da762SYauhen Kharuzhy 		return ret;
274*047da762SYauhen Kharuzhy 
275*047da762SYauhen Kharuzhy 	ret = regmap_read(led->regmap, led->regs->fsm, &saved_regs->fsm);
276*047da762SYauhen Kharuzhy 	if (ret < 0)
277*047da762SYauhen Kharuzhy 		return ret;
278*047da762SYauhen Kharuzhy 
279*047da762SYauhen Kharuzhy 	return regmap_read(led->regmap, led->regs->pwm, &saved_regs->pwm);
280*047da762SYauhen Kharuzhy }
281*047da762SYauhen Kharuzhy 
282*047da762SYauhen Kharuzhy static void cht_wc_led_restore_regs(struct cht_wc_led *led,
283*047da762SYauhen Kharuzhy 				    const struct cht_wc_led_saved_regs *saved_regs)
284*047da762SYauhen Kharuzhy {
285*047da762SYauhen Kharuzhy 	regmap_write(led->regmap, led->regs->ctrl, saved_regs->ctrl);
286*047da762SYauhen Kharuzhy 	regmap_write(led->regmap, led->regs->fsm, saved_regs->fsm);
287*047da762SYauhen Kharuzhy 	regmap_write(led->regmap, led->regs->pwm, saved_regs->pwm);
288*047da762SYauhen Kharuzhy }
289*047da762SYauhen Kharuzhy 
290*047da762SYauhen Kharuzhy static int cht_wc_leds_probe(struct platform_device *pdev)
291*047da762SYauhen Kharuzhy {
292*047da762SYauhen Kharuzhy 	struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
293*047da762SYauhen Kharuzhy 	struct cht_wc_leds *leds;
294*047da762SYauhen Kharuzhy 	int ret;
295*047da762SYauhen Kharuzhy 	int i;
296*047da762SYauhen Kharuzhy 
297*047da762SYauhen Kharuzhy 	/*
298*047da762SYauhen Kharuzhy 	 * On the Lenovo Yoga Tab 3 the LED1 driver output is actually
299*047da762SYauhen Kharuzhy 	 * connected to a haptic feedback motor rather then a LED.
300*047da762SYauhen Kharuzhy 	 * So do not register a LED classdev there (LED2 is unused).
301*047da762SYauhen Kharuzhy 	 */
302*047da762SYauhen Kharuzhy 	if (pmic->cht_wc_model == INTEL_CHT_WC_LENOVO_YT3_X90)
303*047da762SYauhen Kharuzhy 		return -ENODEV;
304*047da762SYauhen Kharuzhy 
305*047da762SYauhen Kharuzhy 	leds = devm_kzalloc(&pdev->dev, sizeof(*leds), GFP_KERNEL);
306*047da762SYauhen Kharuzhy 	if (!leds)
307*047da762SYauhen Kharuzhy 		return -ENOMEM;
308*047da762SYauhen Kharuzhy 
309*047da762SYauhen Kharuzhy 	/*
310*047da762SYauhen Kharuzhy 	 * LED1 might be in hw-controlled mode when this driver gets loaded; and
311*047da762SYauhen Kharuzhy 	 * since the PMIC is always powered by the battery any changes made are
312*047da762SYauhen Kharuzhy 	 * permanent. Save LED1 regs to restore them on remove() or shutdown().
313*047da762SYauhen Kharuzhy 	 */
314*047da762SYauhen Kharuzhy 	leds->leds[0].regs = &cht_wc_led_regs[0];
315*047da762SYauhen Kharuzhy 	leds->leds[0].regmap = pmic->regmap;
316*047da762SYauhen Kharuzhy 	ret = cht_wc_led_save_regs(&leds->leds[0], &leds->led1_initial_regs);
317*047da762SYauhen Kharuzhy 	if (ret < 0)
318*047da762SYauhen Kharuzhy 		return ret;
319*047da762SYauhen Kharuzhy 
320*047da762SYauhen Kharuzhy 	for (i = 0; i < CHT_WC_LED_COUNT; i++) {
321*047da762SYauhen Kharuzhy 		struct cht_wc_led *led = &leds->leds[i];
322*047da762SYauhen Kharuzhy 
323*047da762SYauhen Kharuzhy 		led->regs = &cht_wc_led_regs[i];
324*047da762SYauhen Kharuzhy 		led->regmap = pmic->regmap;
325*047da762SYauhen Kharuzhy 		mutex_init(&led->mutex);
326*047da762SYauhen Kharuzhy 		led->cdev.name = cht_wc_leds_names[i];
327*047da762SYauhen Kharuzhy 		led->cdev.brightness_set_blocking = cht_wc_leds_brightness_set;
328*047da762SYauhen Kharuzhy 		led->cdev.brightness_get = cht_wc_leds_brightness_get;
329*047da762SYauhen Kharuzhy 		led->cdev.blink_set = cht_wc_leds_blink_set;
330*047da762SYauhen Kharuzhy 		led->cdev.max_brightness = 255;
331*047da762SYauhen Kharuzhy 
332*047da762SYauhen Kharuzhy 		ret = led_classdev_register(&pdev->dev, &led->cdev);
333*047da762SYauhen Kharuzhy 		if (ret < 0)
334*047da762SYauhen Kharuzhy 			return ret;
335*047da762SYauhen Kharuzhy 	}
336*047da762SYauhen Kharuzhy 
337*047da762SYauhen Kharuzhy 	platform_set_drvdata(pdev, leds);
338*047da762SYauhen Kharuzhy 	return 0;
339*047da762SYauhen Kharuzhy }
340*047da762SYauhen Kharuzhy 
341*047da762SYauhen Kharuzhy static void cht_wc_leds_remove(struct platform_device *pdev)
342*047da762SYauhen Kharuzhy {
343*047da762SYauhen Kharuzhy 	struct cht_wc_leds *leds = platform_get_drvdata(pdev);
344*047da762SYauhen Kharuzhy 	int i;
345*047da762SYauhen Kharuzhy 
346*047da762SYauhen Kharuzhy 	for (i = 0; i < CHT_WC_LED_COUNT; i++)
347*047da762SYauhen Kharuzhy 		led_classdev_unregister(&leds->leds[i].cdev);
348*047da762SYauhen Kharuzhy 
349*047da762SYauhen Kharuzhy 	/* Restore LED1 regs if hw-control was active else leave LED1 off */
350*047da762SYauhen Kharuzhy 	if (!(leds->led1_initial_regs.ctrl & CHT_WC_LED1_SWCTL))
351*047da762SYauhen Kharuzhy 		cht_wc_led_restore_regs(&leds->leds[0], &leds->led1_initial_regs);
352*047da762SYauhen Kharuzhy }
353*047da762SYauhen Kharuzhy 
354*047da762SYauhen Kharuzhy static void cht_wc_leds_disable(struct platform_device *pdev)
355*047da762SYauhen Kharuzhy {
356*047da762SYauhen Kharuzhy 	struct cht_wc_leds *leds = platform_get_drvdata(pdev);
357*047da762SYauhen Kharuzhy 	int i;
358*047da762SYauhen Kharuzhy 
359*047da762SYauhen Kharuzhy 	for (i = 0; i < CHT_WC_LED_COUNT; i++)
360*047da762SYauhen Kharuzhy 		cht_wc_leds_brightness_set(&leds->leds[i].cdev, 0);
361*047da762SYauhen Kharuzhy 
362*047da762SYauhen Kharuzhy 	/* Restore LED1 regs if hw-control was active else leave LED1 off */
363*047da762SYauhen Kharuzhy 	if (!(leds->led1_initial_regs.ctrl & CHT_WC_LED1_SWCTL))
364*047da762SYauhen Kharuzhy 		cht_wc_led_restore_regs(&leds->leds[0], &leds->led1_initial_regs);
365*047da762SYauhen Kharuzhy }
366*047da762SYauhen Kharuzhy 
367*047da762SYauhen Kharuzhy static struct platform_driver cht_wc_leds_driver = {
368*047da762SYauhen Kharuzhy 	.probe = cht_wc_leds_probe,
369*047da762SYauhen Kharuzhy 	.remove_new = cht_wc_leds_remove,
370*047da762SYauhen Kharuzhy 	.shutdown = cht_wc_leds_disable,
371*047da762SYauhen Kharuzhy 	.driver = {
372*047da762SYauhen Kharuzhy 		.name = "cht_wcove_leds",
373*047da762SYauhen Kharuzhy 	},
374*047da762SYauhen Kharuzhy };
375*047da762SYauhen Kharuzhy module_platform_driver(cht_wc_leds_driver);
376*047da762SYauhen Kharuzhy 
377*047da762SYauhen Kharuzhy MODULE_ALIAS("platform:cht_wcove_leds");
378*047da762SYauhen Kharuzhy MODULE_DESCRIPTION("Intel Cherry Trail Whiskey Cove PMIC LEDs driver");
379*047da762SYauhen Kharuzhy MODULE_AUTHOR("Yauhen Kharuzhy <jekhor@gmail.com>");
380*047da762SYauhen Kharuzhy MODULE_LICENSE("GPL");
381