Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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 enum power_supply_usb_type yoga_c630_psy_adpt_usb_type[] = {
357 POWER_SUPPLY_USB_TYPE_C,
358};
359
360static const struct power_supply_desc yoga_c630_psy_adpt_psy_desc = {
361 .name = "yoga-c630-adapter",
362 .type = POWER_SUPPLY_TYPE_USB,
363 .usb_types = yoga_c630_psy_adpt_usb_type,
364 .num_usb_types = ARRAY_SIZE(yoga_c630_psy_adpt_usb_type),
365 .properties = yoga_c630_psy_adpt_properties,
366 .num_properties = ARRAY_SIZE(yoga_c630_psy_adpt_properties),
367 .get_property = yoga_c630_psy_adpt_get_property,
368};
369
370static int yoga_c630_psy_register_bat_psy(struct yoga_c630_psy *ecbat)
371{
372 struct power_supply_config bat_cfg = {};
373
374 bat_cfg.drv_data = ecbat;
375 bat_cfg.fwnode = ecbat->fwnode;
376 ecbat->bat_psy = power_supply_register_no_ws(ecbat->dev,
377 ecbat->unit_mA ?
378 &yoga_c630_psy_bat_psy_desc_mA :
379 &yoga_c630_psy_bat_psy_desc_mWh,
380 &bat_cfg);
381 if (IS_ERR(ecbat->bat_psy)) {
382 dev_err(ecbat->dev, "failed to register battery supply\n");
383 return PTR_ERR(ecbat->bat_psy);
384 }
385
386 return 0;
387}
388
389static void yoga_c630_ec_refresh_bat_info(struct yoga_c630_psy *ecbat)
390{
391 bool current_unit;
392
393 guard(mutex)(&ecbat->lock);
394
395 current_unit = ecbat->unit_mA;
396
397 yoga_c630_psy_update_bat_info(ecbat);
398
399 if (current_unit != ecbat->unit_mA) {
400 power_supply_unregister(ecbat->bat_psy);
401 yoga_c630_psy_register_bat_psy(ecbat);
402 }
403}
404
405static int yoga_c630_psy_notify(struct notifier_block *nb,
406 unsigned long action, void *data)
407{
408 struct yoga_c630_psy *ecbat = container_of(nb, struct yoga_c630_psy, nb);
409
410 switch (action) {
411 case LENOVO_EC_EVENT_BAT_INFO:
412 yoga_c630_ec_refresh_bat_info(ecbat);
413 break;
414 case LENOVO_EC_EVENT_BAT_ADPT_STATUS:
415 power_supply_changed(ecbat->adp_psy);
416 fallthrough;
417 case LENOVO_EC_EVENT_BAT_STATUS:
418 power_supply_changed(ecbat->bat_psy);
419 break;
420 }
421
422 return NOTIFY_OK;
423}
424
425static int yoga_c630_psy_probe(struct auxiliary_device *adev,
426 const struct auxiliary_device_id *id)
427{
428 struct yoga_c630_ec *ec = adev->dev.platform_data;
429 struct power_supply_config adp_cfg = {};
430 struct device *dev = &adev->dev;
431 struct yoga_c630_psy *ecbat;
432 int ret;
433
434 ecbat = devm_kzalloc(&adev->dev, sizeof(*ecbat), GFP_KERNEL);
435 if (!ecbat)
436 return -ENOMEM;
437
438 ecbat->ec = ec;
439 ecbat->dev = dev;
440 mutex_init(&ecbat->lock);
441 ecbat->fwnode = adev->dev.parent->fwnode;
442 ecbat->nb.notifier_call = yoga_c630_psy_notify;
443
444 auxiliary_set_drvdata(adev, ecbat);
445
446 adp_cfg.drv_data = ecbat;
447 adp_cfg.fwnode = ecbat->fwnode;
448 adp_cfg.supplied_to = (char **)&yoga_c630_psy_bat_psy_desc_mA.name;
449 adp_cfg.num_supplicants = 1;
450 ecbat->adp_psy = devm_power_supply_register_no_ws(dev, &yoga_c630_psy_adpt_psy_desc, &adp_cfg);
451 if (IS_ERR(ecbat->adp_psy)) {
452 dev_err(dev, "failed to register AC adapter supply\n");
453 return PTR_ERR(ecbat->adp_psy);
454 }
455
456 scoped_guard(mutex, &ecbat->lock) {
457 ret = yoga_c630_psy_update_bat_info(ecbat);
458 if (ret)
459 goto err_unreg_bat;
460
461 ret = yoga_c630_psy_register_bat_psy(ecbat);
462 if (ret)
463 goto err_unreg_bat;
464 }
465
466 ret = yoga_c630_ec_register_notify(ecbat->ec, &ecbat->nb);
467 if (ret)
468 goto err_unreg_bat;
469
470 return 0;
471
472err_unreg_bat:
473 power_supply_unregister(ecbat->bat_psy);
474 return ret;
475}
476
477static void yoga_c630_psy_remove(struct auxiliary_device *adev)
478{
479 struct yoga_c630_psy *ecbat = auxiliary_get_drvdata(adev);
480
481 yoga_c630_ec_unregister_notify(ecbat->ec, &ecbat->nb);
482 power_supply_unregister(ecbat->bat_psy);
483}
484
485static const struct auxiliary_device_id yoga_c630_psy_id_table[] = {
486 { .name = YOGA_C630_MOD_NAME "." YOGA_C630_DEV_PSY, },
487 {}
488};
489MODULE_DEVICE_TABLE(auxiliary, yoga_c630_psy_id_table);
490
491static struct auxiliary_driver yoga_c630_psy_driver = {
492 .name = YOGA_C630_DEV_PSY,
493 .id_table = yoga_c630_psy_id_table,
494 .probe = yoga_c630_psy_probe,
495 .remove = yoga_c630_psy_remove,
496};
497
498module_auxiliary_driver(yoga_c630_psy_driver);
499
500MODULE_DESCRIPTION("Lenovo Yoga C630 psy");
501MODULE_LICENSE("GPL");