]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/blame - drivers/power/supply/act8945a_charger.c
power: supply: act8945a_charger: Improve state handling
[mirror_ubuntu-zesty-kernel.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 */
13#include <linux/module.h>
14#include <linux/of.h>
15#include <linux/of_gpio.h>
16#include <linux/platform_device.h>
17#include <linux/power_supply.h>
18#include <linux/regmap.h>
19
20static const char *act8945a_charger_model = "ACT8945A";
21static const char *act8945a_charger_manufacturer = "Active-semi";
22
23/**
24 * ACT8945A Charger Register Map
25 */
26
27/* 0x70: Reserved */
28#define ACT8945A_APCH_CFG 0x71
29#define ACT8945A_APCH_STATUS 0x78
30#define ACT8945A_APCH_CTRL 0x79
31#define ACT8945A_APCH_STATE 0x7A
32
33/* ACT8945A_APCH_CFG */
34#define APCH_CFG_OVPSET (0x3 << 0)
35#define APCH_CFG_OVPSET_6V6 (0x0 << 0)
36#define APCH_CFG_OVPSET_7V (0x1 << 0)
37#define APCH_CFG_OVPSET_7V5 (0x2 << 0)
38#define APCH_CFG_OVPSET_8V (0x3 << 0)
39#define APCH_CFG_PRETIMO (0x3 << 2)
40#define APCH_CFG_PRETIMO_40_MIN (0x0 << 2)
41#define APCH_CFG_PRETIMO_60_MIN (0x1 << 2)
42#define APCH_CFG_PRETIMO_80_MIN (0x2 << 2)
43#define APCH_CFG_PRETIMO_DISABLED (0x3 << 2)
44#define APCH_CFG_TOTTIMO (0x3 << 4)
45#define APCH_CFG_TOTTIMO_3_HOUR (0x0 << 4)
46#define APCH_CFG_TOTTIMO_4_HOUR (0x1 << 4)
47#define APCH_CFG_TOTTIMO_5_HOUR (0x2 << 4)
48#define APCH_CFG_TOTTIMO_DISABLED (0x3 << 4)
49#define APCH_CFG_SUSCHG (0x1 << 7)
50
51#define APCH_STATUS_CHGDAT BIT(0)
52#define APCH_STATUS_INDAT BIT(1)
53#define APCH_STATUS_TEMPDAT BIT(2)
54#define APCH_STATUS_TIMRDAT BIT(3)
55#define APCH_STATUS_CHGSTAT BIT(4)
56#define APCH_STATUS_INSTAT BIT(5)
57#define APCH_STATUS_TEMPSTAT BIT(6)
58#define APCH_STATUS_TIMRSTAT BIT(7)
59
60#define APCH_CTRL_CHGEOCOUT BIT(0)
61#define APCH_CTRL_INDIS BIT(1)
62#define APCH_CTRL_TEMPOUT BIT(2)
63#define APCH_CTRL_TIMRPRE BIT(3)
64#define APCH_CTRL_CHGEOCIN BIT(4)
65#define APCH_CTRL_INCON BIT(5)
66#define APCH_CTRL_TEMPIN BIT(6)
67#define APCH_CTRL_TIMRTOT BIT(7)
68
69#define APCH_STATE_ACINSTAT (0x1 << 1)
70#define APCH_STATE_CSTATE (0x3 << 4)
71#define APCH_STATE_CSTATE_SHIFT 4
72#define APCH_STATE_CSTATE_DISABLED 0x00
73#define APCH_STATE_CSTATE_EOC 0x01
74#define APCH_STATE_CSTATE_FAST 0x02
75#define APCH_STATE_CSTATE_PRE 0x03
76
77struct act8945a_charger {
78 struct regmap *regmap;
5c0e09e0
WY
79};
80
81static int act8945a_get_charger_state(struct regmap *regmap, int *val)
82{
83 int ret;
84 unsigned int status, state;
85
86 ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status);
87 if (ret < 0)
88 return ret;
89
90 ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state);
91 if (ret < 0)
92 return ret;
93
94 state &= APCH_STATE_CSTATE;
95 state >>= APCH_STATE_CSTATE_SHIFT;
96
1f0ba406
WY
97 switch (state) {
98 case APCH_STATE_CSTATE_PRE:
99 case APCH_STATE_CSTATE_FAST:
100 *val = POWER_SUPPLY_STATUS_CHARGING;
101 break;
102 case APCH_STATE_CSTATE_EOC:
5c0e09e0
WY
103 if (status & APCH_STATUS_CHGDAT)
104 *val = POWER_SUPPLY_STATUS_FULL;
1f0ba406
WY
105 else
106 *val = POWER_SUPPLY_STATUS_CHARGING;
107 break;
108 case APCH_STATE_CSTATE_DISABLED:
109 default:
110 if (!(status & APCH_STATUS_INDAT))
111 *val = POWER_SUPPLY_STATUS_DISCHARGING;
5c0e09e0
WY
112 else
113 *val = POWER_SUPPLY_STATUS_NOT_CHARGING;
1f0ba406 114 break;
5c0e09e0
WY
115 }
116
117 return 0;
118}
119
120static int act8945a_get_charge_type(struct regmap *regmap, int *val)
121{
122 int ret;
1f0ba406
WY
123 unsigned int status, state;
124
125 ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status);
126 if (ret < 0)
127 return ret;
5c0e09e0
WY
128
129 ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state);
130 if (ret < 0)
131 return ret;
132
133 state &= APCH_STATE_CSTATE;
134 state >>= APCH_STATE_CSTATE_SHIFT;
135
136 switch (state) {
137 case APCH_STATE_CSTATE_PRE:
138 *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
139 break;
140 case APCH_STATE_CSTATE_FAST:
141 *val = POWER_SUPPLY_CHARGE_TYPE_FAST;
142 break;
143 case APCH_STATE_CSTATE_EOC:
1f0ba406
WY
144 *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
145 break;
5c0e09e0
WY
146 case APCH_STATE_CSTATE_DISABLED:
147 default:
1f0ba406
WY
148 if (!(status & APCH_STATUS_INDAT))
149 *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
150 else
151 *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
152 break;
5c0e09e0
WY
153 }
154
155 return 0;
156}
157
6b021fc9 158static int act8945a_get_battery_health(struct regmap *regmap, int *val)
5c0e09e0
WY
159{
160 int ret;
1f0ba406 161 unsigned int status, state, config;
5c0e09e0
WY
162
163 ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status);
164 if (ret < 0)
165 return ret;
166
1f0ba406
WY
167 ret = regmap_read(regmap, ACT8945A_APCH_CFG, &config);
168 if (ret < 0)
169 return ret;
170
171 ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state);
172 if (ret < 0)
173 return ret;
174
175 state &= APCH_STATE_CSTATE;
176 state >>= APCH_STATE_CSTATE_SHIFT;
177
178 switch (state) {
179 case APCH_STATE_CSTATE_DISABLED:
180 if (config & APCH_CFG_SUSCHG) {
181 *val = POWER_SUPPLY_HEALTH_UNKNOWN;
182 } else if (status & APCH_STATUS_INDAT) {
183 if (!(status & APCH_STATUS_TEMPDAT))
184 *val = POWER_SUPPLY_HEALTH_OVERHEAT;
185 else if (status & APCH_STATUS_TIMRDAT)
186 *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
187 else
188 *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
189 } else {
190 *val = POWER_SUPPLY_HEALTH_GOOD;
191 }
192 break;
193 case APCH_STATE_CSTATE_PRE:
194 case APCH_STATE_CSTATE_FAST:
195 case APCH_STATE_CSTATE_EOC:
196 default:
5c0e09e0 197 *val = POWER_SUPPLY_HEALTH_GOOD;
1f0ba406
WY
198 break;
199 }
5c0e09e0
WY
200
201 return 0;
202}
203
204static enum power_supply_property act8945a_charger_props[] = {
205 POWER_SUPPLY_PROP_STATUS,
206 POWER_SUPPLY_PROP_CHARGE_TYPE,
207 POWER_SUPPLY_PROP_TECHNOLOGY,
208 POWER_SUPPLY_PROP_HEALTH,
209 POWER_SUPPLY_PROP_MODEL_NAME,
210 POWER_SUPPLY_PROP_MANUFACTURER
211};
212
213static int act8945a_charger_get_property(struct power_supply *psy,
214 enum power_supply_property prop,
215 union power_supply_propval *val)
216{
217 struct act8945a_charger *charger = power_supply_get_drvdata(psy);
218 struct regmap *regmap = charger->regmap;
219 int ret = 0;
220
221 switch (prop) {
222 case POWER_SUPPLY_PROP_STATUS:
223 ret = act8945a_get_charger_state(regmap, &val->intval);
224 break;
225 case POWER_SUPPLY_PROP_CHARGE_TYPE:
226 ret = act8945a_get_charge_type(regmap, &val->intval);
227 break;
228 case POWER_SUPPLY_PROP_TECHNOLOGY:
229 val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
230 break;
231 case POWER_SUPPLY_PROP_HEALTH:
6b021fc9 232 ret = act8945a_get_battery_health(regmap, &val->intval);
5c0e09e0
WY
233 break;
234 case POWER_SUPPLY_PROP_MODEL_NAME:
235 val->strval = act8945a_charger_model;
236 break;
237 case POWER_SUPPLY_PROP_MANUFACTURER:
238 val->strval = act8945a_charger_manufacturer;
239 break;
240 default:
241 return -EINVAL;
242 }
243
244 return ret;
245}
246
247static const struct power_supply_desc act8945a_charger_desc = {
248 .name = "act8945a-charger",
249 .type = POWER_SUPPLY_TYPE_BATTERY,
250 .get_property = act8945a_charger_get_property,
251 .properties = act8945a_charger_props,
252 .num_properties = ARRAY_SIZE(act8945a_charger_props),
253};
254
255#define DEFAULT_TOTAL_TIME_OUT 3
256#define DEFAULT_PRE_TIME_OUT 40
257#define DEFAULT_INPUT_OVP_THRESHOLD 6600
258
259static int act8945a_charger_config(struct device *dev,
260 struct act8945a_charger *charger)
261{
262 struct device_node *np = dev->of_node;
263 enum of_gpio_flags flags;
264 struct regmap *regmap = charger->regmap;
265
266 u32 total_time_out;
267 u32 pre_time_out;
268 u32 input_voltage_threshold;
269 int chglev_pin;
1f0ba406 270 int ret;
5c0e09e0 271
1f0ba406 272 unsigned int tmp;
5c0e09e0
WY
273 unsigned int value = 0;
274
275 if (!np) {
276 dev_err(dev, "no charger of node\n");
277 return -EINVAL;
278 }
279
1f0ba406
WY
280 ret = regmap_read(regmap, ACT8945A_APCH_CFG, &tmp);
281 if (ret)
282 return ret;
283
284 if (tmp & APCH_CFG_SUSCHG) {
285 value |= APCH_CFG_SUSCHG;
286 dev_info(dev, "have been suspended\n");
287 }
288
5c0e09e0
WY
289 chglev_pin = of_get_named_gpio_flags(np,
290 "active-semi,chglev-gpios", 0, &flags);
291
292 if (gpio_is_valid(chglev_pin)) {
293 gpio_set_value(chglev_pin,
294 ((flags == OF_GPIO_ACTIVE_LOW) ? 0 : 1));
295 }
296
297 if (of_property_read_u32(np,
298 "active-semi,input-voltage-threshold-microvolt",
299 &input_voltage_threshold))
300 input_voltage_threshold = DEFAULT_INPUT_OVP_THRESHOLD;
301
302 if (of_property_read_u32(np,
303 "active-semi,precondition-timeout",
304 &pre_time_out))
305 pre_time_out = DEFAULT_PRE_TIME_OUT;
306
307 if (of_property_read_u32(np, "active-semi,total-timeout",
308 &total_time_out))
309 total_time_out = DEFAULT_TOTAL_TIME_OUT;
310
311 switch (input_voltage_threshold) {
312 case 8000:
313 value |= APCH_CFG_OVPSET_8V;
314 break;
315 case 7500:
316 value |= APCH_CFG_OVPSET_7V5;
317 break;
318 case 7000:
319 value |= APCH_CFG_OVPSET_7V;
320 break;
321 case 6600:
322 default:
323 value |= APCH_CFG_OVPSET_6V6;
324 break;
325 }
326
327 switch (pre_time_out) {
328 case 60:
329 value |= APCH_CFG_PRETIMO_60_MIN;
330 break;
331 case 80:
332 value |= APCH_CFG_PRETIMO_80_MIN;
333 break;
334 case 0:
335 value |= APCH_CFG_PRETIMO_DISABLED;
336 break;
337 case 40:
338 default:
339 value |= APCH_CFG_PRETIMO_40_MIN;
340 break;
341 }
342
343 switch (total_time_out) {
344 case 4:
345 value |= APCH_CFG_TOTTIMO_4_HOUR;
346 break;
347 case 5:
348 value |= APCH_CFG_TOTTIMO_5_HOUR;
349 break;
350 case 0:
351 value |= APCH_CFG_TOTTIMO_DISABLED;
352 break;
353 case 3:
354 default:
355 value |= APCH_CFG_TOTTIMO_3_HOUR;
356 break;
357 }
358
359 return regmap_write(regmap, ACT8945A_APCH_CFG, value);
360}
361
362static int act8945a_charger_probe(struct platform_device *pdev)
363{
364 struct act8945a_charger *charger;
365 struct power_supply *psy;
366 struct power_supply_config psy_cfg = {};
367 int ret;
368
369 charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
370 if (!charger)
371 return -ENOMEM;
372
373 charger->regmap = dev_get_regmap(pdev->dev.parent, NULL);
374 if (!charger->regmap) {
375 dev_err(&pdev->dev, "Parent did not provide regmap\n");
376 return -EINVAL;
377 }
378
5da643b2 379 ret = act8945a_charger_config(&pdev->dev, charger);
5c0e09e0
WY
380 if (ret)
381 return ret;
382
5da643b2 383 psy_cfg.of_node = pdev->dev.of_node;
5c0e09e0
WY
384 psy_cfg.drv_data = charger;
385
386 psy = devm_power_supply_register(&pdev->dev,
387 &act8945a_charger_desc,
388 &psy_cfg);
389 if (IS_ERR(psy)) {
390 dev_err(&pdev->dev, "failed to register power supply\n");
391 return PTR_ERR(psy);
392 }
393
394 return 0;
395}
396
397static struct platform_driver act8945a_charger_driver = {
398 .driver = {
399 .name = "act8945a-charger",
400 },
401 .probe = act8945a_charger_probe,
402};
403module_platform_driver(act8945a_charger_driver);
404
405MODULE_DESCRIPTION("Active-semi ACT8945A ActivePath charger driver");
406MODULE_AUTHOR("Wenyou Yang <wenyou.yang@atmel.com>");
407MODULE_LICENSE("GPL");