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/extcon-provider.h>
16#include <linux/mod_devicetable.h>
17
18/* Two bytes: type + subtype */
19#define PM8916_PERPH_TYPE 0x04
20#define PM8916_LBC_CHGR_TYPE 0x1502
21#define PM8916_LBC_BAT_IF_TYPE 0x1602
22#define PM8916_LBC_USB_TYPE 0x1702
23#define PM8916_LBC_MISC_TYPE 0x1802
24
25#define PM8916_LBC_CHGR_CHG_OPTION 0x08
26#define PM8916_LBC_CHGR_PMIC_CHARGER BIT(7)
27
28#define PM8916_LBC_CHGR_CHG_STATUS 0x09
29
30#define PM8916_INT_RT_STS 0x10
31
32#define PM8916_LBC_USB_USBIN_VALID BIT(1)
33
34#define PM8916_LBC_CHGR_VDD_MAX 0x40
35#define PM8916_LBC_CHGR_VDD_SAFE 0x41
36#define PM8916_LBC_CHGR_IBAT_MAX 0x44
37#define PM8916_LBC_CHGR_IBAT_SAFE 0x45
38
39#define PM8916_LBC_CHGR_TCHG_MAX_EN 0x60
40#define PM8916_LBC_CHGR_TCHG_MAX_ENABLED BIT(7)
41#define PM8916_LBC_CHGR_TCHG_MAX 0x61
42
43#define PM8916_LBC_CHGR_CHG_CTRL 0x49
44#define PM8916_LBC_CHGR_CHG_EN BIT(7)
45#define PM8916_LBC_CHGR_PSTG_EN BIT(5)
46
47#define PM8916_LBC_CHGR_MIN_CURRENT 90000
48#define PM8916_LBC_CHGR_MAX_CURRENT 1440000
49
50#define PM8916_LBC_CHGR_MIN_VOLTAGE 4000000
51#define PM8916_LBC_CHGR_MAX_VOLTAGE 4775000
52#define PM8916_LBC_CHGR_VOLTAGE_STEP 25000
53
54#define PM8916_LBC_CHGR_MIN_TIME 4
55#define PM8916_LBC_CHGR_MAX_TIME 256
56
57struct pm8916_lbc_charger {
58 struct device *dev;
59 struct extcon_dev *edev;
60 struct power_supply *charger;
61 struct power_supply_battery_info *info;
62 struct regmap *regmap;
63 unsigned int reg[4];
64 bool online;
65 unsigned int charge_voltage_max;
66 unsigned int charge_voltage_safe;
67 unsigned int charge_current_max;
68 unsigned int charge_current_safe;
69};
70
71static const unsigned int pm8916_lbc_charger_cable[] = {
72 EXTCON_USB,
73 EXTCON_NONE,
74};
75
76enum {
77 LBC_CHGR = 0,
78 LBC_BAT_IF,
79 LBC_USB,
80 LBC_MISC,
81};
82
83static int pm8916_lbc_charger_configure(struct pm8916_lbc_charger *chg)
84{
85 int ret = 0;
86 unsigned int tmp;
87
88 chg->charge_voltage_max = clamp_t(u32, chg->charge_voltage_max,
89 PM8916_LBC_CHGR_MIN_VOLTAGE, chg->charge_voltage_safe);
90
91 tmp = chg->charge_voltage_max - PM8916_LBC_CHGR_MIN_VOLTAGE;
92 tmp /= PM8916_LBC_CHGR_VOLTAGE_STEP;
93 chg->charge_voltage_max = PM8916_LBC_CHGR_MIN_VOLTAGE + tmp * PM8916_LBC_CHGR_VOLTAGE_STEP;
94
95 ret = regmap_write(map: chg->regmap, reg: chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_MAX, val: tmp);
96 if (ret)
97 goto error;
98
99 chg->charge_current_max = min(chg->charge_current_max, chg->charge_current_safe);
100
101 tmp = clamp_t(u32, chg->charge_current_max,
102 PM8916_LBC_CHGR_MIN_CURRENT, PM8916_LBC_CHGR_MAX_CURRENT);
103
104 tmp = chg->charge_current_max / PM8916_LBC_CHGR_MIN_CURRENT - 1;
105 chg->charge_current_max = (tmp + 1) * PM8916_LBC_CHGR_MIN_CURRENT;
106
107 ret = regmap_write(map: chg->regmap, reg: chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_IBAT_MAX, val: tmp);
108 if (ret)
109 goto error;
110
111 ret = regmap_write(map: chg->regmap, reg: chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_CHG_CTRL,
112 PM8916_LBC_CHGR_CHG_EN | PM8916_LBC_CHGR_PSTG_EN);
113 if (ret)
114 goto error;
115
116 return ret;
117
118error:
119 dev_err(chg->dev, "Failed to configure charging: %pe\n", ERR_PTR(ret));
120 return ret;
121}
122
123static int pm8916_lbc_charger_get_property(struct power_supply *psy,
124 enum power_supply_property psp,
125 union power_supply_propval *val)
126{
127 struct pm8916_lbc_charger *chg = power_supply_get_drvdata(psy);
128
129 switch (psp) {
130 case POWER_SUPPLY_PROP_ONLINE:
131 val->intval = chg->online;
132 return 0;
133
134 case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
135 val->intval = chg->charge_voltage_max;
136 return 0;
137
138 case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
139 val->intval = chg->charge_current_max;
140 return 0;
141
142 default:
143 return -EINVAL;
144 };
145}
146
147static int pm8916_lbc_charger_set_property(struct power_supply *psy,
148 enum power_supply_property prop,
149 const union power_supply_propval *val)
150{
151 struct pm8916_lbc_charger *chg = power_supply_get_drvdata(psy);
152
153 switch (prop) {
154 case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
155 chg->charge_current_max = val->intval;
156 return pm8916_lbc_charger_configure(chg);
157 default:
158 return -EINVAL;
159 }
160}
161
162static int pm8916_lbc_charger_property_is_writeable(struct power_supply *psy,
163 enum power_supply_property psp)
164{
165 switch (psp) {
166 case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
167 return true;
168 default:
169 return false;
170 }
171}
172
173static enum power_supply_property pm8916_lbc_charger_properties[] = {
174 POWER_SUPPLY_PROP_ONLINE,
175 POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
176 POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
177};
178
179static irqreturn_t pm8916_lbc_charger_state_changed_irq(int irq, void *data)
180{
181 struct pm8916_lbc_charger *chg = data;
182 unsigned int tmp;
183 int ret;
184
185 ret = regmap_read(map: chg->regmap, reg: chg->reg[LBC_USB] + PM8916_INT_RT_STS, val: &tmp);
186 if (ret)
187 return IRQ_HANDLED;
188
189 chg->online = !!(tmp & PM8916_LBC_USB_USBIN_VALID);
190 extcon_set_state_sync(edev: chg->edev, EXTCON_USB, state: chg->online);
191
192 power_supply_changed(psy: chg->charger);
193
194 return IRQ_HANDLED;
195}
196
197static int pm8916_lbc_charger_probe_dt(struct pm8916_lbc_charger *chg)
198{
199 struct device *dev = chg->dev;
200 int ret = 0;
201 unsigned int tmp;
202
203 ret = device_property_read_u32(dev, propname: "qcom,fast-charge-safe-voltage", val: &chg->charge_voltage_safe);
204 if (ret)
205 return ret;
206 if (chg->charge_voltage_safe < PM8916_LBC_CHGR_MIN_VOLTAGE)
207 return -EINVAL;
208
209 chg->charge_voltage_safe = clamp_t(u32, chg->charge_voltage_safe,
210 PM8916_LBC_CHGR_MIN_VOLTAGE, PM8916_LBC_CHGR_MAX_VOLTAGE);
211
212 tmp = chg->charge_voltage_safe - PM8916_LBC_CHGR_MIN_VOLTAGE;
213 tmp /= PM8916_LBC_CHGR_VOLTAGE_STEP;
214 ret = regmap_write(map: chg->regmap, reg: chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_SAFE, val: tmp);
215 if (ret)
216 return ret;
217
218 ret = device_property_read_u32(dev, propname: "qcom,fast-charge-safe-current", val: &chg->charge_current_safe);
219 if (ret)
220 return ret;
221 if (chg->charge_current_safe < PM8916_LBC_CHGR_MIN_CURRENT)
222 return -EINVAL;
223
224 chg->charge_current_safe = clamp_t(u32, chg->charge_current_safe,
225 PM8916_LBC_CHGR_MIN_CURRENT, PM8916_LBC_CHGR_MAX_CURRENT);
226
227 chg->charge_current_max = chg->charge_current_safe;
228
229 tmp = chg->charge_current_safe / PM8916_LBC_CHGR_MIN_CURRENT - 1;
230 ret = regmap_write(map: chg->regmap, reg: chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_IBAT_SAFE, val: tmp);
231 if (ret)
232 return ret;
233
234 /* Disable charger timeout. */
235 ret = regmap_write(map: chg->regmap, reg: chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_TCHG_MAX_EN, val: 0x00);
236 if (ret)
237 return ret;
238
239 return ret;
240}
241
242static const struct power_supply_desc pm8916_lbc_charger_psy_desc = {
243 .name = "pm8916-lbc-chgr",
244 .type = POWER_SUPPLY_TYPE_USB,
245 .properties = pm8916_lbc_charger_properties,
246 .num_properties = ARRAY_SIZE(pm8916_lbc_charger_properties),
247 .get_property = pm8916_lbc_charger_get_property,
248 .set_property = pm8916_lbc_charger_set_property,
249 .property_is_writeable = pm8916_lbc_charger_property_is_writeable,
250};
251
252static int pm8916_lbc_charger_probe(struct platform_device *pdev)
253{
254 struct device *dev = &pdev->dev;
255 struct pm8916_lbc_charger *chg;
256 struct power_supply_config psy_cfg = {};
257 int ret, len, irq;
258 unsigned int tmp;
259
260 chg = devm_kzalloc(dev, size: sizeof(*chg), GFP_KERNEL);
261 if (!chg)
262 return -ENOMEM;
263
264 chg->dev = dev;
265
266 chg->regmap = dev_get_regmap(dev: pdev->dev.parent, NULL);
267 if (!chg->regmap)
268 return -ENODEV;
269
270 len = device_property_count_u32(dev, propname: "reg");
271 if (len < 0)
272 return len;
273 if (len != 4)
274 return dev_err_probe(dev, err: -EINVAL,
275 fmt: "Wrong amount of reg values: %d (4 expected)\n", len);
276
277 irq = platform_get_irq_byname(pdev, "usb_vbus");
278 if (irq < 0)
279 return irq;
280
281 ret = devm_request_threaded_irq(dev, irq, NULL, thread_fn: pm8916_lbc_charger_state_changed_irq,
282 IRQF_ONESHOT, devname: "pm8916_lbc", dev_id: chg);
283 if (ret)
284 return ret;
285
286 ret = device_property_read_u32_array(dev, propname: "reg", val: chg->reg, nval: len);
287 if (ret)
288 return ret;
289
290 ret = regmap_bulk_read(map: chg->regmap, reg: chg->reg[LBC_CHGR] + PM8916_PERPH_TYPE, val: &tmp, val_count: 2);
291 if (ret)
292 goto comm_error;
293 if (tmp != PM8916_LBC_CHGR_TYPE)
294 goto type_error;
295
296 ret = regmap_bulk_read(map: chg->regmap, reg: chg->reg[LBC_BAT_IF] + PM8916_PERPH_TYPE, val: &tmp, val_count: 2);
297 if (ret)
298 goto comm_error;
299 if (tmp != PM8916_LBC_BAT_IF_TYPE)
300 goto type_error;
301
302 ret = regmap_bulk_read(map: chg->regmap, reg: chg->reg[LBC_USB] + PM8916_PERPH_TYPE, val: &tmp, val_count: 2);
303 if (ret)
304 goto comm_error;
305 if (tmp != PM8916_LBC_USB_TYPE)
306 goto type_error;
307
308 ret = regmap_bulk_read(map: chg->regmap, reg: chg->reg[LBC_MISC] + PM8916_PERPH_TYPE, val: &tmp, val_count: 2);
309 if (ret)
310 goto comm_error;
311 if (tmp != PM8916_LBC_MISC_TYPE)
312 goto type_error;
313
314 ret = regmap_read(map: chg->regmap, reg: chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_CHG_OPTION, val: &tmp);
315 if (ret)
316 goto comm_error;
317 if (tmp != PM8916_LBC_CHGR_PMIC_CHARGER)
318 dev_err_probe(dev, err: -ENODEV, fmt: "The system is using an external charger\n");
319
320 ret = pm8916_lbc_charger_probe_dt(chg);
321 if (ret)
322 dev_err_probe(dev, err: ret, fmt: "Error while parsing device tree\n");
323
324 psy_cfg.drv_data = chg;
325 psy_cfg.of_node = dev->of_node;
326
327 chg->charger = devm_power_supply_register(parent: dev, desc: &pm8916_lbc_charger_psy_desc, cfg: &psy_cfg);
328 if (IS_ERR(ptr: chg->charger))
329 return dev_err_probe(dev, err: PTR_ERR(ptr: chg->charger), fmt: "Unable to register charger\n");
330
331 ret = power_supply_get_battery_info(psy: chg->charger, info_out: &chg->info);
332 if (ret)
333 return dev_err_probe(dev, err: ret, fmt: "Unable to get battery info\n");
334
335 chg->edev = devm_extcon_dev_allocate(dev, cable: pm8916_lbc_charger_cable);
336 if (IS_ERR(ptr: chg->edev))
337 return PTR_ERR(ptr: chg->edev);
338
339 ret = devm_extcon_dev_register(dev, edev: chg->edev);
340 if (ret < 0)
341 return dev_err_probe(dev, err: ret, fmt: "failed to register extcon device\n");
342
343 ret = regmap_read(map: chg->regmap, reg: chg->reg[LBC_USB] + PM8916_INT_RT_STS, val: &tmp);
344 if (ret)
345 goto comm_error;
346
347 chg->online = !!(tmp & PM8916_LBC_USB_USBIN_VALID);
348 extcon_set_state_sync(edev: chg->edev, EXTCON_USB, state: chg->online);
349
350 chg->charge_voltage_max = chg->info->voltage_max_design_uv;
351 ret = pm8916_lbc_charger_configure(chg);
352 if (ret)
353 return ret;
354
355 return 0;
356
357comm_error:
358 return dev_err_probe(dev, err: ret, fmt: "Unable to communicate with device\n");
359
360type_error:
361 return dev_err_probe(dev, err: -ENODEV, fmt: "Device reported wrong type: 0x%X\n", tmp);
362}
363
364static const struct of_device_id pm8916_lbc_charger_of_match[] = {
365 { .compatible = "qcom,pm8916-lbc", },
366 {}
367};
368MODULE_DEVICE_TABLE(of, pm8916_lbc_charger_of_match);
369
370static struct platform_driver pm8916_lbc_charger_driver = {
371 .driver = {
372 .name = "pm8916-lbc",
373 .of_match_table = pm8916_lbc_charger_of_match,
374 },
375 .probe = pm8916_lbc_charger_probe,
376};
377module_platform_driver(pm8916_lbc_charger_driver);
378
379MODULE_DESCRIPTION("pm8916 LBC driver");
380MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>");
381MODULE_LICENSE("GPL");
382

source code of linux/drivers/power/supply/pm8916_lbc.c