193 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Charging control driver for the Wilco EC4 *5 * Copyright 2019 Google LLC6 *7 * See Documentation/ABI/testing/sysfs-class-power and8 * Documentation/ABI/testing/sysfs-class-power-wilco for userspace interface9 * and other info.10 */11 12#include <linux/module.h>13#include <linux/platform_device.h>14#include <linux/platform_data/wilco-ec.h>15#include <linux/power_supply.h>16 17#define DRV_NAME "wilco-charger"18 19/* Property IDs and related EC constants */20#define PID_CHARGE_MODE 0x071021#define PID_CHARGE_LOWER_LIMIT 0x071122#define PID_CHARGE_UPPER_LIMIT 0x071223 24enum charge_mode {25 CHARGE_MODE_STD = 1, /* Used for Standard */26 CHARGE_MODE_EXP = 2, /* Express Charge, used for Fast */27 CHARGE_MODE_AC = 3, /* Mostly AC use, used for Trickle */28 CHARGE_MODE_AUTO = 4, /* Used for Adaptive */29 CHARGE_MODE_CUSTOM = 5, /* Used for Custom */30 CHARGE_MODE_LONGLIFE = 6, /* Used for Long Life */31};32 33#define CHARGE_LOWER_LIMIT_MIN 5034#define CHARGE_LOWER_LIMIT_MAX 9535#define CHARGE_UPPER_LIMIT_MIN 5536#define CHARGE_UPPER_LIMIT_MAX 10037 38/* Convert from POWER_SUPPLY_PROP_CHARGE_TYPE value to the EC's charge mode */39static int psp_val_to_charge_mode(int psp_val)40{41 switch (psp_val) {42 case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:43 return CHARGE_MODE_AC;44 case POWER_SUPPLY_CHARGE_TYPE_FAST:45 return CHARGE_MODE_EXP;46 case POWER_SUPPLY_CHARGE_TYPE_STANDARD:47 return CHARGE_MODE_STD;48 case POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE:49 return CHARGE_MODE_AUTO;50 case POWER_SUPPLY_CHARGE_TYPE_CUSTOM:51 return CHARGE_MODE_CUSTOM;52 case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:53 return CHARGE_MODE_LONGLIFE;54 default:55 return -EINVAL;56 }57}58 59/* Convert from EC's charge mode to POWER_SUPPLY_PROP_CHARGE_TYPE value */60static int charge_mode_to_psp_val(enum charge_mode mode)61{62 switch (mode) {63 case CHARGE_MODE_AC:64 return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;65 case CHARGE_MODE_EXP:66 return POWER_SUPPLY_CHARGE_TYPE_FAST;67 case CHARGE_MODE_STD:68 return POWER_SUPPLY_CHARGE_TYPE_STANDARD;69 case CHARGE_MODE_AUTO:70 return POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE;71 case CHARGE_MODE_CUSTOM:72 return POWER_SUPPLY_CHARGE_TYPE_CUSTOM;73 case CHARGE_MODE_LONGLIFE:74 return POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;75 default:76 return -EINVAL;77 }78}79 80static enum power_supply_property wilco_charge_props[] = {81 POWER_SUPPLY_PROP_CHARGE_TYPE,82 POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD,83 POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,84};85 86static int wilco_charge_get_property(struct power_supply *psy,87 enum power_supply_property psp,88 union power_supply_propval *val)89{90 struct wilco_ec_device *ec = power_supply_get_drvdata(psy);91 u32 property_id;92 int ret;93 u8 raw;94 95 switch (psp) {96 case POWER_SUPPLY_PROP_CHARGE_TYPE:97 property_id = PID_CHARGE_MODE;98 break;99 case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:100 property_id = PID_CHARGE_LOWER_LIMIT;101 break;102 case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:103 property_id = PID_CHARGE_UPPER_LIMIT;104 break;105 default:106 return -EINVAL;107 }108 109 ret = wilco_ec_get_byte_property(ec, property_id, &raw);110 if (ret < 0)111 return ret;112 if (property_id == PID_CHARGE_MODE) {113 ret = charge_mode_to_psp_val(raw);114 if (ret < 0)115 return -EBADMSG;116 raw = ret;117 }118 val->intval = raw;119 120 return 0;121}122 123static int wilco_charge_set_property(struct power_supply *psy,124 enum power_supply_property psp,125 const union power_supply_propval *val)126{127 struct wilco_ec_device *ec = power_supply_get_drvdata(psy);128 int mode;129 130 switch (psp) {131 case POWER_SUPPLY_PROP_CHARGE_TYPE:132 mode = psp_val_to_charge_mode(val->intval);133 if (mode < 0)134 return -EINVAL;135 return wilco_ec_set_byte_property(ec, PID_CHARGE_MODE, mode);136 case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:137 if (val->intval < CHARGE_LOWER_LIMIT_MIN ||138 val->intval > CHARGE_LOWER_LIMIT_MAX)139 return -EINVAL;140 return wilco_ec_set_byte_property(ec, PID_CHARGE_LOWER_LIMIT,141 val->intval);142 case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:143 if (val->intval < CHARGE_UPPER_LIMIT_MIN ||144 val->intval > CHARGE_UPPER_LIMIT_MAX)145 return -EINVAL;146 return wilco_ec_set_byte_property(ec, PID_CHARGE_UPPER_LIMIT,147 val->intval);148 default:149 return -EINVAL;150 }151}152 153static int wilco_charge_property_is_writeable(struct power_supply *psy,154 enum power_supply_property psp)155{156 return 1;157}158 159static const struct power_supply_desc wilco_ps_desc = {160 .properties = wilco_charge_props,161 .num_properties = ARRAY_SIZE(wilco_charge_props),162 .get_property = wilco_charge_get_property,163 .set_property = wilco_charge_set_property,164 .property_is_writeable = wilco_charge_property_is_writeable,165 .name = DRV_NAME,166 .type = POWER_SUPPLY_TYPE_MAINS,167};168 169static int wilco_charge_probe(struct platform_device *pdev)170{171 struct wilco_ec_device *ec = dev_get_drvdata(pdev->dev.parent);172 struct power_supply_config psy_cfg = {};173 struct power_supply *psy;174 175 psy_cfg.drv_data = ec;176 psy = devm_power_supply_register(&pdev->dev, &wilco_ps_desc, &psy_cfg);177 178 return PTR_ERR_OR_ZERO(psy);179}180 181static struct platform_driver wilco_charge_driver = {182 .probe = wilco_charge_probe,183 .driver = {184 .name = DRV_NAME,185 }186};187module_platform_driver(wilco_charge_driver);188 189MODULE_ALIAS("platform:" DRV_NAME);190MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");191MODULE_LICENSE("GPL v2");192MODULE_DESCRIPTION("Wilco EC charge control driver");193