1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Charging control driver for the Wilco EC 4 * 5 * Copyright 2019 Google LLC 6 * 7 * See Documentation/ABI/testing/sysfs-class-power and 8 * Documentation/ABI/testing/sysfs-class-power-wilco for userspace interface 9 * 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 0x0710 21 #define PID_CHARGE_LOWER_LIMIT 0x0711 22 #define PID_CHARGE_UPPER_LIMIT 0x0712 23 24 enum 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 50 34 #define CHARGE_LOWER_LIMIT_MAX 95 35 #define CHARGE_UPPER_LIMIT_MIN 55 36 #define CHARGE_UPPER_LIMIT_MAX 100 37 38 /* Convert from POWER_SUPPLY_PROP_CHARGE_TYPE value to the EC's charge mode */ 39 static 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 */ 60 static 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 80 static 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 86 static 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 123 static 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 153 static int wilco_charge_property_is_writeable(struct power_supply *psy, 154 enum power_supply_property psp) 155 { 156 return 1; 157 } 158 159 static 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 169 static 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 181 static struct platform_driver wilco_charge_driver = { 182 .probe = wilco_charge_probe, 183 .driver = { 184 .name = DRV_NAME, 185 } 186 }; 187 module_platform_driver(wilco_charge_driver); 188 189 MODULE_ALIAS("platform:" DRV_NAME); 190 MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>"); 191 MODULE_LICENSE("GPL v2"); 192 MODULE_DESCRIPTION("Wilco EC charge control driver"); 193