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
pm8916_bms_vm_battery_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)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
pm8916_bms_vm_fifo_update_done_irq(int irq,void * data)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
pm8916_bms_vm_battery_probe(struct platform_device * pdev)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
pm8916_bms_vm_battery_suspend(struct platform_device * pdev,pm_message_t state)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
pm8916_bms_vm_battery_resume(struct platform_device * pdev)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