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