1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Battery driver for wm8350 PMIC
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun * Copyright 2007, 2008 Wolfson Microelectronics PLC.
6*4882a593Smuzhiyun *
7*4882a593Smuzhiyun * Based on OLPC Battery Driver
8*4882a593Smuzhiyun *
9*4882a593Smuzhiyun * Copyright 2006 David Woodhouse <dwmw2@infradead.org>
10*4882a593Smuzhiyun */
11*4882a593Smuzhiyun
12*4882a593Smuzhiyun #include <linux/module.h>
13*4882a593Smuzhiyun #include <linux/err.h>
14*4882a593Smuzhiyun #include <linux/platform_device.h>
15*4882a593Smuzhiyun #include <linux/power_supply.h>
16*4882a593Smuzhiyun #include <linux/mfd/wm8350/supply.h>
17*4882a593Smuzhiyun #include <linux/mfd/wm8350/core.h>
18*4882a593Smuzhiyun #include <linux/mfd/wm8350/comparator.h>
19*4882a593Smuzhiyun
wm8350_read_battery_uvolts(struct wm8350 * wm8350)20*4882a593Smuzhiyun static int wm8350_read_battery_uvolts(struct wm8350 *wm8350)
21*4882a593Smuzhiyun {
22*4882a593Smuzhiyun return wm8350_read_auxadc(wm8350, WM8350_AUXADC_BATT, 0, 0)
23*4882a593Smuzhiyun * WM8350_AUX_COEFF;
24*4882a593Smuzhiyun }
25*4882a593Smuzhiyun
wm8350_read_line_uvolts(struct wm8350 * wm8350)26*4882a593Smuzhiyun static int wm8350_read_line_uvolts(struct wm8350 *wm8350)
27*4882a593Smuzhiyun {
28*4882a593Smuzhiyun return wm8350_read_auxadc(wm8350, WM8350_AUXADC_LINE, 0, 0)
29*4882a593Smuzhiyun * WM8350_AUX_COEFF;
30*4882a593Smuzhiyun }
31*4882a593Smuzhiyun
wm8350_read_usb_uvolts(struct wm8350 * wm8350)32*4882a593Smuzhiyun static int wm8350_read_usb_uvolts(struct wm8350 *wm8350)
33*4882a593Smuzhiyun {
34*4882a593Smuzhiyun return wm8350_read_auxadc(wm8350, WM8350_AUXADC_USB, 0, 0)
35*4882a593Smuzhiyun * WM8350_AUX_COEFF;
36*4882a593Smuzhiyun }
37*4882a593Smuzhiyun
38*4882a593Smuzhiyun #define WM8350_BATT_SUPPLY 1
39*4882a593Smuzhiyun #define WM8350_USB_SUPPLY 2
40*4882a593Smuzhiyun #define WM8350_LINE_SUPPLY 4
41*4882a593Smuzhiyun
wm8350_charge_time_min(struct wm8350 * wm8350,int min)42*4882a593Smuzhiyun static inline int wm8350_charge_time_min(struct wm8350 *wm8350, int min)
43*4882a593Smuzhiyun {
44*4882a593Smuzhiyun if (!wm8350->power.rev_g_coeff)
45*4882a593Smuzhiyun return (((min - 30) / 15) & 0xf) << 8;
46*4882a593Smuzhiyun else
47*4882a593Smuzhiyun return (((min - 30) / 30) & 0xf) << 8;
48*4882a593Smuzhiyun }
49*4882a593Smuzhiyun
wm8350_get_supplies(struct wm8350 * wm8350)50*4882a593Smuzhiyun static int wm8350_get_supplies(struct wm8350 *wm8350)
51*4882a593Smuzhiyun {
52*4882a593Smuzhiyun u16 sm, ov, co, chrg;
53*4882a593Smuzhiyun int supplies = 0;
54*4882a593Smuzhiyun
55*4882a593Smuzhiyun sm = wm8350_reg_read(wm8350, WM8350_STATE_MACHINE_STATUS);
56*4882a593Smuzhiyun ov = wm8350_reg_read(wm8350, WM8350_MISC_OVERRIDES);
57*4882a593Smuzhiyun co = wm8350_reg_read(wm8350, WM8350_COMPARATOR_OVERRIDES);
58*4882a593Smuzhiyun chrg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2);
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun /* USB_SM */
61*4882a593Smuzhiyun sm = (sm & WM8350_USB_SM_MASK) >> WM8350_USB_SM_SHIFT;
62*4882a593Smuzhiyun
63*4882a593Smuzhiyun /* CHG_ISEL */
64*4882a593Smuzhiyun chrg &= WM8350_CHG_ISEL_MASK;
65*4882a593Smuzhiyun
66*4882a593Smuzhiyun /* If the USB state machine is active then we're using that with or
67*4882a593Smuzhiyun * without battery, otherwise check for wall supply */
68*4882a593Smuzhiyun if (((sm == WM8350_USB_SM_100_SLV) ||
69*4882a593Smuzhiyun (sm == WM8350_USB_SM_500_SLV) ||
70*4882a593Smuzhiyun (sm == WM8350_USB_SM_STDBY_SLV))
71*4882a593Smuzhiyun && !(ov & WM8350_USB_LIMIT_OVRDE))
72*4882a593Smuzhiyun supplies = WM8350_USB_SUPPLY;
73*4882a593Smuzhiyun else if (((sm == WM8350_USB_SM_100_SLV) ||
74*4882a593Smuzhiyun (sm == WM8350_USB_SM_500_SLV) ||
75*4882a593Smuzhiyun (sm == WM8350_USB_SM_STDBY_SLV))
76*4882a593Smuzhiyun && (ov & WM8350_USB_LIMIT_OVRDE) && (chrg == 0))
77*4882a593Smuzhiyun supplies = WM8350_USB_SUPPLY | WM8350_BATT_SUPPLY;
78*4882a593Smuzhiyun else if (co & WM8350_WALL_FB_OVRDE)
79*4882a593Smuzhiyun supplies = WM8350_LINE_SUPPLY;
80*4882a593Smuzhiyun else
81*4882a593Smuzhiyun supplies = WM8350_BATT_SUPPLY;
82*4882a593Smuzhiyun
83*4882a593Smuzhiyun return supplies;
84*4882a593Smuzhiyun }
85*4882a593Smuzhiyun
wm8350_charger_config(struct wm8350 * wm8350,struct wm8350_charger_policy * policy)86*4882a593Smuzhiyun static int wm8350_charger_config(struct wm8350 *wm8350,
87*4882a593Smuzhiyun struct wm8350_charger_policy *policy)
88*4882a593Smuzhiyun {
89*4882a593Smuzhiyun u16 reg, eoc_mA, fast_limit_mA;
90*4882a593Smuzhiyun
91*4882a593Smuzhiyun if (!policy) {
92*4882a593Smuzhiyun dev_warn(wm8350->dev,
93*4882a593Smuzhiyun "No charger policy, charger not configured.\n");
94*4882a593Smuzhiyun return -EINVAL;
95*4882a593Smuzhiyun }
96*4882a593Smuzhiyun
97*4882a593Smuzhiyun /* make sure USB fast charge current is not > 500mA */
98*4882a593Smuzhiyun if (policy->fast_limit_USB_mA > 500) {
99*4882a593Smuzhiyun dev_err(wm8350->dev, "USB fast charge > 500mA\n");
100*4882a593Smuzhiyun return -EINVAL;
101*4882a593Smuzhiyun }
102*4882a593Smuzhiyun
103*4882a593Smuzhiyun eoc_mA = WM8350_CHG_EOC_mA(policy->eoc_mA);
104*4882a593Smuzhiyun
105*4882a593Smuzhiyun wm8350_reg_unlock(wm8350);
106*4882a593Smuzhiyun
107*4882a593Smuzhiyun reg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1)
108*4882a593Smuzhiyun & WM8350_CHG_ENA_R168;
109*4882a593Smuzhiyun wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1,
110*4882a593Smuzhiyun reg | eoc_mA | policy->trickle_start_mV |
111*4882a593Smuzhiyun WM8350_CHG_TRICKLE_TEMP_CHOKE |
112*4882a593Smuzhiyun WM8350_CHG_TRICKLE_USB_CHOKE |
113*4882a593Smuzhiyun WM8350_CHG_FAST_USB_THROTTLE);
114*4882a593Smuzhiyun
115*4882a593Smuzhiyun if (wm8350_get_supplies(wm8350) & WM8350_USB_SUPPLY) {
116*4882a593Smuzhiyun fast_limit_mA =
117*4882a593Smuzhiyun WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_USB_mA);
118*4882a593Smuzhiyun wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2,
119*4882a593Smuzhiyun policy->charge_mV | policy->trickle_charge_USB_mA |
120*4882a593Smuzhiyun fast_limit_mA | wm8350_charge_time_min(wm8350,
121*4882a593Smuzhiyun policy->charge_timeout));
122*4882a593Smuzhiyun
123*4882a593Smuzhiyun } else {
124*4882a593Smuzhiyun fast_limit_mA =
125*4882a593Smuzhiyun WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_mA);
126*4882a593Smuzhiyun wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2,
127*4882a593Smuzhiyun policy->charge_mV | policy->trickle_charge_mA |
128*4882a593Smuzhiyun fast_limit_mA | wm8350_charge_time_min(wm8350,
129*4882a593Smuzhiyun policy->charge_timeout));
130*4882a593Smuzhiyun }
131*4882a593Smuzhiyun
132*4882a593Smuzhiyun wm8350_reg_lock(wm8350);
133*4882a593Smuzhiyun return 0;
134*4882a593Smuzhiyun }
135*4882a593Smuzhiyun
wm8350_batt_status(struct wm8350 * wm8350)136*4882a593Smuzhiyun static int wm8350_batt_status(struct wm8350 *wm8350)
137*4882a593Smuzhiyun {
138*4882a593Smuzhiyun u16 state;
139*4882a593Smuzhiyun
140*4882a593Smuzhiyun state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2);
141*4882a593Smuzhiyun state &= WM8350_CHG_STS_MASK;
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun switch (state) {
144*4882a593Smuzhiyun case WM8350_CHG_STS_OFF:
145*4882a593Smuzhiyun return POWER_SUPPLY_STATUS_DISCHARGING;
146*4882a593Smuzhiyun
147*4882a593Smuzhiyun case WM8350_CHG_STS_TRICKLE:
148*4882a593Smuzhiyun case WM8350_CHG_STS_FAST:
149*4882a593Smuzhiyun return POWER_SUPPLY_STATUS_CHARGING;
150*4882a593Smuzhiyun
151*4882a593Smuzhiyun default:
152*4882a593Smuzhiyun return POWER_SUPPLY_STATUS_UNKNOWN;
153*4882a593Smuzhiyun }
154*4882a593Smuzhiyun }
155*4882a593Smuzhiyun
charger_state_show(struct device * dev,struct device_attribute * attr,char * buf)156*4882a593Smuzhiyun static ssize_t charger_state_show(struct device *dev,
157*4882a593Smuzhiyun struct device_attribute *attr, char *buf)
158*4882a593Smuzhiyun {
159*4882a593Smuzhiyun struct wm8350 *wm8350 = dev_get_drvdata(dev);
160*4882a593Smuzhiyun char *charge;
161*4882a593Smuzhiyun int state;
162*4882a593Smuzhiyun
163*4882a593Smuzhiyun state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2) &
164*4882a593Smuzhiyun WM8350_CHG_STS_MASK;
165*4882a593Smuzhiyun switch (state) {
166*4882a593Smuzhiyun case WM8350_CHG_STS_OFF:
167*4882a593Smuzhiyun charge = "Charger Off";
168*4882a593Smuzhiyun break;
169*4882a593Smuzhiyun case WM8350_CHG_STS_TRICKLE:
170*4882a593Smuzhiyun charge = "Trickle Charging";
171*4882a593Smuzhiyun break;
172*4882a593Smuzhiyun case WM8350_CHG_STS_FAST:
173*4882a593Smuzhiyun charge = "Fast Charging";
174*4882a593Smuzhiyun break;
175*4882a593Smuzhiyun default:
176*4882a593Smuzhiyun return 0;
177*4882a593Smuzhiyun }
178*4882a593Smuzhiyun
179*4882a593Smuzhiyun return sprintf(buf, "%s\n", charge);
180*4882a593Smuzhiyun }
181*4882a593Smuzhiyun
182*4882a593Smuzhiyun static DEVICE_ATTR_RO(charger_state);
183*4882a593Smuzhiyun
wm8350_charger_handler(int irq,void * data)184*4882a593Smuzhiyun static irqreturn_t wm8350_charger_handler(int irq, void *data)
185*4882a593Smuzhiyun {
186*4882a593Smuzhiyun struct wm8350 *wm8350 = data;
187*4882a593Smuzhiyun struct wm8350_power *power = &wm8350->power;
188*4882a593Smuzhiyun struct wm8350_charger_policy *policy = power->policy;
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun switch (irq - wm8350->irq_base) {
191*4882a593Smuzhiyun case WM8350_IRQ_CHG_BAT_FAIL:
192*4882a593Smuzhiyun dev_err(wm8350->dev, "battery failed\n");
193*4882a593Smuzhiyun break;
194*4882a593Smuzhiyun case WM8350_IRQ_CHG_TO:
195*4882a593Smuzhiyun dev_err(wm8350->dev, "charger timeout\n");
196*4882a593Smuzhiyun power_supply_changed(power->battery);
197*4882a593Smuzhiyun break;
198*4882a593Smuzhiyun
199*4882a593Smuzhiyun case WM8350_IRQ_CHG_BAT_HOT:
200*4882a593Smuzhiyun case WM8350_IRQ_CHG_BAT_COLD:
201*4882a593Smuzhiyun case WM8350_IRQ_CHG_START:
202*4882a593Smuzhiyun case WM8350_IRQ_CHG_END:
203*4882a593Smuzhiyun power_supply_changed(power->battery);
204*4882a593Smuzhiyun break;
205*4882a593Smuzhiyun
206*4882a593Smuzhiyun case WM8350_IRQ_CHG_FAST_RDY:
207*4882a593Smuzhiyun dev_dbg(wm8350->dev, "fast charger ready\n");
208*4882a593Smuzhiyun wm8350_charger_config(wm8350, policy);
209*4882a593Smuzhiyun wm8350_reg_unlock(wm8350);
210*4882a593Smuzhiyun wm8350_set_bits(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1,
211*4882a593Smuzhiyun WM8350_CHG_FAST);
212*4882a593Smuzhiyun wm8350_reg_lock(wm8350);
213*4882a593Smuzhiyun break;
214*4882a593Smuzhiyun
215*4882a593Smuzhiyun case WM8350_IRQ_CHG_VBATT_LT_3P9:
216*4882a593Smuzhiyun dev_warn(wm8350->dev, "battery < 3.9V\n");
217*4882a593Smuzhiyun break;
218*4882a593Smuzhiyun case WM8350_IRQ_CHG_VBATT_LT_3P1:
219*4882a593Smuzhiyun dev_warn(wm8350->dev, "battery < 3.1V\n");
220*4882a593Smuzhiyun break;
221*4882a593Smuzhiyun case WM8350_IRQ_CHG_VBATT_LT_2P85:
222*4882a593Smuzhiyun dev_warn(wm8350->dev, "battery < 2.85V\n");
223*4882a593Smuzhiyun break;
224*4882a593Smuzhiyun
225*4882a593Smuzhiyun /* Supply change. We will overnotify but it should do
226*4882a593Smuzhiyun * no harm. */
227*4882a593Smuzhiyun case WM8350_IRQ_EXT_USB_FB:
228*4882a593Smuzhiyun case WM8350_IRQ_EXT_WALL_FB:
229*4882a593Smuzhiyun wm8350_charger_config(wm8350, policy);
230*4882a593Smuzhiyun fallthrough;
231*4882a593Smuzhiyun case WM8350_IRQ_EXT_BAT_FB:
232*4882a593Smuzhiyun power_supply_changed(power->battery);
233*4882a593Smuzhiyun power_supply_changed(power->usb);
234*4882a593Smuzhiyun power_supply_changed(power->ac);
235*4882a593Smuzhiyun break;
236*4882a593Smuzhiyun
237*4882a593Smuzhiyun default:
238*4882a593Smuzhiyun dev_err(wm8350->dev, "Unknown interrupt %d\n", irq);
239*4882a593Smuzhiyun }
240*4882a593Smuzhiyun
241*4882a593Smuzhiyun return IRQ_HANDLED;
242*4882a593Smuzhiyun }
243*4882a593Smuzhiyun
244*4882a593Smuzhiyun /*********************************************************************
245*4882a593Smuzhiyun * AC Power
246*4882a593Smuzhiyun *********************************************************************/
wm8350_ac_get_prop(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)247*4882a593Smuzhiyun static int wm8350_ac_get_prop(struct power_supply *psy,
248*4882a593Smuzhiyun enum power_supply_property psp,
249*4882a593Smuzhiyun union power_supply_propval *val)
250*4882a593Smuzhiyun {
251*4882a593Smuzhiyun struct wm8350 *wm8350 = dev_get_drvdata(psy->dev.parent);
252*4882a593Smuzhiyun int ret = 0;
253*4882a593Smuzhiyun
254*4882a593Smuzhiyun switch (psp) {
255*4882a593Smuzhiyun case POWER_SUPPLY_PROP_ONLINE:
256*4882a593Smuzhiyun val->intval = !!(wm8350_get_supplies(wm8350) &
257*4882a593Smuzhiyun WM8350_LINE_SUPPLY);
258*4882a593Smuzhiyun break;
259*4882a593Smuzhiyun case POWER_SUPPLY_PROP_VOLTAGE_NOW:
260*4882a593Smuzhiyun val->intval = wm8350_read_line_uvolts(wm8350);
261*4882a593Smuzhiyun break;
262*4882a593Smuzhiyun default:
263*4882a593Smuzhiyun ret = -EINVAL;
264*4882a593Smuzhiyun break;
265*4882a593Smuzhiyun }
266*4882a593Smuzhiyun return ret;
267*4882a593Smuzhiyun }
268*4882a593Smuzhiyun
269*4882a593Smuzhiyun static enum power_supply_property wm8350_ac_props[] = {
270*4882a593Smuzhiyun POWER_SUPPLY_PROP_ONLINE,
271*4882a593Smuzhiyun POWER_SUPPLY_PROP_VOLTAGE_NOW,
272*4882a593Smuzhiyun };
273*4882a593Smuzhiyun
274*4882a593Smuzhiyun /*********************************************************************
275*4882a593Smuzhiyun * USB Power
276*4882a593Smuzhiyun *********************************************************************/
wm8350_usb_get_prop(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)277*4882a593Smuzhiyun static int wm8350_usb_get_prop(struct power_supply *psy,
278*4882a593Smuzhiyun enum power_supply_property psp,
279*4882a593Smuzhiyun union power_supply_propval *val)
280*4882a593Smuzhiyun {
281*4882a593Smuzhiyun struct wm8350 *wm8350 = dev_get_drvdata(psy->dev.parent);
282*4882a593Smuzhiyun int ret = 0;
283*4882a593Smuzhiyun
284*4882a593Smuzhiyun switch (psp) {
285*4882a593Smuzhiyun case POWER_SUPPLY_PROP_ONLINE:
286*4882a593Smuzhiyun val->intval = !!(wm8350_get_supplies(wm8350) &
287*4882a593Smuzhiyun WM8350_USB_SUPPLY);
288*4882a593Smuzhiyun break;
289*4882a593Smuzhiyun case POWER_SUPPLY_PROP_VOLTAGE_NOW:
290*4882a593Smuzhiyun val->intval = wm8350_read_usb_uvolts(wm8350);
291*4882a593Smuzhiyun break;
292*4882a593Smuzhiyun default:
293*4882a593Smuzhiyun ret = -EINVAL;
294*4882a593Smuzhiyun break;
295*4882a593Smuzhiyun }
296*4882a593Smuzhiyun return ret;
297*4882a593Smuzhiyun }
298*4882a593Smuzhiyun
299*4882a593Smuzhiyun static enum power_supply_property wm8350_usb_props[] = {
300*4882a593Smuzhiyun POWER_SUPPLY_PROP_ONLINE,
301*4882a593Smuzhiyun POWER_SUPPLY_PROP_VOLTAGE_NOW,
302*4882a593Smuzhiyun };
303*4882a593Smuzhiyun
304*4882a593Smuzhiyun /*********************************************************************
305*4882a593Smuzhiyun * Battery properties
306*4882a593Smuzhiyun *********************************************************************/
307*4882a593Smuzhiyun
wm8350_bat_check_health(struct wm8350 * wm8350)308*4882a593Smuzhiyun static int wm8350_bat_check_health(struct wm8350 *wm8350)
309*4882a593Smuzhiyun {
310*4882a593Smuzhiyun u16 reg;
311*4882a593Smuzhiyun
312*4882a593Smuzhiyun if (wm8350_read_battery_uvolts(wm8350) < 2850000)
313*4882a593Smuzhiyun return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
314*4882a593Smuzhiyun
315*4882a593Smuzhiyun reg = wm8350_reg_read(wm8350, WM8350_CHARGER_OVERRIDES);
316*4882a593Smuzhiyun if (reg & WM8350_CHG_BATT_HOT_OVRDE)
317*4882a593Smuzhiyun return POWER_SUPPLY_HEALTH_OVERHEAT;
318*4882a593Smuzhiyun
319*4882a593Smuzhiyun if (reg & WM8350_CHG_BATT_COLD_OVRDE)
320*4882a593Smuzhiyun return POWER_SUPPLY_HEALTH_COLD;
321*4882a593Smuzhiyun
322*4882a593Smuzhiyun return POWER_SUPPLY_HEALTH_GOOD;
323*4882a593Smuzhiyun }
324*4882a593Smuzhiyun
wm8350_bat_get_charge_type(struct wm8350 * wm8350)325*4882a593Smuzhiyun static int wm8350_bat_get_charge_type(struct wm8350 *wm8350)
326*4882a593Smuzhiyun {
327*4882a593Smuzhiyun int state;
328*4882a593Smuzhiyun
329*4882a593Smuzhiyun state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2) &
330*4882a593Smuzhiyun WM8350_CHG_STS_MASK;
331*4882a593Smuzhiyun switch (state) {
332*4882a593Smuzhiyun case WM8350_CHG_STS_OFF:
333*4882a593Smuzhiyun return POWER_SUPPLY_CHARGE_TYPE_NONE;
334*4882a593Smuzhiyun case WM8350_CHG_STS_TRICKLE:
335*4882a593Smuzhiyun return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
336*4882a593Smuzhiyun case WM8350_CHG_STS_FAST:
337*4882a593Smuzhiyun return POWER_SUPPLY_CHARGE_TYPE_FAST;
338*4882a593Smuzhiyun default:
339*4882a593Smuzhiyun return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
340*4882a593Smuzhiyun }
341*4882a593Smuzhiyun }
342*4882a593Smuzhiyun
wm8350_bat_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)343*4882a593Smuzhiyun static int wm8350_bat_get_property(struct power_supply *psy,
344*4882a593Smuzhiyun enum power_supply_property psp,
345*4882a593Smuzhiyun union power_supply_propval *val)
346*4882a593Smuzhiyun {
347*4882a593Smuzhiyun struct wm8350 *wm8350 = dev_get_drvdata(psy->dev.parent);
348*4882a593Smuzhiyun int ret = 0;
349*4882a593Smuzhiyun
350*4882a593Smuzhiyun switch (psp) {
351*4882a593Smuzhiyun case POWER_SUPPLY_PROP_STATUS:
352*4882a593Smuzhiyun val->intval = wm8350_batt_status(wm8350);
353*4882a593Smuzhiyun break;
354*4882a593Smuzhiyun case POWER_SUPPLY_PROP_ONLINE:
355*4882a593Smuzhiyun val->intval = !!(wm8350_get_supplies(wm8350) &
356*4882a593Smuzhiyun WM8350_BATT_SUPPLY);
357*4882a593Smuzhiyun break;
358*4882a593Smuzhiyun case POWER_SUPPLY_PROP_VOLTAGE_NOW:
359*4882a593Smuzhiyun val->intval = wm8350_read_battery_uvolts(wm8350);
360*4882a593Smuzhiyun break;
361*4882a593Smuzhiyun case POWER_SUPPLY_PROP_HEALTH:
362*4882a593Smuzhiyun val->intval = wm8350_bat_check_health(wm8350);
363*4882a593Smuzhiyun break;
364*4882a593Smuzhiyun case POWER_SUPPLY_PROP_CHARGE_TYPE:
365*4882a593Smuzhiyun val->intval = wm8350_bat_get_charge_type(wm8350);
366*4882a593Smuzhiyun break;
367*4882a593Smuzhiyun default:
368*4882a593Smuzhiyun ret = -EINVAL;
369*4882a593Smuzhiyun break;
370*4882a593Smuzhiyun }
371*4882a593Smuzhiyun
372*4882a593Smuzhiyun return ret;
373*4882a593Smuzhiyun }
374*4882a593Smuzhiyun
375*4882a593Smuzhiyun static enum power_supply_property wm8350_bat_props[] = {
376*4882a593Smuzhiyun POWER_SUPPLY_PROP_STATUS,
377*4882a593Smuzhiyun POWER_SUPPLY_PROP_ONLINE,
378*4882a593Smuzhiyun POWER_SUPPLY_PROP_VOLTAGE_NOW,
379*4882a593Smuzhiyun POWER_SUPPLY_PROP_HEALTH,
380*4882a593Smuzhiyun POWER_SUPPLY_PROP_CHARGE_TYPE,
381*4882a593Smuzhiyun };
382*4882a593Smuzhiyun
383*4882a593Smuzhiyun static const struct power_supply_desc wm8350_ac_desc = {
384*4882a593Smuzhiyun .name = "wm8350-ac",
385*4882a593Smuzhiyun .type = POWER_SUPPLY_TYPE_MAINS,
386*4882a593Smuzhiyun .properties = wm8350_ac_props,
387*4882a593Smuzhiyun .num_properties = ARRAY_SIZE(wm8350_ac_props),
388*4882a593Smuzhiyun .get_property = wm8350_ac_get_prop,
389*4882a593Smuzhiyun };
390*4882a593Smuzhiyun
391*4882a593Smuzhiyun static const struct power_supply_desc wm8350_battery_desc = {
392*4882a593Smuzhiyun .name = "wm8350-battery",
393*4882a593Smuzhiyun .properties = wm8350_bat_props,
394*4882a593Smuzhiyun .num_properties = ARRAY_SIZE(wm8350_bat_props),
395*4882a593Smuzhiyun .get_property = wm8350_bat_get_property,
396*4882a593Smuzhiyun .use_for_apm = 1,
397*4882a593Smuzhiyun };
398*4882a593Smuzhiyun
399*4882a593Smuzhiyun static const struct power_supply_desc wm8350_usb_desc = {
400*4882a593Smuzhiyun .name = "wm8350-usb",
401*4882a593Smuzhiyun .type = POWER_SUPPLY_TYPE_USB,
402*4882a593Smuzhiyun .properties = wm8350_usb_props,
403*4882a593Smuzhiyun .num_properties = ARRAY_SIZE(wm8350_usb_props),
404*4882a593Smuzhiyun .get_property = wm8350_usb_get_prop,
405*4882a593Smuzhiyun };
406*4882a593Smuzhiyun
407*4882a593Smuzhiyun /*********************************************************************
408*4882a593Smuzhiyun * Initialisation
409*4882a593Smuzhiyun *********************************************************************/
410*4882a593Smuzhiyun
wm8350_init_charger(struct wm8350 * wm8350)411*4882a593Smuzhiyun static int wm8350_init_charger(struct wm8350 *wm8350)
412*4882a593Smuzhiyun {
413*4882a593Smuzhiyun int ret;
414*4882a593Smuzhiyun
415*4882a593Smuzhiyun /* register our interest in charger events */
416*4882a593Smuzhiyun ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT,
417*4882a593Smuzhiyun wm8350_charger_handler, 0, "Battery hot", wm8350);
418*4882a593Smuzhiyun if (ret)
419*4882a593Smuzhiyun goto err;
420*4882a593Smuzhiyun
421*4882a593Smuzhiyun ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD,
422*4882a593Smuzhiyun wm8350_charger_handler, 0, "Battery cold", wm8350);
423*4882a593Smuzhiyun if (ret)
424*4882a593Smuzhiyun goto free_chg_bat_hot;
425*4882a593Smuzhiyun
426*4882a593Smuzhiyun ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL,
427*4882a593Smuzhiyun wm8350_charger_handler, 0, "Battery fail", wm8350);
428*4882a593Smuzhiyun if (ret)
429*4882a593Smuzhiyun goto free_chg_bat_cold;
430*4882a593Smuzhiyun
431*4882a593Smuzhiyun ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_TO,
432*4882a593Smuzhiyun wm8350_charger_handler, 0,
433*4882a593Smuzhiyun "Charger timeout", wm8350);
434*4882a593Smuzhiyun if (ret)
435*4882a593Smuzhiyun goto free_chg_bat_fail;
436*4882a593Smuzhiyun
437*4882a593Smuzhiyun ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_END,
438*4882a593Smuzhiyun wm8350_charger_handler, 0,
439*4882a593Smuzhiyun "Charge end", wm8350);
440*4882a593Smuzhiyun if (ret)
441*4882a593Smuzhiyun goto free_chg_to;
442*4882a593Smuzhiyun
443*4882a593Smuzhiyun ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_START,
444*4882a593Smuzhiyun wm8350_charger_handler, 0,
445*4882a593Smuzhiyun "Charge start", wm8350);
446*4882a593Smuzhiyun if (ret)
447*4882a593Smuzhiyun goto free_chg_end;
448*4882a593Smuzhiyun
449*4882a593Smuzhiyun ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY,
450*4882a593Smuzhiyun wm8350_charger_handler, 0,
451*4882a593Smuzhiyun "Fast charge ready", wm8350);
452*4882a593Smuzhiyun if (ret)
453*4882a593Smuzhiyun goto free_chg_start;
454*4882a593Smuzhiyun
455*4882a593Smuzhiyun ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9,
456*4882a593Smuzhiyun wm8350_charger_handler, 0,
457*4882a593Smuzhiyun "Battery <3.9V", wm8350);
458*4882a593Smuzhiyun if (ret)
459*4882a593Smuzhiyun goto free_chg_fast_rdy;
460*4882a593Smuzhiyun
461*4882a593Smuzhiyun ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1,
462*4882a593Smuzhiyun wm8350_charger_handler, 0,
463*4882a593Smuzhiyun "Battery <3.1V", wm8350);
464*4882a593Smuzhiyun if (ret)
465*4882a593Smuzhiyun goto free_chg_vbatt_lt_3p9;
466*4882a593Smuzhiyun
467*4882a593Smuzhiyun ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85,
468*4882a593Smuzhiyun wm8350_charger_handler, 0,
469*4882a593Smuzhiyun "Battery <2.85V", wm8350);
470*4882a593Smuzhiyun if (ret)
471*4882a593Smuzhiyun goto free_chg_vbatt_lt_3p1;
472*4882a593Smuzhiyun
473*4882a593Smuzhiyun /* and supply change events */
474*4882a593Smuzhiyun ret = wm8350_register_irq(wm8350, WM8350_IRQ_EXT_USB_FB,
475*4882a593Smuzhiyun wm8350_charger_handler, 0, "USB", wm8350);
476*4882a593Smuzhiyun if (ret)
477*4882a593Smuzhiyun goto free_chg_vbatt_lt_2p85;
478*4882a593Smuzhiyun
479*4882a593Smuzhiyun ret = wm8350_register_irq(wm8350, WM8350_IRQ_EXT_WALL_FB,
480*4882a593Smuzhiyun wm8350_charger_handler, 0, "Wall", wm8350);
481*4882a593Smuzhiyun if (ret)
482*4882a593Smuzhiyun goto free_ext_usb_fb;
483*4882a593Smuzhiyun
484*4882a593Smuzhiyun ret = wm8350_register_irq(wm8350, WM8350_IRQ_EXT_BAT_FB,
485*4882a593Smuzhiyun wm8350_charger_handler, 0, "Battery", wm8350);
486*4882a593Smuzhiyun if (ret)
487*4882a593Smuzhiyun goto free_ext_wall_fb;
488*4882a593Smuzhiyun
489*4882a593Smuzhiyun return 0;
490*4882a593Smuzhiyun
491*4882a593Smuzhiyun free_ext_wall_fb:
492*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, wm8350);
493*4882a593Smuzhiyun free_ext_usb_fb:
494*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_EXT_USB_FB, wm8350);
495*4882a593Smuzhiyun free_chg_vbatt_lt_2p85:
496*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, wm8350);
497*4882a593Smuzhiyun free_chg_vbatt_lt_3p1:
498*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, wm8350);
499*4882a593Smuzhiyun free_chg_vbatt_lt_3p9:
500*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, wm8350);
501*4882a593Smuzhiyun free_chg_fast_rdy:
502*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY, wm8350);
503*4882a593Smuzhiyun free_chg_start:
504*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START, wm8350);
505*4882a593Smuzhiyun free_chg_end:
506*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END, wm8350);
507*4882a593Smuzhiyun free_chg_to:
508*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO, wm8350);
509*4882a593Smuzhiyun free_chg_bat_fail:
510*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, wm8350);
511*4882a593Smuzhiyun free_chg_bat_cold:
512*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, wm8350);
513*4882a593Smuzhiyun free_chg_bat_hot:
514*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, wm8350);
515*4882a593Smuzhiyun err:
516*4882a593Smuzhiyun return ret;
517*4882a593Smuzhiyun }
518*4882a593Smuzhiyun
free_charger_irq(struct wm8350 * wm8350)519*4882a593Smuzhiyun static void free_charger_irq(struct wm8350 *wm8350)
520*4882a593Smuzhiyun {
521*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, wm8350);
522*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, wm8350);
523*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, wm8350);
524*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO, wm8350);
525*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END, wm8350);
526*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START, wm8350);
527*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY, wm8350);
528*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, wm8350);
529*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, wm8350);
530*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, wm8350);
531*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_EXT_USB_FB, wm8350);
532*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, wm8350);
533*4882a593Smuzhiyun wm8350_free_irq(wm8350, WM8350_IRQ_EXT_BAT_FB, wm8350);
534*4882a593Smuzhiyun }
535*4882a593Smuzhiyun
wm8350_power_probe(struct platform_device * pdev)536*4882a593Smuzhiyun static int wm8350_power_probe(struct platform_device *pdev)
537*4882a593Smuzhiyun {
538*4882a593Smuzhiyun struct wm8350 *wm8350 = platform_get_drvdata(pdev);
539*4882a593Smuzhiyun struct wm8350_power *power = &wm8350->power;
540*4882a593Smuzhiyun struct wm8350_charger_policy *policy = power->policy;
541*4882a593Smuzhiyun int ret;
542*4882a593Smuzhiyun
543*4882a593Smuzhiyun power->ac = power_supply_register(&pdev->dev, &wm8350_ac_desc, NULL);
544*4882a593Smuzhiyun if (IS_ERR(power->ac))
545*4882a593Smuzhiyun return PTR_ERR(power->ac);
546*4882a593Smuzhiyun
547*4882a593Smuzhiyun power->battery = power_supply_register(&pdev->dev, &wm8350_battery_desc,
548*4882a593Smuzhiyun NULL);
549*4882a593Smuzhiyun if (IS_ERR(power->battery)) {
550*4882a593Smuzhiyun ret = PTR_ERR(power->battery);
551*4882a593Smuzhiyun goto battery_failed;
552*4882a593Smuzhiyun }
553*4882a593Smuzhiyun
554*4882a593Smuzhiyun power->usb = power_supply_register(&pdev->dev, &wm8350_usb_desc, NULL);
555*4882a593Smuzhiyun if (IS_ERR(power->usb)) {
556*4882a593Smuzhiyun ret = PTR_ERR(power->usb);
557*4882a593Smuzhiyun goto usb_failed;
558*4882a593Smuzhiyun }
559*4882a593Smuzhiyun
560*4882a593Smuzhiyun ret = device_create_file(&pdev->dev, &dev_attr_charger_state);
561*4882a593Smuzhiyun if (ret < 0)
562*4882a593Smuzhiyun dev_warn(wm8350->dev, "failed to add charge sysfs: %d\n", ret);
563*4882a593Smuzhiyun ret = 0;
564*4882a593Smuzhiyun
565*4882a593Smuzhiyun wm8350_init_charger(wm8350);
566*4882a593Smuzhiyun if (wm8350_charger_config(wm8350, policy) == 0) {
567*4882a593Smuzhiyun wm8350_reg_unlock(wm8350);
568*4882a593Smuzhiyun wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CHG_ENA);
569*4882a593Smuzhiyun wm8350_reg_lock(wm8350);
570*4882a593Smuzhiyun }
571*4882a593Smuzhiyun
572*4882a593Smuzhiyun return ret;
573*4882a593Smuzhiyun
574*4882a593Smuzhiyun usb_failed:
575*4882a593Smuzhiyun power_supply_unregister(power->battery);
576*4882a593Smuzhiyun battery_failed:
577*4882a593Smuzhiyun power_supply_unregister(power->ac);
578*4882a593Smuzhiyun
579*4882a593Smuzhiyun return ret;
580*4882a593Smuzhiyun }
581*4882a593Smuzhiyun
wm8350_power_remove(struct platform_device * pdev)582*4882a593Smuzhiyun static int wm8350_power_remove(struct platform_device *pdev)
583*4882a593Smuzhiyun {
584*4882a593Smuzhiyun struct wm8350 *wm8350 = platform_get_drvdata(pdev);
585*4882a593Smuzhiyun struct wm8350_power *power = &wm8350->power;
586*4882a593Smuzhiyun
587*4882a593Smuzhiyun free_charger_irq(wm8350);
588*4882a593Smuzhiyun device_remove_file(&pdev->dev, &dev_attr_charger_state);
589*4882a593Smuzhiyun power_supply_unregister(power->battery);
590*4882a593Smuzhiyun power_supply_unregister(power->ac);
591*4882a593Smuzhiyun power_supply_unregister(power->usb);
592*4882a593Smuzhiyun return 0;
593*4882a593Smuzhiyun }
594*4882a593Smuzhiyun
595*4882a593Smuzhiyun static struct platform_driver wm8350_power_driver = {
596*4882a593Smuzhiyun .probe = wm8350_power_probe,
597*4882a593Smuzhiyun .remove = wm8350_power_remove,
598*4882a593Smuzhiyun .driver = {
599*4882a593Smuzhiyun .name = "wm8350-power",
600*4882a593Smuzhiyun },
601*4882a593Smuzhiyun };
602*4882a593Smuzhiyun
603*4882a593Smuzhiyun module_platform_driver(wm8350_power_driver);
604*4882a593Smuzhiyun
605*4882a593Smuzhiyun MODULE_LICENSE("GPL");
606*4882a593Smuzhiyun MODULE_DESCRIPTION("Power supply driver for WM8350");
607*4882a593Smuzhiyun MODULE_ALIAS("platform:wm8350-power");
608