Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * drd.c - DesignWare USB2 DRD Controller Dual-role support
  4 *
  5 * Copyright (C) 2020 STMicroelectronics
  6 *
  7 * Author(s): Amelie Delaunay <amelie.delaunay@st.com>
  8 */
  9
 10#include <linux/iopoll.h>
 11#include <linux/platform_device.h>
 12#include <linux/usb/role.h>
 13#include "core.h"
 14
 15static void dwc2_ovr_init(struct dwc2_hsotg *hsotg)
 16{
 17	unsigned long flags;
 18	u32 gotgctl;
 19
 20	spin_lock_irqsave(&hsotg->lock, flags);
 21
 22	gotgctl = dwc2_readl(hsotg, GOTGCTL);
 23	gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | GOTGCTL_VBVALOEN;
 24	gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS;
 25	gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL);
 26	dwc2_writel(hsotg, gotgctl, GOTGCTL);
 27
 28	dwc2_force_mode(hsotg, false);
 29
 30	spin_unlock_irqrestore(&hsotg->lock, flags);
 31}
 32
 33static int dwc2_ovr_avalid(struct dwc2_hsotg *hsotg, bool valid)
 34{
 35	u32 gotgctl = dwc2_readl(hsotg, GOTGCTL);
 36
 37	/* Check if A-Session is already in the right state */
 38	if ((valid && (gotgctl & GOTGCTL_ASESVLD)) ||
 39	    (!valid && !(gotgctl & GOTGCTL_ASESVLD)))
 40		return -EALREADY;
 41
 42	if (valid)
 43		gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL;
 44	else
 45		gotgctl &= ~(GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL);
 46	dwc2_writel(hsotg, gotgctl, GOTGCTL);
 47
 48	return 0;
 49}
 50
 51static int dwc2_ovr_bvalid(struct dwc2_hsotg *hsotg, bool valid)
 52{
 53	u32 gotgctl = dwc2_readl(hsotg, GOTGCTL);
 54
 55	/* Check if B-Session is already in the right state */
 56	if ((valid && (gotgctl & GOTGCTL_BSESVLD)) ||
 57	    (!valid && !(gotgctl & GOTGCTL_BSESVLD)))
 58		return -EALREADY;
 59
 60	if (valid)
 61		gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL;
 62	else
 63		gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL);
 64	dwc2_writel(hsotg, gotgctl, GOTGCTL);
 65
 66	return 0;
 67}
 68
 69static int dwc2_drd_role_sw_set(struct usb_role_switch *sw, enum usb_role role)
 70{
 71	struct dwc2_hsotg *hsotg = usb_role_switch_get_drvdata(sw);
 72	unsigned long flags;
 73	int already = 0;
 74
 75	/* Skip session not in line with dr_mode */
 76	if ((role == USB_ROLE_DEVICE && hsotg->dr_mode == USB_DR_MODE_HOST) ||
 77	    (role == USB_ROLE_HOST && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL))
 78		return -EINVAL;
 79
 80#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \
 81	IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
 82	/* Skip session if core is in test mode */
 83	if (role == USB_ROLE_NONE && hsotg->test_mode) {
 84		dev_dbg(hsotg->dev, "Core is in test mode\n");
 85		return -EBUSY;
 86	}
 87#endif
 88
 89	spin_lock_irqsave(&hsotg->lock, flags);
 90
 91	if (role == USB_ROLE_HOST) {
 92		already = dwc2_ovr_avalid(hsotg, true);
 93	} else if (role == USB_ROLE_DEVICE) {
 94		already = dwc2_ovr_bvalid(hsotg, true);
 95		/* This clear DCTL.SFTDISCON bit */
 96		dwc2_hsotg_core_connect(hsotg);
 97	} else {
 98		if (dwc2_is_device_mode(hsotg)) {
 99			if (!dwc2_ovr_bvalid(hsotg, false))
100				/* This set DCTL.SFTDISCON bit */
101				dwc2_hsotg_core_disconnect(hsotg);
102		} else {
103			dwc2_ovr_avalid(hsotg, false);
104		}
105	}
106
107	spin_unlock_irqrestore(&hsotg->lock, flags);
108
109	if (!already && hsotg->dr_mode == USB_DR_MODE_OTG)
110		/* This will raise a Connector ID Status Change Interrupt */
111		dwc2_force_mode(hsotg, role == USB_ROLE_HOST);
112
113	dev_dbg(hsotg->dev, "%s-session valid\n",
114		role == USB_ROLE_NONE ? "No" :
115		role == USB_ROLE_HOST ? "A" : "B");
116
117	return 0;
118}
119
120int dwc2_drd_init(struct dwc2_hsotg *hsotg)
121{
122	struct usb_role_switch_desc role_sw_desc = {0};
123	struct usb_role_switch *role_sw;
124	int ret;
125
126	if (!device_property_read_bool(hsotg->dev, "usb-role-switch"))
127		return 0;
128
129	role_sw_desc.driver_data = hsotg;
130	role_sw_desc.fwnode = dev_fwnode(hsotg->dev);
131	role_sw_desc.set = dwc2_drd_role_sw_set;
132	role_sw_desc.allow_userspace_control = true;
133
134	role_sw = usb_role_switch_register(hsotg->dev, &role_sw_desc);
135	if (IS_ERR(role_sw)) {
136		ret = PTR_ERR(role_sw);
137		dev_err(hsotg->dev,
138			"failed to register role switch: %d\n", ret);
139		return ret;
140	}
141
142	hsotg->role_sw = role_sw;
143
144	/* Enable override and initialize values */
145	dwc2_ovr_init(hsotg);
146
147	return 0;
148}
149
150void dwc2_drd_suspend(struct dwc2_hsotg *hsotg)
151{
152	u32 gintsts, gintmsk;
153
154	if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) {
155		gintmsk = dwc2_readl(hsotg, GINTMSK);
156		gintmsk &= ~GINTSTS_CONIDSTSCHNG;
157		dwc2_writel(hsotg, gintmsk, GINTMSK);
158		gintsts = dwc2_readl(hsotg, GINTSTS);
159		dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS);
160	}
161}
162
163void dwc2_drd_resume(struct dwc2_hsotg *hsotg)
164{
165	u32 gintsts, gintmsk;
166
167	if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) {
168		gintsts = dwc2_readl(hsotg, GINTSTS);
169		dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS);
170		gintmsk = dwc2_readl(hsotg, GINTMSK);
171		gintmsk |= GINTSTS_CONIDSTSCHNG;
172		dwc2_writel(hsotg, gintmsk, GINTMSK);
173	}
174}
175
176void dwc2_drd_exit(struct dwc2_hsotg *hsotg)
177{
178	if (hsotg->role_sw)
179		usb_role_switch_unregister(hsotg->role_sw);
180}