1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * power_supply class (battery) driver for the I2C attached embedded controller 4 * found on Vexia EDU ATLA 10 (9V version) tablets. 5 * 6 * This is based on the ACPI Battery device in the DSDT which should work 7 * expect that it expects the I2C controller to be enumerated as an ACPI 8 * device and the tablet's BIOS enumerates all LPSS devices as PCI devices 9 * (and changing the LPSS BIOS settings from PCI -> ACPI does not work). 10 * 11 * Copyright (c) 2024 Hans de Goede <hansg@kernel.org> 12 */ 13 14 #include <linux/bits.h> 15 #include <linux/devm-helpers.h> 16 #include <linux/err.h> 17 #include <linux/i2c.h> 18 #include <linux/module.h> 19 #include <linux/power_supply.h> 20 #include <linux/types.h> 21 #include <linux/workqueue.h> 22 23 #include <asm/byteorder.h> 24 25 /* State field uses ACPI Battery spec status bits */ 26 #define ACPI_BATTERY_STATE_DISCHARGING BIT(0) 27 #define ACPI_BATTERY_STATE_CHARGING BIT(1) 28 29 #define ATLA10_EC_BATTERY_STATE_COMMAND 0x87 30 #define ATLA10_EC_BATTERY_INFO_COMMAND 0x88 31 32 /* From broken ACPI battery device in DSDT */ 33 #define ATLA10_EC_VOLTAGE_MIN_DESIGN_uV 3750000 34 35 /* Update data every 5 seconds */ 36 #define UPDATE_INTERVAL_JIFFIES (5 * HZ) 37 38 struct atla10_ec_battery_state { 39 u8 status; /* Using ACPI Battery spec status bits */ 40 u8 capacity; /* Percent */ 41 __le16 charge_now_mAh; 42 __le16 voltage_now_mV; 43 __le16 current_now_mA; 44 __le16 charge_full_mAh; 45 __le16 temp; /* centi degrees Celsius */ 46 } __packed; 47 48 struct atla10_ec_battery_info { 49 __le16 charge_full_design_mAh; 50 __le16 voltage_now_mV; /* Should be design voltage, but is not ? */ 51 __le16 charge_full_design2_mAh; 52 } __packed; 53 54 struct atla10_ec_data { 55 struct i2c_client *client; 56 struct power_supply *psy; 57 struct delayed_work work; 58 struct mutex update_lock; 59 struct atla10_ec_battery_info info; 60 struct atla10_ec_battery_state state; 61 bool valid; /* true if state is valid */ 62 unsigned long last_update; /* In jiffies */ 63 }; 64 65 static int atla10_ec_cmd(struct atla10_ec_data *data, u8 cmd, u8 len, u8 *values) 66 { 67 struct device *dev = &data->client->dev; 68 u8 buf[I2C_SMBUS_BLOCK_MAX]; 69 int ret; 70 71 ret = i2c_smbus_read_block_data(data->client, cmd, buf); 72 if (ret != len) { 73 dev_err(dev, "I2C command 0x%02x error: %d\n", cmd, ret); 74 return -EIO; 75 } 76 77 memcpy(values, buf, len); 78 return 0; 79 } 80 81 static int atla10_ec_update(struct atla10_ec_data *data) 82 { 83 int ret; 84 85 if (data->valid && time_before(jiffies, data->last_update + UPDATE_INTERVAL_JIFFIES)) 86 return 0; 87 88 ret = atla10_ec_cmd(data, ATLA10_EC_BATTERY_STATE_COMMAND, 89 sizeof(data->state), (u8 *)&data->state); 90 if (ret) 91 return ret; 92 93 data->last_update = jiffies; 94 data->valid = true; 95 return 0; 96 } 97 98 static int atla10_ec_psy_get_property(struct power_supply *psy, 99 enum power_supply_property psp, 100 union power_supply_propval *val) 101 { 102 struct atla10_ec_data *data = power_supply_get_drvdata(psy); 103 int charge_now_mAh, charge_full_mAh, ret; 104 105 guard(mutex)(&data->update_lock); 106 107 ret = atla10_ec_update(data); 108 if (ret) 109 return ret; 110 111 switch (psp) { 112 case POWER_SUPPLY_PROP_STATUS: 113 if (data->state.status & ACPI_BATTERY_STATE_DISCHARGING) 114 val->intval = POWER_SUPPLY_STATUS_DISCHARGING; 115 else if (data->state.status & ACPI_BATTERY_STATE_CHARGING) 116 val->intval = POWER_SUPPLY_STATUS_CHARGING; 117 else if (data->state.capacity == 100) 118 val->intval = POWER_SUPPLY_STATUS_FULL; 119 else 120 val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; 121 break; 122 case POWER_SUPPLY_PROP_CAPACITY: 123 val->intval = data->state.capacity; 124 break; 125 case POWER_SUPPLY_PROP_CHARGE_NOW: 126 /* 127 * The EC has a bug where it reports charge-full-design as 128 * charge-now when the battery is full. Clamp charge-now to 129 * charge-full to workaround this. 130 */ 131 charge_now_mAh = le16_to_cpu(data->state.charge_now_mAh); 132 charge_full_mAh = le16_to_cpu(data->state.charge_full_mAh); 133 val->intval = min(charge_now_mAh, charge_full_mAh) * 1000; 134 break; 135 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 136 val->intval = le16_to_cpu(data->state.voltage_now_mV) * 1000; 137 break; 138 case POWER_SUPPLY_PROP_CURRENT_NOW: 139 val->intval = le16_to_cpu(data->state.current_now_mA) * 1000; 140 /* 141 * Documentation/ABI/testing/sysfs-class-power specifies 142 * negative current for discharging. 143 */ 144 if (data->state.status & ACPI_BATTERY_STATE_DISCHARGING) 145 val->intval = -val->intval; 146 break; 147 case POWER_SUPPLY_PROP_CHARGE_FULL: 148 val->intval = le16_to_cpu(data->state.charge_full_mAh) * 1000; 149 break; 150 case POWER_SUPPLY_PROP_TEMP: 151 val->intval = le16_to_cpu(data->state.temp) / 10; 152 break; 153 case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: 154 val->intval = le16_to_cpu(data->info.charge_full_design_mAh) * 1000; 155 break; 156 case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: 157 val->intval = ATLA10_EC_VOLTAGE_MIN_DESIGN_uV; 158 break; 159 case POWER_SUPPLY_PROP_PRESENT: 160 val->intval = 1; 161 break; 162 case POWER_SUPPLY_PROP_TECHNOLOGY: 163 val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO; 164 break; 165 default: 166 return -EINVAL; 167 } 168 169 return 0; 170 } 171 172 static void atla10_ec_external_power_changed_work(struct work_struct *work) 173 { 174 struct atla10_ec_data *data = container_of(work, struct atla10_ec_data, work.work); 175 176 dev_dbg(&data->client->dev, "External power changed\n"); 177 data->valid = false; 178 power_supply_changed(data->psy); 179 } 180 181 static void atla10_ec_external_power_changed(struct power_supply *psy) 182 { 183 struct atla10_ec_data *data = power_supply_get_drvdata(psy); 184 185 /* After charger plug in/out wait 0.5s for things to stabilize */ 186 mod_delayed_work(system_wq, &data->work, HZ / 2); 187 } 188 189 static const enum power_supply_property atla10_ec_psy_props[] = { 190 POWER_SUPPLY_PROP_STATUS, 191 POWER_SUPPLY_PROP_CAPACITY, 192 POWER_SUPPLY_PROP_CHARGE_NOW, 193 POWER_SUPPLY_PROP_VOLTAGE_NOW, 194 POWER_SUPPLY_PROP_CURRENT_NOW, 195 POWER_SUPPLY_PROP_CHARGE_FULL, 196 POWER_SUPPLY_PROP_TEMP, 197 POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 198 POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, 199 POWER_SUPPLY_PROP_PRESENT, 200 POWER_SUPPLY_PROP_TECHNOLOGY, 201 }; 202 203 static const struct power_supply_desc atla10_ec_psy_desc = { 204 .name = "atla10_ec_battery", 205 .type = POWER_SUPPLY_TYPE_BATTERY, 206 .properties = atla10_ec_psy_props, 207 .num_properties = ARRAY_SIZE(atla10_ec_psy_props), 208 .get_property = atla10_ec_psy_get_property, 209 .external_power_changed = atla10_ec_external_power_changed, 210 }; 211 212 static int atla10_ec_probe(struct i2c_client *client) 213 { 214 struct power_supply_config psy_cfg = { }; 215 struct device *dev = &client->dev; 216 struct atla10_ec_data *data; 217 int ret; 218 219 data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 220 if (!data) 221 return -ENOMEM; 222 223 psy_cfg.drv_data = data; 224 data->client = client; 225 226 ret = devm_mutex_init(dev, &data->update_lock); 227 if (ret) 228 return ret; 229 230 ret = devm_delayed_work_autocancel(dev, &data->work, 231 atla10_ec_external_power_changed_work); 232 if (ret) 233 return ret; 234 235 ret = atla10_ec_cmd(data, ATLA10_EC_BATTERY_INFO_COMMAND, 236 sizeof(data->info), (u8 *)&data->info); 237 if (ret) 238 return ret; 239 240 data->psy = devm_power_supply_register(dev, &atla10_ec_psy_desc, &psy_cfg); 241 return PTR_ERR_OR_ZERO(data->psy); 242 } 243 244 static const struct i2c_device_id atla10_ec_id_table[] = { 245 { "vexia_atla10_ec" }, 246 { } 247 }; 248 MODULE_DEVICE_TABLE(i2c, atla10_ec_id_table); 249 250 static struct i2c_driver atla10_ec_driver = { 251 .driver = { 252 .name = "vexia_atla10_ec", 253 }, 254 .probe = atla10_ec_probe, 255 .id_table = atla10_ec_id_table, 256 }; 257 module_i2c_driver(atla10_ec_driver); 258 259 MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 260 MODULE_DESCRIPTION("Battery driver for Vexia EDU ATLA 10 tablet EC"); 261 MODULE_LICENSE("GPL"); 262