162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Charging control driver for the Wilco EC 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2019 Google LLC 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * See Documentation/ABI/testing/sysfs-class-power and 862306a36Sopenharmony_ci * Documentation/ABI/testing/sysfs-class-power-wilco for userspace interface 962306a36Sopenharmony_ci * and other info. 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/platform_device.h> 1462306a36Sopenharmony_ci#include <linux/platform_data/wilco-ec.h> 1562306a36Sopenharmony_ci#include <linux/power_supply.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define DRV_NAME "wilco-charger" 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/* Property IDs and related EC constants */ 2062306a36Sopenharmony_ci#define PID_CHARGE_MODE 0x0710 2162306a36Sopenharmony_ci#define PID_CHARGE_LOWER_LIMIT 0x0711 2262306a36Sopenharmony_ci#define PID_CHARGE_UPPER_LIMIT 0x0712 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_cienum charge_mode { 2562306a36Sopenharmony_ci CHARGE_MODE_STD = 1, /* Used for Standard */ 2662306a36Sopenharmony_ci CHARGE_MODE_EXP = 2, /* Express Charge, used for Fast */ 2762306a36Sopenharmony_ci CHARGE_MODE_AC = 3, /* Mostly AC use, used for Trickle */ 2862306a36Sopenharmony_ci CHARGE_MODE_AUTO = 4, /* Used for Adaptive */ 2962306a36Sopenharmony_ci CHARGE_MODE_CUSTOM = 5, /* Used for Custom */ 3062306a36Sopenharmony_ci CHARGE_MODE_LONGLIFE = 6, /* Used for Long Life */ 3162306a36Sopenharmony_ci}; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#define CHARGE_LOWER_LIMIT_MIN 50 3462306a36Sopenharmony_ci#define CHARGE_LOWER_LIMIT_MAX 95 3562306a36Sopenharmony_ci#define CHARGE_UPPER_LIMIT_MIN 55 3662306a36Sopenharmony_ci#define CHARGE_UPPER_LIMIT_MAX 100 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci/* Convert from POWER_SUPPLY_PROP_CHARGE_TYPE value to the EC's charge mode */ 3962306a36Sopenharmony_cistatic int psp_val_to_charge_mode(int psp_val) 4062306a36Sopenharmony_ci{ 4162306a36Sopenharmony_ci switch (psp_val) { 4262306a36Sopenharmony_ci case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: 4362306a36Sopenharmony_ci return CHARGE_MODE_AC; 4462306a36Sopenharmony_ci case POWER_SUPPLY_CHARGE_TYPE_FAST: 4562306a36Sopenharmony_ci return CHARGE_MODE_EXP; 4662306a36Sopenharmony_ci case POWER_SUPPLY_CHARGE_TYPE_STANDARD: 4762306a36Sopenharmony_ci return CHARGE_MODE_STD; 4862306a36Sopenharmony_ci case POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE: 4962306a36Sopenharmony_ci return CHARGE_MODE_AUTO; 5062306a36Sopenharmony_ci case POWER_SUPPLY_CHARGE_TYPE_CUSTOM: 5162306a36Sopenharmony_ci return CHARGE_MODE_CUSTOM; 5262306a36Sopenharmony_ci case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE: 5362306a36Sopenharmony_ci return CHARGE_MODE_LONGLIFE; 5462306a36Sopenharmony_ci default: 5562306a36Sopenharmony_ci return -EINVAL; 5662306a36Sopenharmony_ci } 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci/* Convert from EC's charge mode to POWER_SUPPLY_PROP_CHARGE_TYPE value */ 6062306a36Sopenharmony_cistatic int charge_mode_to_psp_val(enum charge_mode mode) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci switch (mode) { 6362306a36Sopenharmony_ci case CHARGE_MODE_AC: 6462306a36Sopenharmony_ci return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; 6562306a36Sopenharmony_ci case CHARGE_MODE_EXP: 6662306a36Sopenharmony_ci return POWER_SUPPLY_CHARGE_TYPE_FAST; 6762306a36Sopenharmony_ci case CHARGE_MODE_STD: 6862306a36Sopenharmony_ci return POWER_SUPPLY_CHARGE_TYPE_STANDARD; 6962306a36Sopenharmony_ci case CHARGE_MODE_AUTO: 7062306a36Sopenharmony_ci return POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE; 7162306a36Sopenharmony_ci case CHARGE_MODE_CUSTOM: 7262306a36Sopenharmony_ci return POWER_SUPPLY_CHARGE_TYPE_CUSTOM; 7362306a36Sopenharmony_ci case CHARGE_MODE_LONGLIFE: 7462306a36Sopenharmony_ci return POWER_SUPPLY_CHARGE_TYPE_LONGLIFE; 7562306a36Sopenharmony_ci default: 7662306a36Sopenharmony_ci return -EINVAL; 7762306a36Sopenharmony_ci } 7862306a36Sopenharmony_ci} 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic enum power_supply_property wilco_charge_props[] = { 8162306a36Sopenharmony_ci POWER_SUPPLY_PROP_CHARGE_TYPE, 8262306a36Sopenharmony_ci POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD, 8362306a36Sopenharmony_ci POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, 8462306a36Sopenharmony_ci}; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic int wilco_charge_get_property(struct power_supply *psy, 8762306a36Sopenharmony_ci enum power_supply_property psp, 8862306a36Sopenharmony_ci union power_supply_propval *val) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci struct wilco_ec_device *ec = power_supply_get_drvdata(psy); 9162306a36Sopenharmony_ci u32 property_id; 9262306a36Sopenharmony_ci int ret; 9362306a36Sopenharmony_ci u8 raw; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci switch (psp) { 9662306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_TYPE: 9762306a36Sopenharmony_ci property_id = PID_CHARGE_MODE; 9862306a36Sopenharmony_ci break; 9962306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: 10062306a36Sopenharmony_ci property_id = PID_CHARGE_LOWER_LIMIT; 10162306a36Sopenharmony_ci break; 10262306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: 10362306a36Sopenharmony_ci property_id = PID_CHARGE_UPPER_LIMIT; 10462306a36Sopenharmony_ci break; 10562306a36Sopenharmony_ci default: 10662306a36Sopenharmony_ci return -EINVAL; 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci ret = wilco_ec_get_byte_property(ec, property_id, &raw); 11062306a36Sopenharmony_ci if (ret < 0) 11162306a36Sopenharmony_ci return ret; 11262306a36Sopenharmony_ci if (property_id == PID_CHARGE_MODE) { 11362306a36Sopenharmony_ci ret = charge_mode_to_psp_val(raw); 11462306a36Sopenharmony_ci if (ret < 0) 11562306a36Sopenharmony_ci return -EBADMSG; 11662306a36Sopenharmony_ci raw = ret; 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci val->intval = raw; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci return 0; 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_cistatic int wilco_charge_set_property(struct power_supply *psy, 12462306a36Sopenharmony_ci enum power_supply_property psp, 12562306a36Sopenharmony_ci const union power_supply_propval *val) 12662306a36Sopenharmony_ci{ 12762306a36Sopenharmony_ci struct wilco_ec_device *ec = power_supply_get_drvdata(psy); 12862306a36Sopenharmony_ci int mode; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci switch (psp) { 13162306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_TYPE: 13262306a36Sopenharmony_ci mode = psp_val_to_charge_mode(val->intval); 13362306a36Sopenharmony_ci if (mode < 0) 13462306a36Sopenharmony_ci return -EINVAL; 13562306a36Sopenharmony_ci return wilco_ec_set_byte_property(ec, PID_CHARGE_MODE, mode); 13662306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: 13762306a36Sopenharmony_ci if (val->intval < CHARGE_LOWER_LIMIT_MIN || 13862306a36Sopenharmony_ci val->intval > CHARGE_LOWER_LIMIT_MAX) 13962306a36Sopenharmony_ci return -EINVAL; 14062306a36Sopenharmony_ci return wilco_ec_set_byte_property(ec, PID_CHARGE_LOWER_LIMIT, 14162306a36Sopenharmony_ci val->intval); 14262306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: 14362306a36Sopenharmony_ci if (val->intval < CHARGE_UPPER_LIMIT_MIN || 14462306a36Sopenharmony_ci val->intval > CHARGE_UPPER_LIMIT_MAX) 14562306a36Sopenharmony_ci return -EINVAL; 14662306a36Sopenharmony_ci return wilco_ec_set_byte_property(ec, PID_CHARGE_UPPER_LIMIT, 14762306a36Sopenharmony_ci val->intval); 14862306a36Sopenharmony_ci default: 14962306a36Sopenharmony_ci return -EINVAL; 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_cistatic int wilco_charge_property_is_writeable(struct power_supply *psy, 15462306a36Sopenharmony_ci enum power_supply_property psp) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci return 1; 15762306a36Sopenharmony_ci} 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_cistatic const struct power_supply_desc wilco_ps_desc = { 16062306a36Sopenharmony_ci .properties = wilco_charge_props, 16162306a36Sopenharmony_ci .num_properties = ARRAY_SIZE(wilco_charge_props), 16262306a36Sopenharmony_ci .get_property = wilco_charge_get_property, 16362306a36Sopenharmony_ci .set_property = wilco_charge_set_property, 16462306a36Sopenharmony_ci .property_is_writeable = wilco_charge_property_is_writeable, 16562306a36Sopenharmony_ci .name = DRV_NAME, 16662306a36Sopenharmony_ci .type = POWER_SUPPLY_TYPE_MAINS, 16762306a36Sopenharmony_ci}; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_cistatic int wilco_charge_probe(struct platform_device *pdev) 17062306a36Sopenharmony_ci{ 17162306a36Sopenharmony_ci struct wilco_ec_device *ec = dev_get_drvdata(pdev->dev.parent); 17262306a36Sopenharmony_ci struct power_supply_config psy_cfg = {}; 17362306a36Sopenharmony_ci struct power_supply *psy; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci psy_cfg.drv_data = ec; 17662306a36Sopenharmony_ci psy = devm_power_supply_register(&pdev->dev, &wilco_ps_desc, &psy_cfg); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci return PTR_ERR_OR_ZERO(psy); 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_cistatic struct platform_driver wilco_charge_driver = { 18262306a36Sopenharmony_ci .probe = wilco_charge_probe, 18362306a36Sopenharmony_ci .driver = { 18462306a36Sopenharmony_ci .name = DRV_NAME, 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci}; 18762306a36Sopenharmony_cimodule_platform_driver(wilco_charge_driver); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME); 19062306a36Sopenharmony_ciMODULE_AUTHOR("Nick Crews <ncrews@chromium.org>"); 19162306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 19262306a36Sopenharmony_ciMODULE_DESCRIPTION("Wilco EC charge control driver"); 193