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