linux/drivers/power/supply/wilco-charger.c
Crag Wang 46cbd0b057 power: supply: wilco_ec: Add long life charging mode
This is a long life mode set in the factory for extended warranty
battery, the power charging rate is customized so that battery at
work last longer.

Presently switching to a different battery charging mode is through
EC PID 0x0710 to configure the battery firmware, this operation will
be blocked by EC with failure code 0x01 when PLL mode is already
in use.

Signed-off-by: Crag Wang <crag.wang@dell.com>
Reviewed-by: Mario Limonciello <mario.limonciello@dell.com>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
2020-07-31 14:33:56 +02:00

193 lines
5.3 KiB
C

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