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
yoga_c630_psy_update_bat_info(struct yoga_c630_psy * ecbat)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
yoga_c630_psy_maybe_update_bat_status(struct yoga_c630_psy * ecbat)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
yoga_c630_psy_update_adapter_status(struct yoga_c630_psy * ecbat)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
yoga_c630_psy_is_charged(struct yoga_c630_psy * ecbat)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
yoga_c630_psy_bat_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)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
yoga_c630_psy_adpt_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)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
yoga_c630_psy_register_bat_psy(struct yoga_c630_psy * ecbat)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 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
yoga_c630_ec_refresh_bat_info(struct yoga_c630_psy * ecbat)385 static 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
yoga_c630_psy_notify(struct notifier_block * nb,unsigned long action,void * data)401 static 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
yoga_c630_psy_probe(struct auxiliary_device * adev,const struct auxiliary_device_id * id)421 static 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
469 err_unreg_bat:
470 power_supply_unregister(ecbat->bat_psy);
471 return ret;
472 }
473
yoga_c630_psy_remove(struct auxiliary_device * adev)474 static 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
482 static const struct auxiliary_device_id yoga_c630_psy_id_table[] = {
483 { .name = YOGA_C630_MOD_NAME "." YOGA_C630_DEV_PSY, },
484 {}
485 };
486 MODULE_DEVICE_TABLE(auxiliary, yoga_c630_psy_id_table);
487
488 static 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
495 module_auxiliary_driver(yoga_c630_psy_driver);
496
497 MODULE_DESCRIPTION("Lenovo Yoga C630 psy");
498 MODULE_LICENSE("GPL");
499