File Index
Files and directories
Sign in
FILE

lenovo_yoga_c630_battery.c

File preview with syntax highlighting.

linux-6.19.14/drivers/power/supply/lenovo_yoga_c630_battery.c C
  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
 19struct 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
 71static 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
128static 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
176static 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
192static 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
206static 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
278static 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
294static 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
310static 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
318static 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
326static 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
351static enum power_supply_property yoga_c630_psy_adpt_properties[] = {
352	POWER_SUPPLY_PROP_ONLINE,
353	POWER_SUPPLY_PROP_USB_TYPE,
354};
355
356static 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
365static 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	bat_cfg.no_wakeup_source = true;
372	ecbat->bat_psy = power_supply_register(ecbat->dev,
373					       ecbat->unit_mA ?
374					       &yoga_c630_psy_bat_psy_desc_mA :
375					       &yoga_c630_psy_bat_psy_desc_mWh,
376					       &bat_cfg);
377	if (IS_ERR(ecbat->bat_psy)) {
378		dev_err(ecbat->dev, "failed to register battery supply\n");
379		return PTR_ERR(ecbat->bat_psy);
380	}
381
382	return 0;
383}
384
385static void yoga_c630_ec_refresh_bat_info(struct yoga_c630_psy *ecbat)
386{
387	bool current_unit;
388
389	guard(mutex)(&ecbat->lock);
390
391	current_unit = ecbat->unit_mA;
392
393	yoga_c630_psy_update_bat_info(ecbat);
394
395	if (current_unit != ecbat->unit_mA) {
396		power_supply_unregister(ecbat->bat_psy);
397		yoga_c630_psy_register_bat_psy(ecbat);
398	}
399}
400
401static int yoga_c630_psy_notify(struct notifier_block *nb,
402				unsigned long action, void *data)
403{
404	struct yoga_c630_psy *ecbat = container_of(nb, struct yoga_c630_psy, nb);
405
406	switch (action) {
407	case LENOVO_EC_EVENT_BAT_INFO:
408		yoga_c630_ec_refresh_bat_info(ecbat);
409		break;
410	case LENOVO_EC_EVENT_BAT_ADPT_STATUS:
411		power_supply_changed(ecbat->adp_psy);
412		fallthrough;
413	case LENOVO_EC_EVENT_BAT_STATUS:
414		power_supply_changed(ecbat->bat_psy);
415		break;
416	}
417
418	return NOTIFY_OK;
419}
420
421static int yoga_c630_psy_probe(struct auxiliary_device *adev,
422				   const struct auxiliary_device_id *id)
423{
424	struct yoga_c630_ec *ec = adev->dev.platform_data;
425	struct power_supply_config adp_cfg = {};
426	struct device *dev = &adev->dev;
427	struct yoga_c630_psy *ecbat;
428	int ret;
429
430	ecbat = devm_kzalloc(&adev->dev, sizeof(*ecbat), GFP_KERNEL);
431	if (!ecbat)
432		return -ENOMEM;
433
434	ecbat->ec = ec;
435	ecbat->dev = dev;
436	mutex_init(&ecbat->lock);
437	ecbat->fwnode = adev->dev.parent->fwnode;
438	ecbat->nb.notifier_call = yoga_c630_psy_notify;
439
440	auxiliary_set_drvdata(adev, ecbat);
441
442	adp_cfg.drv_data = ecbat;
443	adp_cfg.fwnode = ecbat->fwnode;
444	adp_cfg.supplied_to = (char **)&yoga_c630_psy_bat_psy_desc_mA.name;
445	adp_cfg.num_supplicants = 1;
446	adp_cfg.no_wakeup_source = true;
447	ecbat->adp_psy = devm_power_supply_register(dev, &yoga_c630_psy_adpt_psy_desc, &adp_cfg);
448	if (IS_ERR(ecbat->adp_psy)) {
449		dev_err(dev, "failed to register AC adapter supply\n");
450		return PTR_ERR(ecbat->adp_psy);
451	}
452
453	scoped_guard(mutex, &ecbat->lock) {
454		ret = yoga_c630_psy_update_bat_info(ecbat);
455		if (ret)
456			goto err_unreg_bat;
457
458		ret = yoga_c630_psy_register_bat_psy(ecbat);
459		if (ret)
460			goto err_unreg_bat;
461	}
462
463	ret = yoga_c630_ec_register_notify(ecbat->ec, &ecbat->nb);
464	if (ret)
465		goto err_unreg_bat;
466
467	return 0;
468
469err_unreg_bat:
470	power_supply_unregister(ecbat->bat_psy);
471	return ret;
472}
473
474static void yoga_c630_psy_remove(struct auxiliary_device *adev)
475{
476	struct yoga_c630_psy *ecbat = auxiliary_get_drvdata(adev);
477
478	yoga_c630_ec_unregister_notify(ecbat->ec, &ecbat->nb);
479	power_supply_unregister(ecbat->bat_psy);
480}
481
482static const struct auxiliary_device_id yoga_c630_psy_id_table[] = {
483	{ .name = YOGA_C630_MOD_NAME "." YOGA_C630_DEV_PSY, },
484	{}
485};
486MODULE_DEVICE_TABLE(auxiliary, yoga_c630_psy_id_table);
487
488static struct auxiliary_driver yoga_c630_psy_driver = {
489	.name = YOGA_C630_DEV_PSY,
490	.id_table = yoga_c630_psy_id_table,
491	.probe = yoga_c630_psy_probe,
492	.remove = yoga_c630_psy_remove,
493};
494
495module_auxiliary_driver(yoga_c630_psy_driver);
496
497MODULE_DESCRIPTION("Lenovo Yoga C630 psy");
498MODULE_LICENSE("GPL");
~/ Read-only file preview.