File Index
Files and directories
Sign in
FILE

pm8916_lbc.c

File preview with syntax highlighting.

linux-6.19.14/drivers/power/supply/pm8916_lbc.c 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/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(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_MAX, 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(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_IBAT_MAX, tmp);
108	if (ret)
109		goto error;
110
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);
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(chg->regmap, chg->reg[LBC_USB] + PM8916_INT_RT_STS, &tmp);
186	if (ret)
187		return IRQ_HANDLED;
188
189	chg->online = !!(tmp & PM8916_LBC_USB_USBIN_VALID);
190	extcon_set_state_sync(chg->edev, EXTCON_USB, chg->online);
191
192	power_supply_changed(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, "qcom,fast-charge-safe-voltage", &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(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_SAFE, tmp);
215	if (ret)
216		return ret;
217
218	ret = device_property_read_u32(dev, "qcom,fast-charge-safe-current", &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(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_IBAT_SAFE, tmp);
231	if (ret)
232		return ret;
233
234	/* Disable charger timeout. */
235	ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_TCHG_MAX_EN, 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, sizeof(*chg), GFP_KERNEL);
261	if (!chg)
262		return -ENOMEM;
263
264	chg->dev = dev;
265
266	chg->regmap = dev_get_regmap(pdev->dev.parent, NULL);
267	if (!chg->regmap)
268		return -ENODEV;
269
270	len = device_property_count_u32(dev, "reg");
271	if (len < 0)
272		return len;
273	if (len != 4)
274		return dev_err_probe(dev, -EINVAL,
275				     "Wrong amount of reg values: %d (4 expected)\n", len);
276
277	ret = device_property_read_u32_array(dev, "reg", chg->reg, len);
278	if (ret)
279		return ret;
280
281	ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_PERPH_TYPE, &tmp, 2);
282	if (ret)
283		goto comm_error;
284	if (tmp != PM8916_LBC_CHGR_TYPE)
285		goto type_error;
286
287	ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_BAT_IF] + PM8916_PERPH_TYPE, &tmp, 2);
288	if (ret)
289		goto comm_error;
290	if (tmp != PM8916_LBC_BAT_IF_TYPE)
291		goto type_error;
292
293	ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_USB] + PM8916_PERPH_TYPE, &tmp, 2);
294	if (ret)
295		goto comm_error;
296	if (tmp != PM8916_LBC_USB_TYPE)
297		goto type_error;
298
299	ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_MISC] + PM8916_PERPH_TYPE, &tmp, 2);
300	if (ret)
301		goto comm_error;
302	if (tmp != PM8916_LBC_MISC_TYPE)
303		goto type_error;
304
305	ret = regmap_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_CHG_OPTION, &tmp);
306	if (ret)
307		goto comm_error;
308	if (tmp != PM8916_LBC_CHGR_PMIC_CHARGER)
309		dev_err_probe(dev, -ENODEV, "The system is using an external charger\n");
310
311	ret = pm8916_lbc_charger_probe_dt(chg);
312	if (ret)
313		dev_err_probe(dev, ret, "Error while parsing device tree\n");
314
315	psy_cfg.drv_data = chg;
316	psy_cfg.fwnode = dev_fwnode(dev);
317
318	chg->charger = devm_power_supply_register(dev, &pm8916_lbc_charger_psy_desc, &psy_cfg);
319	if (IS_ERR(chg->charger))
320		return dev_err_probe(dev, PTR_ERR(chg->charger), "Unable to register charger\n");
321
322	ret = power_supply_get_battery_info(chg->charger, &chg->info);
323	if (ret)
324		return dev_err_probe(dev, ret, "Unable to get battery info\n");
325
326	irq = platform_get_irq_byname(pdev, "usb_vbus");
327	if (irq < 0)
328		return irq;
329
330	chg->edev = devm_extcon_dev_allocate(dev, pm8916_lbc_charger_cable);
331	if (IS_ERR(chg->edev))
332		return PTR_ERR(chg->edev);
333
334	ret = devm_extcon_dev_register(dev, chg->edev);
335	if (ret < 0)
336		return dev_err_probe(dev, ret, "failed to register extcon device\n");
337
338	ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_lbc_charger_state_changed_irq,
339					IRQF_ONESHOT, "pm8916_lbc", chg);
340	if (ret)
341		return ret;
342
343	ret = regmap_read(chg->regmap, chg->reg[LBC_USB] + PM8916_INT_RT_STS, &tmp);
344	if (ret)
345		goto comm_error;
346
347	chg->online = !!(tmp & PM8916_LBC_USB_USBIN_VALID);
348	extcon_set_state_sync(chg->edev, EXTCON_USB, 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, ret, "Unable to communicate with device\n");
359
360type_error:
361	return dev_err_probe(dev, -ENODEV, "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");
~/ Read-only file preview.