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