1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Battery driver for Acer Iconia Tab A500. |
4 | * |
5 | * Copyright 2020 GRATE-driver project. |
6 | * |
7 | * Based on downstream driver from Acer Inc. |
8 | * Based on NVIDIA Gas Gauge driver for SBS Compliant Batteries. |
9 | * |
10 | * Copyright (c) 2010, NVIDIA Corporation. |
11 | */ |
12 | |
13 | #include <linux/module.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/power_supply.h> |
16 | #include <linux/regmap.h> |
17 | #include <linux/sched.h> |
18 | #include <linux/slab.h> |
19 | #include <linux/workqueue.h> |
20 | |
21 | enum { |
22 | REG_CAPACITY, |
23 | REG_VOLTAGE, |
24 | REG_CURRENT, |
25 | REG_DESIGN_CAPACITY, |
26 | REG_TEMPERATURE, |
27 | }; |
28 | |
29 | #define EC_DATA(_reg, _psp) { \ |
30 | .psp = POWER_SUPPLY_PROP_ ## _psp, \ |
31 | .reg = _reg, \ |
32 | } |
33 | |
34 | static const struct battery_register { |
35 | enum power_supply_property psp; |
36 | unsigned int reg; |
37 | } ec_data[] = { |
38 | [REG_CAPACITY] = EC_DATA(0x00, CAPACITY), |
39 | [REG_VOLTAGE] = EC_DATA(0x01, VOLTAGE_NOW), |
40 | [REG_CURRENT] = EC_DATA(0x03, CURRENT_NOW), |
41 | [REG_DESIGN_CAPACITY] = EC_DATA(0x08, CHARGE_FULL_DESIGN), |
42 | [REG_TEMPERATURE] = EC_DATA(0x0a, TEMP), |
43 | }; |
44 | |
45 | static const enum power_supply_property a500_battery_properties[] = { |
46 | POWER_SUPPLY_PROP_CAPACITY, |
47 | POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, |
48 | POWER_SUPPLY_PROP_CURRENT_NOW, |
49 | POWER_SUPPLY_PROP_PRESENT, |
50 | POWER_SUPPLY_PROP_STATUS, |
51 | POWER_SUPPLY_PROP_TECHNOLOGY, |
52 | POWER_SUPPLY_PROP_TEMP, |
53 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
54 | }; |
55 | |
56 | struct a500_battery { |
57 | struct delayed_work poll_work; |
58 | struct power_supply *psy; |
59 | struct regmap *regmap; |
60 | unsigned int capacity; |
61 | }; |
62 | |
63 | static bool a500_battery_update_capacity(struct a500_battery *bat) |
64 | { |
65 | unsigned int capacity; |
66 | int err; |
67 | |
68 | err = regmap_read(map: bat->regmap, reg: ec_data[REG_CAPACITY].reg, val: &capacity); |
69 | if (err) |
70 | return false; |
71 | |
72 | /* capacity can be >100% even if max value is 100% */ |
73 | capacity = min(capacity, 100u); |
74 | |
75 | if (bat->capacity != capacity) { |
76 | bat->capacity = capacity; |
77 | return true; |
78 | } |
79 | |
80 | return false; |
81 | } |
82 | |
83 | static int a500_battery_get_status(struct a500_battery *bat) |
84 | { |
85 | if (bat->capacity < 100) { |
86 | if (power_supply_am_i_supplied(psy: bat->psy)) |
87 | return POWER_SUPPLY_STATUS_CHARGING; |
88 | else |
89 | return POWER_SUPPLY_STATUS_DISCHARGING; |
90 | } |
91 | |
92 | return POWER_SUPPLY_STATUS_FULL; |
93 | } |
94 | |
95 | static void a500_battery_unit_adjustment(struct device *dev, |
96 | enum power_supply_property psp, |
97 | union power_supply_propval *val) |
98 | { |
99 | const unsigned int base_unit_conversion = 1000; |
100 | const unsigned int temp_kelvin_to_celsius = 2731; |
101 | |
102 | switch (psp) { |
103 | case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: |
104 | case POWER_SUPPLY_PROP_CURRENT_NOW: |
105 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
106 | val->intval *= base_unit_conversion; |
107 | break; |
108 | |
109 | case POWER_SUPPLY_PROP_TEMP: |
110 | val->intval -= temp_kelvin_to_celsius; |
111 | break; |
112 | |
113 | case POWER_SUPPLY_PROP_PRESENT: |
114 | val->intval = !!val->intval; |
115 | break; |
116 | |
117 | default: |
118 | dev_dbg(dev, |
119 | "%s: no need for unit conversion %d\n" , __func__, psp); |
120 | } |
121 | } |
122 | |
123 | static int a500_battery_get_ec_data_index(struct device *dev, |
124 | enum power_supply_property psp) |
125 | { |
126 | unsigned int i; |
127 | |
128 | /* |
129 | * DESIGN_CAPACITY register always returns a non-zero value if |
130 | * battery is connected and zero if disconnected, hence we'll use |
131 | * it for judging the battery presence. |
132 | */ |
133 | if (psp == POWER_SUPPLY_PROP_PRESENT) |
134 | psp = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; |
135 | |
136 | for (i = 0; i < ARRAY_SIZE(ec_data); i++) |
137 | if (psp == ec_data[i].psp) |
138 | return i; |
139 | |
140 | dev_dbg(dev, "%s: invalid property %u\n" , __func__, psp); |
141 | |
142 | return -EINVAL; |
143 | } |
144 | |
145 | static int a500_battery_get_property(struct power_supply *psy, |
146 | enum power_supply_property psp, |
147 | union power_supply_propval *val) |
148 | { |
149 | struct a500_battery *bat = power_supply_get_drvdata(psy); |
150 | struct device *dev = psy->dev.parent; |
151 | int ret = 0; |
152 | |
153 | switch (psp) { |
154 | case POWER_SUPPLY_PROP_STATUS: |
155 | val->intval = a500_battery_get_status(bat); |
156 | break; |
157 | |
158 | case POWER_SUPPLY_PROP_TECHNOLOGY: |
159 | val->intval = POWER_SUPPLY_TECHNOLOGY_LION; |
160 | break; |
161 | |
162 | case POWER_SUPPLY_PROP_CAPACITY: |
163 | a500_battery_update_capacity(bat); |
164 | val->intval = bat->capacity; |
165 | break; |
166 | |
167 | case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: |
168 | case POWER_SUPPLY_PROP_CURRENT_NOW: |
169 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
170 | case POWER_SUPPLY_PROP_PRESENT: |
171 | case POWER_SUPPLY_PROP_TEMP: |
172 | ret = a500_battery_get_ec_data_index(dev, psp); |
173 | if (ret < 0) |
174 | break; |
175 | |
176 | ret = regmap_read(map: bat->regmap, reg: ec_data[ret].reg, val: &val->intval); |
177 | break; |
178 | |
179 | default: |
180 | dev_err(dev, "%s: invalid property %u\n" , __func__, psp); |
181 | return -EINVAL; |
182 | } |
183 | |
184 | if (!ret) { |
185 | /* convert units to match requirements of power supply class */ |
186 | a500_battery_unit_adjustment(dev, psp, val); |
187 | } |
188 | |
189 | dev_dbg(dev, "%s: property = %d, value = %x\n" , |
190 | __func__, psp, val->intval); |
191 | |
192 | /* return NODATA for properties if battery not presents */ |
193 | if (ret) |
194 | return -ENODATA; |
195 | |
196 | return 0; |
197 | } |
198 | |
199 | static void a500_battery_poll_work(struct work_struct *work) |
200 | { |
201 | struct a500_battery *bat; |
202 | bool capacity_changed; |
203 | |
204 | bat = container_of(work, struct a500_battery, poll_work.work); |
205 | capacity_changed = a500_battery_update_capacity(bat); |
206 | |
207 | if (capacity_changed) |
208 | power_supply_changed(psy: bat->psy); |
209 | |
210 | /* continuously send uevent notification */ |
211 | schedule_delayed_work(dwork: &bat->poll_work, delay: 30 * HZ); |
212 | } |
213 | |
214 | static const struct power_supply_desc a500_battery_desc = { |
215 | .name = "ec-battery" , |
216 | .type = POWER_SUPPLY_TYPE_BATTERY, |
217 | .properties = a500_battery_properties, |
218 | .get_property = a500_battery_get_property, |
219 | .num_properties = ARRAY_SIZE(a500_battery_properties), |
220 | .external_power_changed = power_supply_changed, |
221 | }; |
222 | |
223 | static int a500_battery_probe(struct platform_device *pdev) |
224 | { |
225 | struct power_supply_config psy_cfg = {}; |
226 | struct a500_battery *bat; |
227 | |
228 | bat = devm_kzalloc(dev: &pdev->dev, size: sizeof(*bat), GFP_KERNEL); |
229 | if (!bat) |
230 | return -ENOMEM; |
231 | |
232 | platform_set_drvdata(pdev, data: bat); |
233 | |
234 | psy_cfg.of_node = pdev->dev.parent->of_node; |
235 | psy_cfg.drv_data = bat; |
236 | |
237 | bat->regmap = dev_get_regmap(dev: pdev->dev.parent, name: "KB930" ); |
238 | if (!bat->regmap) |
239 | return -EINVAL; |
240 | |
241 | bat->psy = devm_power_supply_register_no_ws(parent: &pdev->dev, |
242 | desc: &a500_battery_desc, |
243 | cfg: &psy_cfg); |
244 | if (IS_ERR(ptr: bat->psy)) |
245 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: bat->psy), |
246 | fmt: "failed to register battery\n" ); |
247 | |
248 | INIT_DELAYED_WORK(&bat->poll_work, a500_battery_poll_work); |
249 | schedule_delayed_work(dwork: &bat->poll_work, HZ); |
250 | |
251 | return 0; |
252 | } |
253 | |
254 | static void a500_battery_remove(struct platform_device *pdev) |
255 | { |
256 | struct a500_battery *bat = dev_get_drvdata(dev: &pdev->dev); |
257 | |
258 | cancel_delayed_work_sync(dwork: &bat->poll_work); |
259 | } |
260 | |
261 | static int __maybe_unused a500_battery_suspend(struct device *dev) |
262 | { |
263 | struct a500_battery *bat = dev_get_drvdata(dev); |
264 | |
265 | cancel_delayed_work_sync(dwork: &bat->poll_work); |
266 | |
267 | return 0; |
268 | } |
269 | |
270 | static int __maybe_unused a500_battery_resume(struct device *dev) |
271 | { |
272 | struct a500_battery *bat = dev_get_drvdata(dev); |
273 | |
274 | schedule_delayed_work(dwork: &bat->poll_work, HZ); |
275 | |
276 | return 0; |
277 | } |
278 | |
279 | static SIMPLE_DEV_PM_OPS(a500_battery_pm_ops, |
280 | a500_battery_suspend, a500_battery_resume); |
281 | |
282 | static struct platform_driver a500_battery_driver = { |
283 | .driver = { |
284 | .name = "acer-a500-iconia-battery" , |
285 | .pm = &a500_battery_pm_ops, |
286 | }, |
287 | .probe = a500_battery_probe, |
288 | .remove_new = a500_battery_remove, |
289 | }; |
290 | module_platform_driver(a500_battery_driver); |
291 | |
292 | MODULE_DESCRIPTION("Battery gauge driver for Acer Iconia Tab A500" ); |
293 | MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>" ); |
294 | MODULE_ALIAS("platform:acer-a500-iconia-battery" ); |
295 | MODULE_LICENSE("GPL" ); |
296 | |