diff options
Diffstat (limited to '')
-rw-r--r-- | drivers/power/supply/wilco-charger.c | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/drivers/power/supply/wilco-charger.c b/drivers/power/supply/wilco-charger.c new file mode 100644 index 000000000..98ade073e --- /dev/null +++ b/drivers/power/supply/wilco-charger.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Charging control driver for the Wilco EC + * + * Copyright 2019 Google LLC + * + * See Documentation/ABI/testing/sysfs-class-power and + * Documentation/ABI/testing/sysfs-class-power-wilco for userspace interface + * and other info. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/platform_data/wilco-ec.h> +#include <linux/power_supply.h> + +#define DRV_NAME "wilco-charger" + +/* Property IDs and related EC constants */ +#define PID_CHARGE_MODE 0x0710 +#define PID_CHARGE_LOWER_LIMIT 0x0711 +#define PID_CHARGE_UPPER_LIMIT 0x0712 + +enum charge_mode { + CHARGE_MODE_STD = 1, /* Used for Standard */ + CHARGE_MODE_EXP = 2, /* Express Charge, used for Fast */ + CHARGE_MODE_AC = 3, /* Mostly AC use, used for Trickle */ + CHARGE_MODE_AUTO = 4, /* Used for Adaptive */ + CHARGE_MODE_CUSTOM = 5, /* Used for Custom */ + CHARGE_MODE_LONGLIFE = 6, /* Used for Long Life */ +}; + +#define CHARGE_LOWER_LIMIT_MIN 50 +#define CHARGE_LOWER_LIMIT_MAX 95 +#define CHARGE_UPPER_LIMIT_MIN 55 +#define CHARGE_UPPER_LIMIT_MAX 100 + +/* Convert from POWER_SUPPLY_PROP_CHARGE_TYPE value to the EC's charge mode */ +static int psp_val_to_charge_mode(int psp_val) +{ + switch (psp_val) { + case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: + return CHARGE_MODE_AC; + case POWER_SUPPLY_CHARGE_TYPE_FAST: + return CHARGE_MODE_EXP; + case POWER_SUPPLY_CHARGE_TYPE_STANDARD: + return CHARGE_MODE_STD; + case POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE: + return CHARGE_MODE_AUTO; + case POWER_SUPPLY_CHARGE_TYPE_CUSTOM: + return CHARGE_MODE_CUSTOM; + case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE: + return CHARGE_MODE_LONGLIFE; + default: + return -EINVAL; + } +} + +/* Convert from EC's charge mode to POWER_SUPPLY_PROP_CHARGE_TYPE value */ +static int charge_mode_to_psp_val(enum charge_mode mode) +{ + switch (mode) { + case CHARGE_MODE_AC: + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + case CHARGE_MODE_EXP: + return POWER_SUPPLY_CHARGE_TYPE_FAST; + case CHARGE_MODE_STD: + return POWER_SUPPLY_CHARGE_TYPE_STANDARD; + case CHARGE_MODE_AUTO: + return POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE; + case CHARGE_MODE_CUSTOM: + return POWER_SUPPLY_CHARGE_TYPE_CUSTOM; + case CHARGE_MODE_LONGLIFE: + return POWER_SUPPLY_CHARGE_TYPE_LONGLIFE; + default: + return -EINVAL; + } +} + +static enum power_supply_property wilco_charge_props[] = { + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD, + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, +}; + +static int wilco_charge_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wilco_ec_device *ec = power_supply_get_drvdata(psy); + u32 property_id; + int ret; + u8 raw; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + property_id = PID_CHARGE_MODE; + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + property_id = PID_CHARGE_LOWER_LIMIT; + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + property_id = PID_CHARGE_UPPER_LIMIT; + break; + default: + return -EINVAL; + } + + ret = wilco_ec_get_byte_property(ec, property_id, &raw); + if (ret < 0) + return ret; + if (property_id == PID_CHARGE_MODE) { + ret = charge_mode_to_psp_val(raw); + if (ret < 0) + return -EBADMSG; + raw = ret; + } + val->intval = raw; + + return 0; +} + +static int wilco_charge_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct wilco_ec_device *ec = power_supply_get_drvdata(psy); + int mode; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + mode = psp_val_to_charge_mode(val->intval); + if (mode < 0) + return -EINVAL; + return wilco_ec_set_byte_property(ec, PID_CHARGE_MODE, mode); + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + if (val->intval < CHARGE_LOWER_LIMIT_MIN || + val->intval > CHARGE_LOWER_LIMIT_MAX) + return -EINVAL; + return wilco_ec_set_byte_property(ec, PID_CHARGE_LOWER_LIMIT, + val->intval); + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + if (val->intval < CHARGE_UPPER_LIMIT_MIN || + val->intval > CHARGE_UPPER_LIMIT_MAX) + return -EINVAL; + return wilco_ec_set_byte_property(ec, PID_CHARGE_UPPER_LIMIT, + val->intval); + default: + return -EINVAL; + } +} + +static int wilco_charge_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + return 1; +} + +static const struct power_supply_desc wilco_ps_desc = { + .properties = wilco_charge_props, + .num_properties = ARRAY_SIZE(wilco_charge_props), + .get_property = wilco_charge_get_property, + .set_property = wilco_charge_set_property, + .property_is_writeable = wilco_charge_property_is_writeable, + .name = DRV_NAME, + .type = POWER_SUPPLY_TYPE_MAINS, +}; + +static int wilco_charge_probe(struct platform_device *pdev) +{ + struct wilco_ec_device *ec = dev_get_drvdata(pdev->dev.parent); + struct power_supply_config psy_cfg = {}; + struct power_supply *psy; + + psy_cfg.drv_data = ec; + psy = devm_power_supply_register(&pdev->dev, &wilco_ps_desc, &psy_cfg); + + return PTR_ERR_OR_ZERO(psy); +} + +static struct platform_driver wilco_charge_driver = { + .probe = wilco_charge_probe, + .driver = { + .name = DRV_NAME, + } +}; +module_platform_driver(wilco_charge_driver); + +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Wilco EC charge control driver"); |