1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * PMU driver for Wolfson Microelectronics wm831x PMICs |
4 | * |
5 | * Copyright 2009 Wolfson Microelectronics PLC. |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/err.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/power_supply.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/usb/phy.h> |
14 | |
15 | #include <linux/mfd/wm831x/core.h> |
16 | #include <linux/mfd/wm831x/auxadc.h> |
17 | #include <linux/mfd/wm831x/pmu.h> |
18 | #include <linux/mfd/wm831x/pdata.h> |
19 | |
20 | struct wm831x_power { |
21 | struct wm831x *wm831x; |
22 | struct power_supply *wall; |
23 | struct power_supply *usb; |
24 | struct power_supply *battery; |
25 | struct power_supply_desc wall_desc; |
26 | struct power_supply_desc usb_desc; |
27 | struct power_supply_desc battery_desc; |
28 | char wall_name[20]; |
29 | char usb_name[20]; |
30 | char battery_name[20]; |
31 | bool have_battery; |
32 | struct usb_phy *usb_phy; |
33 | struct notifier_block usb_notify; |
34 | }; |
35 | |
36 | static int wm831x_power_check_online(struct wm831x *wm831x, int supply, |
37 | union power_supply_propval *val) |
38 | { |
39 | int ret; |
40 | |
41 | ret = wm831x_reg_read(wm831x, WM831X_SYSTEM_STATUS); |
42 | if (ret < 0) |
43 | return ret; |
44 | |
45 | if (ret & supply) |
46 | val->intval = 1; |
47 | else |
48 | val->intval = 0; |
49 | |
50 | return 0; |
51 | } |
52 | |
53 | static int wm831x_power_read_voltage(struct wm831x *wm831x, |
54 | enum wm831x_auxadc src, |
55 | union power_supply_propval *val) |
56 | { |
57 | int ret; |
58 | |
59 | ret = wm831x_auxadc_read_uv(wm831x, input: src); |
60 | if (ret >= 0) |
61 | val->intval = ret; |
62 | |
63 | return ret; |
64 | } |
65 | |
66 | /********************************************************************* |
67 | * WALL Power |
68 | *********************************************************************/ |
69 | static int wm831x_wall_get_prop(struct power_supply *psy, |
70 | enum power_supply_property psp, |
71 | union power_supply_propval *val) |
72 | { |
73 | struct wm831x_power *wm831x_power = dev_get_drvdata(dev: psy->dev.parent); |
74 | struct wm831x *wm831x = wm831x_power->wm831x; |
75 | int ret = 0; |
76 | |
77 | switch (psp) { |
78 | case POWER_SUPPLY_PROP_ONLINE: |
79 | ret = wm831x_power_check_online(wm831x, WM831X_PWR_WALL, val); |
80 | break; |
81 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
82 | ret = wm831x_power_read_voltage(wm831x, src: WM831X_AUX_WALL, val); |
83 | break; |
84 | default: |
85 | ret = -EINVAL; |
86 | break; |
87 | } |
88 | |
89 | return ret; |
90 | } |
91 | |
92 | static enum power_supply_property wm831x_wall_props[] = { |
93 | POWER_SUPPLY_PROP_ONLINE, |
94 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
95 | }; |
96 | |
97 | /********************************************************************* |
98 | * USB Power |
99 | *********************************************************************/ |
100 | static int wm831x_usb_get_prop(struct power_supply *psy, |
101 | enum power_supply_property psp, |
102 | union power_supply_propval *val) |
103 | { |
104 | struct wm831x_power *wm831x_power = dev_get_drvdata(dev: psy->dev.parent); |
105 | struct wm831x *wm831x = wm831x_power->wm831x; |
106 | int ret = 0; |
107 | |
108 | switch (psp) { |
109 | case POWER_SUPPLY_PROP_ONLINE: |
110 | ret = wm831x_power_check_online(wm831x, WM831X_PWR_USB, val); |
111 | break; |
112 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
113 | ret = wm831x_power_read_voltage(wm831x, src: WM831X_AUX_USB, val); |
114 | break; |
115 | default: |
116 | ret = -EINVAL; |
117 | break; |
118 | } |
119 | |
120 | return ret; |
121 | } |
122 | |
123 | static enum power_supply_property wm831x_usb_props[] = { |
124 | POWER_SUPPLY_PROP_ONLINE, |
125 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
126 | }; |
127 | |
128 | /* In milliamps */ |
129 | static const unsigned int wm831x_usb_limits[] = { |
130 | 0, |
131 | 2, |
132 | 100, |
133 | 500, |
134 | 900, |
135 | 1500, |
136 | 1800, |
137 | 550, |
138 | }; |
139 | |
140 | static int wm831x_usb_limit_change(struct notifier_block *nb, |
141 | unsigned long limit, void *data) |
142 | { |
143 | struct wm831x_power *wm831x_power = container_of(nb, |
144 | struct wm831x_power, |
145 | usb_notify); |
146 | unsigned int i, best; |
147 | |
148 | /* Find the highest supported limit */ |
149 | best = 0; |
150 | for (i = 0; i < ARRAY_SIZE(wm831x_usb_limits); i++) { |
151 | if (limit >= wm831x_usb_limits[i] && |
152 | wm831x_usb_limits[best] < wm831x_usb_limits[i]) |
153 | best = i; |
154 | } |
155 | |
156 | dev_dbg(wm831x_power->wm831x->dev, |
157 | "Limiting USB current to %umA" , wm831x_usb_limits[best]); |
158 | |
159 | wm831x_set_bits(wm831x: wm831x_power->wm831x, WM831X_POWER_STATE, |
160 | WM831X_USB_ILIM_MASK, val: best); |
161 | |
162 | return 0; |
163 | } |
164 | |
165 | /********************************************************************* |
166 | * Battery properties |
167 | *********************************************************************/ |
168 | |
169 | struct chg_map { |
170 | int val; |
171 | int reg_val; |
172 | }; |
173 | |
174 | static struct chg_map trickle_ilims[] = { |
175 | { 50, 0 << WM831X_CHG_TRKL_ILIM_SHIFT }, |
176 | { 100, 1 << WM831X_CHG_TRKL_ILIM_SHIFT }, |
177 | { 150, 2 << WM831X_CHG_TRKL_ILIM_SHIFT }, |
178 | { 200, 3 << WM831X_CHG_TRKL_ILIM_SHIFT }, |
179 | }; |
180 | |
181 | static struct chg_map vsels[] = { |
182 | { 4050, 0 << WM831X_CHG_VSEL_SHIFT }, |
183 | { 4100, 1 << WM831X_CHG_VSEL_SHIFT }, |
184 | { 4150, 2 << WM831X_CHG_VSEL_SHIFT }, |
185 | { 4200, 3 << WM831X_CHG_VSEL_SHIFT }, |
186 | }; |
187 | |
188 | static struct chg_map fast_ilims[] = { |
189 | { 0, 0 << WM831X_CHG_FAST_ILIM_SHIFT }, |
190 | { 50, 1 << WM831X_CHG_FAST_ILIM_SHIFT }, |
191 | { 100, 2 << WM831X_CHG_FAST_ILIM_SHIFT }, |
192 | { 150, 3 << WM831X_CHG_FAST_ILIM_SHIFT }, |
193 | { 200, 4 << WM831X_CHG_FAST_ILIM_SHIFT }, |
194 | { 250, 5 << WM831X_CHG_FAST_ILIM_SHIFT }, |
195 | { 300, 6 << WM831X_CHG_FAST_ILIM_SHIFT }, |
196 | { 350, 7 << WM831X_CHG_FAST_ILIM_SHIFT }, |
197 | { 400, 8 << WM831X_CHG_FAST_ILIM_SHIFT }, |
198 | { 450, 9 << WM831X_CHG_FAST_ILIM_SHIFT }, |
199 | { 500, 10 << WM831X_CHG_FAST_ILIM_SHIFT }, |
200 | { 600, 11 << WM831X_CHG_FAST_ILIM_SHIFT }, |
201 | { 700, 12 << WM831X_CHG_FAST_ILIM_SHIFT }, |
202 | { 800, 13 << WM831X_CHG_FAST_ILIM_SHIFT }, |
203 | { 900, 14 << WM831X_CHG_FAST_ILIM_SHIFT }, |
204 | { 1000, 15 << WM831X_CHG_FAST_ILIM_SHIFT }, |
205 | }; |
206 | |
207 | static struct chg_map eoc_iterms[] = { |
208 | { 20, 0 << WM831X_CHG_ITERM_SHIFT }, |
209 | { 30, 1 << WM831X_CHG_ITERM_SHIFT }, |
210 | { 40, 2 << WM831X_CHG_ITERM_SHIFT }, |
211 | { 50, 3 << WM831X_CHG_ITERM_SHIFT }, |
212 | { 60, 4 << WM831X_CHG_ITERM_SHIFT }, |
213 | { 70, 5 << WM831X_CHG_ITERM_SHIFT }, |
214 | { 80, 6 << WM831X_CHG_ITERM_SHIFT }, |
215 | { 90, 7 << WM831X_CHG_ITERM_SHIFT }, |
216 | }; |
217 | |
218 | static struct chg_map chg_times[] = { |
219 | { 60, 0 << WM831X_CHG_TIME_SHIFT }, |
220 | { 90, 1 << WM831X_CHG_TIME_SHIFT }, |
221 | { 120, 2 << WM831X_CHG_TIME_SHIFT }, |
222 | { 150, 3 << WM831X_CHG_TIME_SHIFT }, |
223 | { 180, 4 << WM831X_CHG_TIME_SHIFT }, |
224 | { 210, 5 << WM831X_CHG_TIME_SHIFT }, |
225 | { 240, 6 << WM831X_CHG_TIME_SHIFT }, |
226 | { 270, 7 << WM831X_CHG_TIME_SHIFT }, |
227 | { 300, 8 << WM831X_CHG_TIME_SHIFT }, |
228 | { 330, 9 << WM831X_CHG_TIME_SHIFT }, |
229 | { 360, 10 << WM831X_CHG_TIME_SHIFT }, |
230 | { 390, 11 << WM831X_CHG_TIME_SHIFT }, |
231 | { 420, 12 << WM831X_CHG_TIME_SHIFT }, |
232 | { 450, 13 << WM831X_CHG_TIME_SHIFT }, |
233 | { 480, 14 << WM831X_CHG_TIME_SHIFT }, |
234 | { 510, 15 << WM831X_CHG_TIME_SHIFT }, |
235 | }; |
236 | |
237 | static void wm831x_battery_apply_config(struct wm831x *wm831x, |
238 | struct chg_map *map, int count, int val, |
239 | int *reg, const char *name, |
240 | const char *units) |
241 | { |
242 | int i; |
243 | |
244 | for (i = 0; i < count; i++) |
245 | if (val == map[i].val) |
246 | break; |
247 | if (i == count) { |
248 | dev_err(wm831x->dev, "Invalid %s %d%s\n" , |
249 | name, val, units); |
250 | } else { |
251 | *reg |= map[i].reg_val; |
252 | dev_dbg(wm831x->dev, "Set %s of %d%s\n" , name, val, units); |
253 | } |
254 | } |
255 | |
256 | static void wm831x_config_battery(struct wm831x *wm831x) |
257 | { |
258 | struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data; |
259 | struct wm831x_battery_pdata *pdata; |
260 | int ret, reg1, reg2; |
261 | |
262 | if (!wm831x_pdata || !wm831x_pdata->battery) { |
263 | dev_warn(wm831x->dev, |
264 | "No battery charger configuration\n" ); |
265 | return; |
266 | } |
267 | |
268 | pdata = wm831x_pdata->battery; |
269 | |
270 | reg1 = 0; |
271 | reg2 = 0; |
272 | |
273 | if (!pdata->enable) { |
274 | dev_info(wm831x->dev, "Battery charger disabled\n" ); |
275 | return; |
276 | } |
277 | |
278 | reg1 |= WM831X_CHG_ENA; |
279 | if (pdata->off_mask) |
280 | reg2 |= WM831X_CHG_OFF_MSK; |
281 | if (pdata->fast_enable) |
282 | reg1 |= WM831X_CHG_FAST; |
283 | |
284 | wm831x_battery_apply_config(wm831x, map: trickle_ilims, |
285 | ARRAY_SIZE(trickle_ilims), |
286 | val: pdata->trickle_ilim, reg: ®2, |
287 | name: "trickle charge current limit" , units: "mA" ); |
288 | |
289 | wm831x_battery_apply_config(wm831x, map: vsels, ARRAY_SIZE(vsels), |
290 | val: pdata->vsel, reg: ®2, |
291 | name: "target voltage" , units: "mV" ); |
292 | |
293 | wm831x_battery_apply_config(wm831x, map: fast_ilims, ARRAY_SIZE(fast_ilims), |
294 | val: pdata->fast_ilim, reg: ®2, |
295 | name: "fast charge current limit" , units: "mA" ); |
296 | |
297 | wm831x_battery_apply_config(wm831x, map: eoc_iterms, ARRAY_SIZE(eoc_iterms), |
298 | val: pdata->eoc_iterm, reg: ®1, |
299 | name: "end of charge current threshold" , units: "mA" ); |
300 | |
301 | wm831x_battery_apply_config(wm831x, map: chg_times, ARRAY_SIZE(chg_times), |
302 | val: pdata->timeout, reg: ®2, |
303 | name: "charger timeout" , units: "min" ); |
304 | |
305 | ret = wm831x_reg_unlock(wm831x); |
306 | if (ret != 0) { |
307 | dev_err(wm831x->dev, "Failed to unlock registers: %d\n" , ret); |
308 | return; |
309 | } |
310 | |
311 | ret = wm831x_set_bits(wm831x, WM831X_CHARGER_CONTROL_1, |
312 | WM831X_CHG_ENA_MASK | |
313 | WM831X_CHG_FAST_MASK | |
314 | WM831X_CHG_ITERM_MASK, |
315 | val: reg1); |
316 | if (ret != 0) |
317 | dev_err(wm831x->dev, "Failed to set charger control 1: %d\n" , |
318 | ret); |
319 | |
320 | ret = wm831x_set_bits(wm831x, WM831X_CHARGER_CONTROL_2, |
321 | WM831X_CHG_OFF_MSK | |
322 | WM831X_CHG_TIME_MASK | |
323 | WM831X_CHG_FAST_ILIM_MASK | |
324 | WM831X_CHG_TRKL_ILIM_MASK | |
325 | WM831X_CHG_VSEL_MASK, |
326 | val: reg2); |
327 | if (ret != 0) |
328 | dev_err(wm831x->dev, "Failed to set charger control 2: %d\n" , |
329 | ret); |
330 | |
331 | wm831x_reg_lock(wm831x); |
332 | } |
333 | |
334 | static int wm831x_bat_check_status(struct wm831x *wm831x, int *status) |
335 | { |
336 | int ret; |
337 | |
338 | ret = wm831x_reg_read(wm831x, WM831X_SYSTEM_STATUS); |
339 | if (ret < 0) |
340 | return ret; |
341 | |
342 | if (ret & WM831X_PWR_SRC_BATT) { |
343 | *status = POWER_SUPPLY_STATUS_DISCHARGING; |
344 | return 0; |
345 | } |
346 | |
347 | ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS); |
348 | if (ret < 0) |
349 | return ret; |
350 | |
351 | switch (ret & WM831X_CHG_STATE_MASK) { |
352 | case WM831X_CHG_STATE_OFF: |
353 | *status = POWER_SUPPLY_STATUS_NOT_CHARGING; |
354 | break; |
355 | case WM831X_CHG_STATE_TRICKLE: |
356 | case WM831X_CHG_STATE_FAST: |
357 | *status = POWER_SUPPLY_STATUS_CHARGING; |
358 | break; |
359 | |
360 | default: |
361 | *status = POWER_SUPPLY_STATUS_UNKNOWN; |
362 | break; |
363 | } |
364 | |
365 | return 0; |
366 | } |
367 | |
368 | static int wm831x_bat_check_type(struct wm831x *wm831x, int *type) |
369 | { |
370 | int ret; |
371 | |
372 | ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS); |
373 | if (ret < 0) |
374 | return ret; |
375 | |
376 | switch (ret & WM831X_CHG_STATE_MASK) { |
377 | case WM831X_CHG_STATE_TRICKLE: |
378 | case WM831X_CHG_STATE_TRICKLE_OT: |
379 | *type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; |
380 | break; |
381 | case WM831X_CHG_STATE_FAST: |
382 | case WM831X_CHG_STATE_FAST_OT: |
383 | *type = POWER_SUPPLY_CHARGE_TYPE_FAST; |
384 | break; |
385 | default: |
386 | *type = POWER_SUPPLY_CHARGE_TYPE_NONE; |
387 | break; |
388 | } |
389 | |
390 | return 0; |
391 | } |
392 | |
393 | static int wm831x_bat_check_health(struct wm831x *wm831x, int *health) |
394 | { |
395 | int ret; |
396 | |
397 | ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS); |
398 | if (ret < 0) |
399 | return ret; |
400 | |
401 | if (ret & WM831X_BATT_HOT_STS) { |
402 | *health = POWER_SUPPLY_HEALTH_OVERHEAT; |
403 | return 0; |
404 | } |
405 | |
406 | if (ret & WM831X_BATT_COLD_STS) { |
407 | *health = POWER_SUPPLY_HEALTH_COLD; |
408 | return 0; |
409 | } |
410 | |
411 | if (ret & WM831X_BATT_OV_STS) { |
412 | *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; |
413 | return 0; |
414 | } |
415 | |
416 | switch (ret & WM831X_CHG_STATE_MASK) { |
417 | case WM831X_CHG_STATE_TRICKLE_OT: |
418 | case WM831X_CHG_STATE_FAST_OT: |
419 | *health = POWER_SUPPLY_HEALTH_OVERHEAT; |
420 | break; |
421 | case WM831X_CHG_STATE_DEFECTIVE: |
422 | *health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; |
423 | break; |
424 | default: |
425 | *health = POWER_SUPPLY_HEALTH_GOOD; |
426 | break; |
427 | } |
428 | |
429 | return 0; |
430 | } |
431 | |
432 | static int wm831x_bat_get_prop(struct power_supply *psy, |
433 | enum power_supply_property psp, |
434 | union power_supply_propval *val) |
435 | { |
436 | struct wm831x_power *wm831x_power = dev_get_drvdata(dev: psy->dev.parent); |
437 | struct wm831x *wm831x = wm831x_power->wm831x; |
438 | int ret = 0; |
439 | |
440 | switch (psp) { |
441 | case POWER_SUPPLY_PROP_STATUS: |
442 | ret = wm831x_bat_check_status(wm831x, status: &val->intval); |
443 | break; |
444 | case POWER_SUPPLY_PROP_ONLINE: |
445 | ret = wm831x_power_check_online(wm831x, WM831X_PWR_SRC_BATT, |
446 | val); |
447 | break; |
448 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
449 | ret = wm831x_power_read_voltage(wm831x, src: WM831X_AUX_BATT, val); |
450 | break; |
451 | case POWER_SUPPLY_PROP_HEALTH: |
452 | ret = wm831x_bat_check_health(wm831x, health: &val->intval); |
453 | break; |
454 | case POWER_SUPPLY_PROP_CHARGE_TYPE: |
455 | ret = wm831x_bat_check_type(wm831x, type: &val->intval); |
456 | break; |
457 | default: |
458 | ret = -EINVAL; |
459 | break; |
460 | } |
461 | |
462 | return ret; |
463 | } |
464 | |
465 | static enum power_supply_property wm831x_bat_props[] = { |
466 | POWER_SUPPLY_PROP_STATUS, |
467 | POWER_SUPPLY_PROP_ONLINE, |
468 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
469 | POWER_SUPPLY_PROP_HEALTH, |
470 | POWER_SUPPLY_PROP_CHARGE_TYPE, |
471 | }; |
472 | |
473 | static const char *wm831x_bat_irqs[] = { |
474 | "BATT HOT" , |
475 | "BATT COLD" , |
476 | "BATT FAIL" , |
477 | "OV" , |
478 | "END" , |
479 | "TO" , |
480 | "MODE" , |
481 | "START" , |
482 | }; |
483 | |
484 | static irqreturn_t wm831x_bat_irq(int irq, void *data) |
485 | { |
486 | struct wm831x_power *wm831x_power = data; |
487 | struct wm831x *wm831x = wm831x_power->wm831x; |
488 | |
489 | dev_dbg(wm831x->dev, "Battery status changed: %d\n" , irq); |
490 | |
491 | /* The battery charger is autonomous so we don't need to do |
492 | * anything except kick user space */ |
493 | if (wm831x_power->have_battery) |
494 | power_supply_changed(psy: wm831x_power->battery); |
495 | |
496 | return IRQ_HANDLED; |
497 | } |
498 | |
499 | |
500 | /********************************************************************* |
501 | * Initialisation |
502 | *********************************************************************/ |
503 | |
504 | static irqreturn_t wm831x_syslo_irq(int irq, void *data) |
505 | { |
506 | struct wm831x_power *wm831x_power = data; |
507 | struct wm831x *wm831x = wm831x_power->wm831x; |
508 | |
509 | /* Not much we can actually *do* but tell people for |
510 | * posterity, we're probably about to run out of power. */ |
511 | dev_crit(wm831x->dev, "SYSVDD under voltage\n" ); |
512 | |
513 | return IRQ_HANDLED; |
514 | } |
515 | |
516 | static irqreturn_t wm831x_pwr_src_irq(int irq, void *data) |
517 | { |
518 | struct wm831x_power *wm831x_power = data; |
519 | struct wm831x *wm831x = wm831x_power->wm831x; |
520 | |
521 | dev_dbg(wm831x->dev, "Power source changed\n" ); |
522 | |
523 | /* Just notify for everything - little harm in overnotifying. */ |
524 | if (wm831x_power->have_battery) |
525 | power_supply_changed(psy: wm831x_power->battery); |
526 | power_supply_changed(psy: wm831x_power->usb); |
527 | power_supply_changed(psy: wm831x_power->wall); |
528 | |
529 | return IRQ_HANDLED; |
530 | } |
531 | |
532 | static int wm831x_power_probe(struct platform_device *pdev) |
533 | { |
534 | struct wm831x *wm831x = dev_get_drvdata(dev: pdev->dev.parent); |
535 | struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data; |
536 | struct wm831x_power *power; |
537 | int ret, irq, i; |
538 | |
539 | power = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct wm831x_power), |
540 | GFP_KERNEL); |
541 | if (power == NULL) |
542 | return -ENOMEM; |
543 | |
544 | power->wm831x = wm831x; |
545 | platform_set_drvdata(pdev, data: power); |
546 | |
547 | if (wm831x_pdata && wm831x_pdata->wm831x_num) { |
548 | snprintf(buf: power->wall_name, size: sizeof(power->wall_name), |
549 | fmt: "wm831x-wall.%d" , wm831x_pdata->wm831x_num); |
550 | snprintf(buf: power->battery_name, size: sizeof(power->wall_name), |
551 | fmt: "wm831x-battery.%d" , wm831x_pdata->wm831x_num); |
552 | snprintf(buf: power->usb_name, size: sizeof(power->wall_name), |
553 | fmt: "wm831x-usb.%d" , wm831x_pdata->wm831x_num); |
554 | } else { |
555 | snprintf(buf: power->wall_name, size: sizeof(power->wall_name), |
556 | fmt: "wm831x-wall" ); |
557 | snprintf(buf: power->battery_name, size: sizeof(power->wall_name), |
558 | fmt: "wm831x-battery" ); |
559 | snprintf(buf: power->usb_name, size: sizeof(power->wall_name), |
560 | fmt: "wm831x-usb" ); |
561 | } |
562 | |
563 | /* We ignore configuration failures since we can still read back |
564 | * the status without enabling the charger. |
565 | */ |
566 | wm831x_config_battery(wm831x); |
567 | |
568 | power->wall_desc.name = power->wall_name; |
569 | power->wall_desc.type = POWER_SUPPLY_TYPE_MAINS; |
570 | power->wall_desc.properties = wm831x_wall_props; |
571 | power->wall_desc.num_properties = ARRAY_SIZE(wm831x_wall_props); |
572 | power->wall_desc.get_property = wm831x_wall_get_prop; |
573 | power->wall = power_supply_register(parent: &pdev->dev, desc: &power->wall_desc, |
574 | NULL); |
575 | if (IS_ERR(ptr: power->wall)) { |
576 | ret = PTR_ERR(ptr: power->wall); |
577 | goto err; |
578 | } |
579 | |
580 | power->usb_desc.name = power->usb_name, |
581 | power->usb_desc.type = POWER_SUPPLY_TYPE_USB; |
582 | power->usb_desc.properties = wm831x_usb_props; |
583 | power->usb_desc.num_properties = ARRAY_SIZE(wm831x_usb_props); |
584 | power->usb_desc.get_property = wm831x_usb_get_prop; |
585 | power->usb = power_supply_register(parent: &pdev->dev, desc: &power->usb_desc, NULL); |
586 | if (IS_ERR(ptr: power->usb)) { |
587 | ret = PTR_ERR(ptr: power->usb); |
588 | goto err_wall; |
589 | } |
590 | |
591 | ret = wm831x_reg_read(wm831x, WM831X_CHARGER_CONTROL_1); |
592 | if (ret < 0) |
593 | goto err_wall; |
594 | power->have_battery = ret & WM831X_CHG_ENA; |
595 | |
596 | if (power->have_battery) { |
597 | power->battery_desc.name = power->battery_name; |
598 | power->battery_desc.properties = wm831x_bat_props; |
599 | power->battery_desc.num_properties = ARRAY_SIZE(wm831x_bat_props); |
600 | power->battery_desc.get_property = wm831x_bat_get_prop; |
601 | power->battery_desc.use_for_apm = 1; |
602 | power->battery = power_supply_register(parent: &pdev->dev, |
603 | desc: &power->battery_desc, |
604 | NULL); |
605 | if (IS_ERR(ptr: power->battery)) { |
606 | ret = PTR_ERR(ptr: power->battery); |
607 | goto err_usb; |
608 | } |
609 | } |
610 | |
611 | irq = wm831x_irq(wm831x, irq: platform_get_irq_byname(pdev, "SYSLO" )); |
612 | ret = request_threaded_irq(irq, NULL, thread_fn: wm831x_syslo_irq, |
613 | IRQF_TRIGGER_RISING | IRQF_ONESHOT, name: "System power low" , |
614 | dev: power); |
615 | if (ret != 0) { |
616 | dev_err(&pdev->dev, "Failed to request SYSLO IRQ %d: %d\n" , |
617 | irq, ret); |
618 | goto err_battery; |
619 | } |
620 | |
621 | irq = wm831x_irq(wm831x, irq: platform_get_irq_byname(pdev, "PWR SRC" )); |
622 | ret = request_threaded_irq(irq, NULL, thread_fn: wm831x_pwr_src_irq, |
623 | IRQF_TRIGGER_RISING | IRQF_ONESHOT, name: "Power source" , |
624 | dev: power); |
625 | if (ret != 0) { |
626 | dev_err(&pdev->dev, "Failed to request PWR SRC IRQ %d: %d\n" , |
627 | irq, ret); |
628 | goto err_syslo; |
629 | } |
630 | |
631 | for (i = 0; i < ARRAY_SIZE(wm831x_bat_irqs); i++) { |
632 | irq = wm831x_irq(wm831x, |
633 | irq: platform_get_irq_byname(pdev, |
634 | wm831x_bat_irqs[i])); |
635 | ret = request_threaded_irq(irq, NULL, thread_fn: wm831x_bat_irq, |
636 | IRQF_TRIGGER_RISING | IRQF_ONESHOT, |
637 | name: wm831x_bat_irqs[i], |
638 | dev: power); |
639 | if (ret != 0) { |
640 | dev_err(&pdev->dev, |
641 | "Failed to request %s IRQ %d: %d\n" , |
642 | wm831x_bat_irqs[i], irq, ret); |
643 | goto err_bat_irq; |
644 | } |
645 | } |
646 | |
647 | power->usb_phy = devm_usb_get_phy_by_phandle(dev: &pdev->dev, phandle: "phys" , index: 0); |
648 | ret = PTR_ERR_OR_ZERO(ptr: power->usb_phy); |
649 | |
650 | switch (ret) { |
651 | case 0: |
652 | power->usb_notify.notifier_call = wm831x_usb_limit_change; |
653 | ret = usb_register_notifier(x: power->usb_phy, nb: &power->usb_notify); |
654 | if (ret) { |
655 | dev_err(&pdev->dev, "Failed to register notifier: %d\n" , |
656 | ret); |
657 | goto err_bat_irq; |
658 | } |
659 | break; |
660 | case -EINVAL: |
661 | case -ENODEV: |
662 | /* ignore missing usb-phy, it's optional */ |
663 | power->usb_phy = NULL; |
664 | ret = 0; |
665 | break; |
666 | default: |
667 | dev_err(&pdev->dev, "Failed to find USB phy: %d\n" , ret); |
668 | fallthrough; |
669 | case -EPROBE_DEFER: |
670 | goto err_bat_irq; |
671 | } |
672 | |
673 | return ret; |
674 | |
675 | err_bat_irq: |
676 | --i; |
677 | for (; i >= 0; i--) { |
678 | irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]); |
679 | free_irq(irq, power); |
680 | } |
681 | irq = wm831x_irq(wm831x, irq: platform_get_irq_byname(pdev, "PWR SRC" )); |
682 | free_irq(irq, power); |
683 | err_syslo: |
684 | irq = wm831x_irq(wm831x, irq: platform_get_irq_byname(pdev, "SYSLO" )); |
685 | free_irq(irq, power); |
686 | err_battery: |
687 | if (power->have_battery) |
688 | power_supply_unregister(psy: power->battery); |
689 | err_usb: |
690 | power_supply_unregister(psy: power->usb); |
691 | err_wall: |
692 | power_supply_unregister(psy: power->wall); |
693 | err: |
694 | return ret; |
695 | } |
696 | |
697 | static void wm831x_power_remove(struct platform_device *pdev) |
698 | { |
699 | struct wm831x_power *wm831x_power = platform_get_drvdata(pdev); |
700 | struct wm831x *wm831x = wm831x_power->wm831x; |
701 | int irq, i; |
702 | |
703 | if (wm831x_power->usb_phy) { |
704 | usb_unregister_notifier(x: wm831x_power->usb_phy, |
705 | nb: &wm831x_power->usb_notify); |
706 | } |
707 | |
708 | for (i = 0; i < ARRAY_SIZE(wm831x_bat_irqs); i++) { |
709 | irq = wm831x_irq(wm831x, |
710 | irq: platform_get_irq_byname(pdev, |
711 | wm831x_bat_irqs[i])); |
712 | free_irq(irq, wm831x_power); |
713 | } |
714 | |
715 | irq = wm831x_irq(wm831x, irq: platform_get_irq_byname(pdev, "PWR SRC" )); |
716 | free_irq(irq, wm831x_power); |
717 | |
718 | irq = wm831x_irq(wm831x, irq: platform_get_irq_byname(pdev, "SYSLO" )); |
719 | free_irq(irq, wm831x_power); |
720 | |
721 | if (wm831x_power->have_battery) |
722 | power_supply_unregister(psy: wm831x_power->battery); |
723 | power_supply_unregister(psy: wm831x_power->wall); |
724 | power_supply_unregister(psy: wm831x_power->usb); |
725 | } |
726 | |
727 | static struct platform_driver wm831x_power_driver = { |
728 | .probe = wm831x_power_probe, |
729 | .remove_new = wm831x_power_remove, |
730 | .driver = { |
731 | .name = "wm831x-power" , |
732 | }, |
733 | }; |
734 | |
735 | module_platform_driver(wm831x_power_driver); |
736 | |
737 | MODULE_DESCRIPTION("Power supply driver for WM831x PMICs" ); |
738 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>" ); |
739 | MODULE_LICENSE("GPL" ); |
740 | MODULE_ALIAS("platform:wm831x-power" ); |
741 | |