1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (c) 2023, Nikita Travkin <nikita@trvn.ru> 4 */ 5 6 #include <linux/errno.h> 7 #include <linux/module.h> 8 #include <linux/platform_device.h> 9 #include <linux/power_supply.h> 10 #include <linux/property.h> 11 #include <linux/regmap.h> 12 #include <linux/slab.h> 13 #include <linux/delay.h> 14 #include <linux/interrupt.h> 15 #include <linux/timekeeping.h> 16 17 #define PM8916_PERPH_TYPE 0x04 18 #define PM8916_BMS_VM_TYPE 0x020D 19 20 #define PM8916_SEC_ACCESS 0xD0 21 #define PM8916_SEC_MAGIC 0xA5 22 23 #define PM8916_BMS_VM_STATUS1 0x08 24 #define PM8916_BMS_VM_FSM_STATE(x) (((x) & 0b00111000) >> 3) 25 #define PM8916_BMS_VM_FSM_STATE_S2 0x2 26 27 #define PM8916_BMS_VM_MODE_CTL 0x40 28 #define PM8916_BMS_VM_MODE_FORCE_S3 (BIT(0) | BIT(1)) 29 #define PM8916_BMS_VM_MODE_NORMAL (BIT(1) | BIT(3)) 30 31 #define PM8916_BMS_VM_EN_CTL 0x46 32 #define PM8916_BMS_ENABLED BIT(7) 33 34 #define PM8916_BMS_VM_FIFO_LENGTH_CTL 0x47 35 #define PM8916_BMS_VM_S1_SAMPLE_INTERVAL_CTL 0x55 36 #define PM8916_BMS_VM_S2_SAMPLE_INTERVAL_CTL 0x56 37 #define PM8916_BMS_VM_S3_S7_OCV_DATA0 0x6A 38 #define PM8916_BMS_VM_BMS_FIFO_REG_0_LSB 0xC0 39 40 /* Using only 1 fifo is broken in hardware */ 41 #define PM8916_BMS_VM_FIFO_COUNT 2 /* 2 .. 8 */ 42 43 #define PM8916_BMS_VM_S1_SAMPLE_INTERVAL 10 44 #define PM8916_BMS_VM_S2_SAMPLE_INTERVAL 10 45 46 struct pm8916_bms_vm_battery { 47 struct device *dev; 48 struct power_supply *battery; 49 struct power_supply_battery_info *info; 50 struct regmap *regmap; 51 unsigned int reg; 52 unsigned int last_ocv; 53 time64_t last_ocv_time; 54 unsigned int vbat_now; 55 }; 56 57 static int pm8916_bms_vm_battery_get_property(struct power_supply *psy, 58 enum power_supply_property psp, 59 union power_supply_propval *val) 60 { 61 struct pm8916_bms_vm_battery *bat = power_supply_get_drvdata(psy); 62 struct power_supply_battery_info *info = bat->info; 63 int supplied; 64 65 switch (psp) { 66 case POWER_SUPPLY_PROP_STATUS: 67 supplied = power_supply_am_i_supplied(psy); 68 69 if (supplied < 0 && supplied != -ENODEV) 70 return supplied; 71 else if (supplied && supplied != -ENODEV) 72 val->intval = POWER_SUPPLY_STATUS_CHARGING; 73 else 74 val->intval = POWER_SUPPLY_STATUS_DISCHARGING; 75 return 0; 76 77 case POWER_SUPPLY_PROP_HEALTH: 78 if (bat->vbat_now < info->voltage_min_design_uv) 79 val->intval = POWER_SUPPLY_HEALTH_DEAD; 80 else if (bat->vbat_now > info->voltage_max_design_uv) 81 val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; 82 else 83 val->intval = POWER_SUPPLY_HEALTH_GOOD; 84 return 0; 85 86 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 87 val->intval = bat->vbat_now; 88 return 0; 89 90 case POWER_SUPPLY_PROP_VOLTAGE_OCV: 91 /* 92 * Hardware only reliably measures OCV when the system is off or suspended. 93 * We expose the last known OCV value on boot, invalidating it after 180 seconds. 94 */ 95 if (ktime_get_seconds() - bat->last_ocv_time > 180) 96 return -ENODATA; 97 98 val->intval = bat->last_ocv; 99 return 0; 100 101 default: 102 return -EINVAL; 103 } 104 } 105 106 static enum power_supply_property pm8916_bms_vm_battery_properties[] = { 107 POWER_SUPPLY_PROP_STATUS, 108 POWER_SUPPLY_PROP_VOLTAGE_NOW, 109 POWER_SUPPLY_PROP_VOLTAGE_OCV, 110 POWER_SUPPLY_PROP_HEALTH, 111 }; 112 113 static irqreturn_t pm8916_bms_vm_fifo_update_done_irq(int irq, void *data) 114 { 115 struct pm8916_bms_vm_battery *bat = data; 116 u16 vbat_data[PM8916_BMS_VM_FIFO_COUNT]; 117 int ret; 118 119 ret = regmap_bulk_read(bat->regmap, bat->reg + PM8916_BMS_VM_BMS_FIFO_REG_0_LSB, 120 &vbat_data, PM8916_BMS_VM_FIFO_COUNT * 2); 121 if (ret) 122 return IRQ_HANDLED; 123 124 /* 125 * The VM-BMS hardware only collects voltage data and the software 126 * has to process it to calculate the OCV and SoC. Hardware provides 127 * up to 8 averaged measurements for software to take in account. 128 * 129 * Just use the last measured value for now to report the current 130 * battery voltage. 131 */ 132 bat->vbat_now = vbat_data[PM8916_BMS_VM_FIFO_COUNT - 1] * 300; 133 134 power_supply_changed(bat->battery); 135 136 return IRQ_HANDLED; 137 } 138 139 static const struct power_supply_desc pm8916_bms_vm_battery_psy_desc = { 140 .name = "pm8916-bms-vm", 141 .type = POWER_SUPPLY_TYPE_BATTERY, 142 .properties = pm8916_bms_vm_battery_properties, 143 .num_properties = ARRAY_SIZE(pm8916_bms_vm_battery_properties), 144 .get_property = pm8916_bms_vm_battery_get_property, 145 }; 146 147 static int pm8916_bms_vm_battery_probe(struct platform_device *pdev) 148 { 149 struct device *dev = &pdev->dev; 150 struct pm8916_bms_vm_battery *bat; 151 struct power_supply_config psy_cfg = {}; 152 int ret, irq; 153 unsigned int tmp; 154 155 bat = devm_kzalloc(dev, sizeof(*bat), GFP_KERNEL); 156 if (!bat) 157 return -ENOMEM; 158 159 bat->dev = dev; 160 161 bat->regmap = dev_get_regmap(pdev->dev.parent, NULL); 162 if (!bat->regmap) 163 return -ENODEV; 164 165 ret = device_property_read_u32(dev, "reg", &bat->reg); 166 if (ret < 0) 167 return -EINVAL; 168 169 ret = regmap_bulk_read(bat->regmap, bat->reg + PM8916_PERPH_TYPE, &tmp, 2); 170 if (ret) 171 goto comm_error; 172 173 if (tmp != PM8916_BMS_VM_TYPE) 174 return dev_err_probe(dev, -ENODEV, "Device reported wrong type: 0x%X\n", tmp); 175 176 ret = regmap_write(bat->regmap, bat->reg + PM8916_BMS_VM_S1_SAMPLE_INTERVAL_CTL, 177 PM8916_BMS_VM_S1_SAMPLE_INTERVAL); 178 if (ret) 179 goto comm_error; 180 ret = regmap_write(bat->regmap, bat->reg + PM8916_BMS_VM_S2_SAMPLE_INTERVAL_CTL, 181 PM8916_BMS_VM_S2_SAMPLE_INTERVAL); 182 if (ret) 183 goto comm_error; 184 ret = regmap_write(bat->regmap, bat->reg + PM8916_BMS_VM_FIFO_LENGTH_CTL, 185 PM8916_BMS_VM_FIFO_COUNT << 4 | PM8916_BMS_VM_FIFO_COUNT); 186 if (ret) 187 goto comm_error; 188 ret = regmap_write(bat->regmap, 189 bat->reg + PM8916_BMS_VM_EN_CTL, PM8916_BMS_ENABLED); 190 if (ret) 191 goto comm_error; 192 193 ret = regmap_bulk_read(bat->regmap, 194 bat->reg + PM8916_BMS_VM_S3_S7_OCV_DATA0, &tmp, 2); 195 if (ret) 196 goto comm_error; 197 198 bat->last_ocv_time = ktime_get_seconds(); 199 bat->last_ocv = tmp * 300; 200 bat->vbat_now = bat->last_ocv; 201 202 psy_cfg.drv_data = bat; 203 psy_cfg.fwnode = dev_fwnode(dev); 204 205 bat->battery = devm_power_supply_register(dev, &pm8916_bms_vm_battery_psy_desc, &psy_cfg); 206 if (IS_ERR(bat->battery)) 207 return dev_err_probe(dev, PTR_ERR(bat->battery), "Unable to register battery\n"); 208 209 ret = power_supply_get_battery_info(bat->battery, &bat->info); 210 if (ret) 211 return dev_err_probe(dev, ret, "Unable to get battery info\n"); 212 213 irq = platform_get_irq_byname(pdev, "fifo"); 214 if (irq < 0) 215 return irq; 216 217 ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_bms_vm_fifo_update_done_irq, 218 IRQF_ONESHOT, "pm8916_vm_bms", bat); 219 if (ret) 220 return ret; 221 222 platform_set_drvdata(pdev, bat); 223 224 return 0; 225 226 comm_error: 227 return dev_err_probe(dev, ret, "Unable to communicate with device\n"); 228 } 229 230 static int pm8916_bms_vm_battery_suspend(struct platform_device *pdev, pm_message_t state) 231 { 232 struct pm8916_bms_vm_battery *bat = platform_get_drvdata(pdev); 233 int ret; 234 235 /* 236 * Due to a hardware quirk the FSM doesn't switch states normally. 237 * Instead we unlock the debug registers and force S3 (Measure OCV/Sleep) 238 * mode every time we suspend. 239 */ 240 241 ret = regmap_write(bat->regmap, 242 bat->reg + PM8916_SEC_ACCESS, PM8916_SEC_MAGIC); 243 if (ret) 244 goto error; 245 ret = regmap_write(bat->regmap, 246 bat->reg + PM8916_BMS_VM_MODE_CTL, PM8916_BMS_VM_MODE_FORCE_S3); 247 if (ret) 248 goto error; 249 250 return 0; 251 252 error: 253 dev_err(bat->dev, "Failed to force S3 mode: %pe\n", ERR_PTR(ret)); 254 return ret; 255 } 256 257 static int pm8916_bms_vm_battery_resume(struct platform_device *pdev) 258 { 259 struct pm8916_bms_vm_battery *bat = platform_get_drvdata(pdev); 260 int ret; 261 unsigned int tmp; 262 263 ret = regmap_bulk_read(bat->regmap, 264 bat->reg + PM8916_BMS_VM_S3_S7_OCV_DATA0, &tmp, 2); 265 266 bat->last_ocv_time = ktime_get_seconds(); 267 bat->last_ocv = tmp * 300; 268 269 ret = regmap_write(bat->regmap, 270 bat->reg + PM8916_SEC_ACCESS, PM8916_SEC_MAGIC); 271 if (ret) 272 goto error; 273 ret = regmap_write(bat->regmap, 274 bat->reg + PM8916_BMS_VM_MODE_CTL, PM8916_BMS_VM_MODE_NORMAL); 275 if (ret) 276 goto error; 277 278 return 0; 279 280 error: 281 dev_err(bat->dev, "Failed to return normal mode: %pe\n", ERR_PTR(ret)); 282 return ret; 283 } 284 285 static const struct of_device_id pm8916_bms_vm_battery_of_match[] = { 286 { .compatible = "qcom,pm8916-bms-vm", }, 287 {} 288 }; 289 MODULE_DEVICE_TABLE(of, pm8916_bms_vm_battery_of_match); 290 291 static struct platform_driver pm8916_bms_vm_battery_driver = { 292 .driver = { 293 .name = "pm8916-bms-vm", 294 .of_match_table = pm8916_bms_vm_battery_of_match, 295 }, 296 .probe = pm8916_bms_vm_battery_probe, 297 .suspend = pm8916_bms_vm_battery_suspend, 298 .resume = pm8916_bms_vm_battery_resume, 299 }; 300 module_platform_driver(pm8916_bms_vm_battery_driver); 301 302 MODULE_DESCRIPTION("pm8916 BMS-VM driver"); 303 MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>"); 304 MODULE_LICENSE("GPL"); 305