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