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 }; 31 32 #define CHARGE_LOWER_LIMIT_MIN 50 33 #define CHARGE_LOWER_LIMIT_MAX 95 34 #define CHARGE_UPPER_LIMIT_MIN 55 35 #define CHARGE_UPPER_LIMIT_MAX 100 36 37 /* Convert from POWER_SUPPLY_PROP_CHARGE_TYPE value to the EC's charge mode */ 38 static int psp_val_to_charge_mode(int psp_val) 39 { 40 switch (psp_val) { 41 case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: 42 return CHARGE_MODE_AC; 43 case POWER_SUPPLY_CHARGE_TYPE_FAST: 44 return CHARGE_MODE_EXP; 45 case POWER_SUPPLY_CHARGE_TYPE_STANDARD: 46 return CHARGE_MODE_STD; 47 case POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE: 48 return CHARGE_MODE_AUTO; 49 case POWER_SUPPLY_CHARGE_TYPE_CUSTOM: 50 return CHARGE_MODE_CUSTOM; 51 default: 52 return -EINVAL; 53 } 54 } 55 56 /* Convert from EC's charge mode to POWER_SUPPLY_PROP_CHARGE_TYPE value */ 57 static int charge_mode_to_psp_val(enum charge_mode mode) 58 { 59 switch (mode) { 60 case CHARGE_MODE_AC: 61 return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; 62 case CHARGE_MODE_EXP: 63 return POWER_SUPPLY_CHARGE_TYPE_FAST; 64 case CHARGE_MODE_STD: 65 return POWER_SUPPLY_CHARGE_TYPE_STANDARD; 66 case CHARGE_MODE_AUTO: 67 return POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE; 68 case CHARGE_MODE_CUSTOM: 69 return POWER_SUPPLY_CHARGE_TYPE_CUSTOM; 70 default: 71 return -EINVAL; 72 } 73 } 74 75 static enum power_supply_property wilco_charge_props[] = { 76 POWER_SUPPLY_PROP_CHARGE_TYPE, 77 POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD, 78 POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, 79 }; 80 81 static int wilco_charge_get_property(struct power_supply *psy, 82 enum power_supply_property psp, 83 union power_supply_propval *val) 84 { 85 struct wilco_ec_device *ec = power_supply_get_drvdata(psy); 86 u32 property_id; 87 int ret; 88 u8 raw; 89 90 switch (psp) { 91 case POWER_SUPPLY_PROP_CHARGE_TYPE: 92 property_id = PID_CHARGE_MODE; 93 break; 94 case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: 95 property_id = PID_CHARGE_LOWER_LIMIT; 96 break; 97 case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: 98 property_id = PID_CHARGE_UPPER_LIMIT; 99 break; 100 default: 101 return -EINVAL; 102 } 103 104 ret = wilco_ec_get_byte_property(ec, property_id, &raw); 105 if (ret < 0) 106 return ret; 107 if (property_id == PID_CHARGE_MODE) { 108 ret = charge_mode_to_psp_val(raw); 109 if (ret < 0) 110 return -EBADMSG; 111 raw = ret; 112 } 113 val->intval = raw; 114 115 return 0; 116 } 117 118 static int wilco_charge_set_property(struct power_supply *psy, 119 enum power_supply_property psp, 120 const union power_supply_propval *val) 121 { 122 struct wilco_ec_device *ec = power_supply_get_drvdata(psy); 123 int mode; 124 125 switch (psp) { 126 case POWER_SUPPLY_PROP_CHARGE_TYPE: 127 mode = psp_val_to_charge_mode(val->intval); 128 if (mode < 0) 129 return -EINVAL; 130 return wilco_ec_set_byte_property(ec, PID_CHARGE_MODE, mode); 131 case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: 132 if (val->intval < CHARGE_LOWER_LIMIT_MIN || 133 val->intval > CHARGE_LOWER_LIMIT_MAX) 134 return -EINVAL; 135 return wilco_ec_set_byte_property(ec, PID_CHARGE_LOWER_LIMIT, 136 val->intval); 137 case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: 138 if (val->intval < CHARGE_UPPER_LIMIT_MIN || 139 val->intval > CHARGE_UPPER_LIMIT_MAX) 140 return -EINVAL; 141 return wilco_ec_set_byte_property(ec, PID_CHARGE_UPPER_LIMIT, 142 val->intval); 143 default: 144 return -EINVAL; 145 } 146 } 147 148 static int wilco_charge_property_is_writeable(struct power_supply *psy, 149 enum power_supply_property psp) 150 { 151 return 1; 152 } 153 154 static const struct power_supply_desc wilco_ps_desc = { 155 .properties = wilco_charge_props, 156 .num_properties = ARRAY_SIZE(wilco_charge_props), 157 .get_property = wilco_charge_get_property, 158 .set_property = wilco_charge_set_property, 159 .property_is_writeable = wilco_charge_property_is_writeable, 160 .name = DRV_NAME, 161 .type = POWER_SUPPLY_TYPE_MAINS, 162 }; 163 164 static int wilco_charge_probe(struct platform_device *pdev) 165 { 166 struct wilco_ec_device *ec = dev_get_drvdata(pdev->dev.parent); 167 struct power_supply_config psy_cfg = {}; 168 struct power_supply *psy; 169 170 psy_cfg.drv_data = ec; 171 psy = devm_power_supply_register(&pdev->dev, &wilco_ps_desc, &psy_cfg); 172 173 return PTR_ERR_OR_ZERO(psy); 174 } 175 176 static struct platform_driver wilco_charge_driver = { 177 .probe = wilco_charge_probe, 178 .driver = { 179 .name = DRV_NAME, 180 } 181 }; 182 module_platform_driver(wilco_charge_driver); 183 184 MODULE_ALIAS("platform:" DRV_NAME); 185 MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>"); 186 MODULE_LICENSE("GPL v2"); 187 MODULE_DESCRIPTION("Wilco EC charge control driver"); 188