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;
193dda96847SJiapeng 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
397*c2c3949cSJoe Hattori ret = devm_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
410047da762SYauhen Kharuzhy /* Restore LED1 regs if hw-control was active else leave LED1 off */
411047da762SYauhen Kharuzhy if (!(leds->led1_initial_regs.ctrl & CHT_WC_LED1_SWCTL))
412047da762SYauhen Kharuzhy cht_wc_led_restore_regs(&leds->leds[0], &leds->led1_initial_regs);
413047da762SYauhen Kharuzhy }
414047da762SYauhen Kharuzhy
cht_wc_leds_disable(struct platform_device * pdev)415047da762SYauhen Kharuzhy static void cht_wc_leds_disable(struct platform_device *pdev)
416047da762SYauhen Kharuzhy {
417047da762SYauhen Kharuzhy struct cht_wc_leds *leds = platform_get_drvdata(pdev);
418047da762SYauhen Kharuzhy int i;
419047da762SYauhen Kharuzhy
420047da762SYauhen Kharuzhy for (i = 0; i < CHT_WC_LED_COUNT; i++)
421047da762SYauhen Kharuzhy cht_wc_leds_brightness_set(&leds->leds[i].cdev, 0);
422047da762SYauhen Kharuzhy
423047da762SYauhen Kharuzhy /* Restore LED1 regs if hw-control was active else leave LED1 off */
424047da762SYauhen Kharuzhy if (!(leds->led1_initial_regs.ctrl & CHT_WC_LED1_SWCTL))
425047da762SYauhen Kharuzhy cht_wc_led_restore_regs(&leds->leds[0], &leds->led1_initial_regs);
426047da762SYauhen Kharuzhy }
427047da762SYauhen Kharuzhy
4289697e2f0SHans de Goede /* On suspend save current settings and turn LEDs off */
cht_wc_leds_suspend(struct device * dev)4299697e2f0SHans de Goede static int cht_wc_leds_suspend(struct device *dev)
4309697e2f0SHans de Goede {
4319697e2f0SHans de Goede struct cht_wc_leds *leds = dev_get_drvdata(dev);
4329697e2f0SHans de Goede int i, ret;
4339697e2f0SHans de Goede
4349697e2f0SHans de Goede for (i = 0; i < CHT_WC_LED_COUNT; i++) {
4359697e2f0SHans de Goede ret = cht_wc_led_save_regs(&leds->leds[i], &leds->leds[i].saved_regs);
4369697e2f0SHans de Goede if (ret < 0)
4379697e2f0SHans de Goede return ret;
4389697e2f0SHans de Goede }
4399697e2f0SHans de Goede
4409697e2f0SHans de Goede cht_wc_leds_disable(to_platform_device(dev));
4419697e2f0SHans de Goede return 0;
4429697e2f0SHans de Goede }
4439697e2f0SHans de Goede
4449697e2f0SHans de Goede /* On resume restore the saved settings */
cht_wc_leds_resume(struct device * dev)4459697e2f0SHans de Goede static int cht_wc_leds_resume(struct device *dev)
4469697e2f0SHans de Goede {
4479697e2f0SHans de Goede struct cht_wc_leds *leds = dev_get_drvdata(dev);
4489697e2f0SHans de Goede int i;
4499697e2f0SHans de Goede
4509697e2f0SHans de Goede for (i = 0; i < CHT_WC_LED_COUNT; i++)
4519697e2f0SHans de Goede cht_wc_led_restore_regs(&leds->leds[i], &leds->leds[i].saved_regs);
4529697e2f0SHans de Goede
4539697e2f0SHans de Goede return 0;
4549697e2f0SHans de Goede }
4559697e2f0SHans de Goede
4569697e2f0SHans de Goede static DEFINE_SIMPLE_DEV_PM_OPS(cht_wc_leds_pm, cht_wc_leds_suspend, cht_wc_leds_resume);
4579697e2f0SHans de Goede
458047da762SYauhen Kharuzhy static struct platform_driver cht_wc_leds_driver = {
459047da762SYauhen Kharuzhy .probe = cht_wc_leds_probe,
460047da762SYauhen Kharuzhy .remove_new = cht_wc_leds_remove,
461047da762SYauhen Kharuzhy .shutdown = cht_wc_leds_disable,
462047da762SYauhen Kharuzhy .driver = {
463047da762SYauhen Kharuzhy .name = "cht_wcove_leds",
4649697e2f0SHans de Goede .pm = pm_sleep_ptr(&cht_wc_leds_pm),
465047da762SYauhen Kharuzhy },
466047da762SYauhen Kharuzhy };
467047da762SYauhen Kharuzhy module_platform_driver(cht_wc_leds_driver);
468047da762SYauhen Kharuzhy
469047da762SYauhen Kharuzhy MODULE_ALIAS("platform:cht_wcove_leds");
470047da762SYauhen Kharuzhy MODULE_DESCRIPTION("Intel Cherry Trail Whiskey Cove PMIC LEDs driver");
471047da762SYauhen Kharuzhy MODULE_AUTHOR("Yauhen Kharuzhy <jekhor@gmail.com>");
472047da762SYauhen Kharuzhy MODULE_LICENSE("GPL");
473