497 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright (c) 2022-2024, Linaro Ltd4 * Authors:5 * Bjorn Andersson6 * Dmitry Baryshkov7 */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 0xa353#define LENOVO_EC_ADPT_STATUS_PRESENT BIT(7)54#define LENOVO_EC_BAT_ATTRIBUTES 0xc055#define LENOVO_EC_BAT_ATTRIBUTES_UNIT_IS_MA BIT(1)56#define LENOVO_EC_BAT_STATUS 0xc157#define LENOVO_EC_BAT_STATUS_DISCHARGING BIT(0)58#define LENOVO_EC_BAT_STATUS_CHARGING BIT(1)59#define LENOVO_EC_BAT_REMAIN_CAPACITY 0xc260#define LENOVO_EC_BAT_VOLTAGE 0xc661#define LENOVO_EC_BAT_DESIGN_VOLTAGE 0xc862#define LENOVO_EC_BAT_DESIGN_CAPACITY 0xca63#define LENOVO_EC_BAT_FULL_CAPACITY 0xcc64#define LENOVO_EC_BAT_CURRENT 0xd265#define LENOVO_EC_BAT_FULL_FACTORY 0xd666#define LENOVO_EC_BAT_PRESENT 0xda67#define LENOVO_EC_BAT_PRESENT_IS_PRESENT BIT(0)68#define LENOVO_EC_BAT_FULL_REGISTER 0xdb69#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 else229 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 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 384static 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 400static 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 420static 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 467err_unreg_bat:468 power_supply_unregister(ecbat->bat_psy);469 return ret;470}471 472static 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 480static const struct auxiliary_device_id yoga_c630_psy_id_table[] = {481 { .name = YOGA_C630_MOD_NAME "." YOGA_C630_DEV_PSY, },482 {}483};484MODULE_DEVICE_TABLE(auxiliary, yoga_c630_psy_id_table);485 486static 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 493module_auxiliary_driver(yoga_c630_psy_driver);494 495MODULE_DESCRIPTION("Lenovo Yoga C630 psy");496MODULE_LICENSE("GPL");497