1 // SPDX-License-Identifier: GPL-2.0-only
3 * Copyright (c) 2023, Nikita Travkin <nikita@trvn.ru>
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>
18 #define PM8916_PERPH_TYPE 0x04
19 #define PM8916_BMS_VM_TYPE 0x020D
21 #define PM8916_SEC_ACCESS 0xD0
22 #define PM8916_SEC_MAGIC 0xA5
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
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))
32 #define PM8916_BMS_VM_EN_CTL 0x46
33 #define PM8916_BMS_ENABLED BIT(7)
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
41 /* Using only 1 fifo is broken in hardware */
42 #define PM8916_BMS_VM_FIFO_COUNT 2 /* 2 .. 8 */
44 #define PM8916_BMS_VM_S1_SAMPLE_INTERVAL 10
45 #define PM8916_BMS_VM_S2_SAMPLE_INTERVAL 10
47 struct pm8916_bms_vm_battery
{
49 struct power_supply
*battery
;
50 struct power_supply_battery_info
*info
;
51 struct regmap
*regmap
;
53 unsigned int last_ocv
;
54 time64_t last_ocv_time
;
55 unsigned int vbat_now
;
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
)
62 struct pm8916_bms_vm_battery
*bat
= power_supply_get_drvdata(psy
);
63 struct power_supply_battery_info
*info
= bat
->info
;
67 case POWER_SUPPLY_PROP_STATUS
:
68 supplied
= power_supply_am_i_supplied(psy
);
70 if (supplied
< 0 && supplied
!= -ENODEV
)
72 else if (supplied
&& supplied
!= -ENODEV
)
73 val
->intval
= POWER_SUPPLY_STATUS_CHARGING
;
75 val
->intval
= POWER_SUPPLY_STATUS_DISCHARGING
;
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
;
84 val
->intval
= POWER_SUPPLY_HEALTH_GOOD
;
87 case POWER_SUPPLY_PROP_VOLTAGE_NOW
:
88 val
->intval
= bat
->vbat_now
;
91 case POWER_SUPPLY_PROP_VOLTAGE_OCV
:
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.
96 if (ktime_get_seconds() - bat
->last_ocv_time
> 180)
99 val
->intval
= bat
->last_ocv
;
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
,
114 static irqreturn_t
pm8916_bms_vm_fifo_update_done_irq(int irq
, void *data
)
116 struct pm8916_bms_vm_battery
*bat
= data
;
117 u16 vbat_data
[PM8916_BMS_VM_FIFO_COUNT
];
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);
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.
130 * Just use the last measured value for now to report the current
133 bat
->vbat_now
= vbat_data
[PM8916_BMS_VM_FIFO_COUNT
- 1] * 300;
135 power_supply_changed(bat
->battery
);
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
,
148 static int pm8916_bms_vm_battery_probe(struct platform_device
*pdev
)
150 struct device
*dev
= &pdev
->dev
;
151 struct pm8916_bms_vm_battery
*bat
;
152 struct power_supply_config psy_cfg
= {};
156 bat
= devm_kzalloc(dev
, sizeof(*bat
), GFP_KERNEL
);
162 bat
->regmap
= dev_get_regmap(pdev
->dev
.parent
, NULL
);
166 ret
= device_property_read_u32(dev
, "reg", &bat
->reg
);
170 irq
= platform_get_irq_byname(pdev
, "fifo");
174 ret
= devm_request_threaded_irq(dev
, irq
, NULL
, pm8916_bms_vm_fifo_update_done_irq
,
175 IRQF_ONESHOT
, "pm8916_vm_bms", bat
);
179 ret
= regmap_bulk_read(bat
->regmap
, bat
->reg
+ PM8916_PERPH_TYPE
, &tmp
, 2);
183 if (tmp
!= PM8916_BMS_VM_TYPE
)
184 return dev_err_probe(dev
, -ENODEV
, "Device reported wrong type: 0x%X\n", tmp
);
186 ret
= regmap_write(bat
->regmap
, bat
->reg
+ PM8916_BMS_VM_S1_SAMPLE_INTERVAL_CTL
,
187 PM8916_BMS_VM_S1_SAMPLE_INTERVAL
);
190 ret
= regmap_write(bat
->regmap
, bat
->reg
+ PM8916_BMS_VM_S2_SAMPLE_INTERVAL_CTL
,
191 PM8916_BMS_VM_S2_SAMPLE_INTERVAL
);
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
);
198 ret
= regmap_write(bat
->regmap
,
199 bat
->reg
+ PM8916_BMS_VM_EN_CTL
, PM8916_BMS_ENABLED
);
203 ret
= regmap_bulk_read(bat
->regmap
,
204 bat
->reg
+ PM8916_BMS_VM_S3_S7_OCV_DATA0
, &tmp
, 2);
208 bat
->last_ocv_time
= ktime_get_seconds();
209 bat
->last_ocv
= tmp
* 300;
210 bat
->vbat_now
= bat
->last_ocv
;
212 psy_cfg
.drv_data
= bat
;
213 psy_cfg
.of_node
= dev
->of_node
;
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");
219 ret
= power_supply_get_battery_info(bat
->battery
, &bat
->info
);
221 return dev_err_probe(dev
, ret
, "Unable to get battery info\n");
223 platform_set_drvdata(pdev
, bat
);
228 return dev_err_probe(dev
, ret
, "Unable to communicate with device\n");
231 static int pm8916_bms_vm_battery_suspend(struct platform_device
*pdev
, pm_message_t state
)
233 struct pm8916_bms_vm_battery
*bat
= platform_get_drvdata(pdev
);
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.
242 ret
= regmap_write(bat
->regmap
,
243 bat
->reg
+ PM8916_SEC_ACCESS
, PM8916_SEC_MAGIC
);
246 ret
= regmap_write(bat
->regmap
,
247 bat
->reg
+ PM8916_BMS_VM_MODE_CTL
, PM8916_BMS_VM_MODE_FORCE_S3
);
254 dev_err(bat
->dev
, "Failed to force S3 mode: %pe\n", ERR_PTR(ret
));
258 static int pm8916_bms_vm_battery_resume(struct platform_device
*pdev
)
260 struct pm8916_bms_vm_battery
*bat
= platform_get_drvdata(pdev
);
264 ret
= regmap_bulk_read(bat
->regmap
,
265 bat
->reg
+ PM8916_BMS_VM_S3_S7_OCV_DATA0
, &tmp
, 2);
267 bat
->last_ocv_time
= ktime_get_seconds();
268 bat
->last_ocv
= tmp
* 300;
270 ret
= regmap_write(bat
->regmap
,
271 bat
->reg
+ PM8916_SEC_ACCESS
, PM8916_SEC_MAGIC
);
274 ret
= regmap_write(bat
->regmap
,
275 bat
->reg
+ PM8916_BMS_VM_MODE_CTL
, PM8916_BMS_VM_MODE_NORMAL
);
282 dev_err(bat
->dev
, "Failed to return normal mode: %pe\n", ERR_PTR(ret
));
286 static const struct of_device_id pm8916_bms_vm_battery_of_match
[] = {
287 { .compatible
= "qcom,pm8916-bms-vm", },
290 MODULE_DEVICE_TABLE(of
, pm8916_bms_vm_battery_of_match
);
292 static struct platform_driver pm8916_bms_vm_battery_driver
= {
294 .name
= "pm8916-bms-vm",
295 .of_match_table
= pm8916_bms_vm_battery_of_match
,
297 .probe
= pm8916_bms_vm_battery_probe
,
298 .suspend
= pm8916_bms_vm_battery_suspend
,
299 .resume
= pm8916_bms_vm_battery_resume
,
301 module_platform_driver(pm8916_bms_vm_battery_driver
);
303 MODULE_DESCRIPTION("pm8916 BMS-VM driver");
304 MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>");
305 MODULE_LICENSE("GPL");