13f57fe28SNick Crews // SPDX-License-Identifier: GPL-2.0
23f57fe28SNick Crews /*
33f57fe28SNick Crews  * Charging control driver for the Wilco EC
43f57fe28SNick Crews  *
53f57fe28SNick Crews  * Copyright 2019 Google LLC
63f57fe28SNick Crews  *
73f57fe28SNick Crews  * See Documentation/ABI/testing/sysfs-class-power and
83f57fe28SNick Crews  * Documentation/ABI/testing/sysfs-class-power-wilco for userspace interface
93f57fe28SNick Crews  * and other info.
103f57fe28SNick Crews  */
113f57fe28SNick Crews 
123f57fe28SNick Crews #include <linux/module.h>
133f57fe28SNick Crews #include <linux/platform_device.h>
143f57fe28SNick Crews #include <linux/platform_data/wilco-ec.h>
153f57fe28SNick Crews #include <linux/power_supply.h>
163f57fe28SNick Crews 
173f57fe28SNick Crews #define DRV_NAME "wilco-charger"
183f57fe28SNick Crews 
193f57fe28SNick Crews /* Property IDs and related EC constants */
203f57fe28SNick Crews #define PID_CHARGE_MODE		0x0710
213f57fe28SNick Crews #define PID_CHARGE_LOWER_LIMIT	0x0711
223f57fe28SNick Crews #define PID_CHARGE_UPPER_LIMIT	0x0712
233f57fe28SNick Crews 
243f57fe28SNick Crews enum charge_mode {
253f57fe28SNick Crews 	CHARGE_MODE_STD = 1,	/* Used for Standard */
263f57fe28SNick Crews 	CHARGE_MODE_EXP = 2,	/* Express Charge, used for Fast */
273f57fe28SNick Crews 	CHARGE_MODE_AC = 3,	/* Mostly AC use, used for Trickle */
283f57fe28SNick Crews 	CHARGE_MODE_AUTO = 4,	/* Used for Adaptive */
293f57fe28SNick Crews 	CHARGE_MODE_CUSTOM = 5,	/* Used for Custom */
3046cbd0b0SCrag Wang 	CHARGE_MODE_LONGLIFE = 6, /* Used for Long Life */
313f57fe28SNick Crews };
323f57fe28SNick Crews 
333f57fe28SNick Crews #define CHARGE_LOWER_LIMIT_MIN	50
343f57fe28SNick Crews #define CHARGE_LOWER_LIMIT_MAX	95
353f57fe28SNick Crews #define CHARGE_UPPER_LIMIT_MIN	55
363f57fe28SNick Crews #define CHARGE_UPPER_LIMIT_MAX	100
373f57fe28SNick Crews 
383f57fe28SNick Crews /* Convert from POWER_SUPPLY_PROP_CHARGE_TYPE value to the EC's charge mode */
psp_val_to_charge_mode(int psp_val)393f57fe28SNick Crews static int psp_val_to_charge_mode(int psp_val)
403f57fe28SNick Crews {
413f57fe28SNick Crews 	switch (psp_val) {
423f57fe28SNick Crews 	case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:
433f57fe28SNick Crews 		return CHARGE_MODE_AC;
443f57fe28SNick Crews 	case POWER_SUPPLY_CHARGE_TYPE_FAST:
453f57fe28SNick Crews 		return CHARGE_MODE_EXP;
463f57fe28SNick Crews 	case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
473f57fe28SNick Crews 		return CHARGE_MODE_STD;
483f57fe28SNick Crews 	case POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE:
493f57fe28SNick Crews 		return CHARGE_MODE_AUTO;
503f57fe28SNick Crews 	case POWER_SUPPLY_CHARGE_TYPE_CUSTOM:
513f57fe28SNick Crews 		return CHARGE_MODE_CUSTOM;
5246cbd0b0SCrag Wang 	case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
5346cbd0b0SCrag Wang 		return CHARGE_MODE_LONGLIFE;
543f57fe28SNick Crews 	default:
553f57fe28SNick Crews 		return -EINVAL;
563f57fe28SNick Crews 	}
573f57fe28SNick Crews }
583f57fe28SNick Crews 
593f57fe28SNick Crews /* Convert from EC's charge mode to POWER_SUPPLY_PROP_CHARGE_TYPE value */
charge_mode_to_psp_val(enum charge_mode mode)603f57fe28SNick Crews static int charge_mode_to_psp_val(enum charge_mode mode)
613f57fe28SNick Crews {
623f57fe28SNick Crews 	switch (mode) {
633f57fe28SNick Crews 	case CHARGE_MODE_AC:
643f57fe28SNick Crews 		return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
653f57fe28SNick Crews 	case CHARGE_MODE_EXP:
663f57fe28SNick Crews 		return POWER_SUPPLY_CHARGE_TYPE_FAST;
673f57fe28SNick Crews 	case CHARGE_MODE_STD:
683f57fe28SNick Crews 		return POWER_SUPPLY_CHARGE_TYPE_STANDARD;
693f57fe28SNick Crews 	case CHARGE_MODE_AUTO:
703f57fe28SNick Crews 		return POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE;
713f57fe28SNick Crews 	case CHARGE_MODE_CUSTOM:
723f57fe28SNick Crews 		return POWER_SUPPLY_CHARGE_TYPE_CUSTOM;
7346cbd0b0SCrag Wang 	case CHARGE_MODE_LONGLIFE:
7446cbd0b0SCrag Wang 		return POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
753f57fe28SNick Crews 	default:
763f57fe28SNick Crews 		return -EINVAL;
773f57fe28SNick Crews 	}
783f57fe28SNick Crews }
793f57fe28SNick Crews 
803f57fe28SNick Crews static enum power_supply_property wilco_charge_props[] = {
813f57fe28SNick Crews 	POWER_SUPPLY_PROP_CHARGE_TYPE,
823f57fe28SNick Crews 	POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD,
833f57fe28SNick Crews 	POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
843f57fe28SNick Crews };
853f57fe28SNick Crews 
wilco_charge_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)863f57fe28SNick Crews static int wilco_charge_get_property(struct power_supply *psy,
873f57fe28SNick Crews 				     enum power_supply_property psp,
883f57fe28SNick Crews 				     union power_supply_propval *val)
893f57fe28SNick Crews {
903f57fe28SNick Crews 	struct wilco_ec_device *ec = power_supply_get_drvdata(psy);
913f57fe28SNick Crews 	u32 property_id;
923f57fe28SNick Crews 	int ret;
933f57fe28SNick Crews 	u8 raw;
943f57fe28SNick Crews 
953f57fe28SNick Crews 	switch (psp) {
963f57fe28SNick Crews 	case POWER_SUPPLY_PROP_CHARGE_TYPE:
973f57fe28SNick Crews 		property_id = PID_CHARGE_MODE;
983f57fe28SNick Crews 		break;
993f57fe28SNick Crews 	case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
1003f57fe28SNick Crews 		property_id = PID_CHARGE_LOWER_LIMIT;
1013f57fe28SNick Crews 		break;
1023f57fe28SNick Crews 	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
1033f57fe28SNick Crews 		property_id = PID_CHARGE_UPPER_LIMIT;
1043f57fe28SNick Crews 		break;
1053f57fe28SNick Crews 	default:
1063f57fe28SNick Crews 		return -EINVAL;
1073f57fe28SNick Crews 	}
1083f57fe28SNick Crews 
1093f57fe28SNick Crews 	ret = wilco_ec_get_byte_property(ec, property_id, &raw);
1103f57fe28SNick Crews 	if (ret < 0)
1113f57fe28SNick Crews 		return ret;
1123f57fe28SNick Crews 	if (property_id == PID_CHARGE_MODE) {
1133f57fe28SNick Crews 		ret = charge_mode_to_psp_val(raw);
1143f57fe28SNick Crews 		if (ret < 0)
1153f57fe28SNick Crews 			return -EBADMSG;
1163f57fe28SNick Crews 		raw = ret;
1173f57fe28SNick Crews 	}
1183f57fe28SNick Crews 	val->intval = raw;
1193f57fe28SNick Crews 
1203f57fe28SNick Crews 	return 0;
1213f57fe28SNick Crews }
1223f57fe28SNick Crews 
wilco_charge_set_property(struct power_supply * psy,enum power_supply_property psp,const union power_supply_propval * val)1233f57fe28SNick Crews static int wilco_charge_set_property(struct power_supply *psy,
1243f57fe28SNick Crews 				     enum power_supply_property psp,
1253f57fe28SNick Crews 				     const union power_supply_propval *val)
1263f57fe28SNick Crews {
1273f57fe28SNick Crews 	struct wilco_ec_device *ec = power_supply_get_drvdata(psy);
1283f57fe28SNick Crews 	int mode;
1293f57fe28SNick Crews 
1303f57fe28SNick Crews 	switch (psp) {
1313f57fe28SNick Crews 	case POWER_SUPPLY_PROP_CHARGE_TYPE:
1323f57fe28SNick Crews 		mode = psp_val_to_charge_mode(val->intval);
1333f57fe28SNick Crews 		if (mode < 0)
1343f57fe28SNick Crews 			return -EINVAL;
1353f57fe28SNick Crews 		return wilco_ec_set_byte_property(ec, PID_CHARGE_MODE, mode);
1363f57fe28SNick Crews 	case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
1373f57fe28SNick Crews 		if (val->intval < CHARGE_LOWER_LIMIT_MIN ||
1383f57fe28SNick Crews 		    val->intval > CHARGE_LOWER_LIMIT_MAX)
1393f57fe28SNick Crews 			return -EINVAL;
1403f57fe28SNick Crews 		return wilco_ec_set_byte_property(ec, PID_CHARGE_LOWER_LIMIT,
1413f57fe28SNick Crews 						  val->intval);
1423f57fe28SNick Crews 	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
1433f57fe28SNick Crews 		if (val->intval < CHARGE_UPPER_LIMIT_MIN ||
1443f57fe28SNick Crews 		    val->intval > CHARGE_UPPER_LIMIT_MAX)
1453f57fe28SNick Crews 			return -EINVAL;
1463f57fe28SNick Crews 		return wilco_ec_set_byte_property(ec, PID_CHARGE_UPPER_LIMIT,
1473f57fe28SNick Crews 						  val->intval);
1483f57fe28SNick Crews 	default:
1493f57fe28SNick Crews 		return -EINVAL;
1503f57fe28SNick Crews 	}
1513f57fe28SNick Crews }
1523f57fe28SNick Crews 
wilco_charge_property_is_writeable(struct power_supply * psy,enum power_supply_property psp)1533f57fe28SNick Crews static int wilco_charge_property_is_writeable(struct power_supply *psy,
1543f57fe28SNick Crews 					      enum power_supply_property psp)
1553f57fe28SNick Crews {
1563f57fe28SNick Crews 	return 1;
1573f57fe28SNick Crews }
1583f57fe28SNick Crews 
1593f57fe28SNick Crews static const struct power_supply_desc wilco_ps_desc = {
1603f57fe28SNick Crews 	.properties		= wilco_charge_props,
1613f57fe28SNick Crews 	.num_properties		= ARRAY_SIZE(wilco_charge_props),
1623f57fe28SNick Crews 	.get_property		= wilco_charge_get_property,
1633f57fe28SNick Crews 	.set_property		= wilco_charge_set_property,
1643f57fe28SNick Crews 	.property_is_writeable	= wilco_charge_property_is_writeable,
1653f57fe28SNick Crews 	.name			= DRV_NAME,
1663f57fe28SNick Crews 	.type			= POWER_SUPPLY_TYPE_MAINS,
1673f57fe28SNick Crews };
1683f57fe28SNick Crews 
wilco_charge_probe(struct platform_device * pdev)1693f57fe28SNick Crews static int wilco_charge_probe(struct platform_device *pdev)
1703f57fe28SNick Crews {
1713f57fe28SNick Crews 	struct wilco_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
1723f57fe28SNick Crews 	struct power_supply_config psy_cfg = {};
1733f57fe28SNick Crews 	struct power_supply *psy;
1743f57fe28SNick Crews 
1753f57fe28SNick Crews 	psy_cfg.drv_data = ec;
1763f57fe28SNick Crews 	psy = devm_power_supply_register(&pdev->dev, &wilco_ps_desc, &psy_cfg);
1773f57fe28SNick Crews 
1783f57fe28SNick Crews 	return PTR_ERR_OR_ZERO(psy);
1793f57fe28SNick Crews }
1803f57fe28SNick Crews 
1813f57fe28SNick Crews static struct platform_driver wilco_charge_driver = {
1823f57fe28SNick Crews 	.probe	= wilco_charge_probe,
1833f57fe28SNick Crews 	.driver = {
1843f57fe28SNick Crews 		.name = DRV_NAME,
1853f57fe28SNick Crews 	}
1863f57fe28SNick Crews };
1873f57fe28SNick Crews module_platform_driver(wilco_charge_driver);
1883f57fe28SNick Crews 
1893f57fe28SNick Crews MODULE_ALIAS("platform:" DRV_NAME);
1903f57fe28SNick Crews MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
1913f57fe28SNick Crews MODULE_LICENSE("GPL v2");
1923f57fe28SNick Crews MODULE_DESCRIPTION("Wilco EC charge control driver");
193