xref: /linux/drivers/extcon/extcon-axp288.c (revision d580d74ea2836edbbd49cd791eb5d0acad7b14aa)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
4  *
5  * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com>
6  * Copyright (C) 2015 Intel Corporation
7  * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
8  */
9 
10 #include <linux/acpi.h>
11 #include <linux/module.h>
12 #include <linux/kernel.h>
13 #include <linux/io.h>
14 #include <linux/slab.h>
15 #include <linux/interrupt.h>
16 #include <linux/platform_device.h>
17 #include <linux/property.h>
18 #include <linux/notifier.h>
19 #include <linux/extcon-provider.h>
20 #include <linux/regmap.h>
21 #include <linux/mfd/axp20x.h>
22 #include <linux/usb/role.h>
23 #include <linux/workqueue.h>
24 
25 #include <asm/cpu_device_id.h>
26 #include <asm/intel-family.h>
27 #include <asm/iosf_mbi.h>
28 
29 /* Power source status register */
30 #define PS_STAT_VBUS_TRIGGER		BIT(0)
31 #define PS_STAT_BAT_CHRG_DIR		BIT(2)
32 #define PS_STAT_VBUS_ABOVE_VHOLD	BIT(3)
33 #define PS_STAT_VBUS_VALID		BIT(4)
34 #define PS_STAT_VBUS_PRESENT		BIT(5)
35 
36 /* BC module global register */
37 #define BC_GLOBAL_RUN			BIT(0)
38 #define BC_GLOBAL_DET_STAT		BIT(2)
39 #define BC_GLOBAL_DBP_TOUT		BIT(3)
40 #define BC_GLOBAL_VLGC_COM_SEL		BIT(4)
41 #define BC_GLOBAL_DCD_TOUT_MASK		(BIT(6)|BIT(5))
42 #define BC_GLOBAL_DCD_TOUT_300MS	0
43 #define BC_GLOBAL_DCD_TOUT_100MS	1
44 #define BC_GLOBAL_DCD_TOUT_500MS	2
45 #define BC_GLOBAL_DCD_TOUT_900MS	3
46 #define BC_GLOBAL_DCD_DET_SEL		BIT(7)
47 
48 /* BC module vbus control and status register */
49 #define VBUS_CNTL_DPDM_PD_EN		BIT(4)
50 #define VBUS_CNTL_DPDM_FD_EN		BIT(5)
51 #define VBUS_CNTL_FIRST_PO_STAT		BIT(6)
52 
53 /* BC USB status register */
54 #define USB_STAT_BUS_STAT_MASK		(BIT(3)|BIT(2)|BIT(1)|BIT(0))
55 #define USB_STAT_BUS_STAT_SHIFT		0
56 #define USB_STAT_BUS_STAT_ATHD		0
57 #define USB_STAT_BUS_STAT_CONN		1
58 #define USB_STAT_BUS_STAT_SUSP		2
59 #define USB_STAT_BUS_STAT_CONF		3
60 #define USB_STAT_USB_SS_MODE		BIT(4)
61 #define USB_STAT_DEAD_BAT_DET		BIT(6)
62 #define USB_STAT_DBP_UNCFG		BIT(7)
63 
64 /* BC detect status register */
65 #define DET_STAT_MASK			(BIT(7)|BIT(6)|BIT(5))
66 #define DET_STAT_SHIFT			5
67 #define DET_STAT_SDP			1
68 #define DET_STAT_CDP			2
69 #define DET_STAT_DCP			3
70 
71 enum axp288_extcon_reg {
72 	AXP288_PS_STAT_REG		= 0x00,
73 	AXP288_PS_BOOT_REASON_REG	= 0x02,
74 	AXP288_BC_GLOBAL_REG		= 0x2c,
75 	AXP288_BC_VBUS_CNTL_REG		= 0x2d,
76 	AXP288_BC_USB_STAT_REG		= 0x2e,
77 	AXP288_BC_DET_STAT_REG		= 0x2f,
78 };
79 
80 enum axp288_extcon_irq {
81 	VBUS_FALLING_IRQ = 0,
82 	VBUS_RISING_IRQ,
83 	MV_CHNG_IRQ,
84 	BC_USB_CHNG_IRQ,
85 	EXTCON_IRQ_END,
86 };
87 
88 static const unsigned int axp288_extcon_cables[] = {
89 	EXTCON_CHG_USB_SDP,
90 	EXTCON_CHG_USB_CDP,
91 	EXTCON_CHG_USB_DCP,
92 	EXTCON_USB,
93 	EXTCON_NONE,
94 };
95 
96 struct axp288_extcon_info {
97 	struct device *dev;
98 	struct regmap *regmap;
99 	struct regmap_irq_chip_data *regmap_irqc;
100 	struct usb_role_switch *role_sw;
101 	struct work_struct role_work;
102 	int irq[EXTCON_IRQ_END];
103 	struct extcon_dev *edev;
104 	struct extcon_dev *id_extcon;
105 	struct notifier_block id_nb;
106 	unsigned int previous_cable;
107 	bool vbus_attach;
108 };
109 
110 static const struct x86_cpu_id cherry_trail_cpu_ids[] = {
111 	X86_MATCH_VFM(INTEL_ATOM_AIRMONT,	NULL),
112 	{}
113 };
114 
115 /* Power up/down reason string array */
116 static const char * const axp288_pwr_up_down_info[] = {
117 	"Last wake caused by user pressing the power button",
118 	"Last wake caused by a charger insertion",
119 	"Last wake caused by a battery insertion",
120 	"Last wake caused by SOC initiated global reset",
121 	"Last wake caused by cold reset",
122 	"Last shutdown caused by PMIC UVLO threshold",
123 	"Last shutdown caused by SOC initiated cold off",
124 	"Last shutdown caused by user pressing the power button",
125 };
126 
127 /*
128  * Decode and log the given "reset source indicator" (rsi)
129  * register and then clear it.
130  */
axp288_extcon_log_rsi(struct axp288_extcon_info * info)131 static void axp288_extcon_log_rsi(struct axp288_extcon_info *info)
132 {
133 	unsigned int val, i, clear_mask = 0;
134 	unsigned long bits;
135 	int ret;
136 
137 	ret = regmap_read(info->regmap, AXP288_PS_BOOT_REASON_REG, &val);
138 	if (ret < 0) {
139 		dev_err(info->dev, "failed to read reset source indicator\n");
140 		return;
141 	}
142 
143 	bits = val & GENMASK(ARRAY_SIZE(axp288_pwr_up_down_info) - 1, 0);
144 	for_each_set_bit(i, &bits, ARRAY_SIZE(axp288_pwr_up_down_info))
145 		dev_dbg(info->dev, "%s\n", axp288_pwr_up_down_info[i]);
146 	clear_mask = bits;
147 
148 	/* Clear the register value for next reboot (write 1 to clear bit) */
149 	regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
150 }
151 
152 /*
153  * The below code to control the USB role-switch on devices with an AXP288
154  * may seem out of place, but there are 2 reasons why this is the best place
155  * to control the USB role-switch on such devices:
156  * 1) On many devices the USB role is controlled by AML code, but the AML code
157  *    only switches between the host and none roles, because of Windows not
158  *    really using device mode. To make device mode work we need to toggle
159  *    between the none/device roles based on Vbus presence, and this driver
160  *    gets interrupts on Vbus insertion / removal.
161  * 2) In order for our BC1.2 charger detection to work properly the role
162  *    mux must be properly set to device mode before we do the detection.
163  */
164 
165 /* Returns the id-pin value, note pulled low / false == host-mode */
axp288_get_id_pin(struct axp288_extcon_info * info)166 static bool axp288_get_id_pin(struct axp288_extcon_info *info)
167 {
168 	enum usb_role role;
169 
170 	if (info->id_extcon)
171 		return extcon_get_state(info->id_extcon, EXTCON_USB_HOST) <= 0;
172 
173 	/* We cannot access the id-pin, see what mode the AML code has set */
174 	role = usb_role_switch_get_role(info->role_sw);
175 	return role != USB_ROLE_HOST;
176 }
177 
axp288_usb_role_work(struct work_struct * work)178 static void axp288_usb_role_work(struct work_struct *work)
179 {
180 	struct axp288_extcon_info *info =
181 		container_of(work, struct axp288_extcon_info, role_work);
182 	enum usb_role role;
183 	bool id_pin;
184 	int ret;
185 
186 	id_pin = axp288_get_id_pin(info);
187 	if (!id_pin)
188 		role = USB_ROLE_HOST;
189 	else if (info->vbus_attach)
190 		role = USB_ROLE_DEVICE;
191 	else
192 		role = USB_ROLE_NONE;
193 
194 	ret = usb_role_switch_set_role(info->role_sw, role);
195 	if (ret)
196 		dev_err(info->dev, "failed to set role: %d\n", ret);
197 }
198 
axp288_get_vbus_attach(struct axp288_extcon_info * info)199 static bool axp288_get_vbus_attach(struct axp288_extcon_info *info)
200 {
201 	int ret, pwr_stat;
202 
203 	ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
204 	if (ret < 0) {
205 		dev_err(info->dev, "failed to read vbus status\n");
206 		return false;
207 	}
208 
209 	return !!(pwr_stat & PS_STAT_VBUS_VALID);
210 }
211 
axp288_handle_chrg_det_event(struct axp288_extcon_info * info)212 static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
213 {
214 	int ret, stat, cfg;
215 	u8 chrg_type;
216 	unsigned int cable = info->previous_cable;
217 	bool vbus_attach = false;
218 
219 	ret = iosf_mbi_block_punit_i2c_access();
220 	if (ret < 0)
221 		return ret;
222 
223 	vbus_attach = axp288_get_vbus_attach(info);
224 	if (!vbus_attach)
225 		goto no_vbus;
226 
227 	/* Check charger detection completion status */
228 	ret = regmap_read(info->regmap, AXP288_BC_GLOBAL_REG, &cfg);
229 	if (ret < 0)
230 		goto dev_det_ret;
231 	if (cfg & BC_GLOBAL_DET_STAT) {
232 		dev_dbg(info->dev, "can't complete the charger detection\n");
233 		goto dev_det_ret;
234 	}
235 
236 	ret = regmap_read(info->regmap, AXP288_BC_DET_STAT_REG, &stat);
237 	if (ret < 0)
238 		goto dev_det_ret;
239 
240 	chrg_type = (stat & DET_STAT_MASK) >> DET_STAT_SHIFT;
241 
242 	switch (chrg_type) {
243 	case DET_STAT_SDP:
244 		dev_dbg(info->dev, "sdp cable is connected\n");
245 		cable = EXTCON_CHG_USB_SDP;
246 		break;
247 	case DET_STAT_CDP:
248 		dev_dbg(info->dev, "cdp cable is connected\n");
249 		cable = EXTCON_CHG_USB_CDP;
250 		break;
251 	case DET_STAT_DCP:
252 		dev_dbg(info->dev, "dcp cable is connected\n");
253 		cable = EXTCON_CHG_USB_DCP;
254 		break;
255 	default:
256 		dev_warn(info->dev, "unknown (reserved) bc detect result\n");
257 		cable = EXTCON_CHG_USB_SDP;
258 	}
259 
260 no_vbus:
261 	iosf_mbi_unblock_punit_i2c_access();
262 
263 	extcon_set_state_sync(info->edev, info->previous_cable, false);
264 	if (info->previous_cable == EXTCON_CHG_USB_SDP)
265 		extcon_set_state_sync(info->edev, EXTCON_USB, false);
266 
267 	if (vbus_attach) {
268 		extcon_set_state_sync(info->edev, cable, vbus_attach);
269 		if (cable == EXTCON_CHG_USB_SDP)
270 			extcon_set_state_sync(info->edev, EXTCON_USB,
271 						vbus_attach);
272 
273 		info->previous_cable = cable;
274 	}
275 
276 	if (info->role_sw && info->vbus_attach != vbus_attach) {
277 		info->vbus_attach = vbus_attach;
278 		/* Setting the role can take a while */
279 		queue_work(system_long_wq, &info->role_work);
280 	}
281 
282 	return 0;
283 
284 dev_det_ret:
285 	iosf_mbi_unblock_punit_i2c_access();
286 
287 	if (ret < 0)
288 		dev_err(info->dev, "failed to detect BC Mod\n");
289 
290 	return ret;
291 }
292 
axp288_extcon_id_evt(struct notifier_block * nb,unsigned long event,void * param)293 static int axp288_extcon_id_evt(struct notifier_block *nb,
294 				unsigned long event, void *param)
295 {
296 	struct axp288_extcon_info *info =
297 		container_of(nb, struct axp288_extcon_info, id_nb);
298 
299 	/* We may not sleep and setting the role can take a while */
300 	queue_work(system_long_wq, &info->role_work);
301 
302 	return NOTIFY_OK;
303 }
304 
axp288_extcon_isr(int irq,void * data)305 static irqreturn_t axp288_extcon_isr(int irq, void *data)
306 {
307 	struct axp288_extcon_info *info = data;
308 	int ret;
309 
310 	ret = axp288_handle_chrg_det_event(info);
311 	if (ret < 0)
312 		dev_err(info->dev, "failed to handle the interrupt\n");
313 
314 	return IRQ_HANDLED;
315 }
316 
axp288_extcon_enable(struct axp288_extcon_info * info)317 static int axp288_extcon_enable(struct axp288_extcon_info *info)
318 {
319 	int ret = 0;
320 
321 	ret = iosf_mbi_block_punit_i2c_access();
322 	if (ret < 0)
323 		return ret;
324 
325 	regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
326 						BC_GLOBAL_RUN, 0);
327 	/* Enable the charger detection logic */
328 	regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
329 					BC_GLOBAL_RUN, BC_GLOBAL_RUN);
330 
331 	iosf_mbi_unblock_punit_i2c_access();
332 
333 	return ret;
334 }
335 
axp288_put_role_sw(void * data)336 static void axp288_put_role_sw(void *data)
337 {
338 	struct axp288_extcon_info *info = data;
339 
340 	cancel_work_sync(&info->role_work);
341 	usb_role_switch_put(info->role_sw);
342 }
343 
axp288_extcon_find_role_sw(struct axp288_extcon_info * info)344 static int axp288_extcon_find_role_sw(struct axp288_extcon_info *info)
345 {
346 	const struct software_node *swnode;
347 	struct fwnode_handle *fwnode;
348 
349 	if (!x86_match_cpu(cherry_trail_cpu_ids))
350 		return 0;
351 
352 	swnode = software_node_find_by_name(NULL, "intel-xhci-usb-sw");
353 	if (!swnode)
354 		return -EPROBE_DEFER;
355 
356 	fwnode = software_node_fwnode(swnode);
357 	info->role_sw = usb_role_switch_find_by_fwnode(fwnode);
358 	fwnode_handle_put(fwnode);
359 
360 	return info->role_sw ? 0 : -EPROBE_DEFER;
361 }
362 
axp288_extcon_probe(struct platform_device * pdev)363 static int axp288_extcon_probe(struct platform_device *pdev)
364 {
365 	struct axp288_extcon_info *info;
366 	struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
367 	struct device *dev = &pdev->dev;
368 	struct acpi_device *adev;
369 	int ret, i, pirq;
370 
371 	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
372 	if (!info)
373 		return -ENOMEM;
374 
375 	info->dev = &pdev->dev;
376 	info->regmap = axp20x->regmap;
377 	info->regmap_irqc = axp20x->regmap_irqc;
378 	info->previous_cable = EXTCON_NONE;
379 	INIT_WORK(&info->role_work, axp288_usb_role_work);
380 	info->id_nb.notifier_call = axp288_extcon_id_evt;
381 
382 	platform_set_drvdata(pdev, info);
383 
384 	ret = axp288_extcon_find_role_sw(info);
385 	if (ret)
386 		return ret;
387 
388 	if (info->role_sw) {
389 		ret = devm_add_action_or_reset(dev, axp288_put_role_sw, info);
390 		if (ret)
391 			return ret;
392 
393 		adev = acpi_dev_get_first_match_dev("INT3496", NULL, -1);
394 		if (adev) {
395 			info->id_extcon = extcon_get_extcon_dev(acpi_dev_name(adev));
396 			acpi_dev_put(adev);
397 			if (IS_ERR(info->id_extcon))
398 				return PTR_ERR(info->id_extcon);
399 
400 			dev_info(dev, "controlling USB role\n");
401 		} else {
402 			dev_info(dev, "controlling USB role based on Vbus presence\n");
403 		}
404 	}
405 
406 	ret = iosf_mbi_block_punit_i2c_access();
407 	if (ret < 0)
408 		return ret;
409 
410 	info->vbus_attach = axp288_get_vbus_attach(info);
411 
412 	axp288_extcon_log_rsi(info);
413 
414 	iosf_mbi_unblock_punit_i2c_access();
415 
416 	/* Initialize extcon device */
417 	info->edev = devm_extcon_dev_allocate(&pdev->dev,
418 					      axp288_extcon_cables);
419 	if (IS_ERR(info->edev)) {
420 		dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
421 		return PTR_ERR(info->edev);
422 	}
423 
424 	/* Register extcon device */
425 	ret = devm_extcon_dev_register(&pdev->dev, info->edev);
426 	if (ret) {
427 		dev_err(&pdev->dev, "failed to register extcon device\n");
428 		return ret;
429 	}
430 
431 	for (i = 0; i < EXTCON_IRQ_END; i++) {
432 		pirq = platform_get_irq(pdev, i);
433 		if (pirq < 0)
434 			return pirq;
435 
436 		info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
437 		if (info->irq[i] < 0) {
438 			dev_err(&pdev->dev,
439 				"failed to get virtual interrupt=%d\n", pirq);
440 			ret = info->irq[i];
441 			return ret;
442 		}
443 
444 		ret = devm_request_threaded_irq(&pdev->dev, info->irq[i],
445 				NULL, axp288_extcon_isr,
446 				IRQF_ONESHOT | IRQF_NO_SUSPEND,
447 				pdev->name, info);
448 		if (ret) {
449 			dev_err(&pdev->dev, "failed to request interrupt=%d\n",
450 							info->irq[i]);
451 			return ret;
452 		}
453 	}
454 
455 	if (info->id_extcon) {
456 		ret = devm_extcon_register_notifier_all(dev, info->id_extcon,
457 							&info->id_nb);
458 		if (ret)
459 			return ret;
460 	}
461 
462 	/* Make sure the role-sw is set correctly before doing BC detection */
463 	if (info->role_sw) {
464 		queue_work(system_long_wq, &info->role_work);
465 		flush_work(&info->role_work);
466 	}
467 
468 	/* Start charger cable type detection */
469 	ret = axp288_extcon_enable(info);
470 	if (ret < 0)
471 		return ret;
472 
473 	device_init_wakeup(dev, true);
474 	platform_set_drvdata(pdev, info);
475 
476 	return 0;
477 }
478 
axp288_extcon_suspend(struct device * dev)479 static int __maybe_unused axp288_extcon_suspend(struct device *dev)
480 {
481 	struct axp288_extcon_info *info = dev_get_drvdata(dev);
482 
483 	if (device_may_wakeup(dev))
484 		enable_irq_wake(info->irq[VBUS_RISING_IRQ]);
485 
486 	return 0;
487 }
488 
axp288_extcon_resume(struct device * dev)489 static int __maybe_unused axp288_extcon_resume(struct device *dev)
490 {
491 	struct axp288_extcon_info *info = dev_get_drvdata(dev);
492 
493 	/*
494 	 * Wakeup when a charger is connected to do charger-type
495 	 * connection and generate an extcon event which makes the
496 	 * axp288 charger driver set the input current limit.
497 	 */
498 	if (device_may_wakeup(dev))
499 		disable_irq_wake(info->irq[VBUS_RISING_IRQ]);
500 
501 	return 0;
502 }
503 
504 static SIMPLE_DEV_PM_OPS(axp288_extcon_pm_ops, axp288_extcon_suspend,
505 			 axp288_extcon_resume);
506 
507 static const struct platform_device_id axp288_extcon_table[] = {
508 	{ .name = "axp288_extcon" },
509 	{},
510 };
511 MODULE_DEVICE_TABLE(platform, axp288_extcon_table);
512 
513 static struct platform_driver axp288_extcon_driver = {
514 	.probe = axp288_extcon_probe,
515 	.id_table = axp288_extcon_table,
516 	.driver = {
517 		.name = "axp288_extcon",
518 		.pm = &axp288_extcon_pm_ops,
519 	},
520 };
521 module_platform_driver(axp288_extcon_driver);
522 
523 MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
524 MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
525 MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
526 MODULE_LICENSE("GPL v2");
527