1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Battery driver for Maxim MAX8925 |
4 | * |
5 | * Copyright (c) 2009-2010 Marvell International Ltd. |
6 | * Haojian Zhuang <haojian.zhuang@marvell.com> |
7 | */ |
8 | |
9 | #include <linux/module.h> |
10 | #include <linux/err.h> |
11 | #include <linux/slab.h> |
12 | #include <linux/of.h> |
13 | #include <linux/i2c.h> |
14 | #include <linux/interrupt.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/power_supply.h> |
17 | #include <linux/mfd/max8925.h> |
18 | |
19 | /* registers in GPM */ |
20 | #define MAX8925_OUT5VEN 0x54 |
21 | #define MAX8925_OUT3VEN 0x58 |
22 | #define MAX8925_CHG_CNTL1 0x7c |
23 | |
24 | /* bits definition */ |
25 | #define MAX8925_CHG_STAT_VSYSLOW (1 << 0) |
26 | #define MAX8925_CHG_STAT_MODE_MASK (3 << 2) |
27 | #define MAX8925_CHG_STAT_EN_MASK (1 << 4) |
28 | #define MAX8925_CHG_MBDET (1 << 1) |
29 | #define MAX8925_CHG_AC_RANGE_MASK (3 << 6) |
30 | |
31 | /* registers in ADC */ |
32 | #define MAX8925_ADC_RES_CNFG1 0x06 |
33 | #define MAX8925_ADC_AVG_CNFG1 0x07 |
34 | #define MAX8925_ADC_ACQ_CNFG1 0x08 |
35 | #define MAX8925_ADC_ACQ_CNFG2 0x09 |
36 | /* 2 bytes registers in below. MSB is 1st, LSB is 2nd. */ |
37 | #define MAX8925_ADC_AUX2 0x62 |
38 | #define MAX8925_ADC_VCHG 0x64 |
39 | #define MAX8925_ADC_VBBATT 0x66 |
40 | #define MAX8925_ADC_VMBATT 0x68 |
41 | #define MAX8925_ADC_ISNS 0x6a |
42 | #define MAX8925_ADC_THM 0x6c |
43 | #define MAX8925_ADC_TDIE 0x6e |
44 | #define MAX8925_CMD_AUX2 0xc8 |
45 | #define MAX8925_CMD_VCHG 0xd0 |
46 | #define MAX8925_CMD_VBBATT 0xd8 |
47 | #define MAX8925_CMD_VMBATT 0xe0 |
48 | #define MAX8925_CMD_ISNS 0xe8 |
49 | #define MAX8925_CMD_THM 0xf0 |
50 | #define MAX8925_CMD_TDIE 0xf8 |
51 | |
52 | enum { |
53 | MEASURE_AUX2, |
54 | MEASURE_VCHG, |
55 | MEASURE_VBBATT, |
56 | MEASURE_VMBATT, |
57 | MEASURE_ISNS, |
58 | MEASURE_THM, |
59 | MEASURE_TDIE, |
60 | MEASURE_MAX, |
61 | }; |
62 | |
63 | struct max8925_power_info { |
64 | struct max8925_chip *chip; |
65 | struct i2c_client *gpm; |
66 | struct i2c_client *adc; |
67 | |
68 | struct power_supply *ac; |
69 | struct power_supply *usb; |
70 | struct power_supply *battery; |
71 | int irq_base; |
72 | unsigned ac_online:1; |
73 | unsigned usb_online:1; |
74 | unsigned bat_online:1; |
75 | unsigned chg_mode:2; |
76 | unsigned batt_detect:1; /* detecing MB by ID pin */ |
77 | unsigned topoff_threshold:2; |
78 | unsigned fast_charge:3; |
79 | unsigned no_temp_support:1; |
80 | unsigned no_insert_detect:1; |
81 | |
82 | int (*set_charger) (int); |
83 | }; |
84 | |
85 | static int __set_charger(struct max8925_power_info *info, int enable) |
86 | { |
87 | struct max8925_chip *chip = info->chip; |
88 | if (enable) { |
89 | /* enable charger in platform */ |
90 | if (info->set_charger) |
91 | info->set_charger(1); |
92 | /* enable charger */ |
93 | max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 0); |
94 | } else { |
95 | /* disable charge */ |
96 | max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7); |
97 | if (info->set_charger) |
98 | info->set_charger(0); |
99 | } |
100 | dev_dbg(chip->dev, "%s\n" , (enable) ? "Enable charger" |
101 | : "Disable charger" ); |
102 | return 0; |
103 | } |
104 | |
105 | static irqreturn_t max8925_charger_handler(int irq, void *data) |
106 | { |
107 | struct max8925_power_info *info = (struct max8925_power_info *)data; |
108 | struct max8925_chip *chip = info->chip; |
109 | |
110 | switch (irq - chip->irq_base) { |
111 | case MAX8925_IRQ_VCHG_DC_R: |
112 | info->ac_online = 1; |
113 | __set_charger(info, enable: 1); |
114 | dev_dbg(chip->dev, "Adapter inserted\n" ); |
115 | break; |
116 | case MAX8925_IRQ_VCHG_DC_F: |
117 | info->ac_online = 0; |
118 | __set_charger(info, enable: 0); |
119 | dev_dbg(chip->dev, "Adapter removed\n" ); |
120 | break; |
121 | case MAX8925_IRQ_VCHG_THM_OK_F: |
122 | /* Battery is not ready yet */ |
123 | dev_dbg(chip->dev, "Battery temperature is out of range\n" ); |
124 | fallthrough; |
125 | case MAX8925_IRQ_VCHG_DC_OVP: |
126 | dev_dbg(chip->dev, "Error detection\n" ); |
127 | __set_charger(info, enable: 0); |
128 | break; |
129 | case MAX8925_IRQ_VCHG_THM_OK_R: |
130 | /* Battery is ready now */ |
131 | dev_dbg(chip->dev, "Battery temperature is in range\n" ); |
132 | break; |
133 | case MAX8925_IRQ_VCHG_SYSLOW_R: |
134 | /* VSYS is low */ |
135 | dev_info(chip->dev, "Sys power is too low\n" ); |
136 | break; |
137 | case MAX8925_IRQ_VCHG_SYSLOW_F: |
138 | dev_dbg(chip->dev, "Sys power is above low threshold\n" ); |
139 | break; |
140 | case MAX8925_IRQ_VCHG_DONE: |
141 | __set_charger(info, enable: 0); |
142 | dev_dbg(chip->dev, "Charging is done\n" ); |
143 | break; |
144 | case MAX8925_IRQ_VCHG_TOPOFF: |
145 | dev_dbg(chip->dev, "Charging in top-off mode\n" ); |
146 | break; |
147 | case MAX8925_IRQ_VCHG_TMR_FAULT: |
148 | __set_charger(info, enable: 0); |
149 | dev_dbg(chip->dev, "Safe timer is expired\n" ); |
150 | break; |
151 | case MAX8925_IRQ_VCHG_RST: |
152 | __set_charger(info, enable: 0); |
153 | dev_dbg(chip->dev, "Charger is reset\n" ); |
154 | break; |
155 | } |
156 | return IRQ_HANDLED; |
157 | } |
158 | |
159 | static int start_measure(struct max8925_power_info *info, int type) |
160 | { |
161 | unsigned char buf[2] = {0, 0}; |
162 | int meas_cmd; |
163 | int meas_reg = 0, ret; |
164 | |
165 | switch (type) { |
166 | case MEASURE_VCHG: |
167 | meas_cmd = MAX8925_CMD_VCHG; |
168 | meas_reg = MAX8925_ADC_VCHG; |
169 | break; |
170 | case MEASURE_VBBATT: |
171 | meas_cmd = MAX8925_CMD_VBBATT; |
172 | meas_reg = MAX8925_ADC_VBBATT; |
173 | break; |
174 | case MEASURE_VMBATT: |
175 | meas_cmd = MAX8925_CMD_VMBATT; |
176 | meas_reg = MAX8925_ADC_VMBATT; |
177 | break; |
178 | case MEASURE_ISNS: |
179 | meas_cmd = MAX8925_CMD_ISNS; |
180 | meas_reg = MAX8925_ADC_ISNS; |
181 | break; |
182 | default: |
183 | return -EINVAL; |
184 | } |
185 | |
186 | max8925_reg_write(info->adc, meas_cmd, 0); |
187 | max8925_bulk_read(info->adc, meas_reg, 2, buf); |
188 | ret = ((buf[0]<<8) | buf[1]) >> 4; |
189 | |
190 | return ret; |
191 | } |
192 | |
193 | static int max8925_ac_get_prop(struct power_supply *psy, |
194 | enum power_supply_property psp, |
195 | union power_supply_propval *val) |
196 | { |
197 | struct max8925_power_info *info = dev_get_drvdata(dev: psy->dev.parent); |
198 | int ret = 0; |
199 | |
200 | switch (psp) { |
201 | case POWER_SUPPLY_PROP_ONLINE: |
202 | val->intval = info->ac_online; |
203 | break; |
204 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
205 | if (info->ac_online) { |
206 | ret = start_measure(info, type: MEASURE_VCHG); |
207 | if (ret >= 0) { |
208 | val->intval = ret * 2000; /* unit is uV */ |
209 | goto out; |
210 | } |
211 | } |
212 | ret = -ENODATA; |
213 | break; |
214 | default: |
215 | ret = -ENODEV; |
216 | break; |
217 | } |
218 | out: |
219 | return ret; |
220 | } |
221 | |
222 | static enum power_supply_property max8925_ac_props[] = { |
223 | POWER_SUPPLY_PROP_ONLINE, |
224 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
225 | }; |
226 | |
227 | static int max8925_usb_get_prop(struct power_supply *psy, |
228 | enum power_supply_property psp, |
229 | union power_supply_propval *val) |
230 | { |
231 | struct max8925_power_info *info = dev_get_drvdata(dev: psy->dev.parent); |
232 | int ret = 0; |
233 | |
234 | switch (psp) { |
235 | case POWER_SUPPLY_PROP_ONLINE: |
236 | val->intval = info->usb_online; |
237 | break; |
238 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
239 | if (info->usb_online) { |
240 | ret = start_measure(info, type: MEASURE_VCHG); |
241 | if (ret >= 0) { |
242 | val->intval = ret * 2000; /* unit is uV */ |
243 | goto out; |
244 | } |
245 | } |
246 | ret = -ENODATA; |
247 | break; |
248 | default: |
249 | ret = -ENODEV; |
250 | break; |
251 | } |
252 | out: |
253 | return ret; |
254 | } |
255 | |
256 | static enum power_supply_property max8925_usb_props[] = { |
257 | POWER_SUPPLY_PROP_ONLINE, |
258 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
259 | }; |
260 | |
261 | static int max8925_bat_get_prop(struct power_supply *psy, |
262 | enum power_supply_property psp, |
263 | union power_supply_propval *val) |
264 | { |
265 | struct max8925_power_info *info = dev_get_drvdata(dev: psy->dev.parent); |
266 | int ret = 0; |
267 | |
268 | switch (psp) { |
269 | case POWER_SUPPLY_PROP_ONLINE: |
270 | val->intval = info->bat_online; |
271 | break; |
272 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
273 | if (info->bat_online) { |
274 | ret = start_measure(info, type: MEASURE_VMBATT); |
275 | if (ret >= 0) { |
276 | val->intval = ret * 2000; /* unit is uV */ |
277 | ret = 0; |
278 | break; |
279 | } |
280 | } |
281 | ret = -ENODATA; |
282 | break; |
283 | case POWER_SUPPLY_PROP_CURRENT_NOW: |
284 | if (info->bat_online) { |
285 | ret = start_measure(info, type: MEASURE_ISNS); |
286 | if (ret >= 0) { |
287 | /* assume r_sns is 0.02 */ |
288 | ret = ((ret * 6250) - 3125) /* uA */; |
289 | val->intval = 0; |
290 | if (ret > 0) |
291 | val->intval = ret; /* unit is mA */ |
292 | ret = 0; |
293 | break; |
294 | } |
295 | } |
296 | ret = -ENODATA; |
297 | break; |
298 | case POWER_SUPPLY_PROP_CHARGE_TYPE: |
299 | if (!info->bat_online) { |
300 | ret = -ENODATA; |
301 | break; |
302 | } |
303 | ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS); |
304 | ret = (ret & MAX8925_CHG_STAT_MODE_MASK) >> 2; |
305 | switch (ret) { |
306 | case 1: |
307 | val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; |
308 | break; |
309 | case 0: |
310 | case 2: |
311 | val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; |
312 | break; |
313 | case 3: |
314 | val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; |
315 | break; |
316 | } |
317 | ret = 0; |
318 | break; |
319 | case POWER_SUPPLY_PROP_STATUS: |
320 | if (!info->bat_online) { |
321 | ret = -ENODATA; |
322 | break; |
323 | } |
324 | ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS); |
325 | if (info->usb_online || info->ac_online) { |
326 | val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; |
327 | if (ret & MAX8925_CHG_STAT_EN_MASK) |
328 | val->intval = POWER_SUPPLY_STATUS_CHARGING; |
329 | } else |
330 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; |
331 | ret = 0; |
332 | break; |
333 | default: |
334 | ret = -ENODEV; |
335 | break; |
336 | } |
337 | return ret; |
338 | } |
339 | |
340 | static enum power_supply_property max8925_battery_props[] = { |
341 | POWER_SUPPLY_PROP_ONLINE, |
342 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
343 | POWER_SUPPLY_PROP_CURRENT_NOW, |
344 | POWER_SUPPLY_PROP_CHARGE_TYPE, |
345 | POWER_SUPPLY_PROP_STATUS, |
346 | }; |
347 | |
348 | static const struct power_supply_desc ac_desc = { |
349 | .name = "max8925-ac" , |
350 | .type = POWER_SUPPLY_TYPE_MAINS, |
351 | .properties = max8925_ac_props, |
352 | .num_properties = ARRAY_SIZE(max8925_ac_props), |
353 | .get_property = max8925_ac_get_prop, |
354 | }; |
355 | |
356 | static const struct power_supply_desc usb_desc = { |
357 | .name = "max8925-usb" , |
358 | .type = POWER_SUPPLY_TYPE_USB, |
359 | .properties = max8925_usb_props, |
360 | .num_properties = ARRAY_SIZE(max8925_usb_props), |
361 | .get_property = max8925_usb_get_prop, |
362 | }; |
363 | |
364 | static const struct power_supply_desc battery_desc = { |
365 | .name = "max8925-battery" , |
366 | .type = POWER_SUPPLY_TYPE_BATTERY, |
367 | .properties = max8925_battery_props, |
368 | .num_properties = ARRAY_SIZE(max8925_battery_props), |
369 | .get_property = max8925_bat_get_prop, |
370 | }; |
371 | |
372 | #define REQUEST_IRQ(_irq, _name) \ |
373 | do { \ |
374 | ret = request_threaded_irq(chip->irq_base + _irq, NULL, \ |
375 | max8925_charger_handler, \ |
376 | IRQF_ONESHOT, _name, info); \ |
377 | if (ret) \ |
378 | dev_err(chip->dev, "Failed to request IRQ #%d: %d\n", \ |
379 | _irq, ret); \ |
380 | } while (0) |
381 | |
382 | static int max8925_init_charger(struct max8925_chip *chip, |
383 | struct max8925_power_info *info) |
384 | { |
385 | int ret; |
386 | |
387 | REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_OVP, "ac-ovp" ); |
388 | if (!info->no_insert_detect) { |
389 | REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove" ); |
390 | REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert" ); |
391 | } |
392 | if (!info->no_temp_support) { |
393 | REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_R, "batt-temp-in-range" ); |
394 | REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_F, "batt-temp-out-range" ); |
395 | } |
396 | REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_F, "vsys-high" ); |
397 | REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_R, "vsys-low" ); |
398 | REQUEST_IRQ(MAX8925_IRQ_VCHG_RST, "charger-reset" ); |
399 | REQUEST_IRQ(MAX8925_IRQ_VCHG_DONE, "charger-done" ); |
400 | REQUEST_IRQ(MAX8925_IRQ_VCHG_TOPOFF, "charger-topoff" ); |
401 | REQUEST_IRQ(MAX8925_IRQ_VCHG_TMR_FAULT, "charger-timer-expire" ); |
402 | |
403 | info->usb_online = 0; |
404 | info->bat_online = 0; |
405 | |
406 | /* check for power - can miss interrupt at boot time */ |
407 | if (start_measure(info, type: MEASURE_VCHG) * 2000 > 500000) |
408 | info->ac_online = 1; |
409 | else |
410 | info->ac_online = 0; |
411 | |
412 | ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS); |
413 | if (ret >= 0) { |
414 | /* |
415 | * If battery detection is enabled, ID pin of battery is |
416 | * connected to MBDET pin of MAX8925. It could be used to |
417 | * detect battery presence. |
418 | * Otherwise, we have to assume that battery is always on. |
419 | */ |
420 | if (info->batt_detect) |
421 | info->bat_online = (ret & MAX8925_CHG_MBDET) ? 0 : 1; |
422 | else |
423 | info->bat_online = 1; |
424 | if (ret & MAX8925_CHG_AC_RANGE_MASK) |
425 | info->ac_online = 1; |
426 | else |
427 | info->ac_online = 0; |
428 | } |
429 | /* disable charge */ |
430 | max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7); |
431 | /* set charging current in charge topoff mode */ |
432 | max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 3 << 5, |
433 | info->topoff_threshold << 5); |
434 | /* set charing current in fast charge mode */ |
435 | max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 7, info->fast_charge); |
436 | |
437 | return 0; |
438 | } |
439 | |
440 | static int max8925_deinit_charger(struct max8925_power_info *info) |
441 | { |
442 | struct max8925_chip *chip = info->chip; |
443 | int irq; |
444 | |
445 | irq = chip->irq_base + MAX8925_IRQ_VCHG_DC_OVP; |
446 | for (; irq <= chip->irq_base + MAX8925_IRQ_VCHG_TMR_FAULT; irq++) |
447 | free_irq(irq, info); |
448 | |
449 | return 0; |
450 | } |
451 | |
452 | #ifdef CONFIG_OF |
453 | static struct max8925_power_pdata * |
454 | max8925_power_dt_init(struct platform_device *pdev) |
455 | { |
456 | struct device_node *nproot = pdev->dev.parent->of_node; |
457 | struct device_node *np; |
458 | int batt_detect; |
459 | int topoff_threshold; |
460 | int fast_charge; |
461 | int no_temp_support; |
462 | int no_insert_detect; |
463 | struct max8925_power_pdata *pdata; |
464 | |
465 | if (!nproot) |
466 | return pdev->dev.platform_data; |
467 | |
468 | np = of_get_child_by_name(node: nproot, name: "charger" ); |
469 | if (!np) { |
470 | dev_err(&pdev->dev, "failed to find charger node\n" ); |
471 | return NULL; |
472 | } |
473 | |
474 | pdata = devm_kzalloc(dev: &pdev->dev, |
475 | size: sizeof(struct max8925_power_pdata), |
476 | GFP_KERNEL); |
477 | if (!pdata) |
478 | goto ret; |
479 | |
480 | of_property_read_u32(np, propname: "topoff-threshold" , out_value: &topoff_threshold); |
481 | of_property_read_u32(np, propname: "batt-detect" , out_value: &batt_detect); |
482 | of_property_read_u32(np, propname: "fast-charge" , out_value: &fast_charge); |
483 | of_property_read_u32(np, propname: "no-insert-detect" , out_value: &no_insert_detect); |
484 | of_property_read_u32(np, propname: "no-temp-support" , out_value: &no_temp_support); |
485 | |
486 | pdata->batt_detect = batt_detect; |
487 | pdata->fast_charge = fast_charge; |
488 | pdata->topoff_threshold = topoff_threshold; |
489 | pdata->no_insert_detect = no_insert_detect; |
490 | pdata->no_temp_support = no_temp_support; |
491 | |
492 | ret: |
493 | of_node_put(node: np); |
494 | return pdata; |
495 | } |
496 | #else |
497 | static struct max8925_power_pdata * |
498 | max8925_power_dt_init(struct platform_device *pdev) |
499 | { |
500 | return pdev->dev.platform_data; |
501 | } |
502 | #endif |
503 | |
504 | static int max8925_power_probe(struct platform_device *pdev) |
505 | { |
506 | struct max8925_chip *chip = dev_get_drvdata(dev: pdev->dev.parent); |
507 | struct power_supply_config psy_cfg = {}; /* Only for ac and usb */ |
508 | struct max8925_power_pdata *pdata = NULL; |
509 | struct max8925_power_info *info; |
510 | int ret; |
511 | |
512 | pdata = max8925_power_dt_init(pdev); |
513 | if (!pdata) { |
514 | dev_err(&pdev->dev, "platform data isn't assigned to " |
515 | "power supply\n" ); |
516 | return -EINVAL; |
517 | } |
518 | |
519 | info = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct max8925_power_info), |
520 | GFP_KERNEL); |
521 | if (!info) |
522 | return -ENOMEM; |
523 | info->chip = chip; |
524 | info->gpm = chip->i2c; |
525 | info->adc = chip->adc; |
526 | platform_set_drvdata(pdev, data: info); |
527 | |
528 | psy_cfg.supplied_to = pdata->supplied_to; |
529 | psy_cfg.num_supplicants = pdata->num_supplicants; |
530 | |
531 | info->ac = power_supply_register(parent: &pdev->dev, desc: &ac_desc, cfg: &psy_cfg); |
532 | if (IS_ERR(ptr: info->ac)) { |
533 | ret = PTR_ERR(ptr: info->ac); |
534 | goto out; |
535 | } |
536 | info->ac->dev.parent = &pdev->dev; |
537 | |
538 | info->usb = power_supply_register(parent: &pdev->dev, desc: &usb_desc, cfg: &psy_cfg); |
539 | if (IS_ERR(ptr: info->usb)) { |
540 | ret = PTR_ERR(ptr: info->usb); |
541 | goto out_unregister_ac; |
542 | } |
543 | info->usb->dev.parent = &pdev->dev; |
544 | |
545 | info->battery = power_supply_register(parent: &pdev->dev, desc: &battery_desc, NULL); |
546 | if (IS_ERR(ptr: info->battery)) { |
547 | ret = PTR_ERR(ptr: info->battery); |
548 | goto out_unregister_usb; |
549 | } |
550 | info->battery->dev.parent = &pdev->dev; |
551 | |
552 | info->batt_detect = pdata->batt_detect; |
553 | info->topoff_threshold = pdata->topoff_threshold; |
554 | info->fast_charge = pdata->fast_charge; |
555 | info->set_charger = pdata->set_charger; |
556 | info->no_temp_support = pdata->no_temp_support; |
557 | info->no_insert_detect = pdata->no_insert_detect; |
558 | |
559 | max8925_init_charger(chip, info); |
560 | return 0; |
561 | out_unregister_usb: |
562 | power_supply_unregister(psy: info->usb); |
563 | out_unregister_ac: |
564 | power_supply_unregister(psy: info->ac); |
565 | out: |
566 | return ret; |
567 | } |
568 | |
569 | static void max8925_power_remove(struct platform_device *pdev) |
570 | { |
571 | struct max8925_power_info *info = platform_get_drvdata(pdev); |
572 | |
573 | if (info) { |
574 | power_supply_unregister(psy: info->ac); |
575 | power_supply_unregister(psy: info->usb); |
576 | power_supply_unregister(psy: info->battery); |
577 | max8925_deinit_charger(info); |
578 | } |
579 | } |
580 | |
581 | static struct platform_driver max8925_power_driver = { |
582 | .probe = max8925_power_probe, |
583 | .remove_new = max8925_power_remove, |
584 | .driver = { |
585 | .name = "max8925-power" , |
586 | }, |
587 | }; |
588 | |
589 | module_platform_driver(max8925_power_driver); |
590 | |
591 | MODULE_LICENSE("GPL" ); |
592 | MODULE_DESCRIPTION("Power supply driver for MAX8925" ); |
593 | MODULE_ALIAS("platform:max8925-power" ); |
594 | |