18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Charging control driver for the Wilco EC 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2019 Google LLC 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * See Documentation/ABI/testing/sysfs-class-power and 88c2ecf20Sopenharmony_ci * Documentation/ABI/testing/sysfs-class-power-wilco for userspace interface 98c2ecf20Sopenharmony_ci * and other info. 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 148c2ecf20Sopenharmony_ci#include <linux/platform_data/wilco-ec.h> 158c2ecf20Sopenharmony_ci#include <linux/power_supply.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#define DRV_NAME "wilco-charger" 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci/* Property IDs and related EC constants */ 208c2ecf20Sopenharmony_ci#define PID_CHARGE_MODE 0x0710 218c2ecf20Sopenharmony_ci#define PID_CHARGE_LOWER_LIMIT 0x0711 228c2ecf20Sopenharmony_ci#define PID_CHARGE_UPPER_LIMIT 0x0712 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_cienum charge_mode { 258c2ecf20Sopenharmony_ci CHARGE_MODE_STD = 1, /* Used for Standard */ 268c2ecf20Sopenharmony_ci CHARGE_MODE_EXP = 2, /* Express Charge, used for Fast */ 278c2ecf20Sopenharmony_ci CHARGE_MODE_AC = 3, /* Mostly AC use, used for Trickle */ 288c2ecf20Sopenharmony_ci CHARGE_MODE_AUTO = 4, /* Used for Adaptive */ 298c2ecf20Sopenharmony_ci CHARGE_MODE_CUSTOM = 5, /* Used for Custom */ 308c2ecf20Sopenharmony_ci CHARGE_MODE_LONGLIFE = 6, /* Used for Long Life */ 318c2ecf20Sopenharmony_ci}; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci#define CHARGE_LOWER_LIMIT_MIN 50 348c2ecf20Sopenharmony_ci#define CHARGE_LOWER_LIMIT_MAX 95 358c2ecf20Sopenharmony_ci#define CHARGE_UPPER_LIMIT_MIN 55 368c2ecf20Sopenharmony_ci#define CHARGE_UPPER_LIMIT_MAX 100 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci/* Convert from POWER_SUPPLY_PROP_CHARGE_TYPE value to the EC's charge mode */ 398c2ecf20Sopenharmony_cistatic int psp_val_to_charge_mode(int psp_val) 408c2ecf20Sopenharmony_ci{ 418c2ecf20Sopenharmony_ci switch (psp_val) { 428c2ecf20Sopenharmony_ci case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: 438c2ecf20Sopenharmony_ci return CHARGE_MODE_AC; 448c2ecf20Sopenharmony_ci case POWER_SUPPLY_CHARGE_TYPE_FAST: 458c2ecf20Sopenharmony_ci return CHARGE_MODE_EXP; 468c2ecf20Sopenharmony_ci case POWER_SUPPLY_CHARGE_TYPE_STANDARD: 478c2ecf20Sopenharmony_ci return CHARGE_MODE_STD; 488c2ecf20Sopenharmony_ci case POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE: 498c2ecf20Sopenharmony_ci return CHARGE_MODE_AUTO; 508c2ecf20Sopenharmony_ci case POWER_SUPPLY_CHARGE_TYPE_CUSTOM: 518c2ecf20Sopenharmony_ci return CHARGE_MODE_CUSTOM; 528c2ecf20Sopenharmony_ci case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE: 538c2ecf20Sopenharmony_ci return CHARGE_MODE_LONGLIFE; 548c2ecf20Sopenharmony_ci default: 558c2ecf20Sopenharmony_ci return -EINVAL; 568c2ecf20Sopenharmony_ci } 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci/* Convert from EC's charge mode to POWER_SUPPLY_PROP_CHARGE_TYPE value */ 608c2ecf20Sopenharmony_cistatic int charge_mode_to_psp_val(enum charge_mode mode) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci switch (mode) { 638c2ecf20Sopenharmony_ci case CHARGE_MODE_AC: 648c2ecf20Sopenharmony_ci return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; 658c2ecf20Sopenharmony_ci case CHARGE_MODE_EXP: 668c2ecf20Sopenharmony_ci return POWER_SUPPLY_CHARGE_TYPE_FAST; 678c2ecf20Sopenharmony_ci case CHARGE_MODE_STD: 688c2ecf20Sopenharmony_ci return POWER_SUPPLY_CHARGE_TYPE_STANDARD; 698c2ecf20Sopenharmony_ci case CHARGE_MODE_AUTO: 708c2ecf20Sopenharmony_ci return POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE; 718c2ecf20Sopenharmony_ci case CHARGE_MODE_CUSTOM: 728c2ecf20Sopenharmony_ci return POWER_SUPPLY_CHARGE_TYPE_CUSTOM; 738c2ecf20Sopenharmony_ci case CHARGE_MODE_LONGLIFE: 748c2ecf20Sopenharmony_ci return POWER_SUPPLY_CHARGE_TYPE_LONGLIFE; 758c2ecf20Sopenharmony_ci default: 768c2ecf20Sopenharmony_ci return -EINVAL; 778c2ecf20Sopenharmony_ci } 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic enum power_supply_property wilco_charge_props[] = { 818c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_CHARGE_TYPE, 828c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD, 838c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, 848c2ecf20Sopenharmony_ci}; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic int wilco_charge_get_property(struct power_supply *psy, 878c2ecf20Sopenharmony_ci enum power_supply_property psp, 888c2ecf20Sopenharmony_ci union power_supply_propval *val) 898c2ecf20Sopenharmony_ci{ 908c2ecf20Sopenharmony_ci struct wilco_ec_device *ec = power_supply_get_drvdata(psy); 918c2ecf20Sopenharmony_ci u32 property_id; 928c2ecf20Sopenharmony_ci int ret; 938c2ecf20Sopenharmony_ci u8 raw; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci switch (psp) { 968c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_TYPE: 978c2ecf20Sopenharmony_ci property_id = PID_CHARGE_MODE; 988c2ecf20Sopenharmony_ci break; 998c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: 1008c2ecf20Sopenharmony_ci property_id = PID_CHARGE_LOWER_LIMIT; 1018c2ecf20Sopenharmony_ci break; 1028c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: 1038c2ecf20Sopenharmony_ci property_id = PID_CHARGE_UPPER_LIMIT; 1048c2ecf20Sopenharmony_ci break; 1058c2ecf20Sopenharmony_ci default: 1068c2ecf20Sopenharmony_ci return -EINVAL; 1078c2ecf20Sopenharmony_ci } 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci ret = wilco_ec_get_byte_property(ec, property_id, &raw); 1108c2ecf20Sopenharmony_ci if (ret < 0) 1118c2ecf20Sopenharmony_ci return ret; 1128c2ecf20Sopenharmony_ci if (property_id == PID_CHARGE_MODE) { 1138c2ecf20Sopenharmony_ci ret = charge_mode_to_psp_val(raw); 1148c2ecf20Sopenharmony_ci if (ret < 0) 1158c2ecf20Sopenharmony_ci return -EBADMSG; 1168c2ecf20Sopenharmony_ci raw = ret; 1178c2ecf20Sopenharmony_ci } 1188c2ecf20Sopenharmony_ci val->intval = raw; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci return 0; 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_cistatic int wilco_charge_set_property(struct power_supply *psy, 1248c2ecf20Sopenharmony_ci enum power_supply_property psp, 1258c2ecf20Sopenharmony_ci const union power_supply_propval *val) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci struct wilco_ec_device *ec = power_supply_get_drvdata(psy); 1288c2ecf20Sopenharmony_ci int mode; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci switch (psp) { 1318c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_TYPE: 1328c2ecf20Sopenharmony_ci mode = psp_val_to_charge_mode(val->intval); 1338c2ecf20Sopenharmony_ci if (mode < 0) 1348c2ecf20Sopenharmony_ci return -EINVAL; 1358c2ecf20Sopenharmony_ci return wilco_ec_set_byte_property(ec, PID_CHARGE_MODE, mode); 1368c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: 1378c2ecf20Sopenharmony_ci if (val->intval < CHARGE_LOWER_LIMIT_MIN || 1388c2ecf20Sopenharmony_ci val->intval > CHARGE_LOWER_LIMIT_MAX) 1398c2ecf20Sopenharmony_ci return -EINVAL; 1408c2ecf20Sopenharmony_ci return wilco_ec_set_byte_property(ec, PID_CHARGE_LOWER_LIMIT, 1418c2ecf20Sopenharmony_ci val->intval); 1428c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: 1438c2ecf20Sopenharmony_ci if (val->intval < CHARGE_UPPER_LIMIT_MIN || 1448c2ecf20Sopenharmony_ci val->intval > CHARGE_UPPER_LIMIT_MAX) 1458c2ecf20Sopenharmony_ci return -EINVAL; 1468c2ecf20Sopenharmony_ci return wilco_ec_set_byte_property(ec, PID_CHARGE_UPPER_LIMIT, 1478c2ecf20Sopenharmony_ci val->intval); 1488c2ecf20Sopenharmony_ci default: 1498c2ecf20Sopenharmony_ci return -EINVAL; 1508c2ecf20Sopenharmony_ci } 1518c2ecf20Sopenharmony_ci} 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_cistatic int wilco_charge_property_is_writeable(struct power_supply *psy, 1548c2ecf20Sopenharmony_ci enum power_supply_property psp) 1558c2ecf20Sopenharmony_ci{ 1568c2ecf20Sopenharmony_ci return 1; 1578c2ecf20Sopenharmony_ci} 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic const struct power_supply_desc wilco_ps_desc = { 1608c2ecf20Sopenharmony_ci .properties = wilco_charge_props, 1618c2ecf20Sopenharmony_ci .num_properties = ARRAY_SIZE(wilco_charge_props), 1628c2ecf20Sopenharmony_ci .get_property = wilco_charge_get_property, 1638c2ecf20Sopenharmony_ci .set_property = wilco_charge_set_property, 1648c2ecf20Sopenharmony_ci .property_is_writeable = wilco_charge_property_is_writeable, 1658c2ecf20Sopenharmony_ci .name = DRV_NAME, 1668c2ecf20Sopenharmony_ci .type = POWER_SUPPLY_TYPE_MAINS, 1678c2ecf20Sopenharmony_ci}; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_cistatic int wilco_charge_probe(struct platform_device *pdev) 1708c2ecf20Sopenharmony_ci{ 1718c2ecf20Sopenharmony_ci struct wilco_ec_device *ec = dev_get_drvdata(pdev->dev.parent); 1728c2ecf20Sopenharmony_ci struct power_supply_config psy_cfg = {}; 1738c2ecf20Sopenharmony_ci struct power_supply *psy; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci psy_cfg.drv_data = ec; 1768c2ecf20Sopenharmony_ci psy = devm_power_supply_register(&pdev->dev, &wilco_ps_desc, &psy_cfg); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci return PTR_ERR_OR_ZERO(psy); 1798c2ecf20Sopenharmony_ci} 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_cistatic struct platform_driver wilco_charge_driver = { 1828c2ecf20Sopenharmony_ci .probe = wilco_charge_probe, 1838c2ecf20Sopenharmony_ci .driver = { 1848c2ecf20Sopenharmony_ci .name = DRV_NAME, 1858c2ecf20Sopenharmony_ci } 1868c2ecf20Sopenharmony_ci}; 1878c2ecf20Sopenharmony_cimodule_platform_driver(wilco_charge_driver); 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME); 1908c2ecf20Sopenharmony_ciMODULE_AUTHOR("Nick Crews <ncrews@chromium.org>"); 1918c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 1928c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Wilco EC charge control driver"); 193