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