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/extcon-provider.h>
16 #include <linux/mod_devicetable.h>
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
25 #define PM8916_LBC_CHGR_CHG_OPTION 0x08
26 #define PM8916_LBC_CHGR_PMIC_CHARGER BIT(7)
28 #define PM8916_LBC_CHGR_CHG_STATUS 0x09
30 #define PM8916_INT_RT_STS 0x10
32 #define PM8916_LBC_USB_USBIN_VALID BIT(1)
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
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
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)
47 #define PM8916_LBC_CHGR_MIN_CURRENT 90000
48 #define PM8916_LBC_CHGR_MAX_CURRENT 1440000
50 #define PM8916_LBC_CHGR_MIN_VOLTAGE 4000000
51 #define PM8916_LBC_CHGR_MAX_VOLTAGE 4775000
52 #define PM8916_LBC_CHGR_VOLTAGE_STEP 25000
54 #define PM8916_LBC_CHGR_MIN_TIME 4
55 #define PM8916_LBC_CHGR_MAX_TIME 256
57 struct pm8916_lbc_charger
{
59 struct extcon_dev
*edev
;
60 struct power_supply
*charger
;
61 struct power_supply_battery_info
*info
;
62 struct regmap
*regmap
;
65 unsigned int charge_voltage_max
;
66 unsigned int charge_voltage_safe
;
67 unsigned int charge_current_max
;
68 unsigned int charge_current_safe
;
71 static const unsigned int pm8916_lbc_charger_cable
[] = {
83 static int pm8916_lbc_charger_configure(struct pm8916_lbc_charger
*chg
)
88 chg
->charge_voltage_max
= clamp_t(u32
, chg
->charge_voltage_max
,
89 PM8916_LBC_CHGR_MIN_VOLTAGE
, chg
->charge_voltage_safe
);
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
;
95 ret
= regmap_write(chg
->regmap
, chg
->reg
[LBC_CHGR
] + PM8916_LBC_CHGR_VDD_MAX
, tmp
);
99 chg
->charge_current_max
= min(chg
->charge_current_max
, chg
->charge_current_safe
);
101 tmp
= clamp_t(u32
, chg
->charge_current_max
,
102 PM8916_LBC_CHGR_MIN_CURRENT
, PM8916_LBC_CHGR_MAX_CURRENT
);
104 tmp
= chg
->charge_current_max
/ PM8916_LBC_CHGR_MIN_CURRENT
- 1;
105 chg
->charge_current_max
= (tmp
+ 1) * PM8916_LBC_CHGR_MIN_CURRENT
;
107 ret
= regmap_write(chg
->regmap
, chg
->reg
[LBC_CHGR
] + PM8916_LBC_CHGR_IBAT_MAX
, tmp
);
111 ret
= regmap_write(chg
->regmap
, chg
->reg
[LBC_CHGR
] + PM8916_LBC_CHGR_CHG_CTRL
,
112 PM8916_LBC_CHGR_CHG_EN
| PM8916_LBC_CHGR_PSTG_EN
);
119 dev_err(chg
->dev
, "Failed to configure charging: %pe\n", ERR_PTR(ret
));
123 static int pm8916_lbc_charger_get_property(struct power_supply
*psy
,
124 enum power_supply_property psp
,
125 union power_supply_propval
*val
)
127 struct pm8916_lbc_charger
*chg
= power_supply_get_drvdata(psy
);
130 case POWER_SUPPLY_PROP_ONLINE
:
131 val
->intval
= chg
->online
;
134 case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX
:
135 val
->intval
= chg
->charge_voltage_max
;
138 case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT
:
139 val
->intval
= chg
->charge_current_max
;
147 static int pm8916_lbc_charger_set_property(struct power_supply
*psy
,
148 enum power_supply_property prop
,
149 const union power_supply_propval
*val
)
151 struct pm8916_lbc_charger
*chg
= power_supply_get_drvdata(psy
);
154 case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT
:
155 chg
->charge_current_max
= val
->intval
;
156 return pm8916_lbc_charger_configure(chg
);
162 static int pm8916_lbc_charger_property_is_writeable(struct power_supply
*psy
,
163 enum power_supply_property psp
)
166 case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT
:
173 static 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
,
179 static irqreturn_t
pm8916_lbc_charger_state_changed_irq(int irq
, void *data
)
181 struct pm8916_lbc_charger
*chg
= data
;
185 ret
= regmap_read(chg
->regmap
, chg
->reg
[LBC_USB
] + PM8916_INT_RT_STS
, &tmp
);
189 chg
->online
= !!(tmp
& PM8916_LBC_USB_USBIN_VALID
);
190 extcon_set_state_sync(chg
->edev
, EXTCON_USB
, chg
->online
);
192 power_supply_changed(chg
->charger
);
197 static int pm8916_lbc_charger_probe_dt(struct pm8916_lbc_charger
*chg
)
199 struct device
*dev
= chg
->dev
;
203 ret
= device_property_read_u32(dev
, "qcom,fast-charge-safe-voltage", &chg
->charge_voltage_safe
);
206 if (chg
->charge_voltage_safe
< PM8916_LBC_CHGR_MIN_VOLTAGE
)
209 chg
->charge_voltage_safe
= clamp_t(u32
, chg
->charge_voltage_safe
,
210 PM8916_LBC_CHGR_MIN_VOLTAGE
, PM8916_LBC_CHGR_MAX_VOLTAGE
);
212 tmp
= chg
->charge_voltage_safe
- PM8916_LBC_CHGR_MIN_VOLTAGE
;
213 tmp
/= PM8916_LBC_CHGR_VOLTAGE_STEP
;
214 ret
= regmap_write(chg
->regmap
, chg
->reg
[LBC_CHGR
] + PM8916_LBC_CHGR_VDD_SAFE
, tmp
);
218 ret
= device_property_read_u32(dev
, "qcom,fast-charge-safe-current", &chg
->charge_current_safe
);
221 if (chg
->charge_current_safe
< PM8916_LBC_CHGR_MIN_CURRENT
)
224 chg
->charge_current_safe
= clamp_t(u32
, chg
->charge_current_safe
,
225 PM8916_LBC_CHGR_MIN_CURRENT
, PM8916_LBC_CHGR_MAX_CURRENT
);
227 chg
->charge_current_max
= chg
->charge_current_safe
;
229 tmp
= chg
->charge_current_safe
/ PM8916_LBC_CHGR_MIN_CURRENT
- 1;
230 ret
= regmap_write(chg
->regmap
, chg
->reg
[LBC_CHGR
] + PM8916_LBC_CHGR_IBAT_SAFE
, tmp
);
234 /* Disable charger timeout. */
235 ret
= regmap_write(chg
->regmap
, chg
->reg
[LBC_CHGR
] + PM8916_LBC_CHGR_TCHG_MAX_EN
, 0x00);
242 static 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
,
252 static int pm8916_lbc_charger_probe(struct platform_device
*pdev
)
254 struct device
*dev
= &pdev
->dev
;
255 struct pm8916_lbc_charger
*chg
;
256 struct power_supply_config psy_cfg
= {};
260 chg
= devm_kzalloc(dev
, sizeof(*chg
), GFP_KERNEL
);
266 chg
->regmap
= dev_get_regmap(pdev
->dev
.parent
, NULL
);
270 len
= device_property_count_u32(dev
, "reg");
274 return dev_err_probe(dev
, -EINVAL
,
275 "Wrong amount of reg values: %d (4 expected)\n", len
);
277 irq
= platform_get_irq_byname(pdev
, "usb_vbus");
281 ret
= devm_request_threaded_irq(dev
, irq
, NULL
, pm8916_lbc_charger_state_changed_irq
,
282 IRQF_ONESHOT
, "pm8916_lbc", chg
);
286 ret
= device_property_read_u32_array(dev
, "reg", chg
->reg
, len
);
290 ret
= regmap_bulk_read(chg
->regmap
, chg
->reg
[LBC_CHGR
] + PM8916_PERPH_TYPE
, &tmp
, 2);
293 if (tmp
!= PM8916_LBC_CHGR_TYPE
)
296 ret
= regmap_bulk_read(chg
->regmap
, chg
->reg
[LBC_BAT_IF
] + PM8916_PERPH_TYPE
, &tmp
, 2);
299 if (tmp
!= PM8916_LBC_BAT_IF_TYPE
)
302 ret
= regmap_bulk_read(chg
->regmap
, chg
->reg
[LBC_USB
] + PM8916_PERPH_TYPE
, &tmp
, 2);
305 if (tmp
!= PM8916_LBC_USB_TYPE
)
308 ret
= regmap_bulk_read(chg
->regmap
, chg
->reg
[LBC_MISC
] + PM8916_PERPH_TYPE
, &tmp
, 2);
311 if (tmp
!= PM8916_LBC_MISC_TYPE
)
314 ret
= regmap_read(chg
->regmap
, chg
->reg
[LBC_CHGR
] + PM8916_LBC_CHGR_CHG_OPTION
, &tmp
);
317 if (tmp
!= PM8916_LBC_CHGR_PMIC_CHARGER
)
318 dev_err_probe(dev
, -ENODEV
, "The system is using an external charger\n");
320 ret
= pm8916_lbc_charger_probe_dt(chg
);
322 dev_err_probe(dev
, ret
, "Error while parsing device tree\n");
324 psy_cfg
.drv_data
= chg
;
325 psy_cfg
.of_node
= dev
->of_node
;
327 chg
->charger
= devm_power_supply_register(dev
, &pm8916_lbc_charger_psy_desc
, &psy_cfg
);
328 if (IS_ERR(chg
->charger
))
329 return dev_err_probe(dev
, PTR_ERR(chg
->charger
), "Unable to register charger\n");
331 ret
= power_supply_get_battery_info(chg
->charger
, &chg
->info
);
333 return dev_err_probe(dev
, ret
, "Unable to get battery info\n");
335 chg
->edev
= devm_extcon_dev_allocate(dev
, pm8916_lbc_charger_cable
);
336 if (IS_ERR(chg
->edev
))
337 return PTR_ERR(chg
->edev
);
339 ret
= devm_extcon_dev_register(dev
, chg
->edev
);
341 return dev_err_probe(dev
, ret
, "failed to register extcon device\n");
343 ret
= regmap_read(chg
->regmap
, chg
->reg
[LBC_USB
] + PM8916_INT_RT_STS
, &tmp
);
347 chg
->online
= !!(tmp
& PM8916_LBC_USB_USBIN_VALID
);
348 extcon_set_state_sync(chg
->edev
, EXTCON_USB
, chg
->online
);
350 chg
->charge_voltage_max
= chg
->info
->voltage_max_design_uv
;
351 ret
= pm8916_lbc_charger_configure(chg
);
358 return dev_err_probe(dev
, ret
, "Unable to communicate with device\n");
361 return dev_err_probe(dev
, -ENODEV
, "Device reported wrong type: 0x%X\n", tmp
);
364 static const struct of_device_id pm8916_lbc_charger_of_match
[] = {
365 { .compatible
= "qcom,pm8916-lbc", },
368 MODULE_DEVICE_TABLE(of
, pm8916_lbc_charger_of_match
);
370 static struct platform_driver pm8916_lbc_charger_driver
= {
372 .name
= "pm8916-lbc",
373 .of_match_table
= pm8916_lbc_charger_of_match
,
375 .probe
= pm8916_lbc_charger_probe
,
377 module_platform_driver(pm8916_lbc_charger_driver
);
379 MODULE_DESCRIPTION("pm8916 LBC driver");
380 MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>");
381 MODULE_LICENSE("GPL");