xref: /linux/drivers/power/supply/pm8916_bms_vm.c (revision 0ea5c948cb64bab5bc7a5516774eb8536f05aa0d)
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