arm64: dts: qcom: sm8550: add TRNG node
[linux-modified.git] / drivers / power / supply / pm8916_bms_vm.c
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");