1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * |
4 | * h3xxx atmel micro companion support, battery subdevice |
5 | * based on previous kernel 2.4 version |
6 | * Author : Alessandro Gardich <gremlin@gremlin.it> |
7 | * Author : Linus Walleij <linus.walleij@linaro.org> |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/init.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/mfd/ipaq-micro.h> |
14 | #include <linux/power_supply.h> |
15 | #include <linux/workqueue.h> |
16 | |
17 | #define BATT_PERIOD 100000 /* 100 seconds in milliseconds */ |
18 | |
19 | #define MICRO_BATT_CHEM_ALKALINE 0x01 |
20 | #define MICRO_BATT_CHEM_NICD 0x02 |
21 | #define MICRO_BATT_CHEM_NIMH 0x03 |
22 | #define MICRO_BATT_CHEM_LION 0x04 |
23 | #define MICRO_BATT_CHEM_LIPOLY 0x05 |
24 | #define MICRO_BATT_CHEM_NOT_INSTALLED 0x06 |
25 | #define MICRO_BATT_CHEM_UNKNOWN 0xff |
26 | |
27 | #define MICRO_BATT_STATUS_HIGH 0x01 |
28 | #define MICRO_BATT_STATUS_LOW 0x02 |
29 | #define MICRO_BATT_STATUS_CRITICAL 0x04 |
30 | #define MICRO_BATT_STATUS_CHARGING 0x08 |
31 | #define MICRO_BATT_STATUS_CHARGEMAIN 0x10 |
32 | #define MICRO_BATT_STATUS_DEAD 0x20 /* Battery will not charge */ |
33 | #define MICRO_BATT_STATUS_NOTINSTALLED 0x20 /* For expansion pack batteries */ |
34 | #define MICRO_BATT_STATUS_FULL 0x40 /* Battery fully charged */ |
35 | #define MICRO_BATT_STATUS_NOBATTERY 0x80 |
36 | #define MICRO_BATT_STATUS_UNKNOWN 0xff |
37 | |
38 | struct micro_battery { |
39 | struct ipaq_micro *micro; |
40 | struct workqueue_struct *wq; |
41 | struct delayed_work update; |
42 | u8 ac; |
43 | u8 chemistry; |
44 | unsigned int voltage; |
45 | u16 temperature; |
46 | u8 flag; |
47 | }; |
48 | |
49 | static void micro_battery_work(struct work_struct *work) |
50 | { |
51 | struct micro_battery *mb = container_of(work, |
52 | struct micro_battery, update.work); |
53 | struct ipaq_micro_msg msg_battery = { |
54 | .id = MSG_BATTERY, |
55 | }; |
56 | struct ipaq_micro_msg msg_sensor = { |
57 | .id = MSG_THERMAL_SENSOR, |
58 | }; |
59 | |
60 | /* First send battery message */ |
61 | ipaq_micro_tx_msg_sync(micro: mb->micro, msg: &msg_battery); |
62 | if (msg_battery.rx_len < 4) |
63 | pr_info("ERROR" ); |
64 | |
65 | /* |
66 | * Returned message format: |
67 | * byte 0: 0x00 = Not plugged in |
68 | * 0x01 = AC adapter plugged in |
69 | * byte 1: chemistry |
70 | * byte 2: voltage LSB |
71 | * byte 3: voltage MSB |
72 | * byte 4: flags |
73 | * byte 5-9: same for battery 2 |
74 | */ |
75 | mb->ac = msg_battery.rx_data[0]; |
76 | mb->chemistry = msg_battery.rx_data[1]; |
77 | mb->voltage = ((((unsigned short)msg_battery.rx_data[3] << 8) + |
78 | msg_battery.rx_data[2]) * 5000L) * 1000 / 1024; |
79 | mb->flag = msg_battery.rx_data[4]; |
80 | |
81 | if (msg_battery.rx_len == 9) |
82 | pr_debug("second battery ignored\n" ); |
83 | |
84 | /* Then read the sensor */ |
85 | ipaq_micro_tx_msg_sync(micro: mb->micro, msg: &msg_sensor); |
86 | mb->temperature = msg_sensor.rx_data[1] << 8 | msg_sensor.rx_data[0]; |
87 | |
88 | queue_delayed_work(wq: mb->wq, dwork: &mb->update, delay: msecs_to_jiffies(BATT_PERIOD)); |
89 | } |
90 | |
91 | static int get_capacity(struct power_supply *b) |
92 | { |
93 | struct micro_battery *mb = dev_get_drvdata(dev: b->dev.parent); |
94 | |
95 | switch (mb->flag & 0x07) { |
96 | case MICRO_BATT_STATUS_HIGH: |
97 | return 100; |
98 | break; |
99 | case MICRO_BATT_STATUS_LOW: |
100 | return 50; |
101 | break; |
102 | case MICRO_BATT_STATUS_CRITICAL: |
103 | return 5; |
104 | break; |
105 | default: |
106 | break; |
107 | } |
108 | return 0; |
109 | } |
110 | |
111 | static int get_status(struct power_supply *b) |
112 | { |
113 | struct micro_battery *mb = dev_get_drvdata(dev: b->dev.parent); |
114 | |
115 | if (mb->flag == MICRO_BATT_STATUS_UNKNOWN) |
116 | return POWER_SUPPLY_STATUS_UNKNOWN; |
117 | |
118 | if (mb->flag & MICRO_BATT_STATUS_FULL) |
119 | return POWER_SUPPLY_STATUS_FULL; |
120 | |
121 | if ((mb->flag & MICRO_BATT_STATUS_CHARGING) || |
122 | (mb->flag & MICRO_BATT_STATUS_CHARGEMAIN)) |
123 | return POWER_SUPPLY_STATUS_CHARGING; |
124 | |
125 | return POWER_SUPPLY_STATUS_DISCHARGING; |
126 | } |
127 | |
128 | static int micro_batt_get_property(struct power_supply *b, |
129 | enum power_supply_property psp, |
130 | union power_supply_propval *val) |
131 | { |
132 | struct micro_battery *mb = dev_get_drvdata(dev: b->dev.parent); |
133 | |
134 | switch (psp) { |
135 | case POWER_SUPPLY_PROP_TECHNOLOGY: |
136 | switch (mb->chemistry) { |
137 | case MICRO_BATT_CHEM_NICD: |
138 | val->intval = POWER_SUPPLY_TECHNOLOGY_NiCd; |
139 | break; |
140 | case MICRO_BATT_CHEM_NIMH: |
141 | val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH; |
142 | break; |
143 | case MICRO_BATT_CHEM_LION: |
144 | val->intval = POWER_SUPPLY_TECHNOLOGY_LION; |
145 | break; |
146 | case MICRO_BATT_CHEM_LIPOLY: |
147 | val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO; |
148 | break; |
149 | default: |
150 | val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; |
151 | break; |
152 | } |
153 | break; |
154 | case POWER_SUPPLY_PROP_STATUS: |
155 | val->intval = get_status(b); |
156 | break; |
157 | case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: |
158 | val->intval = 4700000; |
159 | break; |
160 | case POWER_SUPPLY_PROP_CAPACITY: |
161 | val->intval = get_capacity(b); |
162 | break; |
163 | case POWER_SUPPLY_PROP_TEMP: |
164 | val->intval = mb->temperature; |
165 | break; |
166 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
167 | val->intval = mb->voltage; |
168 | break; |
169 | default: |
170 | return -EINVAL; |
171 | } |
172 | |
173 | return 0; |
174 | } |
175 | |
176 | static int micro_ac_get_property(struct power_supply *b, |
177 | enum power_supply_property psp, |
178 | union power_supply_propval *val) |
179 | { |
180 | struct micro_battery *mb = dev_get_drvdata(dev: b->dev.parent); |
181 | |
182 | switch (psp) { |
183 | case POWER_SUPPLY_PROP_ONLINE: |
184 | val->intval = mb->ac; |
185 | break; |
186 | default: |
187 | return -EINVAL; |
188 | } |
189 | |
190 | return 0; |
191 | } |
192 | |
193 | static enum power_supply_property micro_batt_power_props[] = { |
194 | POWER_SUPPLY_PROP_TECHNOLOGY, |
195 | POWER_SUPPLY_PROP_STATUS, |
196 | POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, |
197 | POWER_SUPPLY_PROP_CAPACITY, |
198 | POWER_SUPPLY_PROP_TEMP, |
199 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
200 | }; |
201 | |
202 | static const struct power_supply_desc micro_batt_power_desc = { |
203 | .name = "main-battery" , |
204 | .type = POWER_SUPPLY_TYPE_BATTERY, |
205 | .properties = micro_batt_power_props, |
206 | .num_properties = ARRAY_SIZE(micro_batt_power_props), |
207 | .get_property = micro_batt_get_property, |
208 | .use_for_apm = 1, |
209 | }; |
210 | |
211 | static enum power_supply_property micro_ac_power_props[] = { |
212 | POWER_SUPPLY_PROP_ONLINE, |
213 | }; |
214 | |
215 | static const struct power_supply_desc micro_ac_power_desc = { |
216 | .name = "ac" , |
217 | .type = POWER_SUPPLY_TYPE_MAINS, |
218 | .properties = micro_ac_power_props, |
219 | .num_properties = ARRAY_SIZE(micro_ac_power_props), |
220 | .get_property = micro_ac_get_property, |
221 | }; |
222 | |
223 | static struct power_supply *micro_batt_power, *micro_ac_power; |
224 | |
225 | static int micro_batt_probe(struct platform_device *pdev) |
226 | { |
227 | struct micro_battery *mb; |
228 | int ret; |
229 | |
230 | mb = devm_kzalloc(dev: &pdev->dev, size: sizeof(*mb), GFP_KERNEL); |
231 | if (!mb) |
232 | return -ENOMEM; |
233 | |
234 | mb->micro = dev_get_drvdata(dev: pdev->dev.parent); |
235 | mb->wq = alloc_workqueue(fmt: "ipaq-battery-wq" , flags: WQ_MEM_RECLAIM, max_active: 0); |
236 | if (!mb->wq) |
237 | return -ENOMEM; |
238 | |
239 | INIT_DELAYED_WORK(&mb->update, micro_battery_work); |
240 | platform_set_drvdata(pdev, data: mb); |
241 | queue_delayed_work(wq: mb->wq, dwork: &mb->update, delay: 1); |
242 | |
243 | micro_batt_power = power_supply_register(parent: &pdev->dev, |
244 | desc: µ_batt_power_desc, NULL); |
245 | if (IS_ERR(ptr: micro_batt_power)) { |
246 | ret = PTR_ERR(ptr: micro_batt_power); |
247 | goto batt_err; |
248 | } |
249 | |
250 | micro_ac_power = power_supply_register(parent: &pdev->dev, |
251 | desc: µ_ac_power_desc, NULL); |
252 | if (IS_ERR(ptr: micro_ac_power)) { |
253 | ret = PTR_ERR(ptr: micro_ac_power); |
254 | goto ac_err; |
255 | } |
256 | |
257 | dev_info(&pdev->dev, "iPAQ micro battery driver\n" ); |
258 | return 0; |
259 | |
260 | ac_err: |
261 | power_supply_unregister(psy: micro_batt_power); |
262 | batt_err: |
263 | cancel_delayed_work_sync(dwork: &mb->update); |
264 | destroy_workqueue(wq: mb->wq); |
265 | return ret; |
266 | } |
267 | |
268 | static void micro_batt_remove(struct platform_device *pdev) |
269 | |
270 | { |
271 | struct micro_battery *mb = platform_get_drvdata(pdev); |
272 | |
273 | power_supply_unregister(psy: micro_ac_power); |
274 | power_supply_unregister(psy: micro_batt_power); |
275 | cancel_delayed_work_sync(dwork: &mb->update); |
276 | destroy_workqueue(wq: mb->wq); |
277 | } |
278 | |
279 | static int __maybe_unused micro_batt_suspend(struct device *dev) |
280 | { |
281 | struct micro_battery *mb = dev_get_drvdata(dev); |
282 | |
283 | cancel_delayed_work_sync(dwork: &mb->update); |
284 | return 0; |
285 | } |
286 | |
287 | static int __maybe_unused micro_batt_resume(struct device *dev) |
288 | { |
289 | struct micro_battery *mb = dev_get_drvdata(dev); |
290 | |
291 | queue_delayed_work(wq: mb->wq, dwork: &mb->update, delay: msecs_to_jiffies(BATT_PERIOD)); |
292 | return 0; |
293 | } |
294 | |
295 | static const struct dev_pm_ops micro_batt_dev_pm_ops = { |
296 | SET_SYSTEM_SLEEP_PM_OPS(micro_batt_suspend, micro_batt_resume) |
297 | }; |
298 | |
299 | static struct platform_driver micro_batt_device_driver = { |
300 | .driver = { |
301 | .name = "ipaq-micro-battery" , |
302 | .pm = µ_batt_dev_pm_ops, |
303 | }, |
304 | .probe = micro_batt_probe, |
305 | .remove_new = micro_batt_remove, |
306 | }; |
307 | module_platform_driver(micro_batt_device_driver); |
308 | |
309 | MODULE_LICENSE("GPL" ); |
310 | MODULE_DESCRIPTION("driver for iPAQ Atmel micro battery" ); |
311 | MODULE_ALIAS("platform:ipaq-micro-battery" ); |
312 | |