]> Git Repo - J-linux.git/blob - drivers/usb/dwc2/drd.c
Merge tag 'trace-v5.13-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rostedt...
[J-linux.git] / drivers / usb / dwc2 / drd.c
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 <[email protected]>
8  */
9
10 #include <linux/iopoll.h>
11 #include <linux/platform_device.h>
12 #include <linux/usb/role.h>
13 #include "core.h"
14
15 static 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
33 static 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
51 static 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
69 static 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
120 int 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
150 void 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
163 void 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
176 void dwc2_drd_exit(struct dwc2_hsotg *hsotg)
177 {
178         if (hsotg->role_sw)
179                 usb_role_switch_unregister(hsotg->role_sw);
180 }
This page took 0.040558 seconds and 4 git commands to generate.