power: supply: act8945a_charger: Add capacity level property
[deliverable/linux.git] / drivers / power / supply / act8945a_charger.c
CommitLineData
5c0e09e0
WY
1/*
2 * Power supply driver for the Active-semi ACT8945A PMIC
3 *
4 * Copyright (C) 2015 Atmel Corporation
5 *
6 * Author: Wenyou Yang <wenyou.yang@atmel.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 */
a09209ac 13#include <linux/interrupt.h>
5c0e09e0
WY
14#include <linux/module.h>
15#include <linux/of.h>
16#include <linux/of_gpio.h>
a09209ac 17#include <linux/of_irq.h>
5c0e09e0
WY
18#include <linux/platform_device.h>
19#include <linux/power_supply.h>
20#include <linux/regmap.h>
369eba09 21#include <linux/gpio/consumer.h>
5c0e09e0
WY
22
23static const char *act8945a_charger_model = "ACT8945A";
24static const char *act8945a_charger_manufacturer = "Active-semi";
25
26/**
27 * ACT8945A Charger Register Map
28 */
29
30/* 0x70: Reserved */
31#define ACT8945A_APCH_CFG 0x71
32#define ACT8945A_APCH_STATUS 0x78
33#define ACT8945A_APCH_CTRL 0x79
34#define ACT8945A_APCH_STATE 0x7A
35
36/* ACT8945A_APCH_CFG */
37#define APCH_CFG_OVPSET (0x3 << 0)
38#define APCH_CFG_OVPSET_6V6 (0x0 << 0)
39#define APCH_CFG_OVPSET_7V (0x1 << 0)
40#define APCH_CFG_OVPSET_7V5 (0x2 << 0)
41#define APCH_CFG_OVPSET_8V (0x3 << 0)
42#define APCH_CFG_PRETIMO (0x3 << 2)
43#define APCH_CFG_PRETIMO_40_MIN (0x0 << 2)
44#define APCH_CFG_PRETIMO_60_MIN (0x1 << 2)
45#define APCH_CFG_PRETIMO_80_MIN (0x2 << 2)
46#define APCH_CFG_PRETIMO_DISABLED (0x3 << 2)
47#define APCH_CFG_TOTTIMO (0x3 << 4)
48#define APCH_CFG_TOTTIMO_3_HOUR (0x0 << 4)
49#define APCH_CFG_TOTTIMO_4_HOUR (0x1 << 4)
50#define APCH_CFG_TOTTIMO_5_HOUR (0x2 << 4)
51#define APCH_CFG_TOTTIMO_DISABLED (0x3 << 4)
52#define APCH_CFG_SUSCHG (0x1 << 7)
53
54#define APCH_STATUS_CHGDAT BIT(0)
55#define APCH_STATUS_INDAT BIT(1)
56#define APCH_STATUS_TEMPDAT BIT(2)
57#define APCH_STATUS_TIMRDAT BIT(3)
58#define APCH_STATUS_CHGSTAT BIT(4)
59#define APCH_STATUS_INSTAT BIT(5)
60#define APCH_STATUS_TEMPSTAT BIT(6)
61#define APCH_STATUS_TIMRSTAT BIT(7)
62
63#define APCH_CTRL_CHGEOCOUT BIT(0)
64#define APCH_CTRL_INDIS BIT(1)
65#define APCH_CTRL_TEMPOUT BIT(2)
66#define APCH_CTRL_TIMRPRE BIT(3)
67#define APCH_CTRL_CHGEOCIN BIT(4)
68#define APCH_CTRL_INCON BIT(5)
69#define APCH_CTRL_TEMPIN BIT(6)
70#define APCH_CTRL_TIMRTOT BIT(7)
71
72#define APCH_STATE_ACINSTAT (0x1 << 1)
73#define APCH_STATE_CSTATE (0x3 << 4)
74#define APCH_STATE_CSTATE_SHIFT 4
75#define APCH_STATE_CSTATE_DISABLED 0x00
76#define APCH_STATE_CSTATE_EOC 0x01
77#define APCH_STATE_CSTATE_FAST 0x02
78#define APCH_STATE_CSTATE_PRE 0x03
79
80struct act8945a_charger {
a09209ac 81 struct power_supply *psy;
10ca08b0 82 struct power_supply_desc desc;
5c0e09e0 83 struct regmap *regmap;
a09209ac
WY
84 struct work_struct work;
85
86 bool init_done;
369eba09 87 struct gpio_desc *lbo_gpio;
5c0e09e0
WY
88};
89
90static int act8945a_get_charger_state(struct regmap *regmap, int *val)
91{
92 int ret;
93 unsigned int status, state;
94
95 ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status);
96 if (ret < 0)
97 return ret;
98
99 ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state);
100 if (ret < 0)
101 return ret;
102
103 state &= APCH_STATE_CSTATE;
104 state >>= APCH_STATE_CSTATE_SHIFT;
105
1f0ba406
WY
106 switch (state) {
107 case APCH_STATE_CSTATE_PRE:
108 case APCH_STATE_CSTATE_FAST:
109 *val = POWER_SUPPLY_STATUS_CHARGING;
110 break;
111 case APCH_STATE_CSTATE_EOC:
5c0e09e0
WY
112 if (status & APCH_STATUS_CHGDAT)
113 *val = POWER_SUPPLY_STATUS_FULL;
1f0ba406
WY
114 else
115 *val = POWER_SUPPLY_STATUS_CHARGING;
116 break;
117 case APCH_STATE_CSTATE_DISABLED:
118 default:
119 if (!(status & APCH_STATUS_INDAT))
120 *val = POWER_SUPPLY_STATUS_DISCHARGING;
5c0e09e0
WY
121 else
122 *val = POWER_SUPPLY_STATUS_NOT_CHARGING;
1f0ba406 123 break;
5c0e09e0
WY
124 }
125
126 return 0;
127}
128
129static int act8945a_get_charge_type(struct regmap *regmap, int *val)
130{
131 int ret;
1f0ba406
WY
132 unsigned int status, state;
133
134 ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status);
135 if (ret < 0)
136 return ret;
5c0e09e0
WY
137
138 ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state);
139 if (ret < 0)
140 return ret;
141
142 state &= APCH_STATE_CSTATE;
143 state >>= APCH_STATE_CSTATE_SHIFT;
144
145 switch (state) {
146 case APCH_STATE_CSTATE_PRE:
147 *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
148 break;
149 case APCH_STATE_CSTATE_FAST:
150 *val = POWER_SUPPLY_CHARGE_TYPE_FAST;
151 break;
152 case APCH_STATE_CSTATE_EOC:
1f0ba406
WY
153 *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
154 break;
5c0e09e0
WY
155 case APCH_STATE_CSTATE_DISABLED:
156 default:
1f0ba406
WY
157 if (!(status & APCH_STATUS_INDAT))
158 *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
159 else
160 *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
161 break;
5c0e09e0
WY
162 }
163
164 return 0;
165}
166
6b021fc9 167static int act8945a_get_battery_health(struct regmap *regmap, int *val)
5c0e09e0
WY
168{
169 int ret;
1f0ba406 170 unsigned int status, state, config;
5c0e09e0
WY
171
172 ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status);
173 if (ret < 0)
174 return ret;
175
1f0ba406
WY
176 ret = regmap_read(regmap, ACT8945A_APCH_CFG, &config);
177 if (ret < 0)
178 return ret;
179
180 ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state);
181 if (ret < 0)
182 return ret;
183
184 state &= APCH_STATE_CSTATE;
185 state >>= APCH_STATE_CSTATE_SHIFT;
186
187 switch (state) {
188 case APCH_STATE_CSTATE_DISABLED:
189 if (config & APCH_CFG_SUSCHG) {
190 *val = POWER_SUPPLY_HEALTH_UNKNOWN;
191 } else if (status & APCH_STATUS_INDAT) {
192 if (!(status & APCH_STATUS_TEMPDAT))
193 *val = POWER_SUPPLY_HEALTH_OVERHEAT;
194 else if (status & APCH_STATUS_TIMRDAT)
195 *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
196 else
197 *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
198 } else {
199 *val = POWER_SUPPLY_HEALTH_GOOD;
200 }
201 break;
202 case APCH_STATE_CSTATE_PRE:
203 case APCH_STATE_CSTATE_FAST:
204 case APCH_STATE_CSTATE_EOC:
205 default:
5c0e09e0 206 *val = POWER_SUPPLY_HEALTH_GOOD;
1f0ba406
WY
207 break;
208 }
5c0e09e0
WY
209
210 return 0;
211}
212
369eba09
WY
213static int act8945a_get_capacity_level(struct act8945a_charger *charger,
214 struct regmap *regmap, int *val)
215{
216 int ret;
217 unsigned int status, state, config;
218 int lbo_level = gpiod_get_value(charger->lbo_gpio);
219
220 ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status);
221 if (ret < 0)
222 return ret;
223
224 ret = regmap_read(regmap, ACT8945A_APCH_CFG, &config);
225 if (ret < 0)
226 return ret;
227
228 ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state);
229 if (ret < 0)
230 return ret;
231
232 state &= APCH_STATE_CSTATE;
233 state >>= APCH_STATE_CSTATE_SHIFT;
234
235 switch (state) {
236 case APCH_STATE_CSTATE_PRE:
237 *val = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
238 break;
239 case APCH_STATE_CSTATE_FAST:
240 if (lbo_level)
241 *val = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
242 else
243 *val = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
244 break;
245 case APCH_STATE_CSTATE_EOC:
246 if (status & APCH_STATUS_CHGDAT)
247 *val = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
248 else
249 *val = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
250 break;
251 case APCH_STATE_CSTATE_DISABLED:
252 default:
253 if (config & APCH_CFG_SUSCHG) {
254 *val = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
255 } else {
256 *val = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
257 if (!(status & APCH_STATUS_INDAT)) {
258 if (!lbo_level)
259 *val = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
260 }
261 }
262 break;
263 }
264
265 return 0;
266}
267
5c0e09e0
WY
268static enum power_supply_property act8945a_charger_props[] = {
269 POWER_SUPPLY_PROP_STATUS,
270 POWER_SUPPLY_PROP_CHARGE_TYPE,
271 POWER_SUPPLY_PROP_TECHNOLOGY,
272 POWER_SUPPLY_PROP_HEALTH,
369eba09 273 POWER_SUPPLY_PROP_CAPACITY_LEVEL,
5c0e09e0
WY
274 POWER_SUPPLY_PROP_MODEL_NAME,
275 POWER_SUPPLY_PROP_MANUFACTURER
276};
277
278static int act8945a_charger_get_property(struct power_supply *psy,
279 enum power_supply_property prop,
280 union power_supply_propval *val)
281{
282 struct act8945a_charger *charger = power_supply_get_drvdata(psy);
283 struct regmap *regmap = charger->regmap;
284 int ret = 0;
285
286 switch (prop) {
287 case POWER_SUPPLY_PROP_STATUS:
288 ret = act8945a_get_charger_state(regmap, &val->intval);
289 break;
290 case POWER_SUPPLY_PROP_CHARGE_TYPE:
291 ret = act8945a_get_charge_type(regmap, &val->intval);
292 break;
293 case POWER_SUPPLY_PROP_TECHNOLOGY:
294 val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
295 break;
296 case POWER_SUPPLY_PROP_HEALTH:
6b021fc9 297 ret = act8945a_get_battery_health(regmap, &val->intval);
5c0e09e0 298 break;
369eba09
WY
299 case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
300 ret = act8945a_get_capacity_level(charger,
301 regmap, &val->intval);
302 break;
5c0e09e0
WY
303 case POWER_SUPPLY_PROP_MODEL_NAME:
304 val->strval = act8945a_charger_model;
305 break;
306 case POWER_SUPPLY_PROP_MANUFACTURER:
307 val->strval = act8945a_charger_manufacturer;
308 break;
309 default:
310 return -EINVAL;
311 }
312
313 return ret;
314}
315
a09209ac
WY
316static int act8945a_enable_interrupt(struct act8945a_charger *charger)
317{
318 struct regmap *regmap = charger->regmap;
319 unsigned char ctrl;
320 int ret;
321
322 ctrl = APCH_CTRL_CHGEOCOUT | APCH_CTRL_CHGEOCIN |
323 APCH_CTRL_INDIS | APCH_CTRL_INCON |
324 APCH_CTRL_TEMPOUT | APCH_CTRL_TEMPIN |
325 APCH_CTRL_TIMRPRE | APCH_CTRL_TIMRTOT;
326 ret = regmap_write(regmap, ACT8945A_APCH_CTRL, ctrl);
327 if (ret)
328 return ret;
329
330 ctrl = APCH_STATUS_CHGSTAT | APCH_STATUS_INSTAT |
331 APCH_STATUS_TEMPSTAT | APCH_STATUS_TIMRSTAT;
332 ret = regmap_write(regmap, ACT8945A_APCH_STATUS, ctrl);
333 if (ret)
334 return ret;
335
336 return 0;
337}
338
10ca08b0
WY
339static unsigned int act8945a_set_supply_type(struct act8945a_charger *charger,
340 unsigned int *type)
341{
342 unsigned int status, state;
343 int ret;
344
345 ret = regmap_read(charger->regmap, ACT8945A_APCH_STATUS, &status);
346 if (ret < 0)
347 return ret;
348
349 ret = regmap_read(charger->regmap, ACT8945A_APCH_STATE, &state);
350 if (ret < 0)
351 return ret;
352
353 if (status & APCH_STATUS_INDAT) {
354 if (state & APCH_STATE_ACINSTAT)
355 *type = POWER_SUPPLY_TYPE_MAINS;
356 else
357 *type = POWER_SUPPLY_TYPE_USB;
358 } else {
359 *type = POWER_SUPPLY_TYPE_BATTERY;
360 }
361
362 return 0;
363}
364
a09209ac
WY
365static void act8945a_work(struct work_struct *work)
366{
367 struct act8945a_charger *charger =
368 container_of(work, struct act8945a_charger, work);
369
10ca08b0
WY
370 act8945a_set_supply_type(charger, &charger->desc.type);
371
a09209ac
WY
372 power_supply_changed(charger->psy);
373}
374
375static irqreturn_t act8945a_status_changed(int irq, void *dev_id)
376{
377 struct act8945a_charger *charger = dev_id;
378
379 if (charger->init_done)
380 schedule_work(&charger->work);
381
382 return IRQ_HANDLED;
383}
384
5c0e09e0
WY
385#define DEFAULT_TOTAL_TIME_OUT 3
386#define DEFAULT_PRE_TIME_OUT 40
387#define DEFAULT_INPUT_OVP_THRESHOLD 6600
388
389static int act8945a_charger_config(struct device *dev,
390 struct act8945a_charger *charger)
391{
392 struct device_node *np = dev->of_node;
393 enum of_gpio_flags flags;
394 struct regmap *regmap = charger->regmap;
395
396 u32 total_time_out;
397 u32 pre_time_out;
398 u32 input_voltage_threshold;
399 int chglev_pin;
369eba09 400 int err, ret;
5c0e09e0 401
1f0ba406 402 unsigned int tmp;
5c0e09e0
WY
403 unsigned int value = 0;
404
405 if (!np) {
406 dev_err(dev, "no charger of node\n");
407 return -EINVAL;
408 }
409
1f0ba406
WY
410 ret = regmap_read(regmap, ACT8945A_APCH_CFG, &tmp);
411 if (ret)
412 return ret;
413
414 if (tmp & APCH_CFG_SUSCHG) {
415 value |= APCH_CFG_SUSCHG;
416 dev_info(dev, "have been suspended\n");
417 }
418
369eba09
WY
419 charger->lbo_gpio = devm_gpiod_get_optional(dev, "active-semi,lbo",
420 GPIOD_IN);
421 if (IS_ERR(charger->lbo_gpio)) {
422 err = PTR_ERR(charger->lbo_gpio);
423 dev_err(dev, "unable to claim gpio \"lbo\": %d\n", err);
424 return err;
425 }
426
427 ret = devm_request_irq(dev, gpiod_to_irq(charger->lbo_gpio),
428 act8945a_status_changed,
429 (IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING),
430 "act8945a_lbo_detect", charger);
431 if (ret)
432 dev_info(dev, "failed to request gpio \"lbo\" IRQ\n");
433
5c0e09e0
WY
434 chglev_pin = of_get_named_gpio_flags(np,
435 "active-semi,chglev-gpios", 0, &flags);
436
437 if (gpio_is_valid(chglev_pin)) {
438 gpio_set_value(chglev_pin,
439 ((flags == OF_GPIO_ACTIVE_LOW) ? 0 : 1));
440 }
441
442 if (of_property_read_u32(np,
443 "active-semi,input-voltage-threshold-microvolt",
444 &input_voltage_threshold))
445 input_voltage_threshold = DEFAULT_INPUT_OVP_THRESHOLD;
446
447 if (of_property_read_u32(np,
448 "active-semi,precondition-timeout",
449 &pre_time_out))
450 pre_time_out = DEFAULT_PRE_TIME_OUT;
451
452 if (of_property_read_u32(np, "active-semi,total-timeout",
453 &total_time_out))
454 total_time_out = DEFAULT_TOTAL_TIME_OUT;
455
456 switch (input_voltage_threshold) {
457 case 8000:
458 value |= APCH_CFG_OVPSET_8V;
459 break;
460 case 7500:
461 value |= APCH_CFG_OVPSET_7V5;
462 break;
463 case 7000:
464 value |= APCH_CFG_OVPSET_7V;
465 break;
466 case 6600:
467 default:
468 value |= APCH_CFG_OVPSET_6V6;
469 break;
470 }
471
472 switch (pre_time_out) {
473 case 60:
474 value |= APCH_CFG_PRETIMO_60_MIN;
475 break;
476 case 80:
477 value |= APCH_CFG_PRETIMO_80_MIN;
478 break;
479 case 0:
480 value |= APCH_CFG_PRETIMO_DISABLED;
481 break;
482 case 40:
483 default:
484 value |= APCH_CFG_PRETIMO_40_MIN;
485 break;
486 }
487
488 switch (total_time_out) {
489 case 4:
490 value |= APCH_CFG_TOTTIMO_4_HOUR;
491 break;
492 case 5:
493 value |= APCH_CFG_TOTTIMO_5_HOUR;
494 break;
495 case 0:
496 value |= APCH_CFG_TOTTIMO_DISABLED;
497 break;
498 case 3:
499 default:
500 value |= APCH_CFG_TOTTIMO_3_HOUR;
501 break;
502 }
503
504 return regmap_write(regmap, ACT8945A_APCH_CFG, value);
505}
506
507static int act8945a_charger_probe(struct platform_device *pdev)
508{
509 struct act8945a_charger *charger;
5c0e09e0 510 struct power_supply_config psy_cfg = {};
a09209ac 511 int irq, ret;
5c0e09e0
WY
512
513 charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
514 if (!charger)
515 return -ENOMEM;
516
517 charger->regmap = dev_get_regmap(pdev->dev.parent, NULL);
518 if (!charger->regmap) {
519 dev_err(&pdev->dev, "Parent did not provide regmap\n");
520 return -EINVAL;
521 }
522
5da643b2 523 ret = act8945a_charger_config(&pdev->dev, charger);
5c0e09e0
WY
524 if (ret)
525 return ret;
526
a09209ac
WY
527 irq = of_irq_get(pdev->dev.of_node, 0);
528 if (irq == -EPROBE_DEFER) {
529 dev_err(&pdev->dev, "failed to find IRQ number\n");
530 return -EPROBE_DEFER;
531 }
532
533 ret = devm_request_irq(&pdev->dev, irq, act8945a_status_changed,
534 IRQF_TRIGGER_FALLING, "act8945a_interrupt",
535 charger);
536 if (ret) {
537 dev_err(&pdev->dev, "failed to request nIRQ pin IRQ\n");
538 return ret;
539 }
540
10ca08b0
WY
541 charger->desc.name = "act8945a-charger";
542 charger->desc.get_property = act8945a_charger_get_property;
543 charger->desc.properties = act8945a_charger_props;
544 charger->desc.num_properties = ARRAY_SIZE(act8945a_charger_props);
545
546 ret = act8945a_set_supply_type(charger, &charger->desc.type);
547 if (ret)
548 return -EINVAL;
549
5da643b2 550 psy_cfg.of_node = pdev->dev.of_node;
5c0e09e0
WY
551 psy_cfg.drv_data = charger;
552
a09209ac 553 charger->psy = devm_power_supply_register(&pdev->dev,
10ca08b0 554 &charger->desc,
a09209ac
WY
555 &psy_cfg);
556 if (IS_ERR(charger->psy)) {
5c0e09e0 557 dev_err(&pdev->dev, "failed to register power supply\n");
a09209ac 558 return PTR_ERR(charger->psy);
5c0e09e0
WY
559 }
560
a09209ac
WY
561 platform_set_drvdata(pdev, charger);
562
563 INIT_WORK(&charger->work, act8945a_work);
564
565 ret = act8945a_enable_interrupt(charger);
566 if (ret)
567 return -EIO;
568
569 charger->init_done = true;
570
571 return 0;
572}
573
574static int act8945a_charger_remove(struct platform_device *pdev)
575{
576 struct act8945a_charger *charger = platform_get_drvdata(pdev);
577
578 charger->init_done = false;
579 cancel_work_sync(&charger->work);
580
5c0e09e0
WY
581 return 0;
582}
583
584static struct platform_driver act8945a_charger_driver = {
585 .driver = {
586 .name = "act8945a-charger",
587 },
588 .probe = act8945a_charger_probe,
a09209ac 589 .remove = act8945a_charger_remove,
5c0e09e0
WY
590};
591module_platform_driver(act8945a_charger_driver);
592
593MODULE_DESCRIPTION("Active-semi ACT8945A ActivePath charger driver");
594MODULE_AUTHOR("Wenyou Yang <wenyou.yang@atmel.com>");
595MODULE_LICENSE("GPL");
This page took 0.110621 seconds and 5 git commands to generate.