1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * nvec_power: power supply driver for a NVIDIA compliant embedded controller |
4 | * |
5 | * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.launchpad.net> |
6 | * |
7 | * Authors: Ilya Petrov <ilya.muromec@gmail.com> |
8 | * Marc Dietrich <marvin24@gmx.de> |
9 | */ |
10 | |
11 | #include <linux/module.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/err.h> |
14 | #include <linux/power_supply.h> |
15 | #include <linux/slab.h> |
16 | #include <linux/workqueue.h> |
17 | #include <linux/delay.h> |
18 | |
19 | #include "nvec.h" |
20 | |
21 | #define GET_SYSTEM_STATUS 0x00 |
22 | |
23 | struct nvec_power { |
24 | struct notifier_block notifier; |
25 | struct delayed_work poller; |
26 | struct nvec_chip *nvec; |
27 | int on; |
28 | int bat_present; |
29 | int bat_status; |
30 | int bat_voltage_now; |
31 | int bat_current_now; |
32 | int bat_current_avg; |
33 | int time_remain; |
34 | int charge_full_design; |
35 | int charge_last_full; |
36 | int critical_capacity; |
37 | int capacity_remain; |
38 | int bat_temperature; |
39 | int bat_cap; |
40 | int bat_type_enum; |
41 | char bat_manu[30]; |
42 | char bat_model[30]; |
43 | char bat_type[30]; |
44 | }; |
45 | |
46 | enum { |
47 | SLOT_STATUS, |
48 | VOLTAGE, |
49 | TIME_REMAINING, |
50 | CURRENT, |
51 | AVERAGE_CURRENT, |
52 | AVERAGING_TIME_INTERVAL, |
53 | CAPACITY_REMAINING, |
54 | LAST_FULL_CHARGE_CAPACITY, |
55 | DESIGN_CAPACITY, |
56 | CRITICAL_CAPACITY, |
57 | TEMPERATURE, |
58 | MANUFACTURER, |
59 | MODEL, |
60 | TYPE, |
61 | }; |
62 | |
63 | enum { |
64 | AC, |
65 | BAT, |
66 | }; |
67 | |
68 | struct bat_response { |
69 | u8 event_type; |
70 | u8 length; |
71 | u8 sub_type; |
72 | u8 status; |
73 | /* payload */ |
74 | union { |
75 | char plc[30]; |
76 | u16 plu; |
77 | s16 pls; |
78 | }; |
79 | }; |
80 | |
81 | static struct power_supply *nvec_bat_psy; |
82 | static struct power_supply *nvec_psy; |
83 | |
84 | static int nvec_power_notifier(struct notifier_block *nb, |
85 | unsigned long event_type, void *data) |
86 | { |
87 | struct nvec_power *power = |
88 | container_of(nb, struct nvec_power, notifier); |
89 | struct bat_response *res = data; |
90 | |
91 | if (event_type != NVEC_SYS) |
92 | return NOTIFY_DONE; |
93 | |
94 | if (res->sub_type == 0) { |
95 | if (power->on != res->plu) { |
96 | power->on = res->plu; |
97 | power_supply_changed(psy: nvec_psy); |
98 | } |
99 | return NOTIFY_STOP; |
100 | } |
101 | return NOTIFY_OK; |
102 | } |
103 | |
104 | static const int bat_init[] = { |
105 | LAST_FULL_CHARGE_CAPACITY, DESIGN_CAPACITY, CRITICAL_CAPACITY, |
106 | MANUFACTURER, MODEL, TYPE, |
107 | }; |
108 | |
109 | static void get_bat_mfg_data(struct nvec_power *power) |
110 | { |
111 | int i; |
112 | char buf[] = { NVEC_BAT, SLOT_STATUS }; |
113 | |
114 | for (i = 0; i < ARRAY_SIZE(bat_init); i++) { |
115 | buf[1] = bat_init[i]; |
116 | nvec_write_async(nvec: power->nvec, data: buf, size: 2); |
117 | } |
118 | } |
119 | |
120 | static int nvec_power_bat_notifier(struct notifier_block *nb, |
121 | unsigned long event_type, void *data) |
122 | { |
123 | struct nvec_power *power = |
124 | container_of(nb, struct nvec_power, notifier); |
125 | struct bat_response *res = data; |
126 | int status_changed = 0; |
127 | |
128 | if (event_type != NVEC_BAT) |
129 | return NOTIFY_DONE; |
130 | |
131 | switch (res->sub_type) { |
132 | case SLOT_STATUS: |
133 | if (res->plc[0] & 1) { |
134 | if (power->bat_present == 0) { |
135 | status_changed = 1; |
136 | get_bat_mfg_data(power); |
137 | } |
138 | |
139 | power->bat_present = 1; |
140 | |
141 | switch ((res->plc[0] >> 1) & 3) { |
142 | case 0: |
143 | power->bat_status = |
144 | POWER_SUPPLY_STATUS_NOT_CHARGING; |
145 | break; |
146 | case 1: |
147 | power->bat_status = |
148 | POWER_SUPPLY_STATUS_CHARGING; |
149 | break; |
150 | case 2: |
151 | power->bat_status = |
152 | POWER_SUPPLY_STATUS_DISCHARGING; |
153 | break; |
154 | default: |
155 | power->bat_status = POWER_SUPPLY_STATUS_UNKNOWN; |
156 | } |
157 | } else { |
158 | if (power->bat_present == 1) |
159 | status_changed = 1; |
160 | |
161 | power->bat_present = 0; |
162 | power->bat_status = POWER_SUPPLY_STATUS_UNKNOWN; |
163 | } |
164 | power->bat_cap = res->plc[1]; |
165 | if (status_changed) |
166 | power_supply_changed(psy: nvec_bat_psy); |
167 | break; |
168 | case VOLTAGE: |
169 | power->bat_voltage_now = res->plu * 1000; |
170 | break; |
171 | case TIME_REMAINING: |
172 | power->time_remain = res->plu * 3600; |
173 | break; |
174 | case CURRENT: |
175 | power->bat_current_now = res->pls * 1000; |
176 | break; |
177 | case AVERAGE_CURRENT: |
178 | power->bat_current_avg = res->pls * 1000; |
179 | break; |
180 | case CAPACITY_REMAINING: |
181 | power->capacity_remain = res->plu * 1000; |
182 | break; |
183 | case LAST_FULL_CHARGE_CAPACITY: |
184 | power->charge_last_full = res->plu * 1000; |
185 | break; |
186 | case DESIGN_CAPACITY: |
187 | power->charge_full_design = res->plu * 1000; |
188 | break; |
189 | case CRITICAL_CAPACITY: |
190 | power->critical_capacity = res->plu * 1000; |
191 | break; |
192 | case TEMPERATURE: |
193 | power->bat_temperature = res->plu - 2732; |
194 | break; |
195 | case MANUFACTURER: |
196 | memcpy(power->bat_manu, &res->plc, res->length - 2); |
197 | power->bat_model[res->length - 2] = '\0'; |
198 | break; |
199 | case MODEL: |
200 | memcpy(power->bat_model, &res->plc, res->length - 2); |
201 | power->bat_model[res->length - 2] = '\0'; |
202 | break; |
203 | case TYPE: |
204 | memcpy(power->bat_type, &res->plc, res->length - 2); |
205 | power->bat_type[res->length - 2] = '\0'; |
206 | /* |
207 | * This differs a little from the spec fill in more if you find |
208 | * some. |
209 | */ |
210 | if (!strncmp(power->bat_type, "Li" , 30)) |
211 | power->bat_type_enum = POWER_SUPPLY_TECHNOLOGY_LION; |
212 | else |
213 | power->bat_type_enum = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; |
214 | break; |
215 | default: |
216 | return NOTIFY_STOP; |
217 | } |
218 | |
219 | return NOTIFY_STOP; |
220 | } |
221 | |
222 | static int nvec_power_get_property(struct power_supply *psy, |
223 | enum power_supply_property psp, |
224 | union power_supply_propval *val) |
225 | { |
226 | struct nvec_power *power = dev_get_drvdata(dev: psy->dev.parent); |
227 | |
228 | switch (psp) { |
229 | case POWER_SUPPLY_PROP_ONLINE: |
230 | val->intval = power->on; |
231 | break; |
232 | default: |
233 | return -EINVAL; |
234 | } |
235 | return 0; |
236 | } |
237 | |
238 | static int nvec_battery_get_property(struct power_supply *psy, |
239 | enum power_supply_property psp, |
240 | union power_supply_propval *val) |
241 | { |
242 | struct nvec_power *power = dev_get_drvdata(dev: psy->dev.parent); |
243 | |
244 | switch (psp) { |
245 | case POWER_SUPPLY_PROP_STATUS: |
246 | val->intval = power->bat_status; |
247 | break; |
248 | case POWER_SUPPLY_PROP_CAPACITY: |
249 | val->intval = power->bat_cap; |
250 | break; |
251 | case POWER_SUPPLY_PROP_PRESENT: |
252 | val->intval = power->bat_present; |
253 | break; |
254 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
255 | val->intval = power->bat_voltage_now; |
256 | break; |
257 | case POWER_SUPPLY_PROP_CURRENT_NOW: |
258 | val->intval = power->bat_current_now; |
259 | break; |
260 | case POWER_SUPPLY_PROP_CURRENT_AVG: |
261 | val->intval = power->bat_current_avg; |
262 | break; |
263 | case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: |
264 | val->intval = power->time_remain; |
265 | break; |
266 | case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: |
267 | val->intval = power->charge_full_design; |
268 | break; |
269 | case POWER_SUPPLY_PROP_CHARGE_FULL: |
270 | val->intval = power->charge_last_full; |
271 | break; |
272 | case POWER_SUPPLY_PROP_CHARGE_EMPTY: |
273 | val->intval = power->critical_capacity; |
274 | break; |
275 | case POWER_SUPPLY_PROP_CHARGE_NOW: |
276 | val->intval = power->capacity_remain; |
277 | break; |
278 | case POWER_SUPPLY_PROP_TEMP: |
279 | val->intval = power->bat_temperature; |
280 | break; |
281 | case POWER_SUPPLY_PROP_MANUFACTURER: |
282 | val->strval = power->bat_manu; |
283 | break; |
284 | case POWER_SUPPLY_PROP_MODEL_NAME: |
285 | val->strval = power->bat_model; |
286 | break; |
287 | case POWER_SUPPLY_PROP_TECHNOLOGY: |
288 | val->intval = power->bat_type_enum; |
289 | break; |
290 | default: |
291 | return -EINVAL; |
292 | } |
293 | return 0; |
294 | } |
295 | |
296 | static enum power_supply_property nvec_power_props[] = { |
297 | POWER_SUPPLY_PROP_ONLINE, |
298 | }; |
299 | |
300 | static enum power_supply_property nvec_battery_props[] = { |
301 | POWER_SUPPLY_PROP_STATUS, |
302 | POWER_SUPPLY_PROP_PRESENT, |
303 | POWER_SUPPLY_PROP_CAPACITY, |
304 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
305 | POWER_SUPPLY_PROP_CURRENT_NOW, |
306 | #ifdef EC_FULL_DIAG |
307 | POWER_SUPPLY_PROP_CURRENT_AVG, |
308 | POWER_SUPPLY_PROP_TEMP, |
309 | POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, |
310 | #endif |
311 | POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, |
312 | POWER_SUPPLY_PROP_CHARGE_FULL, |
313 | POWER_SUPPLY_PROP_CHARGE_EMPTY, |
314 | POWER_SUPPLY_PROP_CHARGE_NOW, |
315 | POWER_SUPPLY_PROP_MANUFACTURER, |
316 | POWER_SUPPLY_PROP_MODEL_NAME, |
317 | POWER_SUPPLY_PROP_TECHNOLOGY, |
318 | }; |
319 | |
320 | static char *nvec_power_supplied_to[] = { |
321 | "battery" , |
322 | }; |
323 | |
324 | static const struct power_supply_desc nvec_bat_psy_desc = { |
325 | .name = "battery" , |
326 | .type = POWER_SUPPLY_TYPE_BATTERY, |
327 | .properties = nvec_battery_props, |
328 | .num_properties = ARRAY_SIZE(nvec_battery_props), |
329 | .get_property = nvec_battery_get_property, |
330 | }; |
331 | |
332 | static const struct power_supply_desc nvec_psy_desc = { |
333 | .name = "ac" , |
334 | .type = POWER_SUPPLY_TYPE_MAINS, |
335 | .properties = nvec_power_props, |
336 | .num_properties = ARRAY_SIZE(nvec_power_props), |
337 | .get_property = nvec_power_get_property, |
338 | }; |
339 | |
340 | static int counter; |
341 | static const int bat_iter[] = { |
342 | SLOT_STATUS, VOLTAGE, CURRENT, CAPACITY_REMAINING, |
343 | #ifdef EC_FULL_DIAG |
344 | AVERAGE_CURRENT, TEMPERATURE, TIME_REMAINING, |
345 | #endif |
346 | }; |
347 | |
348 | static void nvec_power_poll(struct work_struct *work) |
349 | { |
350 | char buf[] = { NVEC_SYS, GET_SYSTEM_STATUS }; |
351 | struct nvec_power *power = container_of(work, struct nvec_power, |
352 | poller.work); |
353 | |
354 | if (counter >= ARRAY_SIZE(bat_iter)) |
355 | counter = 0; |
356 | |
357 | /* AC status via sys req */ |
358 | nvec_write_async(nvec: power->nvec, data: buf, size: 2); |
359 | msleep(msecs: 100); |
360 | |
361 | /* |
362 | * Select a battery request function via round robin doing it all at |
363 | * once seems to overload the power supply. |
364 | */ |
365 | buf[0] = NVEC_BAT; |
366 | buf[1] = bat_iter[counter++]; |
367 | nvec_write_async(nvec: power->nvec, data: buf, size: 2); |
368 | |
369 | schedule_delayed_work(dwork: to_delayed_work(work), delay: msecs_to_jiffies(m: 5000)); |
370 | }; |
371 | |
372 | static int nvec_power_probe(struct platform_device *pdev) |
373 | { |
374 | struct power_supply **psy; |
375 | const struct power_supply_desc *psy_desc; |
376 | struct nvec_power *power; |
377 | struct nvec_chip *nvec = dev_get_drvdata(dev: pdev->dev.parent); |
378 | struct power_supply_config psy_cfg = {}; |
379 | |
380 | power = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct nvec_power), GFP_NOWAIT); |
381 | if (!power) |
382 | return -ENOMEM; |
383 | |
384 | dev_set_drvdata(dev: &pdev->dev, data: power); |
385 | power->nvec = nvec; |
386 | |
387 | switch (pdev->id) { |
388 | case AC: |
389 | psy = &nvec_psy; |
390 | psy_desc = &nvec_psy_desc; |
391 | psy_cfg.supplied_to = nvec_power_supplied_to; |
392 | psy_cfg.num_supplicants = ARRAY_SIZE(nvec_power_supplied_to); |
393 | |
394 | power->notifier.notifier_call = nvec_power_notifier; |
395 | |
396 | INIT_DELAYED_WORK(&power->poller, nvec_power_poll); |
397 | schedule_delayed_work(dwork: &power->poller, delay: msecs_to_jiffies(m: 5000)); |
398 | break; |
399 | case BAT: |
400 | psy = &nvec_bat_psy; |
401 | psy_desc = &nvec_bat_psy_desc; |
402 | |
403 | power->notifier.notifier_call = nvec_power_bat_notifier; |
404 | break; |
405 | default: |
406 | return -ENODEV; |
407 | } |
408 | |
409 | nvec_register_notifier(nvec, nb: &power->notifier, events: NVEC_SYS); |
410 | |
411 | if (pdev->id == BAT) |
412 | get_bat_mfg_data(power); |
413 | |
414 | *psy = power_supply_register(parent: &pdev->dev, desc: psy_desc, cfg: &psy_cfg); |
415 | |
416 | return PTR_ERR_OR_ZERO(ptr: *psy); |
417 | } |
418 | |
419 | static void nvec_power_remove(struct platform_device *pdev) |
420 | { |
421 | struct nvec_power *power = platform_get_drvdata(pdev); |
422 | |
423 | cancel_delayed_work_sync(dwork: &power->poller); |
424 | nvec_unregister_notifier(dev: power->nvec, nb: &power->notifier); |
425 | switch (pdev->id) { |
426 | case AC: |
427 | power_supply_unregister(psy: nvec_psy); |
428 | break; |
429 | case BAT: |
430 | power_supply_unregister(psy: nvec_bat_psy); |
431 | } |
432 | } |
433 | |
434 | static struct platform_driver nvec_power_driver = { |
435 | .probe = nvec_power_probe, |
436 | .remove_new = nvec_power_remove, |
437 | .driver = { |
438 | .name = "nvec-power" , |
439 | } |
440 | }; |
441 | |
442 | module_platform_driver(nvec_power_driver); |
443 | |
444 | MODULE_AUTHOR("Ilya Petrov <ilya.muromec@gmail.com>" ); |
445 | MODULE_LICENSE("GPL" ); |
446 | MODULE_DESCRIPTION("NVEC battery and AC driver" ); |
447 | MODULE_ALIAS("platform:nvec-power" ); |
448 | |