xref: /linux/drivers/usb/dwc2/drd.c (revision 3a39d672e7f48b8d6b91a09afa4b55352773b4b5)
117f93402SAmelie Delaunay // SPDX-License-Identifier: GPL-2.0
217f93402SAmelie Delaunay /*
317f93402SAmelie Delaunay  * drd.c - DesignWare USB2 DRD Controller Dual-role support
417f93402SAmelie Delaunay  *
517f93402SAmelie Delaunay  * Copyright (C) 2020 STMicroelectronics
617f93402SAmelie Delaunay  *
717f93402SAmelie Delaunay  * Author(s): Amelie Delaunay <amelie.delaunay@st.com>
817f93402SAmelie Delaunay  */
917f93402SAmelie Delaunay 
108d387f61SAmelie Delaunay #include <linux/clk.h>
1117f93402SAmelie Delaunay #include <linux/iopoll.h>
1217f93402SAmelie Delaunay #include <linux/platform_device.h>
1317f93402SAmelie Delaunay #include <linux/usb/role.h>
1417f93402SAmelie Delaunay #include "core.h"
1517f93402SAmelie Delaunay 
163ad02e0eSFabrice Gasnier #define dwc2_ovr_gotgctl(gotgctl) \
173ad02e0eSFabrice Gasnier 	((gotgctl) |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | GOTGCTL_VBVALOEN | \
183ad02e0eSFabrice Gasnier 	 GOTGCTL_DBNCE_FLTR_BYPASS)
193ad02e0eSFabrice Gasnier 
dwc2_ovr_init(struct dwc2_hsotg * hsotg)2017f93402SAmelie Delaunay static void dwc2_ovr_init(struct dwc2_hsotg *hsotg)
2117f93402SAmelie Delaunay {
2217f93402SAmelie Delaunay 	unsigned long flags;
2317f93402SAmelie Delaunay 	u32 gotgctl;
2417f93402SAmelie Delaunay 
2517f93402SAmelie Delaunay 	spin_lock_irqsave(&hsotg->lock, flags);
2617f93402SAmelie Delaunay 
2717f93402SAmelie Delaunay 	gotgctl = dwc2_readl(hsotg, GOTGCTL);
283ad02e0eSFabrice Gasnier 	dwc2_ovr_gotgctl(gotgctl);
2917f93402SAmelie Delaunay 	gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL);
30e14acb87SFabrice Gasnier 	if (hsotg->role_sw_default_mode == USB_DR_MODE_HOST)
31e14acb87SFabrice Gasnier 		gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL;
32e14acb87SFabrice Gasnier 	else if (hsotg->role_sw_default_mode == USB_DR_MODE_PERIPHERAL)
33e14acb87SFabrice Gasnier 		gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL;
3417f93402SAmelie Delaunay 	dwc2_writel(hsotg, gotgctl, GOTGCTL);
3517f93402SAmelie Delaunay 
3617f93402SAmelie Delaunay 	spin_unlock_irqrestore(&hsotg->lock, flags);
37b2cab2a2SAmelie Delaunay 
3882f5332dSZiyang Huang 	dwc2_force_mode(hsotg, (hsotg->dr_mode == USB_DR_MODE_HOST) ||
3982f5332dSZiyang Huang 				(hsotg->role_sw_default_mode == USB_DR_MODE_HOST));
4017f93402SAmelie Delaunay }
4117f93402SAmelie Delaunay 
dwc2_ovr_avalid(struct dwc2_hsotg * hsotg,bool valid)4217f93402SAmelie Delaunay static int dwc2_ovr_avalid(struct dwc2_hsotg *hsotg, bool valid)
4317f93402SAmelie Delaunay {
4417f93402SAmelie Delaunay 	u32 gotgctl = dwc2_readl(hsotg, GOTGCTL);
4517f93402SAmelie Delaunay 
4617f93402SAmelie Delaunay 	/* Check if A-Session is already in the right state */
4717f93402SAmelie Delaunay 	if ((valid && (gotgctl & GOTGCTL_ASESVLD)) ||
4817f93402SAmelie Delaunay 	    (!valid && !(gotgctl & GOTGCTL_ASESVLD)))
4917f93402SAmelie Delaunay 		return -EALREADY;
5017f93402SAmelie Delaunay 
513ad02e0eSFabrice Gasnier 	/* Always enable overrides to handle the resume case */
523ad02e0eSFabrice Gasnier 	dwc2_ovr_gotgctl(gotgctl);
533ad02e0eSFabrice Gasnier 
541ad707f5SAmelie Delaunay 	gotgctl &= ~GOTGCTL_BVALOVAL;
5517f93402SAmelie Delaunay 	if (valid)
5617f93402SAmelie Delaunay 		gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL;
5717f93402SAmelie Delaunay 	else
5817f93402SAmelie Delaunay 		gotgctl &= ~(GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL);
5917f93402SAmelie Delaunay 	dwc2_writel(hsotg, gotgctl, GOTGCTL);
6017f93402SAmelie Delaunay 
6117f93402SAmelie Delaunay 	return 0;
6217f93402SAmelie Delaunay }
6317f93402SAmelie Delaunay 
dwc2_ovr_bvalid(struct dwc2_hsotg * hsotg,bool valid)6417f93402SAmelie Delaunay static int dwc2_ovr_bvalid(struct dwc2_hsotg *hsotg, bool valid)
6517f93402SAmelie Delaunay {
6617f93402SAmelie Delaunay 	u32 gotgctl = dwc2_readl(hsotg, GOTGCTL);
6717f93402SAmelie Delaunay 
6817f93402SAmelie Delaunay 	/* Check if B-Session is already in the right state */
6917f93402SAmelie Delaunay 	if ((valid && (gotgctl & GOTGCTL_BSESVLD)) ||
7017f93402SAmelie Delaunay 	    (!valid && !(gotgctl & GOTGCTL_BSESVLD)))
7117f93402SAmelie Delaunay 		return -EALREADY;
7217f93402SAmelie Delaunay 
733ad02e0eSFabrice Gasnier 	/* Always enable overrides to handle the resume case */
743ad02e0eSFabrice Gasnier 	dwc2_ovr_gotgctl(gotgctl);
753ad02e0eSFabrice Gasnier 
761ad707f5SAmelie Delaunay 	gotgctl &= ~GOTGCTL_AVALOVAL;
7717f93402SAmelie Delaunay 	if (valid)
7817f93402SAmelie Delaunay 		gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL;
7917f93402SAmelie Delaunay 	else
8017f93402SAmelie Delaunay 		gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL);
8117f93402SAmelie Delaunay 	dwc2_writel(hsotg, gotgctl, GOTGCTL);
8217f93402SAmelie Delaunay 
8317f93402SAmelie Delaunay 	return 0;
8417f93402SAmelie Delaunay }
8517f93402SAmelie Delaunay 
dwc2_drd_role_sw_set(struct usb_role_switch * sw,enum usb_role role)8617f93402SAmelie Delaunay static int dwc2_drd_role_sw_set(struct usb_role_switch *sw, enum usb_role role)
8717f93402SAmelie Delaunay {
8817f93402SAmelie Delaunay 	struct dwc2_hsotg *hsotg = usb_role_switch_get_drvdata(sw);
8917f93402SAmelie Delaunay 	unsigned long flags;
9017f93402SAmelie Delaunay 	int already = 0;
9117f93402SAmelie Delaunay 
9217f93402SAmelie Delaunay 	/* Skip session not in line with dr_mode */
9317f93402SAmelie Delaunay 	if ((role == USB_ROLE_DEVICE && hsotg->dr_mode == USB_DR_MODE_HOST) ||
9417f93402SAmelie Delaunay 	    (role == USB_ROLE_HOST && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL))
9517f93402SAmelie Delaunay 		return -EINVAL;
9617f93402SAmelie Delaunay 
9717f93402SAmelie Delaunay #if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \
9817f93402SAmelie Delaunay 	IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
9917f93402SAmelie Delaunay 	/* Skip session if core is in test mode */
10017f93402SAmelie Delaunay 	if (role == USB_ROLE_NONE && hsotg->test_mode) {
10117f93402SAmelie Delaunay 		dev_dbg(hsotg->dev, "Core is in test mode\n");
10217f93402SAmelie Delaunay 		return -EBUSY;
10317f93402SAmelie Delaunay 	}
10417f93402SAmelie Delaunay #endif
10517f93402SAmelie Delaunay 
1068d387f61SAmelie Delaunay 	/*
1078d387f61SAmelie Delaunay 	 * In case of USB_DR_MODE_PERIPHERAL, clock is disabled at the end of
1088d387f61SAmelie Delaunay 	 * the probe and enabled on udc_start.
1098d387f61SAmelie Delaunay 	 * If role-switch set is called before the udc_start, we need to enable
1108d387f61SAmelie Delaunay 	 * the clock to read/write GOTGCTL and GUSBCFG registers to override
1118d387f61SAmelie Delaunay 	 * mode and sessions. It is the case if cable is plugged at boot.
1128d387f61SAmelie Delaunay 	 */
1138d387f61SAmelie Delaunay 	if (!hsotg->ll_hw_enabled && hsotg->clk) {
1148d387f61SAmelie Delaunay 		int ret = clk_prepare_enable(hsotg->clk);
1158d387f61SAmelie Delaunay 
1168d387f61SAmelie Delaunay 		if (ret)
1178d387f61SAmelie Delaunay 			return ret;
1188d387f61SAmelie Delaunay 	}
1198d387f61SAmelie Delaunay 
12017f93402SAmelie Delaunay 	spin_lock_irqsave(&hsotg->lock, flags);
12117f93402SAmelie Delaunay 
122e14acb87SFabrice Gasnier 	if (role == USB_ROLE_NONE) {
123e14acb87SFabrice Gasnier 		/* default operation mode when usb role is USB_ROLE_NONE */
124e14acb87SFabrice Gasnier 		if (hsotg->role_sw_default_mode == USB_DR_MODE_HOST)
125e14acb87SFabrice Gasnier 			role = USB_ROLE_HOST;
126e14acb87SFabrice Gasnier 		else if (hsotg->role_sw_default_mode == USB_DR_MODE_PERIPHERAL)
127e14acb87SFabrice Gasnier 			role = USB_ROLE_DEVICE;
128e14acb87SFabrice Gasnier 	}
129e14acb87SFabrice Gasnier 
130*2c6b6afaSTomas Marek 	if ((IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) ||
131*2c6b6afaSTomas Marek 	     IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)) &&
132*2c6b6afaSTomas Marek 	     dwc2_is_device_mode(hsotg) &&
133*2c6b6afaSTomas Marek 	     hsotg->lx_state == DWC2_L2 &&
134*2c6b6afaSTomas Marek 	     hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE &&
135*2c6b6afaSTomas Marek 	     hsotg->bus_suspended &&
136*2c6b6afaSTomas Marek 	     !hsotg->params.no_clock_gating)
137*2c6b6afaSTomas Marek 		dwc2_gadget_exit_clock_gating(hsotg, 0);
138*2c6b6afaSTomas Marek 
13917f93402SAmelie Delaunay 	if (role == USB_ROLE_HOST) {
14017f93402SAmelie Delaunay 		already = dwc2_ovr_avalid(hsotg, true);
14117f93402SAmelie Delaunay 	} else if (role == USB_ROLE_DEVICE) {
14217f93402SAmelie Delaunay 		already = dwc2_ovr_bvalid(hsotg, true);
14332fde843SFabrice Gasnier 		if (dwc2_is_device_enabled(hsotg)) {
14417f93402SAmelie Delaunay 			/* This clear DCTL.SFTDISCON bit */
14517f93402SAmelie Delaunay 			dwc2_hsotg_core_connect(hsotg);
14632fde843SFabrice Gasnier 		}
14717f93402SAmelie Delaunay 	} else {
14817f93402SAmelie Delaunay 		if (dwc2_is_device_mode(hsotg)) {
14917f93402SAmelie Delaunay 			if (!dwc2_ovr_bvalid(hsotg, false))
15017f93402SAmelie Delaunay 				/* This set DCTL.SFTDISCON bit */
15117f93402SAmelie Delaunay 				dwc2_hsotg_core_disconnect(hsotg);
15217f93402SAmelie Delaunay 		} else {
15317f93402SAmelie Delaunay 			dwc2_ovr_avalid(hsotg, false);
15417f93402SAmelie Delaunay 		}
15517f93402SAmelie Delaunay 	}
15617f93402SAmelie Delaunay 
15717f93402SAmelie Delaunay 	spin_unlock_irqrestore(&hsotg->lock, flags);
15817f93402SAmelie Delaunay 
15917f93402SAmelie Delaunay 	if (!already && hsotg->dr_mode == USB_DR_MODE_OTG)
16017f93402SAmelie Delaunay 		/* This will raise a Connector ID Status Change Interrupt */
16117f93402SAmelie Delaunay 		dwc2_force_mode(hsotg, role == USB_ROLE_HOST);
16217f93402SAmelie Delaunay 
1638d387f61SAmelie Delaunay 	if (!hsotg->ll_hw_enabled && hsotg->clk)
1648d387f61SAmelie Delaunay 		clk_disable_unprepare(hsotg->clk);
1658d387f61SAmelie Delaunay 
16617f93402SAmelie Delaunay 	dev_dbg(hsotg->dev, "%s-session valid\n",
16717f93402SAmelie Delaunay 		role == USB_ROLE_NONE ? "No" :
16817f93402SAmelie Delaunay 		role == USB_ROLE_HOST ? "A" : "B");
16917f93402SAmelie Delaunay 
17017f93402SAmelie Delaunay 	return 0;
17117f93402SAmelie Delaunay }
17217f93402SAmelie Delaunay 
dwc2_drd_init(struct dwc2_hsotg * hsotg)17317f93402SAmelie Delaunay int dwc2_drd_init(struct dwc2_hsotg *hsotg)
17417f93402SAmelie Delaunay {
17517f93402SAmelie Delaunay 	struct usb_role_switch_desc role_sw_desc = {0};
17617f93402SAmelie Delaunay 	struct usb_role_switch *role_sw;
17717f93402SAmelie Delaunay 	int ret;
17817f93402SAmelie Delaunay 
17917f93402SAmelie Delaunay 	if (!device_property_read_bool(hsotg->dev, "usb-role-switch"))
18017f93402SAmelie Delaunay 		return 0;
18117f93402SAmelie Delaunay 
182e14acb87SFabrice Gasnier 	hsotg->role_sw_default_mode = usb_get_role_switch_default_mode(hsotg->dev);
18317f93402SAmelie Delaunay 	role_sw_desc.driver_data = hsotg;
18417f93402SAmelie Delaunay 	role_sw_desc.fwnode = dev_fwnode(hsotg->dev);
18517f93402SAmelie Delaunay 	role_sw_desc.set = dwc2_drd_role_sw_set;
18617f93402SAmelie Delaunay 	role_sw_desc.allow_userspace_control = true;
18717f93402SAmelie Delaunay 
18817f93402SAmelie Delaunay 	role_sw = usb_role_switch_register(hsotg->dev, &role_sw_desc);
18917f93402SAmelie Delaunay 	if (IS_ERR(role_sw)) {
19017f93402SAmelie Delaunay 		ret = PTR_ERR(role_sw);
19117f93402SAmelie Delaunay 		dev_err(hsotg->dev,
19217f93402SAmelie Delaunay 			"failed to register role switch: %d\n", ret);
19317f93402SAmelie Delaunay 		return ret;
19417f93402SAmelie Delaunay 	}
19517f93402SAmelie Delaunay 
19617f93402SAmelie Delaunay 	hsotg->role_sw = role_sw;
19717f93402SAmelie Delaunay 
19817f93402SAmelie Delaunay 	/* Enable override and initialize values */
19917f93402SAmelie Delaunay 	dwc2_ovr_init(hsotg);
20017f93402SAmelie Delaunay 
20117f93402SAmelie Delaunay 	return 0;
20217f93402SAmelie Delaunay }
20317f93402SAmelie Delaunay 
dwc2_drd_suspend(struct dwc2_hsotg * hsotg)20417f93402SAmelie Delaunay void dwc2_drd_suspend(struct dwc2_hsotg *hsotg)
20517f93402SAmelie Delaunay {
20617f93402SAmelie Delaunay 	u32 gintsts, gintmsk;
20717f93402SAmelie Delaunay 
20817f93402SAmelie Delaunay 	if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) {
20917f93402SAmelie Delaunay 		gintmsk = dwc2_readl(hsotg, GINTMSK);
21017f93402SAmelie Delaunay 		gintmsk &= ~GINTSTS_CONIDSTSCHNG;
21117f93402SAmelie Delaunay 		dwc2_writel(hsotg, gintmsk, GINTMSK);
21217f93402SAmelie Delaunay 		gintsts = dwc2_readl(hsotg, GINTSTS);
21317f93402SAmelie Delaunay 		dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS);
21417f93402SAmelie Delaunay 	}
21517f93402SAmelie Delaunay }
21617f93402SAmelie Delaunay 
dwc2_drd_resume(struct dwc2_hsotg * hsotg)21717f93402SAmelie Delaunay void dwc2_drd_resume(struct dwc2_hsotg *hsotg)
21817f93402SAmelie Delaunay {
21917f93402SAmelie Delaunay 	u32 gintsts, gintmsk;
2203ad02e0eSFabrice Gasnier 	enum usb_role role;
2213ad02e0eSFabrice Gasnier 
2223ad02e0eSFabrice Gasnier 	if (hsotg->role_sw) {
2233ad02e0eSFabrice Gasnier 		/* get last known role (as the get ops isn't implemented by this driver) */
2243ad02e0eSFabrice Gasnier 		role = usb_role_switch_get_role(hsotg->role_sw);
2253ad02e0eSFabrice Gasnier 
2263ad02e0eSFabrice Gasnier 		if (role == USB_ROLE_NONE) {
2273ad02e0eSFabrice Gasnier 			if (hsotg->role_sw_default_mode == USB_DR_MODE_HOST)
2283ad02e0eSFabrice Gasnier 				role = USB_ROLE_HOST;
2293ad02e0eSFabrice Gasnier 			else if (hsotg->role_sw_default_mode == USB_DR_MODE_PERIPHERAL)
2303ad02e0eSFabrice Gasnier 				role = USB_ROLE_DEVICE;
2313ad02e0eSFabrice Gasnier 		}
2323ad02e0eSFabrice Gasnier 
2333ad02e0eSFabrice Gasnier 		/* restore last role that may have been lost */
2343ad02e0eSFabrice Gasnier 		if (role == USB_ROLE_HOST)
2353ad02e0eSFabrice Gasnier 			dwc2_ovr_avalid(hsotg, true);
2363ad02e0eSFabrice Gasnier 		else if (role == USB_ROLE_DEVICE)
2373ad02e0eSFabrice Gasnier 			dwc2_ovr_bvalid(hsotg, true);
2383ad02e0eSFabrice Gasnier 
2393ad02e0eSFabrice Gasnier 		dwc2_force_mode(hsotg, role == USB_ROLE_HOST);
2403ad02e0eSFabrice Gasnier 
2413ad02e0eSFabrice Gasnier 		dev_dbg(hsotg->dev, "resuming %s-session valid\n",
2423ad02e0eSFabrice Gasnier 			role == USB_ROLE_NONE ? "No" :
2433ad02e0eSFabrice Gasnier 			role == USB_ROLE_HOST ? "A" : "B");
2443ad02e0eSFabrice Gasnier 	}
24517f93402SAmelie Delaunay 
24617f93402SAmelie Delaunay 	if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) {
24717f93402SAmelie Delaunay 		gintsts = dwc2_readl(hsotg, GINTSTS);
24817f93402SAmelie Delaunay 		dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS);
24917f93402SAmelie Delaunay 		gintmsk = dwc2_readl(hsotg, GINTMSK);
25017f93402SAmelie Delaunay 		gintmsk |= GINTSTS_CONIDSTSCHNG;
25117f93402SAmelie Delaunay 		dwc2_writel(hsotg, gintmsk, GINTMSK);
25217f93402SAmelie Delaunay 	}
25317f93402SAmelie Delaunay }
25417f93402SAmelie Delaunay 
dwc2_drd_exit(struct dwc2_hsotg * hsotg)25517f93402SAmelie Delaunay void dwc2_drd_exit(struct dwc2_hsotg *hsotg)
25617f93402SAmelie Delaunay {
25717f93402SAmelie Delaunay 	if (hsotg->role_sw)
25817f93402SAmelie Delaunay 		usb_role_switch_unregister(hsotg->role_sw);
25917f93402SAmelie Delaunay }
260