1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Battery measurement code for WM97xx |
4 | * |
5 | * based on tosa_battery.c |
6 | * |
7 | * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com> |
8 | */ |
9 | |
10 | #include <linux/init.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/module.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/power_supply.h> |
15 | #include <linux/wm97xx.h> |
16 | #include <linux/spinlock.h> |
17 | #include <linux/interrupt.h> |
18 | #include <linux/gpio/consumer.h> |
19 | #include <linux/irq.h> |
20 | #include <linux/slab.h> |
21 | |
22 | static struct work_struct bat_work; |
23 | static struct gpio_desc *charge_gpiod; |
24 | static DEFINE_MUTEX(work_lock); |
25 | static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN; |
26 | static enum power_supply_property *prop; |
27 | |
28 | static unsigned long wm97xx_read_bat(struct power_supply *bat_ps) |
29 | { |
30 | struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(psy: bat_ps); |
31 | |
32 | return wm97xx_read_aux_adc(wm: dev_get_drvdata(dev: bat_ps->dev.parent), |
33 | adcsel: pdata->batt_aux) * pdata->batt_mult / |
34 | pdata->batt_div; |
35 | } |
36 | |
37 | static unsigned long wm97xx_read_temp(struct power_supply *bat_ps) |
38 | { |
39 | struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(psy: bat_ps); |
40 | |
41 | return wm97xx_read_aux_adc(wm: dev_get_drvdata(dev: bat_ps->dev.parent), |
42 | adcsel: pdata->temp_aux) * pdata->temp_mult / |
43 | pdata->temp_div; |
44 | } |
45 | |
46 | static int wm97xx_bat_get_property(struct power_supply *bat_ps, |
47 | enum power_supply_property psp, |
48 | union power_supply_propval *val) |
49 | { |
50 | struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(psy: bat_ps); |
51 | |
52 | switch (psp) { |
53 | case POWER_SUPPLY_PROP_STATUS: |
54 | val->intval = bat_status; |
55 | break; |
56 | case POWER_SUPPLY_PROP_TECHNOLOGY: |
57 | val->intval = pdata->batt_tech; |
58 | break; |
59 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
60 | if (pdata->batt_aux >= 0) |
61 | val->intval = wm97xx_read_bat(bat_ps); |
62 | else |
63 | return -EINVAL; |
64 | break; |
65 | case POWER_SUPPLY_PROP_TEMP: |
66 | if (pdata->temp_aux >= 0) |
67 | val->intval = wm97xx_read_temp(bat_ps); |
68 | else |
69 | return -EINVAL; |
70 | break; |
71 | case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
72 | if (pdata->max_voltage >= 0) |
73 | val->intval = pdata->max_voltage; |
74 | else |
75 | return -EINVAL; |
76 | break; |
77 | case POWER_SUPPLY_PROP_VOLTAGE_MIN: |
78 | if (pdata->min_voltage >= 0) |
79 | val->intval = pdata->min_voltage; |
80 | else |
81 | return -EINVAL; |
82 | break; |
83 | case POWER_SUPPLY_PROP_PRESENT: |
84 | val->intval = 1; |
85 | break; |
86 | default: |
87 | return -EINVAL; |
88 | } |
89 | return 0; |
90 | } |
91 | |
92 | static void wm97xx_bat_external_power_changed(struct power_supply *bat_ps) |
93 | { |
94 | schedule_work(work: &bat_work); |
95 | } |
96 | |
97 | static void wm97xx_bat_update(struct power_supply *bat_ps) |
98 | { |
99 | int old_status = bat_status; |
100 | |
101 | mutex_lock(&work_lock); |
102 | |
103 | bat_status = (charge_gpiod) ? |
104 | (gpiod_get_value(desc: charge_gpiod) ? |
105 | POWER_SUPPLY_STATUS_DISCHARGING : |
106 | POWER_SUPPLY_STATUS_CHARGING) : |
107 | POWER_SUPPLY_STATUS_UNKNOWN; |
108 | |
109 | if (old_status != bat_status) { |
110 | pr_debug("%s: %i -> %i\n" , bat_ps->desc->name, old_status, |
111 | bat_status); |
112 | power_supply_changed(psy: bat_ps); |
113 | } |
114 | |
115 | mutex_unlock(lock: &work_lock); |
116 | } |
117 | |
118 | static struct power_supply *bat_psy; |
119 | static struct power_supply_desc bat_psy_desc = { |
120 | .type = POWER_SUPPLY_TYPE_BATTERY, |
121 | .get_property = wm97xx_bat_get_property, |
122 | .external_power_changed = wm97xx_bat_external_power_changed, |
123 | .use_for_apm = 1, |
124 | }; |
125 | |
126 | static void wm97xx_bat_work(struct work_struct *work) |
127 | { |
128 | wm97xx_bat_update(bat_ps: bat_psy); |
129 | } |
130 | |
131 | static irqreturn_t wm97xx_chrg_irq(int irq, void *data) |
132 | { |
133 | schedule_work(work: &bat_work); |
134 | return IRQ_HANDLED; |
135 | } |
136 | |
137 | #ifdef CONFIG_PM |
138 | static int wm97xx_bat_suspend(struct device *dev) |
139 | { |
140 | flush_work(work: &bat_work); |
141 | return 0; |
142 | } |
143 | |
144 | static int wm97xx_bat_resume(struct device *dev) |
145 | { |
146 | schedule_work(work: &bat_work); |
147 | return 0; |
148 | } |
149 | |
150 | static const struct dev_pm_ops wm97xx_bat_pm_ops = { |
151 | .suspend = wm97xx_bat_suspend, |
152 | .resume = wm97xx_bat_resume, |
153 | }; |
154 | #endif |
155 | |
156 | static int wm97xx_bat_probe(struct platform_device *dev) |
157 | { |
158 | int ret = 0; |
159 | int props = 1; /* POWER_SUPPLY_PROP_PRESENT */ |
160 | int i = 0; |
161 | struct wm97xx_batt_pdata *pdata = dev->dev.platform_data; |
162 | struct power_supply_config cfg = {}; |
163 | |
164 | if (!pdata) { |
165 | dev_err(&dev->dev, "No platform data supplied\n" ); |
166 | return -EINVAL; |
167 | } |
168 | |
169 | cfg.drv_data = pdata; |
170 | |
171 | if (dev->id != -1) |
172 | return -EINVAL; |
173 | |
174 | charge_gpiod = devm_gpiod_get_optional(dev: &dev->dev, NULL, flags: GPIOD_IN); |
175 | if (IS_ERR(ptr: charge_gpiod)) |
176 | return dev_err_probe(dev: &dev->dev, |
177 | err: PTR_ERR(ptr: charge_gpiod), |
178 | fmt: "failed to get charge GPIO\n" ); |
179 | if (charge_gpiod) { |
180 | gpiod_set_consumer_name(desc: charge_gpiod, name: "BATT CHRG" ); |
181 | ret = request_irq(irq: gpiod_to_irq(desc: charge_gpiod), |
182 | handler: wm97xx_chrg_irq, flags: 0, |
183 | name: "AC Detect" , dev); |
184 | if (ret) |
185 | return dev_err_probe(dev: &dev->dev, err: ret, |
186 | fmt: "failed to request GPIO irq\n" ); |
187 | props++; /* POWER_SUPPLY_PROP_STATUS */ |
188 | } |
189 | |
190 | if (pdata->batt_tech >= 0) |
191 | props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ |
192 | if (pdata->temp_aux >= 0) |
193 | props++; /* POWER_SUPPLY_PROP_TEMP */ |
194 | if (pdata->batt_aux >= 0) |
195 | props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ |
196 | if (pdata->max_voltage >= 0) |
197 | props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ |
198 | if (pdata->min_voltage >= 0) |
199 | props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ |
200 | |
201 | prop = kcalloc(n: props, size: sizeof(*prop), GFP_KERNEL); |
202 | if (!prop) { |
203 | ret = -ENOMEM; |
204 | goto err3; |
205 | } |
206 | |
207 | prop[i++] = POWER_SUPPLY_PROP_PRESENT; |
208 | if (charge_gpiod) |
209 | prop[i++] = POWER_SUPPLY_PROP_STATUS; |
210 | if (pdata->batt_tech >= 0) |
211 | prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; |
212 | if (pdata->temp_aux >= 0) |
213 | prop[i++] = POWER_SUPPLY_PROP_TEMP; |
214 | if (pdata->batt_aux >= 0) |
215 | prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; |
216 | if (pdata->max_voltage >= 0) |
217 | prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; |
218 | if (pdata->min_voltage >= 0) |
219 | prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; |
220 | |
221 | INIT_WORK(&bat_work, wm97xx_bat_work); |
222 | |
223 | if (!pdata->batt_name) { |
224 | dev_info(&dev->dev, "Please consider setting proper battery " |
225 | "name in platform definition file, falling " |
226 | "back to name \"wm97xx-batt\"\n" ); |
227 | bat_psy_desc.name = "wm97xx-batt" ; |
228 | } else |
229 | bat_psy_desc.name = pdata->batt_name; |
230 | |
231 | bat_psy_desc.properties = prop; |
232 | bat_psy_desc.num_properties = props; |
233 | |
234 | bat_psy = power_supply_register(parent: &dev->dev, desc: &bat_psy_desc, cfg: &cfg); |
235 | if (!IS_ERR(ptr: bat_psy)) { |
236 | schedule_work(work: &bat_work); |
237 | } else { |
238 | ret = PTR_ERR(ptr: bat_psy); |
239 | goto err4; |
240 | } |
241 | |
242 | return 0; |
243 | err4: |
244 | kfree(objp: prop); |
245 | err3: |
246 | if (charge_gpiod) |
247 | free_irq(gpiod_to_irq(desc: charge_gpiod), dev); |
248 | return ret; |
249 | } |
250 | |
251 | static void wm97xx_bat_remove(struct platform_device *dev) |
252 | { |
253 | if (charge_gpiod) |
254 | free_irq(gpiod_to_irq(desc: charge_gpiod), dev); |
255 | cancel_work_sync(work: &bat_work); |
256 | power_supply_unregister(psy: bat_psy); |
257 | kfree(objp: prop); |
258 | } |
259 | |
260 | static struct platform_driver wm97xx_bat_driver = { |
261 | .driver = { |
262 | .name = "wm97xx-battery" , |
263 | #ifdef CONFIG_PM |
264 | .pm = &wm97xx_bat_pm_ops, |
265 | #endif |
266 | }, |
267 | .probe = wm97xx_bat_probe, |
268 | .remove_new = wm97xx_bat_remove, |
269 | }; |
270 | |
271 | module_platform_driver(wm97xx_bat_driver); |
272 | |
273 | MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>" ); |
274 | MODULE_DESCRIPTION("WM97xx battery driver" ); |
275 | |