blob: b5c00e45741e01a6f57977c355a2bb0108bb3e12 [file] [log] [blame]
Wenyou Yang5c0e09e2016-02-15 16:22:03 +08001/*
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;
79 bool battery_temperature;
80};
81
82static int act8945a_get_charger_state(struct regmap *regmap, int *val)
83{
84 int ret;
85 unsigned int status, state;
86
87 ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status);
88 if (ret < 0)
89 return ret;
90
91 ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state);
92 if (ret < 0)
93 return ret;
94
95 state &= APCH_STATE_CSTATE;
96 state >>= APCH_STATE_CSTATE_SHIFT;
97
98 if (state == APCH_STATE_CSTATE_EOC) {
99 if (status & APCH_STATUS_CHGDAT)
100 *val = POWER_SUPPLY_STATUS_FULL;
101 else
102 *val = POWER_SUPPLY_STATUS_NOT_CHARGING;
103 } else if ((state == APCH_STATE_CSTATE_FAST) ||
104 (state == APCH_STATE_CSTATE_PRE)) {
105 *val = POWER_SUPPLY_STATUS_CHARGING;
106 } else {
107 *val = POWER_SUPPLY_STATUS_NOT_CHARGING;
108 }
109
110 return 0;
111}
112
113static int act8945a_get_charge_type(struct regmap *regmap, int *val)
114{
115 int ret;
116 unsigned int state;
117
118 ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state);
119 if (ret < 0)
120 return ret;
121
122 state &= APCH_STATE_CSTATE;
123 state >>= APCH_STATE_CSTATE_SHIFT;
124
125 switch (state) {
126 case APCH_STATE_CSTATE_PRE:
127 *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
128 break;
129 case APCH_STATE_CSTATE_FAST:
130 *val = POWER_SUPPLY_CHARGE_TYPE_FAST;
131 break;
132 case APCH_STATE_CSTATE_EOC:
133 case APCH_STATE_CSTATE_DISABLED:
134 default:
135 *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
136 }
137
138 return 0;
139}
140
141static int act8945a_get_battery_health(struct act8945a_charger *charger,
142 struct regmap *regmap, int *val)
143{
144 int ret;
145 unsigned int status;
146
147 ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status);
148 if (ret < 0)
149 return ret;
150
151 if (charger->battery_temperature && !(status & APCH_STATUS_TEMPDAT))
152 *val = POWER_SUPPLY_HEALTH_OVERHEAT;
153 else if (!(status & APCH_STATUS_INDAT))
154 *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
155 else if (status & APCH_STATUS_TIMRDAT)
156 *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
157 else
158 *val = POWER_SUPPLY_HEALTH_GOOD;
159
160 return 0;
161}
162
163static enum power_supply_property act8945a_charger_props[] = {
164 POWER_SUPPLY_PROP_STATUS,
165 POWER_SUPPLY_PROP_CHARGE_TYPE,
166 POWER_SUPPLY_PROP_TECHNOLOGY,
167 POWER_SUPPLY_PROP_HEALTH,
168 POWER_SUPPLY_PROP_MODEL_NAME,
169 POWER_SUPPLY_PROP_MANUFACTURER
170};
171
172static int act8945a_charger_get_property(struct power_supply *psy,
173 enum power_supply_property prop,
174 union power_supply_propval *val)
175{
176 struct act8945a_charger *charger = power_supply_get_drvdata(psy);
177 struct regmap *regmap = charger->regmap;
178 int ret = 0;
179
180 switch (prop) {
181 case POWER_SUPPLY_PROP_STATUS:
182 ret = act8945a_get_charger_state(regmap, &val->intval);
183 break;
184 case POWER_SUPPLY_PROP_CHARGE_TYPE:
185 ret = act8945a_get_charge_type(regmap, &val->intval);
186 break;
187 case POWER_SUPPLY_PROP_TECHNOLOGY:
188 val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
189 break;
190 case POWER_SUPPLY_PROP_HEALTH:
191 ret = act8945a_get_battery_health(charger,
192 regmap, &val->intval);
193 break;
194 case POWER_SUPPLY_PROP_MODEL_NAME:
195 val->strval = act8945a_charger_model;
196 break;
197 case POWER_SUPPLY_PROP_MANUFACTURER:
198 val->strval = act8945a_charger_manufacturer;
199 break;
200 default:
201 return -EINVAL;
202 }
203
204 return ret;
205}
206
207static const struct power_supply_desc act8945a_charger_desc = {
208 .name = "act8945a-charger",
209 .type = POWER_SUPPLY_TYPE_BATTERY,
210 .get_property = act8945a_charger_get_property,
211 .properties = act8945a_charger_props,
212 .num_properties = ARRAY_SIZE(act8945a_charger_props),
213};
214
215#define DEFAULT_TOTAL_TIME_OUT 3
216#define DEFAULT_PRE_TIME_OUT 40
217#define DEFAULT_INPUT_OVP_THRESHOLD 6600
218
219static int act8945a_charger_config(struct device *dev,
220 struct act8945a_charger *charger)
221{
222 struct device_node *np = dev->of_node;
223 enum of_gpio_flags flags;
224 struct regmap *regmap = charger->regmap;
225
226 u32 total_time_out;
227 u32 pre_time_out;
228 u32 input_voltage_threshold;
229 int chglev_pin;
230
231 unsigned int value = 0;
232
233 if (!np) {
234 dev_err(dev, "no charger of node\n");
235 return -EINVAL;
236 }
237
238 charger->battery_temperature = of_property_read_bool(np,
239 "active-semi,check-battery-temperature");
240
241 chglev_pin = of_get_named_gpio_flags(np,
242 "active-semi,chglev-gpios", 0, &flags);
243
244 if (gpio_is_valid(chglev_pin)) {
245 gpio_set_value(chglev_pin,
246 ((flags == OF_GPIO_ACTIVE_LOW) ? 0 : 1));
247 }
248
249 if (of_property_read_u32(np,
250 "active-semi,input-voltage-threshold-microvolt",
251 &input_voltage_threshold))
252 input_voltage_threshold = DEFAULT_INPUT_OVP_THRESHOLD;
253
254 if (of_property_read_u32(np,
255 "active-semi,precondition-timeout",
256 &pre_time_out))
257 pre_time_out = DEFAULT_PRE_TIME_OUT;
258
259 if (of_property_read_u32(np, "active-semi,total-timeout",
260 &total_time_out))
261 total_time_out = DEFAULT_TOTAL_TIME_OUT;
262
263 switch (input_voltage_threshold) {
264 case 8000:
265 value |= APCH_CFG_OVPSET_8V;
266 break;
267 case 7500:
268 value |= APCH_CFG_OVPSET_7V5;
269 break;
270 case 7000:
271 value |= APCH_CFG_OVPSET_7V;
272 break;
273 case 6600:
274 default:
275 value |= APCH_CFG_OVPSET_6V6;
276 break;
277 }
278
279 switch (pre_time_out) {
280 case 60:
281 value |= APCH_CFG_PRETIMO_60_MIN;
282 break;
283 case 80:
284 value |= APCH_CFG_PRETIMO_80_MIN;
285 break;
286 case 0:
287 value |= APCH_CFG_PRETIMO_DISABLED;
288 break;
289 case 40:
290 default:
291 value |= APCH_CFG_PRETIMO_40_MIN;
292 break;
293 }
294
295 switch (total_time_out) {
296 case 4:
297 value |= APCH_CFG_TOTTIMO_4_HOUR;
298 break;
299 case 5:
300 value |= APCH_CFG_TOTTIMO_5_HOUR;
301 break;
302 case 0:
303 value |= APCH_CFG_TOTTIMO_DISABLED;
304 break;
305 case 3:
306 default:
307 value |= APCH_CFG_TOTTIMO_3_HOUR;
308 break;
309 }
310
311 return regmap_write(regmap, ACT8945A_APCH_CFG, value);
312}
313
314static int act8945a_charger_probe(struct platform_device *pdev)
315{
316 struct act8945a_charger *charger;
317 struct power_supply *psy;
318 struct power_supply_config psy_cfg = {};
319 int ret;
320
321 charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
322 if (!charger)
323 return -ENOMEM;
324
325 charger->regmap = dev_get_regmap(pdev->dev.parent, NULL);
326 if (!charger->regmap) {
327 dev_err(&pdev->dev, "Parent did not provide regmap\n");
328 return -EINVAL;
329 }
330
331 ret = act8945a_charger_config(pdev->dev.parent, charger);
332 if (ret)
333 return ret;
334
335 psy_cfg.of_node = pdev->dev.parent->of_node;
336 psy_cfg.drv_data = charger;
337
338 psy = devm_power_supply_register(&pdev->dev,
339 &act8945a_charger_desc,
340 &psy_cfg);
341 if (IS_ERR(psy)) {
342 dev_err(&pdev->dev, "failed to register power supply\n");
343 return PTR_ERR(psy);
344 }
345
346 return 0;
347}
348
349static struct platform_driver act8945a_charger_driver = {
350 .driver = {
351 .name = "act8945a-charger",
352 },
353 .probe = act8945a_charger_probe,
354};
355module_platform_driver(act8945a_charger_driver);
356
357MODULE_DESCRIPTION("Active-semi ACT8945A ActivePath charger driver");
358MODULE_AUTHOR("Wenyou Yang <wenyou.yang@atmel.com>");
359MODULE_LICENSE("GPL");