| // 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"); |