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
23struct 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
46enum {
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
63enum {
64	AC,
65	BAT,
66};
67
68struct 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
81static struct power_supply *nvec_bat_psy;
82static struct power_supply *nvec_psy;
83
84static 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(nvec_psy);
98		}
99		return NOTIFY_STOP;
100	}
101	return NOTIFY_OK;
102}
103
104static const int bat_init[] = {
105	LAST_FULL_CHARGE_CAPACITY, DESIGN_CAPACITY, CRITICAL_CAPACITY,
106	MANUFACTURER, MODEL, TYPE,
107};
108
109static 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(power->nvec, buf, 2);
117	}
118}
119
120static 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(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
222static 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(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
238static 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(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
296static enum power_supply_property nvec_power_props[] = {
297	POWER_SUPPLY_PROP_ONLINE,
298};
299
300static 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
320static char *nvec_power_supplied_to[] = {
321	"battery",
322};
323
324static 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
332static 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
340static int counter;
341static 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
348static 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(power->nvec, buf, 2);
359	msleep(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(power->nvec, buf, 2);
368
369	schedule_delayed_work(to_delayed_work(work), msecs_to_jiffies(5000));
370};
371
372static 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(pdev->dev.parent);
378	struct power_supply_config psy_cfg = {};
379
380	power = devm_kzalloc(&pdev->dev, sizeof(struct nvec_power), GFP_NOWAIT);
381	if (!power)
382		return -ENOMEM;
383
384	dev_set_drvdata(&pdev->dev, 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(&power->poller, msecs_to_jiffies(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, &power->notifier, NVEC_SYS);
410
411	if (pdev->id == BAT)
412		get_bat_mfg_data(power);
413
414	*psy = power_supply_register(&pdev->dev, psy_desc, &psy_cfg);
415
416	return PTR_ERR_OR_ZERO(*psy);
417}
418
419static void nvec_power_remove(struct platform_device *pdev)
420{
421	struct nvec_power *power = platform_get_drvdata(pdev);
422
423	cancel_delayed_work_sync(&power->poller);
424	nvec_unregister_notifier(power->nvec, &power->notifier);
425	switch (pdev->id) {
426	case AC:
427		power_supply_unregister(nvec_psy);
428		break;
429	case BAT:
430		power_supply_unregister(nvec_bat_psy);
431	}
432}
433
434static 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
442module_platform_driver(nvec_power_driver);
443
444MODULE_AUTHOR("Ilya Petrov <ilya.muromec@gmail.com>");
445MODULE_LICENSE("GPL");
446MODULE_DESCRIPTION("NVEC battery and AC driver");
447MODULE_ALIAS("platform:nvec-power");
448