diff options
Diffstat (limited to 'drivers/power/88pm860x_battery.c')
-rw-r--r-- | drivers/power/88pm860x_battery.c | 1041 |
1 files changed, 1041 insertions, 0 deletions
diff --git a/drivers/power/88pm860x_battery.c b/drivers/power/88pm860x_battery.c new file mode 100644 index 000000000000..5e905f3db4bf --- /dev/null +++ b/drivers/power/88pm860x_battery.c | |||
@@ -0,0 +1,1041 @@ | |||
1 | /* | ||
2 | * Battery driver for Marvell 88PM860x PMIC | ||
3 | * | ||
4 | * Copyright (c) 2012 Marvell International Ltd. | ||
5 | * Author: Jett Zhou <jtzhou@marvell.com> | ||
6 | * Haojian Zhuang <haojian.zhuang@marvell.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License version 2 as | ||
10 | * published by the Free Software Foundation. | ||
11 | */ | ||
12 | |||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/module.h> | ||
15 | #include <linux/platform_device.h> | ||
16 | #include <linux/slab.h> | ||
17 | #include <linux/mutex.h> | ||
18 | #include <linux/string.h> | ||
19 | #include <linux/power_supply.h> | ||
20 | #include <linux/mfd/88pm860x.h> | ||
21 | #include <linux/delay.h> | ||
22 | |||
23 | /* bit definitions of Status Query Interface 2 */ | ||
24 | #define STATUS2_CHG (1 << 2) | ||
25 | #define STATUS2_BAT (1 << 3) | ||
26 | #define STATUS2_VBUS (1 << 4) | ||
27 | |||
28 | /* bit definitions of Measurement Enable 1 Register */ | ||
29 | #define MEAS1_TINT (1 << 3) | ||
30 | #define MEAS1_GP1 (1 << 5) | ||
31 | |||
32 | /* bit definitions of Measurement Enable 3 Register */ | ||
33 | #define MEAS3_IBAT (1 << 0) | ||
34 | #define MEAS3_BAT_DET (1 << 1) | ||
35 | #define MEAS3_CC (1 << 2) | ||
36 | |||
37 | /* bit definitions of Measurement Off Time Register */ | ||
38 | #define MEAS_OFF_SLEEP_EN (1 << 1) | ||
39 | |||
40 | /* bit definitions of GPADC Bias Current 2 Register */ | ||
41 | #define GPBIAS2_GPADC1_SET (2 << 4) | ||
42 | /* GPADC1 Bias Current value in uA unit */ | ||
43 | #define GPBIAS2_GPADC1_UA ((GPBIAS2_GPADC1_SET >> 4) * 5 + 1) | ||
44 | |||
45 | /* bit definitions of GPADC Misc 1 Register */ | ||
46 | #define GPMISC1_GPADC_EN (1 << 0) | ||
47 | |||
48 | /* bit definitions of Charger Control 6 Register */ | ||
49 | #define CC6_BAT_DET_GPADC1 1 | ||
50 | |||
51 | /* bit definitions of Coulomb Counter Reading Register */ | ||
52 | #define CCNT_AVG_SEL (4 << 3) | ||
53 | |||
54 | /* bit definitions of RTC miscellaneous Register1 */ | ||
55 | #define RTC_SOC_5LSB (0x1F << 3) | ||
56 | |||
57 | /* bit definitions of RTC Register1 */ | ||
58 | #define RTC_SOC_3MSB (0x7) | ||
59 | |||
60 | /* bit definitions of Power up Log register */ | ||
61 | #define BAT_WU_LOG (1<<6) | ||
62 | |||
63 | /* coulomb counter index */ | ||
64 | #define CCNT_POS1 0 | ||
65 | #define CCNT_POS2 1 | ||
66 | #define CCNT_NEG1 2 | ||
67 | #define CCNT_NEG2 3 | ||
68 | #define CCNT_SPOS 4 | ||
69 | #define CCNT_SNEG 5 | ||
70 | |||
71 | /* OCV -- Open Circuit Voltage */ | ||
72 | #define OCV_MODE_ACTIVE 0 | ||
73 | #define OCV_MODE_SLEEP 1 | ||
74 | |||
75 | /* Vbat range of CC for measuring Rbat */ | ||
76 | #define LOW_BAT_THRESHOLD 3600 | ||
77 | #define VBATT_RESISTOR_MIN 3800 | ||
78 | #define VBATT_RESISTOR_MAX 4100 | ||
79 | |||
80 | /* TBAT for batt, TINT for chip itself */ | ||
81 | #define PM860X_TEMP_TINT (0) | ||
82 | #define PM860X_TEMP_TBAT (1) | ||
83 | |||
84 | /* | ||
85 | * Battery temperature based on NTC resistor, defined | ||
86 | * corresponding resistor value -- Ohm / C degeree. | ||
87 | */ | ||
88 | #define TBAT_NEG_25D 127773 /* -25 */ | ||
89 | #define TBAT_NEG_10D 54564 /* -10 */ | ||
90 | #define TBAT_0D 32330 /* 0 */ | ||
91 | #define TBAT_10D 19785 /* 10 */ | ||
92 | #define TBAT_20D 12468 /* 20 */ | ||
93 | #define TBAT_30D 8072 /* 30 */ | ||
94 | #define TBAT_40D 5356 /* 40 */ | ||
95 | |||
96 | struct pm860x_battery_info { | ||
97 | struct pm860x_chip *chip; | ||
98 | struct i2c_client *i2c; | ||
99 | struct device *dev; | ||
100 | |||
101 | struct power_supply battery; | ||
102 | struct mutex lock; | ||
103 | int status; | ||
104 | int irq_cc; | ||
105 | int irq_batt; | ||
106 | int max_capacity; | ||
107 | int resistor; /* Battery Internal Resistor */ | ||
108 | int last_capacity; | ||
109 | int start_soc; | ||
110 | unsigned present:1; | ||
111 | unsigned temp_type:1; /* TINT or TBAT */ | ||
112 | }; | ||
113 | |||
114 | struct ccnt { | ||
115 | unsigned long long int pos; | ||
116 | unsigned long long int neg; | ||
117 | unsigned int spos; | ||
118 | unsigned int sneg; | ||
119 | |||
120 | int total_chg; /* mAh(3.6C) */ | ||
121 | int total_dischg; /* mAh(3.6C) */ | ||
122 | }; | ||
123 | |||
124 | /* | ||
125 | * State of Charge. | ||
126 | * The first number is mAh(=3.6C), and the second number is percent point. | ||
127 | */ | ||
128 | int array_soc[][2] = { | ||
129 | {4170, 100}, {4154, 99}, {4136, 98}, {4122, 97}, {4107, 96}, | ||
130 | {4102, 95}, {4088, 94}, {4081, 93}, {4070, 92}, {4060, 91}, | ||
131 | {4053, 90}, {4044, 89}, {4035, 88}, {4028, 87}, {4019, 86}, | ||
132 | {4013, 85}, {4006, 84}, {3995, 83}, {3987, 82}, {3982, 81}, | ||
133 | {3976, 80}, {3968, 79}, {3962, 78}, {3954, 77}, {3946, 76}, | ||
134 | {3941, 75}, {3934, 74}, {3929, 73}, {3922, 72}, {3916, 71}, | ||
135 | {3910, 70}, {3904, 69}, {3898, 68}, {3892, 67}, {3887, 66}, | ||
136 | {3880, 65}, {3874, 64}, {3868, 63}, {3862, 62}, {3854, 61}, | ||
137 | {3849, 60}, {3843, 59}, {3840, 58}, {3833, 57}, {3829, 56}, | ||
138 | {3824, 55}, {3818, 54}, {3815, 53}, {3810, 52}, {3808, 51}, | ||
139 | {3804, 50}, {3801, 49}, {3798, 48}, {3796, 47}, {3792, 46}, | ||
140 | {3789, 45}, {3785, 44}, {3784, 43}, {3782, 42}, {3780, 41}, | ||
141 | {3777, 40}, {3776, 39}, {3774, 38}, {3772, 37}, {3771, 36}, | ||
142 | {3769, 35}, {3768, 34}, {3764, 33}, {3763, 32}, {3760, 31}, | ||
143 | {3760, 30}, {3754, 29}, {3750, 28}, {3749, 27}, {3744, 26}, | ||
144 | {3740, 25}, {3734, 24}, {3732, 23}, {3728, 22}, {3726, 21}, | ||
145 | {3720, 20}, {3716, 19}, {3709, 18}, {3703, 17}, {3698, 16}, | ||
146 | {3692, 15}, {3683, 14}, {3675, 13}, {3670, 12}, {3665, 11}, | ||
147 | {3661, 10}, {3649, 9}, {3637, 8}, {3622, 7}, {3609, 6}, | ||
148 | {3580, 5}, {3558, 4}, {3540, 3}, {3510, 2}, {3429, 1}, | ||
149 | }; | ||
150 | |||
151 | static struct ccnt ccnt_data; | ||
152 | |||
153 | /* | ||
154 | * register 1 bit[7:0] -- bit[11:4] of measured value of voltage | ||
155 | * register 0 bit[3:0] -- bit[3:0] of measured value of voltage | ||
156 | */ | ||
157 | static int measure_12bit_voltage(struct pm860x_battery_info *info, | ||
158 | int offset, int *data) | ||
159 | { | ||
160 | unsigned char buf[2]; | ||
161 | int ret; | ||
162 | |||
163 | ret = pm860x_bulk_read(info->i2c, offset, 2, buf); | ||
164 | if (ret < 0) | ||
165 | return ret; | ||
166 | |||
167 | *data = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f); | ||
168 | /* V_MEAS(mV) = data * 1.8 * 1000 / (2^12) */ | ||
169 | *data = ((*data & 0xfff) * 9 * 25) >> 9; | ||
170 | return 0; | ||
171 | } | ||
172 | |||
173 | static int measure_vbatt(struct pm860x_battery_info *info, int state, | ||
174 | int *data) | ||
175 | { | ||
176 | unsigned char buf[5]; | ||
177 | int ret; | ||
178 | |||
179 | switch (state) { | ||
180 | case OCV_MODE_ACTIVE: | ||
181 | ret = measure_12bit_voltage(info, PM8607_VBAT_MEAS1, data); | ||
182 | if (ret) | ||
183 | return ret; | ||
184 | /* V_BATT_MEAS(mV) = value * 3 * 1.8 * 1000 / (2^12) */ | ||
185 | *data *= 3; | ||
186 | break; | ||
187 | case OCV_MODE_SLEEP: | ||
188 | /* | ||
189 | * voltage value of VBATT in sleep mode is saved in different | ||
190 | * registers. | ||
191 | * bit[11:10] -- bit[7:6] of LDO9(0x18) | ||
192 | * bit[9:8] -- bit[7:6] of LDO8(0x17) | ||
193 | * bit[7:6] -- bit[7:6] of LDO7(0x16) | ||
194 | * bit[5:4] -- bit[7:6] of LDO6(0x15) | ||
195 | * bit[3:0] -- bit[7:4] of LDO5(0x14) | ||
196 | */ | ||
197 | ret = pm860x_bulk_read(info->i2c, PM8607_LDO5, 5, buf); | ||
198 | if (ret < 0) | ||
199 | return ret; | ||
200 | ret = ((buf[4] >> 6) << 10) | ((buf[3] >> 6) << 8) | ||
201 | | ((buf[2] >> 6) << 6) | ((buf[1] >> 6) << 4) | ||
202 | | (buf[0] >> 4); | ||
203 | /* V_BATT_MEAS(mV) = data * 3 * 1.8 * 1000 / (2^12) */ | ||
204 | *data = ((*data & 0xff) * 27 * 25) >> 9; | ||
205 | break; | ||
206 | default: | ||
207 | return -EINVAL; | ||
208 | } | ||
209 | return 0; | ||
210 | } | ||
211 | |||
212 | /* | ||
213 | * Return value is signed data. | ||
214 | * Negative value means discharging, and positive value means charging. | ||
215 | */ | ||
216 | static int measure_current(struct pm860x_battery_info *info, int *data) | ||
217 | { | ||
218 | unsigned char buf[2]; | ||
219 | short s; | ||
220 | int ret; | ||
221 | |||
222 | ret = pm860x_bulk_read(info->i2c, PM8607_IBAT_MEAS1, 2, buf); | ||
223 | if (ret < 0) | ||
224 | return ret; | ||
225 | |||
226 | s = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff); | ||
227 | /* current(mA) = value * 0.125 */ | ||
228 | *data = s >> 3; | ||
229 | return 0; | ||
230 | } | ||
231 | |||
232 | static int set_charger_current(struct pm860x_battery_info *info, int data, | ||
233 | int *old) | ||
234 | { | ||
235 | int ret; | ||
236 | |||
237 | if (data < 50 || data > 1600 || !old) | ||
238 | return -EINVAL; | ||
239 | |||
240 | data = ((data - 50) / 50) & 0x1f; | ||
241 | *old = pm860x_reg_read(info->i2c, PM8607_CHG_CTRL2); | ||
242 | *old = (*old & 0x1f) * 50 + 50; | ||
243 | ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL2, 0x1f, data); | ||
244 | if (ret < 0) | ||
245 | return ret; | ||
246 | return 0; | ||
247 | } | ||
248 | |||
249 | static int read_ccnt(struct pm860x_battery_info *info, int offset, | ||
250 | int *ccnt) | ||
251 | { | ||
252 | unsigned char buf[2]; | ||
253 | int ret; | ||
254 | |||
255 | ret = pm860x_set_bits(info->i2c, PM8607_CCNT, 7, offset & 7); | ||
256 | if (ret < 0) | ||
257 | goto out; | ||
258 | ret = pm860x_bulk_read(info->i2c, PM8607_CCNT_MEAS1, 2, buf); | ||
259 | if (ret < 0) | ||
260 | goto out; | ||
261 | *ccnt = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff); | ||
262 | return 0; | ||
263 | out: | ||
264 | return ret; | ||
265 | } | ||
266 | |||
267 | static int calc_ccnt(struct pm860x_battery_info *info, struct ccnt *ccnt) | ||
268 | { | ||
269 | unsigned int sum; | ||
270 | int ret; | ||
271 | int data; | ||
272 | |||
273 | ret = read_ccnt(info, CCNT_POS1, &data); | ||
274 | if (ret) | ||
275 | goto out; | ||
276 | sum = data & 0xffff; | ||
277 | ret = read_ccnt(info, CCNT_POS2, &data); | ||
278 | if (ret) | ||
279 | goto out; | ||
280 | sum |= (data & 0xffff) << 16; | ||
281 | ccnt->pos += sum; | ||
282 | |||
283 | ret = read_ccnt(info, CCNT_NEG1, &data); | ||
284 | if (ret) | ||
285 | goto out; | ||
286 | sum = data & 0xffff; | ||
287 | ret = read_ccnt(info, CCNT_NEG2, &data); | ||
288 | if (ret) | ||
289 | goto out; | ||
290 | sum |= (data & 0xffff) << 16; | ||
291 | sum = ~sum + 1; /* since it's negative */ | ||
292 | ccnt->neg += sum; | ||
293 | |||
294 | ret = read_ccnt(info, CCNT_SPOS, &data); | ||
295 | if (ret) | ||
296 | goto out; | ||
297 | ccnt->spos += data; | ||
298 | ret = read_ccnt(info, CCNT_SNEG, &data); | ||
299 | if (ret) | ||
300 | goto out; | ||
301 | |||
302 | /* | ||
303 | * charge(mAh) = count * 1.6984 * 1e(-8) | ||
304 | * = count * 16984 * 1.024 * 1.024 * 1.024 / (2 ^ 40) | ||
305 | * = count * 18236 / (2 ^ 40) | ||
306 | */ | ||
307 | ccnt->total_chg = (int) ((ccnt->pos * 18236) >> 40); | ||
308 | ccnt->total_dischg = (int) ((ccnt->neg * 18236) >> 40); | ||
309 | return 0; | ||
310 | out: | ||
311 | return ret; | ||
312 | } | ||
313 | |||
314 | static int clear_ccnt(struct pm860x_battery_info *info, struct ccnt *ccnt) | ||
315 | { | ||
316 | int data; | ||
317 | |||
318 | memset(ccnt, 0, sizeof(*ccnt)); | ||
319 | /* read to clear ccnt */ | ||
320 | read_ccnt(info, CCNT_POS1, &data); | ||
321 | read_ccnt(info, CCNT_POS2, &data); | ||
322 | read_ccnt(info, CCNT_NEG1, &data); | ||
323 | read_ccnt(info, CCNT_NEG2, &data); | ||
324 | read_ccnt(info, CCNT_SPOS, &data); | ||
325 | read_ccnt(info, CCNT_SNEG, &data); | ||
326 | return 0; | ||
327 | } | ||
328 | |||
329 | /* Calculate Open Circuit Voltage */ | ||
330 | static int calc_ocv(struct pm860x_battery_info *info, int *ocv) | ||
331 | { | ||
332 | int ret; | ||
333 | int i; | ||
334 | int data; | ||
335 | int vbatt_avg; | ||
336 | int vbatt_sum; | ||
337 | int ibatt_avg; | ||
338 | int ibatt_sum; | ||
339 | |||
340 | if (!ocv) | ||
341 | return -EINVAL; | ||
342 | |||
343 | for (i = 0, ibatt_sum = 0, vbatt_sum = 0; i < 10; i++) { | ||
344 | ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); | ||
345 | if (ret) | ||
346 | goto out; | ||
347 | vbatt_sum += data; | ||
348 | ret = measure_current(info, &data); | ||
349 | if (ret) | ||
350 | goto out; | ||
351 | ibatt_sum += data; | ||
352 | } | ||
353 | vbatt_avg = vbatt_sum / 10; | ||
354 | ibatt_avg = ibatt_sum / 10; | ||
355 | |||
356 | mutex_lock(&info->lock); | ||
357 | if (info->present) | ||
358 | *ocv = vbatt_avg - ibatt_avg * info->resistor / 1000; | ||
359 | else | ||
360 | *ocv = vbatt_avg; | ||
361 | mutex_unlock(&info->lock); | ||
362 | dev_dbg(info->dev, "VBAT average:%d, OCV:%d\n", vbatt_avg, *ocv); | ||
363 | return 0; | ||
364 | out: | ||
365 | return ret; | ||
366 | } | ||
367 | |||
368 | /* Calculate State of Charge (percent points) */ | ||
369 | static int calc_soc(struct pm860x_battery_info *info, int state, int *soc) | ||
370 | { | ||
371 | int i; | ||
372 | int ocv; | ||
373 | int count; | ||
374 | int ret = -EINVAL; | ||
375 | |||
376 | if (!soc) | ||
377 | return -EINVAL; | ||
378 | |||
379 | switch (state) { | ||
380 | case OCV_MODE_ACTIVE: | ||
381 | ret = calc_ocv(info, &ocv); | ||
382 | break; | ||
383 | case OCV_MODE_SLEEP: | ||
384 | ret = measure_vbatt(info, OCV_MODE_SLEEP, &ocv); | ||
385 | break; | ||
386 | } | ||
387 | if (ret) | ||
388 | return ret; | ||
389 | |||
390 | count = ARRAY_SIZE(array_soc); | ||
391 | if (ocv < array_soc[count - 1][0]) { | ||
392 | *soc = 0; | ||
393 | return 0; | ||
394 | } | ||
395 | |||
396 | for (i = 0; i < count; i++) { | ||
397 | if (ocv >= array_soc[i][0]) { | ||
398 | *soc = array_soc[i][1]; | ||
399 | break; | ||
400 | } | ||
401 | } | ||
402 | return 0; | ||
403 | } | ||
404 | |||
405 | static irqreturn_t pm860x_coulomb_handler(int irq, void *data) | ||
406 | { | ||
407 | struct pm860x_battery_info *info = data; | ||
408 | |||
409 | calc_ccnt(info, &ccnt_data); | ||
410 | return IRQ_HANDLED; | ||
411 | } | ||
412 | |||
413 | static irqreturn_t pm860x_batt_handler(int irq, void *data) | ||
414 | { | ||
415 | struct pm860x_battery_info *info = data; | ||
416 | int ret; | ||
417 | |||
418 | mutex_lock(&info->lock); | ||
419 | ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); | ||
420 | if (ret & STATUS2_BAT) { | ||
421 | info->present = 1; | ||
422 | info->temp_type = PM860X_TEMP_TBAT; | ||
423 | } else { | ||
424 | info->present = 0; | ||
425 | info->temp_type = PM860X_TEMP_TINT; | ||
426 | } | ||
427 | mutex_unlock(&info->lock); | ||
428 | /* clear ccnt since battery is attached or dettached */ | ||
429 | clear_ccnt(info, &ccnt_data); | ||
430 | return IRQ_HANDLED; | ||
431 | } | ||
432 | |||
433 | static void pm860x_init_battery(struct pm860x_battery_info *info) | ||
434 | { | ||
435 | unsigned char buf[2]; | ||
436 | int ret; | ||
437 | int data; | ||
438 | int bat_remove; | ||
439 | int soc; | ||
440 | |||
441 | /* measure enable on GPADC1 */ | ||
442 | data = MEAS1_GP1; | ||
443 | if (info->temp_type == PM860X_TEMP_TINT) | ||
444 | data |= MEAS1_TINT; | ||
445 | ret = pm860x_set_bits(info->i2c, PM8607_MEAS_EN1, data, data); | ||
446 | if (ret) | ||
447 | goto out; | ||
448 | |||
449 | /* measure enable on IBAT, BAT_DET, CC. IBAT is depend on CC. */ | ||
450 | data = MEAS3_IBAT | MEAS3_BAT_DET | MEAS3_CC; | ||
451 | ret = pm860x_set_bits(info->i2c, PM8607_MEAS_EN3, data, data); | ||
452 | if (ret) | ||
453 | goto out; | ||
454 | |||
455 | /* measure disable CC in sleep time */ | ||
456 | ret = pm860x_reg_write(info->i2c, PM8607_MEAS_OFF_TIME1, 0x82); | ||
457 | if (ret) | ||
458 | goto out; | ||
459 | ret = pm860x_reg_write(info->i2c, PM8607_MEAS_OFF_TIME2, 0x6c); | ||
460 | if (ret) | ||
461 | goto out; | ||
462 | |||
463 | /* enable GPADC */ | ||
464 | ret = pm860x_set_bits(info->i2c, PM8607_GPADC_MISC1, | ||
465 | GPMISC1_GPADC_EN, GPMISC1_GPADC_EN); | ||
466 | if (ret < 0) | ||
467 | goto out; | ||
468 | |||
469 | /* detect battery via GPADC1 */ | ||
470 | ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL6, | ||
471 | CC6_BAT_DET_GPADC1, CC6_BAT_DET_GPADC1); | ||
472 | if (ret < 0) | ||
473 | goto out; | ||
474 | |||
475 | ret = pm860x_set_bits(info->i2c, PM8607_CCNT, 7 << 3, | ||
476 | CCNT_AVG_SEL); | ||
477 | if (ret < 0) | ||
478 | goto out; | ||
479 | |||
480 | /* set GPADC1 bias */ | ||
481 | ret = pm860x_set_bits(info->i2c, PM8607_GP_BIAS2, 0xF << 4, | ||
482 | GPBIAS2_GPADC1_SET); | ||
483 | if (ret < 0) | ||
484 | goto out; | ||
485 | |||
486 | /* check whether battery present) */ | ||
487 | mutex_lock(&info->lock); | ||
488 | ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); | ||
489 | if (ret < 0) { | ||
490 | mutex_unlock(&info->lock); | ||
491 | goto out; | ||
492 | } | ||
493 | if (ret & STATUS2_BAT) { | ||
494 | info->present = 1; | ||
495 | info->temp_type = PM860X_TEMP_TBAT; | ||
496 | } else { | ||
497 | info->present = 0; | ||
498 | info->temp_type = PM860X_TEMP_TINT; | ||
499 | } | ||
500 | mutex_unlock(&info->lock); | ||
501 | |||
502 | calc_soc(info, OCV_MODE_ACTIVE, &soc); | ||
503 | |||
504 | data = pm860x_reg_read(info->i2c, PM8607_POWER_UP_LOG); | ||
505 | bat_remove = data & BAT_WU_LOG; | ||
506 | |||
507 | dev_dbg(info->dev, "battery wake up? %s\n", | ||
508 | bat_remove != 0 ? "yes" : "no"); | ||
509 | |||
510 | /* restore SOC from RTC domain register */ | ||
511 | if (bat_remove == 0) { | ||
512 | buf[0] = pm860x_reg_read(info->i2c, PM8607_RTC_MISC2); | ||
513 | buf[1] = pm860x_reg_read(info->i2c, PM8607_RTC1); | ||
514 | data = ((buf[1] & 0x3) << 5) | ((buf[0] >> 3) & 0x1F); | ||
515 | if (data > soc + 15) | ||
516 | info->start_soc = soc; | ||
517 | else if (data < soc - 15) | ||
518 | info->start_soc = soc; | ||
519 | else | ||
520 | info->start_soc = data; | ||
521 | dev_dbg(info->dev, "soc_rtc %d, soc_ocv :%d\n", data, soc); | ||
522 | } else { | ||
523 | pm860x_set_bits(info->i2c, PM8607_POWER_UP_LOG, | ||
524 | BAT_WU_LOG, BAT_WU_LOG); | ||
525 | info->start_soc = soc; | ||
526 | } | ||
527 | info->last_capacity = info->start_soc; | ||
528 | dev_dbg(info->dev, "init soc : %d\n", info->last_capacity); | ||
529 | out: | ||
530 | return; | ||
531 | } | ||
532 | |||
533 | static void set_temp_threshold(struct pm860x_battery_info *info, | ||
534 | int min, int max) | ||
535 | { | ||
536 | int data; | ||
537 | |||
538 | /* (tmp << 8) / 1800 */ | ||
539 | if (min <= 0) | ||
540 | data = 0; | ||
541 | else | ||
542 | data = (min << 8) / 1800; | ||
543 | pm860x_reg_write(info->i2c, PM8607_GPADC1_HIGHTH, data); | ||
544 | dev_dbg(info->dev, "TEMP_HIGHTH : min: %d, 0x%x\n", min, data); | ||
545 | |||
546 | if (max <= 0) | ||
547 | data = 0xff; | ||
548 | else | ||
549 | data = (max << 8) / 1800; | ||
550 | pm860x_reg_write(info->i2c, PM8607_GPADC1_LOWTH, data); | ||
551 | dev_dbg(info->dev, "TEMP_LOWTH:max : %d, 0x%x\n", max, data); | ||
552 | } | ||
553 | |||
554 | static int measure_temp(struct pm860x_battery_info *info, int *data) | ||
555 | { | ||
556 | int ret; | ||
557 | int temp; | ||
558 | int min; | ||
559 | int max; | ||
560 | |||
561 | if (info->temp_type == PM860X_TEMP_TINT) { | ||
562 | ret = measure_12bit_voltage(info, PM8607_TINT_MEAS1, data); | ||
563 | if (ret) | ||
564 | return ret; | ||
565 | *data = (*data - 884) * 1000 / 3611; | ||
566 | } else { | ||
567 | ret = measure_12bit_voltage(info, PM8607_GPADC1_MEAS1, data); | ||
568 | if (ret) | ||
569 | return ret; | ||
570 | /* meausered Vtbat(mV) / Ibias_current(11uA)*/ | ||
571 | *data = (*data * 1000) / GPBIAS2_GPADC1_UA; | ||
572 | |||
573 | if (*data > TBAT_NEG_25D) { | ||
574 | temp = -30; /* over cold , suppose -30 roughly */ | ||
575 | max = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; | ||
576 | set_temp_threshold(info, 0, max); | ||
577 | } else if (*data > TBAT_NEG_10D) { | ||
578 | temp = -15; /* -15 degree, code */ | ||
579 | max = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; | ||
580 | set_temp_threshold(info, 0, max); | ||
581 | } else if (*data > TBAT_0D) { | ||
582 | temp = -5; /* -5 degree */ | ||
583 | min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; | ||
584 | max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; | ||
585 | set_temp_threshold(info, min, max); | ||
586 | } else if (*data > TBAT_10D) { | ||
587 | temp = 5; /* in range of (0, 10) */ | ||
588 | min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; | ||
589 | max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; | ||
590 | set_temp_threshold(info, min, max); | ||
591 | } else if (*data > TBAT_20D) { | ||
592 | temp = 15; /* in range of (10, 20) */ | ||
593 | min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; | ||
594 | max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; | ||
595 | set_temp_threshold(info, min, max); | ||
596 | } else if (*data > TBAT_30D) { | ||
597 | temp = 25; /* in range of (20, 30) */ | ||
598 | min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; | ||
599 | max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; | ||
600 | set_temp_threshold(info, min, max); | ||
601 | } else if (*data > TBAT_40D) { | ||
602 | temp = 35; /* in range of (30, 40) */ | ||
603 | min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; | ||
604 | max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; | ||
605 | set_temp_threshold(info, min, max); | ||
606 | } else { | ||
607 | min = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; | ||
608 | set_temp_threshold(info, min, 0); | ||
609 | temp = 45; /* over heat ,suppose 45 roughly */ | ||
610 | } | ||
611 | |||
612 | dev_dbg(info->dev, "temp_C:%d C,temp_mv:%d mv\n", temp, *data); | ||
613 | *data = temp; | ||
614 | } | ||
615 | return 0; | ||
616 | } | ||
617 | |||
618 | static int calc_resistor(struct pm860x_battery_info *info) | ||
619 | { | ||
620 | int vbatt_sum1; | ||
621 | int vbatt_sum2; | ||
622 | int chg_current; | ||
623 | int ibatt_sum1; | ||
624 | int ibatt_sum2; | ||
625 | int data; | ||
626 | int ret; | ||
627 | int i; | ||
628 | |||
629 | ret = measure_current(info, &data); | ||
630 | /* make sure that charging is launched by data > 0 */ | ||
631 | if (ret || data < 0) | ||
632 | goto out; | ||
633 | |||
634 | ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); | ||
635 | if (ret) | ||
636 | goto out; | ||
637 | /* calculate resistor only in CC charge mode */ | ||
638 | if (data < VBATT_RESISTOR_MIN || data > VBATT_RESISTOR_MAX) | ||
639 | goto out; | ||
640 | |||
641 | /* current is saved */ | ||
642 | if (set_charger_current(info, 500, &chg_current)) | ||
643 | goto out; | ||
644 | |||
645 | /* | ||
646 | * set charge current as 500mA, wait about 500ms till charging | ||
647 | * process is launched and stable with the newer charging current. | ||
648 | */ | ||
649 | msleep(500); | ||
650 | |||
651 | for (i = 0, vbatt_sum1 = 0, ibatt_sum1 = 0; i < 10; i++) { | ||
652 | ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); | ||
653 | if (ret) | ||
654 | goto out_meas; | ||
655 | vbatt_sum1 += data; | ||
656 | ret = measure_current(info, &data); | ||
657 | if (ret) | ||
658 | goto out_meas; | ||
659 | |||
660 | if (data < 0) | ||
661 | ibatt_sum1 = ibatt_sum1 - data; /* discharging */ | ||
662 | else | ||
663 | ibatt_sum1 = ibatt_sum1 + data; /* charging */ | ||
664 | } | ||
665 | |||
666 | if (set_charger_current(info, 100, &ret)) | ||
667 | goto out_meas; | ||
668 | /* | ||
669 | * set charge current as 100mA, wait about 500ms till charging | ||
670 | * process is launched and stable with the newer charging current. | ||
671 | */ | ||
672 | msleep(500); | ||
673 | |||
674 | for (i = 0, vbatt_sum2 = 0, ibatt_sum2 = 0; i < 10; i++) { | ||
675 | ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); | ||
676 | if (ret) | ||
677 | goto out_meas; | ||
678 | vbatt_sum2 += data; | ||
679 | ret = measure_current(info, &data); | ||
680 | if (ret) | ||
681 | goto out_meas; | ||
682 | |||
683 | if (data < 0) | ||
684 | ibatt_sum2 = ibatt_sum2 - data; /* discharging */ | ||
685 | else | ||
686 | ibatt_sum2 = ibatt_sum2 + data; /* charging */ | ||
687 | } | ||
688 | |||
689 | /* restore current setting */ | ||
690 | if (set_charger_current(info, chg_current, &ret)) | ||
691 | goto out_meas; | ||
692 | |||
693 | if ((vbatt_sum1 > vbatt_sum2) && (ibatt_sum1 > ibatt_sum2) && | ||
694 | (ibatt_sum2 > 0)) { | ||
695 | /* calculate resistor in discharging case */ | ||
696 | data = 1000 * (vbatt_sum1 - vbatt_sum2) | ||
697 | / (ibatt_sum1 - ibatt_sum2); | ||
698 | if ((data - info->resistor > 0) && | ||
699 | (data - info->resistor < info->resistor)) | ||
700 | info->resistor = data; | ||
701 | if ((info->resistor - data > 0) && | ||
702 | (info->resistor - data < data)) | ||
703 | info->resistor = data; | ||
704 | } | ||
705 | return 0; | ||
706 | |||
707 | out_meas: | ||
708 | set_charger_current(info, chg_current, &ret); | ||
709 | out: | ||
710 | return -EINVAL; | ||
711 | } | ||
712 | |||
713 | static int calc_capacity(struct pm860x_battery_info *info, int *cap) | ||
714 | { | ||
715 | int ret; | ||
716 | int data; | ||
717 | int ibat; | ||
718 | int cap_ocv = 0; | ||
719 | int cap_cc = 0; | ||
720 | |||
721 | ret = calc_ccnt(info, &ccnt_data); | ||
722 | if (ret) | ||
723 | goto out; | ||
724 | soc: | ||
725 | data = info->max_capacity * info->start_soc / 100; | ||
726 | if (ccnt_data.total_dischg - ccnt_data.total_chg <= data) { | ||
727 | cap_cc = | ||
728 | data + ccnt_data.total_chg - ccnt_data.total_dischg; | ||
729 | } else { | ||
730 | clear_ccnt(info, &ccnt_data); | ||
731 | calc_soc(info, OCV_MODE_ACTIVE, &info->start_soc); | ||
732 | dev_dbg(info->dev, "restart soc = %d !\n", | ||
733 | info->start_soc); | ||
734 | goto soc; | ||
735 | } | ||
736 | |||
737 | cap_cc = cap_cc * 100 / info->max_capacity; | ||
738 | if (cap_cc < 0) | ||
739 | cap_cc = 0; | ||
740 | else if (cap_cc > 100) | ||
741 | cap_cc = 100; | ||
742 | |||
743 | dev_dbg(info->dev, "%s, last cap : %d", __func__, | ||
744 | info->last_capacity); | ||
745 | |||
746 | ret = measure_current(info, &ibat); | ||
747 | if (ret) | ||
748 | goto out; | ||
749 | /* Calculate the capacity when discharging(ibat < 0) */ | ||
750 | if (ibat < 0) { | ||
751 | ret = calc_soc(info, OCV_MODE_ACTIVE, &cap_ocv); | ||
752 | if (ret) | ||
753 | cap_ocv = info->last_capacity; | ||
754 | ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); | ||
755 | if (ret) | ||
756 | goto out; | ||
757 | if (data <= LOW_BAT_THRESHOLD) { | ||
758 | /* choose the lower capacity value to report | ||
759 | * between vbat and CC when vbat < 3.6v; | ||
760 | * than 3.6v; | ||
761 | */ | ||
762 | *cap = min(cap_ocv, cap_cc); | ||
763 | } else { | ||
764 | /* when detect vbat > 3.6v, but cap_cc < 15,and | ||
765 | * cap_ocv is 10% larger than cap_cc, we can think | ||
766 | * CC have some accumulation error, switch to OCV | ||
767 | * to estimate capacity; | ||
768 | * */ | ||
769 | if (cap_cc < 15 && cap_ocv - cap_cc > 10) | ||
770 | *cap = cap_ocv; | ||
771 | else | ||
772 | *cap = cap_cc; | ||
773 | } | ||
774 | /* when discharging, make sure current capacity | ||
775 | * is lower than last*/ | ||
776 | if (*cap > info->last_capacity) | ||
777 | *cap = info->last_capacity; | ||
778 | } else { | ||
779 | *cap = cap_cc; | ||
780 | } | ||
781 | info->last_capacity = *cap; | ||
782 | |||
783 | dev_dbg(info->dev, "%s, cap_ocv:%d cap_cc:%d, cap:%d\n", | ||
784 | (ibat < 0) ? "discharging" : "charging", | ||
785 | cap_ocv, cap_cc, *cap); | ||
786 | /* | ||
787 | * store the current capacity to RTC domain register, | ||
788 | * after next power up , it will be restored. | ||
789 | */ | ||
790 | pm860x_set_bits(info->i2c, PM8607_RTC_MISC2, RTC_SOC_5LSB, | ||
791 | (*cap & 0x1F) << 3); | ||
792 | pm860x_set_bits(info->i2c, PM8607_RTC1, RTC_SOC_3MSB, | ||
793 | ((*cap >> 5) & 0x3)); | ||
794 | return 0; | ||
795 | out: | ||
796 | return ret; | ||
797 | } | ||
798 | |||
799 | static void pm860x_external_power_changed(struct power_supply *psy) | ||
800 | { | ||
801 | struct pm860x_battery_info *info; | ||
802 | |||
803 | info = container_of(psy, struct pm860x_battery_info, battery); | ||
804 | calc_resistor(info); | ||
805 | } | ||
806 | |||
807 | static int pm860x_batt_get_prop(struct power_supply *psy, | ||
808 | enum power_supply_property psp, | ||
809 | union power_supply_propval *val) | ||
810 | { | ||
811 | struct pm860x_battery_info *info = dev_get_drvdata(psy->dev->parent); | ||
812 | int data; | ||
813 | int ret; | ||
814 | |||
815 | switch (psp) { | ||
816 | case POWER_SUPPLY_PROP_PRESENT: | ||
817 | val->intval = info->present; | ||
818 | break; | ||
819 | case POWER_SUPPLY_PROP_CAPACITY: | ||
820 | ret = calc_capacity(info, &data); | ||
821 | if (ret) | ||
822 | return ret; | ||
823 | if (data < 0) | ||
824 | data = 0; | ||
825 | else if (data > 100) | ||
826 | data = 100; | ||
827 | /* return 100 if battery is not attached */ | ||
828 | if (!info->present) | ||
829 | data = 100; | ||
830 | val->intval = data; | ||
831 | break; | ||
832 | case POWER_SUPPLY_PROP_TECHNOLOGY: | ||
833 | val->intval = POWER_SUPPLY_TECHNOLOGY_LION; | ||
834 | break; | ||
835 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: | ||
836 | /* return real vbatt Voltage */ | ||
837 | ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); | ||
838 | if (ret) | ||
839 | return ret; | ||
840 | val->intval = data * 1000; | ||
841 | break; | ||
842 | case POWER_SUPPLY_PROP_VOLTAGE_AVG: | ||
843 | /* return Open Circuit Voltage (not measured voltage) */ | ||
844 | ret = calc_ocv(info, &data); | ||
845 | if (ret) | ||
846 | return ret; | ||
847 | val->intval = data * 1000; | ||
848 | break; | ||
849 | case POWER_SUPPLY_PROP_CURRENT_NOW: | ||
850 | ret = measure_current(info, &data); | ||
851 | if (ret) | ||
852 | return ret; | ||
853 | val->intval = data; | ||
854 | break; | ||
855 | case POWER_SUPPLY_PROP_TEMP: | ||
856 | if (info->present) { | ||
857 | ret = measure_temp(info, &data); | ||
858 | if (ret) | ||
859 | return ret; | ||
860 | data *= 10; | ||
861 | } else { | ||
862 | /* Fake Temp 25C Without Battery */ | ||
863 | data = 250; | ||
864 | } | ||
865 | val->intval = data; | ||
866 | break; | ||
867 | default: | ||
868 | return -ENODEV; | ||
869 | } | ||
870 | return 0; | ||
871 | } | ||
872 | |||
873 | static int pm860x_batt_set_prop(struct power_supply *psy, | ||
874 | enum power_supply_property psp, | ||
875 | const union power_supply_propval *val) | ||
876 | { | ||
877 | struct pm860x_battery_info *info = dev_get_drvdata(psy->dev->parent); | ||
878 | |||
879 | switch (psp) { | ||
880 | case POWER_SUPPLY_PROP_CHARGE_FULL: | ||
881 | clear_ccnt(info, &ccnt_data); | ||
882 | info->start_soc = 100; | ||
883 | dev_dbg(info->dev, "chg done, update soc = %d\n", | ||
884 | info->start_soc); | ||
885 | break; | ||
886 | default: | ||
887 | return -EPERM; | ||
888 | } | ||
889 | |||
890 | return 0; | ||
891 | } | ||
892 | |||
893 | |||
894 | static enum power_supply_property pm860x_batt_props[] = { | ||
895 | POWER_SUPPLY_PROP_PRESENT, | ||
896 | POWER_SUPPLY_PROP_CAPACITY, | ||
897 | POWER_SUPPLY_PROP_TECHNOLOGY, | ||
898 | POWER_SUPPLY_PROP_VOLTAGE_NOW, | ||
899 | POWER_SUPPLY_PROP_VOLTAGE_AVG, | ||
900 | POWER_SUPPLY_PROP_CURRENT_NOW, | ||
901 | POWER_SUPPLY_PROP_TEMP, | ||
902 | }; | ||
903 | |||
904 | static __devinit int pm860x_battery_probe(struct platform_device *pdev) | ||
905 | { | ||
906 | struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); | ||
907 | struct pm860x_battery_info *info; | ||
908 | struct pm860x_power_pdata *pdata; | ||
909 | int ret; | ||
910 | |||
911 | info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); | ||
912 | if (!info) | ||
913 | return -ENOMEM; | ||
914 | |||
915 | info->irq_cc = platform_get_irq(pdev, 0); | ||
916 | if (info->irq_cc <= 0) { | ||
917 | dev_err(&pdev->dev, "No IRQ resource!\n"); | ||
918 | ret = -EINVAL; | ||
919 | goto out; | ||
920 | } | ||
921 | |||
922 | info->irq_batt = platform_get_irq(pdev, 1); | ||
923 | if (info->irq_batt <= 0) { | ||
924 | dev_err(&pdev->dev, "No IRQ resource!\n"); | ||
925 | ret = -EINVAL; | ||
926 | goto out; | ||
927 | } | ||
928 | |||
929 | info->chip = chip; | ||
930 | info->i2c = | ||
931 | (chip->id == CHIP_PM8607) ? chip->client : chip->companion; | ||
932 | info->dev = &pdev->dev; | ||
933 | info->status = POWER_SUPPLY_STATUS_UNKNOWN; | ||
934 | pdata = pdev->dev.platform_data; | ||
935 | |||
936 | mutex_init(&info->lock); | ||
937 | platform_set_drvdata(pdev, info); | ||
938 | |||
939 | pm860x_init_battery(info); | ||
940 | |||
941 | info->battery.name = "battery-monitor"; | ||
942 | info->battery.type = POWER_SUPPLY_TYPE_BATTERY; | ||
943 | info->battery.properties = pm860x_batt_props; | ||
944 | info->battery.num_properties = ARRAY_SIZE(pm860x_batt_props); | ||
945 | info->battery.get_property = pm860x_batt_get_prop; | ||
946 | info->battery.set_property = pm860x_batt_set_prop; | ||
947 | info->battery.external_power_changed = pm860x_external_power_changed; | ||
948 | |||
949 | if (pdata && pdata->max_capacity) | ||
950 | info->max_capacity = pdata->max_capacity; | ||
951 | else | ||
952 | info->max_capacity = 1500; /* set default capacity */ | ||
953 | if (pdata && pdata->resistor) | ||
954 | info->resistor = pdata->resistor; | ||
955 | else | ||
956 | info->resistor = 300; /* set default internal resistor */ | ||
957 | |||
958 | ret = power_supply_register(&pdev->dev, &info->battery); | ||
959 | if (ret) | ||
960 | goto out; | ||
961 | info->battery.dev->parent = &pdev->dev; | ||
962 | |||
963 | ret = request_threaded_irq(info->irq_cc, NULL, | ||
964 | pm860x_coulomb_handler, IRQF_ONESHOT, | ||
965 | "coulomb", info); | ||
966 | if (ret < 0) { | ||
967 | dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", | ||
968 | info->irq_cc, ret); | ||
969 | goto out_reg; | ||
970 | } | ||
971 | |||
972 | ret = request_threaded_irq(info->irq_batt, NULL, pm860x_batt_handler, | ||
973 | IRQF_ONESHOT, "battery", info); | ||
974 | if (ret < 0) { | ||
975 | dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", | ||
976 | info->irq_batt, ret); | ||
977 | goto out_coulomb; | ||
978 | } | ||
979 | |||
980 | |||
981 | return 0; | ||
982 | |||
983 | out_coulomb: | ||
984 | free_irq(info->irq_cc, info); | ||
985 | out_reg: | ||
986 | power_supply_unregister(&info->battery); | ||
987 | out: | ||
988 | kfree(info); | ||
989 | return ret; | ||
990 | } | ||
991 | |||
992 | static int __devexit pm860x_battery_remove(struct platform_device *pdev) | ||
993 | { | ||
994 | struct pm860x_battery_info *info = platform_get_drvdata(pdev); | ||
995 | |||
996 | power_supply_unregister(&info->battery); | ||
997 | free_irq(info->irq_batt, info); | ||
998 | free_irq(info->irq_cc, info); | ||
999 | kfree(info); | ||
1000 | platform_set_drvdata(pdev, NULL); | ||
1001 | return 0; | ||
1002 | } | ||
1003 | |||
1004 | #ifdef CONFIG_PM_SLEEP | ||
1005 | static int pm860x_battery_suspend(struct device *dev) | ||
1006 | { | ||
1007 | struct platform_device *pdev = to_platform_device(dev); | ||
1008 | struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); | ||
1009 | |||
1010 | if (device_may_wakeup(dev)) | ||
1011 | chip->wakeup_flag |= 1 << PM8607_IRQ_CC; | ||
1012 | return 0; | ||
1013 | } | ||
1014 | |||
1015 | static int pm860x_battery_resume(struct device *dev) | ||
1016 | { | ||
1017 | struct platform_device *pdev = to_platform_device(dev); | ||
1018 | struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); | ||
1019 | |||
1020 | if (device_may_wakeup(dev)) | ||
1021 | chip->wakeup_flag &= ~(1 << PM8607_IRQ_CC); | ||
1022 | return 0; | ||
1023 | } | ||
1024 | #endif | ||
1025 | |||
1026 | static SIMPLE_DEV_PM_OPS(pm860x_battery_pm_ops, | ||
1027 | pm860x_battery_suspend, pm860x_battery_resume); | ||
1028 | |||
1029 | static struct platform_driver pm860x_battery_driver = { | ||
1030 | .driver = { | ||
1031 | .name = "88pm860x-battery", | ||
1032 | .owner = THIS_MODULE, | ||
1033 | .pm = &pm860x_battery_pm_ops, | ||
1034 | }, | ||
1035 | .probe = pm860x_battery_probe, | ||
1036 | .remove = __devexit_p(pm860x_battery_remove), | ||
1037 | }; | ||
1038 | module_platform_driver(pm860x_battery_driver); | ||
1039 | |||
1040 | MODULE_DESCRIPTION("Marvell 88PM860x Battery driver"); | ||
1041 | MODULE_LICENSE("GPL"); | ||