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