xref: /linux/drivers/power/supply/lenovo_yoga_c630_battery.c (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (c) 2022-2024, Linaro Ltd
4  * Authors:
5  *    Bjorn Andersson
6  *    Dmitry Baryshkov
7  */
8 #include <linux/auxiliary_bus.h>
9 #include <linux/bits.h>
10 #include <linux/cleanup.h>
11 #include <linux/delay.h>
12 #include <linux/jiffies.h>
13 #include <linux/module.h>
14 #include <linux/mutex.h>
15 #include <linux/notifier.h>
16 #include <linux/power_supply.h>
17 #include <linux/platform_data/lenovo-yoga-c630.h>
18 
19 struct yoga_c630_psy {
20 	struct yoga_c630_ec *ec;
21 	struct device *dev;
22 	struct fwnode_handle *fwnode;
23 	struct notifier_block nb;
24 
25 	/* guards all battery properties and registration of power supplies */
26 	struct mutex lock;
27 
28 	struct power_supply *adp_psy;
29 	struct power_supply *bat_psy;
30 
31 	unsigned long last_status_update;
32 
33 	bool adapter_online;
34 
35 	bool unit_mA;
36 
37 	bool bat_present;
38 	unsigned int bat_status;
39 	unsigned int design_capacity;
40 	unsigned int design_voltage;
41 	unsigned int full_charge_capacity;
42 
43 	unsigned int capacity_now;
44 	unsigned int voltage_now;
45 
46 	int current_now;
47 	int rate_now;
48 };
49 
50 #define LENOVO_EC_CACHE_TIME		(10 * HZ)
51 
52 #define LENOVO_EC_ADPT_STATUS		0xa3
53 #define LENOVO_EC_ADPT_STATUS_PRESENT		BIT(7)
54 #define LENOVO_EC_BAT_ATTRIBUTES	0xc0
55 #define LENOVO_EC_BAT_ATTRIBUTES_UNIT_IS_MA	BIT(1)
56 #define LENOVO_EC_BAT_STATUS		0xc1
57 #define LENOVO_EC_BAT_STATUS_DISCHARGING	BIT(0)
58 #define LENOVO_EC_BAT_STATUS_CHARGING		BIT(1)
59 #define LENOVO_EC_BAT_REMAIN_CAPACITY	0xc2
60 #define LENOVO_EC_BAT_VOLTAGE		0xc6
61 #define LENOVO_EC_BAT_DESIGN_VOLTAGE	0xc8
62 #define LENOVO_EC_BAT_DESIGN_CAPACITY	0xca
63 #define LENOVO_EC_BAT_FULL_CAPACITY	0xcc
64 #define LENOVO_EC_BAT_CURRENT		0xd2
65 #define LENOVO_EC_BAT_FULL_FACTORY	0xd6
66 #define LENOVO_EC_BAT_PRESENT		0xda
67 #define LENOVO_EC_BAT_PRESENT_IS_PRESENT	BIT(0)
68 #define LENOVO_EC_BAT_FULL_REGISTER	0xdb
69 #define LENOVO_EC_BAT_FULL_REGISTER_IS_FACTORY	BIT(0)
70 
71 static int yoga_c630_psy_update_bat_info(struct yoga_c630_psy *ecbat)
72 {
73 	struct yoga_c630_ec *ec = ecbat->ec;
74 	int val;
75 
76 	lockdep_assert_held(&ecbat->lock);
77 
78 	val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_PRESENT);
79 	if (val < 0)
80 		return val;
81 	ecbat->bat_present = !!(val & LENOVO_EC_BAT_PRESENT_IS_PRESENT);
82 	if (!ecbat->bat_present)
83 		return val;
84 
85 	val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_ATTRIBUTES);
86 	if (val < 0)
87 		return val;
88 	ecbat->unit_mA = val & LENOVO_EC_BAT_ATTRIBUTES_UNIT_IS_MA;
89 
90 	val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_DESIGN_CAPACITY);
91 	if (val < 0)
92 		return val;
93 	ecbat->design_capacity = val * 1000;
94 
95 	/*
96 	 * DSDT has delays after most of EC reads in these methods.
97 	 * Having no documentation for the EC we have to follow and sleep here.
98 	 */
99 	msleep(50);
100 
101 	val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_DESIGN_VOLTAGE);
102 	if (val < 0)
103 		return val;
104 	ecbat->design_voltage = val;
105 
106 	msleep(50);
107 
108 	val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_FULL_REGISTER);
109 	if (val < 0)
110 		return val;
111 	val = yoga_c630_ec_read16(ec,
112 				  val & LENOVO_EC_BAT_FULL_REGISTER_IS_FACTORY ?
113 				  LENOVO_EC_BAT_FULL_FACTORY :
114 				  LENOVO_EC_BAT_FULL_CAPACITY);
115 	if (val < 0)
116 		return val;
117 
118 	ecbat->full_charge_capacity = val * 1000;
119 
120 	if (!ecbat->unit_mA) {
121 		ecbat->design_capacity *= 10;
122 		ecbat->full_charge_capacity *= 10;
123 	}
124 
125 	return 0;
126 }
127 
128 static int yoga_c630_psy_maybe_update_bat_status(struct yoga_c630_psy *ecbat)
129 {
130 	struct yoga_c630_ec *ec = ecbat->ec;
131 	int current_mA;
132 	int val;
133 
134 	guard(mutex)(&ecbat->lock);
135 	if (time_before(jiffies, ecbat->last_status_update + LENOVO_EC_CACHE_TIME))
136 		return 0;
137 
138 	val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_STATUS);
139 	if (val < 0)
140 		return val;
141 	ecbat->bat_status = val;
142 
143 	msleep(50);
144 
145 	val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_REMAIN_CAPACITY);
146 	if (val < 0)
147 		return val;
148 	ecbat->capacity_now = val * 1000;
149 
150 	msleep(50);
151 
152 	val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_VOLTAGE);
153 	if (val < 0)
154 		return val;
155 	ecbat->voltage_now = val * 1000;
156 
157 	msleep(50);
158 
159 	val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_CURRENT);
160 	if (val < 0)
161 		return val;
162 	current_mA = sign_extend32(val, 15);
163 	ecbat->current_now = current_mA * 1000;
164 	ecbat->rate_now = current_mA * (ecbat->voltage_now / 1000);
165 
166 	msleep(50);
167 
168 	if (!ecbat->unit_mA)
169 		ecbat->capacity_now *= 10;
170 
171 	ecbat->last_status_update = jiffies;
172 
173 	return 0;
174 }
175 
176 static int yoga_c630_psy_update_adapter_status(struct yoga_c630_psy *ecbat)
177 {
178 	struct yoga_c630_ec *ec = ecbat->ec;
179 	int val;
180 
181 	guard(mutex)(&ecbat->lock);
182 
183 	val = yoga_c630_ec_read8(ec, LENOVO_EC_ADPT_STATUS);
184 	if (val < 0)
185 		return val;
186 
187 	ecbat->adapter_online = !!(val & LENOVO_EC_ADPT_STATUS_PRESENT);
188 
189 	return 0;
190 }
191 
192 static bool yoga_c630_psy_is_charged(struct yoga_c630_psy *ecbat)
193 {
194 	if (ecbat->bat_status != 0)
195 		return false;
196 
197 	if (ecbat->full_charge_capacity <= ecbat->capacity_now)
198 		return true;
199 
200 	if (ecbat->design_capacity <= ecbat->capacity_now)
201 		return true;
202 
203 	return false;
204 }
205 
206 static int yoga_c630_psy_bat_get_property(struct power_supply *psy,
207 					 enum power_supply_property psp,
208 					 union power_supply_propval *val)
209 {
210 	struct yoga_c630_psy *ecbat = power_supply_get_drvdata(psy);
211 	int rc = 0;
212 
213 	if (!ecbat->bat_present && psp != POWER_SUPPLY_PROP_PRESENT)
214 		return -ENODEV;
215 
216 	rc = yoga_c630_psy_maybe_update_bat_status(ecbat);
217 	if (rc)
218 		return rc;
219 
220 	switch (psp) {
221 	case POWER_SUPPLY_PROP_STATUS:
222 		if (ecbat->bat_status & LENOVO_EC_BAT_STATUS_DISCHARGING)
223 			val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
224 		else if (ecbat->bat_status & LENOVO_EC_BAT_STATUS_CHARGING)
225 			val->intval = POWER_SUPPLY_STATUS_CHARGING;
226 		else if (yoga_c630_psy_is_charged(ecbat))
227 			val->intval = POWER_SUPPLY_STATUS_FULL;
228 		else
229 			val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
230 		break;
231 	case POWER_SUPPLY_PROP_PRESENT:
232 		val->intval = ecbat->bat_present;
233 		break;
234 	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
235 		val->intval = ecbat->design_voltage;
236 		break;
237 	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
238 	case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
239 		val->intval = ecbat->design_capacity;
240 		break;
241 	case POWER_SUPPLY_PROP_CHARGE_FULL:
242 	case POWER_SUPPLY_PROP_ENERGY_FULL:
243 		val->intval = ecbat->full_charge_capacity;
244 		break;
245 	case POWER_SUPPLY_PROP_CHARGE_NOW:
246 	case POWER_SUPPLY_PROP_ENERGY_NOW:
247 		val->intval = ecbat->capacity_now;
248 		break;
249 	case POWER_SUPPLY_PROP_CURRENT_NOW:
250 		val->intval = ecbat->current_now;
251 		break;
252 	case POWER_SUPPLY_PROP_POWER_NOW:
253 		val->intval = ecbat->rate_now;
254 		break;
255 	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
256 		val->intval = ecbat->voltage_now;
257 		break;
258 	case POWER_SUPPLY_PROP_TECHNOLOGY:
259 		val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
260 		break;
261 	case POWER_SUPPLY_PROP_MODEL_NAME:
262 		val->strval = "PABAS0241231";
263 		break;
264 	case POWER_SUPPLY_PROP_MANUFACTURER:
265 		val->strval = "Compal";
266 		break;
267 	case POWER_SUPPLY_PROP_SCOPE:
268 		val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
269 		break;
270 	default:
271 		rc = -EINVAL;
272 		break;
273 	}
274 
275 	return rc;
276 }
277 
278 static enum power_supply_property yoga_c630_psy_bat_mA_properties[] = {
279 	POWER_SUPPLY_PROP_STATUS,
280 	POWER_SUPPLY_PROP_PRESENT,
281 	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
282 	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
283 	POWER_SUPPLY_PROP_CHARGE_FULL,
284 	POWER_SUPPLY_PROP_CHARGE_NOW,
285 	POWER_SUPPLY_PROP_CURRENT_NOW,
286 	POWER_SUPPLY_PROP_POWER_NOW,
287 	POWER_SUPPLY_PROP_VOLTAGE_NOW,
288 	POWER_SUPPLY_PROP_TECHNOLOGY,
289 	POWER_SUPPLY_PROP_MODEL_NAME,
290 	POWER_SUPPLY_PROP_MANUFACTURER,
291 	POWER_SUPPLY_PROP_SCOPE,
292 };
293 
294 static enum power_supply_property yoga_c630_psy_bat_mWh_properties[] = {
295 	POWER_SUPPLY_PROP_STATUS,
296 	POWER_SUPPLY_PROP_PRESENT,
297 	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
298 	POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
299 	POWER_SUPPLY_PROP_ENERGY_FULL,
300 	POWER_SUPPLY_PROP_ENERGY_NOW,
301 	POWER_SUPPLY_PROP_CURRENT_NOW,
302 	POWER_SUPPLY_PROP_POWER_NOW,
303 	POWER_SUPPLY_PROP_VOLTAGE_NOW,
304 	POWER_SUPPLY_PROP_TECHNOLOGY,
305 	POWER_SUPPLY_PROP_MODEL_NAME,
306 	POWER_SUPPLY_PROP_MANUFACTURER,
307 	POWER_SUPPLY_PROP_SCOPE,
308 };
309 
310 static const struct power_supply_desc yoga_c630_psy_bat_psy_desc_mA = {
311 	.name = "yoga-c630-battery",
312 	.type = POWER_SUPPLY_TYPE_BATTERY,
313 	.properties = yoga_c630_psy_bat_mA_properties,
314 	.num_properties = ARRAY_SIZE(yoga_c630_psy_bat_mA_properties),
315 	.get_property = yoga_c630_psy_bat_get_property,
316 };
317 
318 static const struct power_supply_desc yoga_c630_psy_bat_psy_desc_mWh = {
319 	.name = "yoga-c630-battery",
320 	.type = POWER_SUPPLY_TYPE_BATTERY,
321 	.properties = yoga_c630_psy_bat_mWh_properties,
322 	.num_properties = ARRAY_SIZE(yoga_c630_psy_bat_mWh_properties),
323 	.get_property = yoga_c630_psy_bat_get_property,
324 };
325 
326 static int yoga_c630_psy_adpt_get_property(struct power_supply *psy,
327 					  enum power_supply_property psp,
328 					  union power_supply_propval *val)
329 {
330 	struct yoga_c630_psy *ecbat = power_supply_get_drvdata(psy);
331 	int ret = 0;
332 
333 	ret = yoga_c630_psy_update_adapter_status(ecbat);
334 	if (ret < 0)
335 		return ret;
336 
337 	switch (psp) {
338 	case POWER_SUPPLY_PROP_ONLINE:
339 		val->intval = ecbat->adapter_online;
340 		break;
341 	case POWER_SUPPLY_PROP_USB_TYPE:
342 		val->intval = POWER_SUPPLY_USB_TYPE_C;
343 		break;
344 	default:
345 		return -EINVAL;
346 	}
347 
348 	return 0;
349 }
350 
351 static enum power_supply_property yoga_c630_psy_adpt_properties[] = {
352 	POWER_SUPPLY_PROP_ONLINE,
353 	POWER_SUPPLY_PROP_USB_TYPE,
354 };
355 
356 static const struct power_supply_desc yoga_c630_psy_adpt_psy_desc = {
357 	.name = "yoga-c630-adapter",
358 	.type = POWER_SUPPLY_TYPE_USB,
359 	.usb_types = BIT(POWER_SUPPLY_USB_TYPE_C),
360 	.properties = yoga_c630_psy_adpt_properties,
361 	.num_properties = ARRAY_SIZE(yoga_c630_psy_adpt_properties),
362 	.get_property = yoga_c630_psy_adpt_get_property,
363 };
364 
365 static int yoga_c630_psy_register_bat_psy(struct yoga_c630_psy *ecbat)
366 {
367 	struct power_supply_config bat_cfg = {};
368 
369 	bat_cfg.drv_data = ecbat;
370 	bat_cfg.fwnode = ecbat->fwnode;
371 	ecbat->bat_psy = power_supply_register_no_ws(ecbat->dev,
372 						     ecbat->unit_mA ?
373 						     &yoga_c630_psy_bat_psy_desc_mA :
374 						     &yoga_c630_psy_bat_psy_desc_mWh,
375 						     &bat_cfg);
376 	if (IS_ERR(ecbat->bat_psy)) {
377 		dev_err(ecbat->dev, "failed to register battery supply\n");
378 		return PTR_ERR(ecbat->bat_psy);
379 	}
380 
381 	return 0;
382 }
383 
384 static void yoga_c630_ec_refresh_bat_info(struct yoga_c630_psy *ecbat)
385 {
386 	bool current_unit;
387 
388 	guard(mutex)(&ecbat->lock);
389 
390 	current_unit = ecbat->unit_mA;
391 
392 	yoga_c630_psy_update_bat_info(ecbat);
393 
394 	if (current_unit != ecbat->unit_mA) {
395 		power_supply_unregister(ecbat->bat_psy);
396 		yoga_c630_psy_register_bat_psy(ecbat);
397 	}
398 }
399 
400 static int yoga_c630_psy_notify(struct notifier_block *nb,
401 				unsigned long action, void *data)
402 {
403 	struct yoga_c630_psy *ecbat = container_of(nb, struct yoga_c630_psy, nb);
404 
405 	switch (action) {
406 	case LENOVO_EC_EVENT_BAT_INFO:
407 		yoga_c630_ec_refresh_bat_info(ecbat);
408 		break;
409 	case LENOVO_EC_EVENT_BAT_ADPT_STATUS:
410 		power_supply_changed(ecbat->adp_psy);
411 		fallthrough;
412 	case LENOVO_EC_EVENT_BAT_STATUS:
413 		power_supply_changed(ecbat->bat_psy);
414 		break;
415 	}
416 
417 	return NOTIFY_OK;
418 }
419 
420 static int yoga_c630_psy_probe(struct auxiliary_device *adev,
421 				   const struct auxiliary_device_id *id)
422 {
423 	struct yoga_c630_ec *ec = adev->dev.platform_data;
424 	struct power_supply_config adp_cfg = {};
425 	struct device *dev = &adev->dev;
426 	struct yoga_c630_psy *ecbat;
427 	int ret;
428 
429 	ecbat = devm_kzalloc(&adev->dev, sizeof(*ecbat), GFP_KERNEL);
430 	if (!ecbat)
431 		return -ENOMEM;
432 
433 	ecbat->ec = ec;
434 	ecbat->dev = dev;
435 	mutex_init(&ecbat->lock);
436 	ecbat->fwnode = adev->dev.parent->fwnode;
437 	ecbat->nb.notifier_call = yoga_c630_psy_notify;
438 
439 	auxiliary_set_drvdata(adev, ecbat);
440 
441 	adp_cfg.drv_data = ecbat;
442 	adp_cfg.fwnode = ecbat->fwnode;
443 	adp_cfg.supplied_to = (char **)&yoga_c630_psy_bat_psy_desc_mA.name;
444 	adp_cfg.num_supplicants = 1;
445 	ecbat->adp_psy = devm_power_supply_register_no_ws(dev, &yoga_c630_psy_adpt_psy_desc, &adp_cfg);
446 	if (IS_ERR(ecbat->adp_psy)) {
447 		dev_err(dev, "failed to register AC adapter supply\n");
448 		return PTR_ERR(ecbat->adp_psy);
449 	}
450 
451 	scoped_guard(mutex, &ecbat->lock) {
452 		ret = yoga_c630_psy_update_bat_info(ecbat);
453 		if (ret)
454 			goto err_unreg_bat;
455 
456 		ret = yoga_c630_psy_register_bat_psy(ecbat);
457 		if (ret)
458 			goto err_unreg_bat;
459 	}
460 
461 	ret = yoga_c630_ec_register_notify(ecbat->ec, &ecbat->nb);
462 	if (ret)
463 		goto err_unreg_bat;
464 
465 	return 0;
466 
467 err_unreg_bat:
468 	power_supply_unregister(ecbat->bat_psy);
469 	return ret;
470 }
471 
472 static void yoga_c630_psy_remove(struct auxiliary_device *adev)
473 {
474 	struct yoga_c630_psy *ecbat = auxiliary_get_drvdata(adev);
475 
476 	yoga_c630_ec_unregister_notify(ecbat->ec, &ecbat->nb);
477 	power_supply_unregister(ecbat->bat_psy);
478 }
479 
480 static const struct auxiliary_device_id yoga_c630_psy_id_table[] = {
481 	{ .name = YOGA_C630_MOD_NAME "." YOGA_C630_DEV_PSY, },
482 	{}
483 };
484 MODULE_DEVICE_TABLE(auxiliary, yoga_c630_psy_id_table);
485 
486 static struct auxiliary_driver yoga_c630_psy_driver = {
487 	.name = YOGA_C630_DEV_PSY,
488 	.id_table = yoga_c630_psy_id_table,
489 	.probe = yoga_c630_psy_probe,
490 	.remove = yoga_c630_psy_remove,
491 };
492 
493 module_auxiliary_driver(yoga_c630_psy_driver);
494 
495 MODULE_DESCRIPTION("Lenovo Yoga C630 psy");
496 MODULE_LICENSE("GPL");
497