]>
Commit | Line | Data |
---|---|---|
c9999337 NA |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * USB Glue for Amlogic G12A SoCs | |
4 | * | |
5 | * Copyright (c) 2019 BayLibre, SAS | |
6 | * Author: Neil Armstrong <narmstrong@baylibre.com> | |
7 | */ | |
8 | ||
9 | /* | |
10 | * The USB is organized with a glue around the DWC3 Controller IP as : | |
11 | * - Control registers for each USB2 Ports | |
12 | * - Control registers for the USB PHY layer | |
13 | * - SuperSpeed PHY can be enabled only if port is used | |
f90db107 | 14 | * - Dynamic OTG switching with ID change interrupt |
c9999337 NA |
15 | */ |
16 | ||
17 | #include <linux/module.h> | |
18 | #include <linux/kernel.h> | |
19 | #include <linux/platform_device.h> | |
20 | #include <linux/clk.h> | |
21 | #include <linux/of.h> | |
22 | #include <linux/of_platform.h> | |
23 | #include <linux/pm_runtime.h> | |
24 | #include <linux/regmap.h> | |
25 | #include <linux/bitfield.h> | |
26 | #include <linux/bitops.h> | |
27 | #include <linux/reset.h> | |
28 | #include <linux/phy/phy.h> | |
29 | #include <linux/usb/otg.h> | |
30 | #include <linux/usb/role.h> | |
31 | #include <linux/regulator/consumer.h> | |
32 | ||
013af227 | 33 | /* USB2 Ports Control Registers, offsets are per-port */ |
c9999337 NA |
34 | |
35 | #define U2P_REG_SIZE 0x20 | |
36 | ||
37 | #define U2P_R0 0x0 | |
38 | #define U2P_R0_HOST_DEVICE BIT(0) | |
39 | #define U2P_R0_POWER_OK BIT(1) | |
40 | #define U2P_R0_HAST_MODE BIT(2) | |
41 | #define U2P_R0_POWER_ON_RESET BIT(3) | |
42 | #define U2P_R0_ID_PULLUP BIT(4) | |
43 | #define U2P_R0_DRV_VBUS BIT(5) | |
44 | ||
45 | #define U2P_R1 0x4 | |
46 | #define U2P_R1_PHY_READY BIT(0) | |
47 | #define U2P_R1_ID_DIG BIT(1) | |
48 | #define U2P_R1_OTG_SESSION_VALID BIT(2) | |
49 | #define U2P_R1_VBUS_VALID BIT(3) | |
50 | ||
51 | /* USB Glue Control Registers */ | |
52 | ||
013af227 NA |
53 | #define G12A_GLUE_OFFSET 0x80 |
54 | ||
55 | #define USB_R0 0x00 | |
c9999337 NA |
56 | #define USB_R0_P30_LANE0_TX2RX_LOOPBACK BIT(17) |
57 | #define USB_R0_P30_LANE0_EXT_PCLK_REQ BIT(18) | |
58 | #define USB_R0_P30_PCS_RX_LOS_MASK_VAL_MASK GENMASK(28, 19) | |
59 | #define USB_R0_U2D_SS_SCALEDOWN_MODE_MASK GENMASK(30, 29) | |
60 | #define USB_R0_U2D_ACT BIT(31) | |
61 | ||
013af227 | 62 | #define USB_R1 0x04 |
c9999337 NA |
63 | #define USB_R1_U3H_BIGENDIAN_GS BIT(0) |
64 | #define USB_R1_U3H_PME_ENABLE BIT(1) | |
65 | #define USB_R1_U3H_HUB_PORT_OVERCURRENT_MASK GENMASK(4, 2) | |
66 | #define USB_R1_U3H_HUB_PORT_PERM_ATTACH_MASK GENMASK(9, 7) | |
67 | #define USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK GENMASK(13, 12) | |
68 | #define USB_R1_U3H_HOST_U3_PORT_DISABLE BIT(16) | |
69 | #define USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT BIT(17) | |
70 | #define USB_R1_U3H_HOST_MSI_ENABLE BIT(18) | |
71 | #define USB_R1_U3H_FLADJ_30MHZ_REG_MASK GENMASK(24, 19) | |
72 | #define USB_R1_P30_PCS_TX_SWING_FULL_MASK GENMASK(31, 25) | |
73 | ||
013af227 | 74 | #define USB_R2 0x08 |
c9999337 NA |
75 | #define USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK GENMASK(25, 20) |
76 | #define USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK GENMASK(31, 26) | |
77 | ||
013af227 | 78 | #define USB_R3 0x0c |
c9999337 NA |
79 | #define USB_R3_P30_SSC_ENABLE BIT(0) |
80 | #define USB_R3_P30_SSC_RANGE_MASK GENMASK(3, 1) | |
81 | #define USB_R3_P30_SSC_REF_CLK_SEL_MASK GENMASK(12, 4) | |
82 | #define USB_R3_P30_REF_SSP_EN BIT(13) | |
83 | ||
013af227 | 84 | #define USB_R4 0x10 |
c9999337 NA |
85 | #define USB_R4_P21_PORT_RESET_0 BIT(0) |
86 | #define USB_R4_P21_SLEEP_M0 BIT(1) | |
87 | #define USB_R4_MEM_PD_MASK GENMASK(3, 2) | |
88 | #define USB_R4_P21_ONLY BIT(4) | |
89 | ||
013af227 | 90 | #define USB_R5 0x14 |
c9999337 NA |
91 | #define USB_R5_ID_DIG_SYNC BIT(0) |
92 | #define USB_R5_ID_DIG_REG BIT(1) | |
93 | #define USB_R5_ID_DIG_CFG_MASK GENMASK(3, 2) | |
94 | #define USB_R5_ID_DIG_EN_0 BIT(4) | |
95 | #define USB_R5_ID_DIG_EN_1 BIT(5) | |
96 | #define USB_R5_ID_DIG_CURR BIT(6) | |
97 | #define USB_R5_ID_DIG_IRQ BIT(7) | |
98 | #define USB_R5_ID_DIG_TH_MASK GENMASK(15, 8) | |
99 | #define USB_R5_ID_DIG_CNT_MASK GENMASK(23, 16) | |
100 | ||
5174564c NA |
101 | #define PHY_COUNT 3 |
102 | #define USB2_OTG_PHY 1 | |
c9999337 | 103 | |
a9fc15e0 NA |
104 | static struct clk_bulk_data meson_gxl_clocks[] = { |
105 | { .id = "usb_ctrl" }, | |
106 | { .id = "ddr" }, | |
107 | }; | |
108 | ||
1e355f21 HL |
109 | static struct clk_bulk_data meson_g12a_clocks[] = { |
110 | { .id = NULL }, | |
111 | }; | |
112 | ||
113 | static struct clk_bulk_data meson_a1_clocks[] = { | |
114 | { .id = "usb_ctrl" }, | |
115 | { .id = "usb_bus" }, | |
116 | { .id = "xtal_usb_ctrl" }, | |
117 | }; | |
118 | ||
a9fc15e0 NA |
119 | static const char *meson_gxm_phy_names[] = { |
120 | "usb2-phy0", "usb2-phy1", "usb2-phy2", | |
121 | }; | |
122 | ||
5174564c NA |
123 | static const char *meson_g12a_phy_names[] = { |
124 | "usb2-phy0", "usb2-phy1", "usb3-phy0", | |
125 | }; | |
126 | ||
127 | /* | |
128 | * Amlogic A1 has a single physical PHY, in slot 1, but still has the | |
129 | * two U2 PHY controls register blocks like G12A. | |
130 | * Handling the first PHY on slot 1 would need a large amount of code | |
131 | * changes, and the current management is generic enough to handle it | |
132 | * correctly when only the "usb2-phy1" phy is specified on-par with the | |
133 | * DT bindings. | |
134 | */ | |
135 | static const char *meson_a1_phy_names[] = { | |
136 | "usb2-phy0", "usb2-phy1" | |
137 | }; | |
138 | ||
013af227 NA |
139 | struct dwc3_meson_g12a; |
140 | ||
1e355f21 HL |
141 | struct dwc3_meson_g12a_drvdata { |
142 | bool otg_switch_supported; | |
df7e3745 | 143 | bool otg_phy_host_port_disable; |
1e355f21 HL |
144 | struct clk_bulk_data *clks; |
145 | int num_clks; | |
5174564c NA |
146 | const char **phy_names; |
147 | int num_phys; | |
013af227 | 148 | int (*setup_regmaps)(struct dwc3_meson_g12a *priv, void __iomem *base); |
31306821 NA |
149 | int (*usb2_init_phy)(struct dwc3_meson_g12a *priv, int i, |
150 | enum phy_mode mode); | |
151 | int (*set_phy_mode)(struct dwc3_meson_g12a *priv, int i, | |
152 | enum phy_mode mode); | |
5b0ba0ca NA |
153 | int (*usb_init)(struct dwc3_meson_g12a *priv); |
154 | int (*usb_post_init)(struct dwc3_meson_g12a *priv); | |
1e355f21 HL |
155 | }; |
156 | ||
a9fc15e0 NA |
157 | static int dwc3_meson_gxl_setup_regmaps(struct dwc3_meson_g12a *priv, |
158 | void __iomem *base); | |
013af227 NA |
159 | static int dwc3_meson_g12a_setup_regmaps(struct dwc3_meson_g12a *priv, |
160 | void __iomem *base); | |
161 | ||
31306821 | 162 | static int dwc3_meson_g12a_usb2_init_phy(struct dwc3_meson_g12a *priv, int i, |
a9fc15e0 NA |
163 | enum phy_mode mode); |
164 | static int dwc3_meson_gxl_usb2_init_phy(struct dwc3_meson_g12a *priv, int i, | |
165 | enum phy_mode mode); | |
31306821 NA |
166 | |
167 | static int dwc3_meson_g12a_set_phy_mode(struct dwc3_meson_g12a *priv, | |
168 | int i, enum phy_mode mode); | |
a9fc15e0 NA |
169 | static int dwc3_meson_gxl_set_phy_mode(struct dwc3_meson_g12a *priv, |
170 | int i, enum phy_mode mode); | |
31306821 | 171 | |
5b0ba0ca | 172 | static int dwc3_meson_g12a_usb_init(struct dwc3_meson_g12a *priv); |
a9fc15e0 NA |
173 | static int dwc3_meson_gxl_usb_init(struct dwc3_meson_g12a *priv); |
174 | ||
175 | static int dwc3_meson_gxl_usb_post_init(struct dwc3_meson_g12a *priv); | |
5b0ba0ca | 176 | |
df7e3745 NA |
177 | /* |
178 | * For GXL and GXM SoCs: | |
179 | * USB Phy muxing between the DWC2 Device controller and the DWC3 Host | |
180 | * controller is buggy when switching from Device to Host when USB port | |
181 | * is unpopulated, it causes the DWC3 to hard crash. | |
182 | * When populated (including OTG switching with ID pin), the switch works | |
183 | * like a charm like on the G12A platforms. | |
184 | * In order to still switch from Host to Device on an USB Type-A port, | |
185 | * an U2_PORT_DISABLE bit has been added to disconnect the DWC3 Host | |
186 | * controller from the port, but when used the DWC3 controller must be | |
187 | * reset to recover usage of the port. | |
188 | */ | |
189 | ||
a9fc15e0 NA |
190 | static struct dwc3_meson_g12a_drvdata gxl_drvdata = { |
191 | .otg_switch_supported = true, | |
192 | .otg_phy_host_port_disable = true, | |
193 | .clks = meson_gxl_clocks, | |
194 | .num_clks = ARRAY_SIZE(meson_g12a_clocks), | |
195 | .phy_names = meson_a1_phy_names, | |
196 | .num_phys = ARRAY_SIZE(meson_a1_phy_names), | |
197 | .setup_regmaps = dwc3_meson_gxl_setup_regmaps, | |
198 | .usb2_init_phy = dwc3_meson_gxl_usb2_init_phy, | |
199 | .set_phy_mode = dwc3_meson_gxl_set_phy_mode, | |
200 | .usb_init = dwc3_meson_gxl_usb_init, | |
201 | .usb_post_init = dwc3_meson_gxl_usb_post_init, | |
202 | }; | |
203 | ||
204 | static struct dwc3_meson_g12a_drvdata gxm_drvdata = { | |
205 | .otg_switch_supported = true, | |
206 | .otg_phy_host_port_disable = true, | |
207 | .clks = meson_gxl_clocks, | |
208 | .num_clks = ARRAY_SIZE(meson_g12a_clocks), | |
209 | .phy_names = meson_gxm_phy_names, | |
210 | .num_phys = ARRAY_SIZE(meson_gxm_phy_names), | |
211 | .setup_regmaps = dwc3_meson_gxl_setup_regmaps, | |
212 | .usb2_init_phy = dwc3_meson_gxl_usb2_init_phy, | |
213 | .set_phy_mode = dwc3_meson_gxl_set_phy_mode, | |
214 | .usb_init = dwc3_meson_gxl_usb_init, | |
215 | .usb_post_init = dwc3_meson_gxl_usb_post_init, | |
216 | }; | |
217 | ||
1e355f21 HL |
218 | static struct dwc3_meson_g12a_drvdata g12a_drvdata = { |
219 | .otg_switch_supported = true, | |
220 | .clks = meson_g12a_clocks, | |
221 | .num_clks = ARRAY_SIZE(meson_g12a_clocks), | |
5174564c NA |
222 | .phy_names = meson_g12a_phy_names, |
223 | .num_phys = ARRAY_SIZE(meson_g12a_phy_names), | |
013af227 | 224 | .setup_regmaps = dwc3_meson_g12a_setup_regmaps, |
31306821 NA |
225 | .usb2_init_phy = dwc3_meson_g12a_usb2_init_phy, |
226 | .set_phy_mode = dwc3_meson_g12a_set_phy_mode, | |
5b0ba0ca | 227 | .usb_init = dwc3_meson_g12a_usb_init, |
1e355f21 HL |
228 | }; |
229 | ||
230 | static struct dwc3_meson_g12a_drvdata a1_drvdata = { | |
231 | .otg_switch_supported = false, | |
232 | .clks = meson_a1_clocks, | |
233 | .num_clks = ARRAY_SIZE(meson_a1_clocks), | |
5174564c NA |
234 | .phy_names = meson_a1_phy_names, |
235 | .num_phys = ARRAY_SIZE(meson_a1_phy_names), | |
013af227 | 236 | .setup_regmaps = dwc3_meson_g12a_setup_regmaps, |
31306821 NA |
237 | .usb2_init_phy = dwc3_meson_g12a_usb2_init_phy, |
238 | .set_phy_mode = dwc3_meson_g12a_set_phy_mode, | |
5b0ba0ca | 239 | .usb_init = dwc3_meson_g12a_usb_init, |
1e355f21 HL |
240 | }; |
241 | ||
c9999337 NA |
242 | struct dwc3_meson_g12a { |
243 | struct device *dev; | |
013af227 NA |
244 | struct regmap *u2p_regmap[PHY_COUNT]; |
245 | struct regmap *usb_glue_regmap; | |
c9999337 NA |
246 | struct reset_control *reset; |
247 | struct phy *phys[PHY_COUNT]; | |
248 | enum usb_dr_mode otg_mode; | |
249 | enum phy_mode otg_phy_mode; | |
250 | unsigned int usb2_ports; | |
251 | unsigned int usb3_ports; | |
252 | struct regulator *vbus; | |
253 | struct usb_role_switch_desc switch_desc; | |
254 | struct usb_role_switch *role_switch; | |
1e355f21 | 255 | const struct dwc3_meson_g12a_drvdata *drvdata; |
c9999337 NA |
256 | }; |
257 | ||
a9fc15e0 NA |
258 | static int dwc3_meson_gxl_set_phy_mode(struct dwc3_meson_g12a *priv, |
259 | int i, enum phy_mode mode) | |
260 | { | |
261 | return phy_set_mode(priv->phys[i], mode); | |
262 | } | |
263 | ||
264 | static int dwc3_meson_gxl_usb2_init_phy(struct dwc3_meson_g12a *priv, int i, | |
265 | enum phy_mode mode) | |
266 | { | |
267 | /* On GXL PHY must be started in device mode for DWC2 init */ | |
268 | return priv->drvdata->set_phy_mode(priv, i, | |
269 | (i == USB2_OTG_PHY) ? PHY_MODE_USB_DEVICE | |
270 | : PHY_MODE_USB_HOST); | |
271 | } | |
272 | ||
31306821 NA |
273 | static int dwc3_meson_g12a_set_phy_mode(struct dwc3_meson_g12a *priv, |
274 | int i, enum phy_mode mode) | |
c9999337 NA |
275 | { |
276 | if (mode == PHY_MODE_USB_HOST) | |
013af227 | 277 | regmap_update_bits(priv->u2p_regmap[i], U2P_R0, |
c9999337 NA |
278 | U2P_R0_HOST_DEVICE, |
279 | U2P_R0_HOST_DEVICE); | |
280 | else | |
013af227 | 281 | regmap_update_bits(priv->u2p_regmap[i], U2P_R0, |
c9999337 | 282 | U2P_R0_HOST_DEVICE, 0); |
31306821 NA |
283 | |
284 | return 0; | |
285 | } | |
286 | ||
287 | static int dwc3_meson_g12a_usb2_init_phy(struct dwc3_meson_g12a *priv, int i, | |
288 | enum phy_mode mode) | |
289 | { | |
290 | int ret; | |
291 | ||
292 | regmap_update_bits(priv->u2p_regmap[i], U2P_R0, | |
293 | U2P_R0_POWER_ON_RESET, | |
294 | U2P_R0_POWER_ON_RESET); | |
295 | ||
296 | if (priv->drvdata->otg_switch_supported && i == USB2_OTG_PHY) { | |
297 | regmap_update_bits(priv->u2p_regmap[i], U2P_R0, | |
298 | U2P_R0_ID_PULLUP | U2P_R0_DRV_VBUS, | |
299 | U2P_R0_ID_PULLUP | U2P_R0_DRV_VBUS); | |
300 | ||
301 | ret = priv->drvdata->set_phy_mode(priv, i, mode); | |
302 | } else | |
303 | ret = priv->drvdata->set_phy_mode(priv, i, | |
304 | PHY_MODE_USB_HOST); | |
305 | ||
306 | if (ret) | |
307 | return ret; | |
308 | ||
309 | regmap_update_bits(priv->u2p_regmap[i], U2P_R0, | |
310 | U2P_R0_POWER_ON_RESET, 0); | |
311 | ||
312 | return 0; | |
c9999337 NA |
313 | } |
314 | ||
5b0ba0ca NA |
315 | static int dwc3_meson_g12a_usb2_init(struct dwc3_meson_g12a *priv, |
316 | enum phy_mode mode) | |
c9999337 | 317 | { |
31306821 | 318 | int i, ret; |
c9999337 | 319 | |
5174564c | 320 | for (i = 0; i < priv->drvdata->num_phys; ++i) { |
c9999337 NA |
321 | if (!priv->phys[i]) |
322 | continue; | |
323 | ||
5174564c NA |
324 | if (!strstr(priv->drvdata->phy_names[i], "usb2")) |
325 | continue; | |
326 | ||
5b0ba0ca | 327 | ret = priv->drvdata->usb2_init_phy(priv, i, mode); |
31306821 NA |
328 | if (ret) |
329 | return ret; | |
c9999337 NA |
330 | } |
331 | ||
332 | return 0; | |
333 | } | |
334 | ||
335 | static void dwc3_meson_g12a_usb3_init(struct dwc3_meson_g12a *priv) | |
336 | { | |
013af227 | 337 | regmap_update_bits(priv->usb_glue_regmap, USB_R3, |
c9999337 NA |
338 | USB_R3_P30_SSC_RANGE_MASK | |
339 | USB_R3_P30_REF_SSP_EN, | |
340 | USB_R3_P30_SSC_ENABLE | | |
341 | FIELD_PREP(USB_R3_P30_SSC_RANGE_MASK, 2) | | |
342 | USB_R3_P30_REF_SSP_EN); | |
343 | udelay(2); | |
344 | ||
013af227 | 345 | regmap_update_bits(priv->usb_glue_regmap, USB_R2, |
c9999337 NA |
346 | USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK, |
347 | FIELD_PREP(USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK, 0x15)); | |
348 | ||
013af227 | 349 | regmap_update_bits(priv->usb_glue_regmap, USB_R2, |
c9999337 NA |
350 | USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK, |
351 | FIELD_PREP(USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK, 0x20)); | |
352 | ||
353 | udelay(2); | |
354 | ||
013af227 | 355 | regmap_update_bits(priv->usb_glue_regmap, USB_R1, |
c9999337 NA |
356 | USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT, |
357 | USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT); | |
358 | ||
013af227 | 359 | regmap_update_bits(priv->usb_glue_regmap, USB_R1, |
c9999337 NA |
360 | USB_R1_P30_PCS_TX_SWING_FULL_MASK, |
361 | FIELD_PREP(USB_R1_P30_PCS_TX_SWING_FULL_MASK, 127)); | |
362 | } | |
363 | ||
5b0ba0ca NA |
364 | static void dwc3_meson_g12a_usb_otg_apply_mode(struct dwc3_meson_g12a *priv, |
365 | enum phy_mode mode) | |
c9999337 | 366 | { |
5b0ba0ca | 367 | if (mode == PHY_MODE_USB_DEVICE) { |
df7e3745 NA |
368 | if (priv->otg_mode != USB_DR_MODE_OTG && |
369 | priv->drvdata->otg_phy_host_port_disable) | |
370 | /* Isolate the OTG PHY port from the Host Controller */ | |
371 | regmap_update_bits(priv->usb_glue_regmap, USB_R1, | |
372 | USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK, | |
373 | FIELD_PREP(USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK, | |
374 | BIT(USB2_OTG_PHY))); | |
375 | ||
013af227 | 376 | regmap_update_bits(priv->usb_glue_regmap, USB_R0, |
c9999337 | 377 | USB_R0_U2D_ACT, USB_R0_U2D_ACT); |
013af227 | 378 | regmap_update_bits(priv->usb_glue_regmap, USB_R0, |
c9999337 | 379 | USB_R0_U2D_SS_SCALEDOWN_MODE_MASK, 0); |
013af227 | 380 | regmap_update_bits(priv->usb_glue_regmap, USB_R4, |
c9999337 NA |
381 | USB_R4_P21_SLEEP_M0, USB_R4_P21_SLEEP_M0); |
382 | } else { | |
df7e3745 NA |
383 | if (priv->otg_mode != USB_DR_MODE_OTG && |
384 | priv->drvdata->otg_phy_host_port_disable) { | |
385 | regmap_update_bits(priv->usb_glue_regmap, USB_R1, | |
386 | USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK, 0); | |
387 | msleep(500); | |
388 | } | |
013af227 | 389 | regmap_update_bits(priv->usb_glue_regmap, USB_R0, |
c9999337 | 390 | USB_R0_U2D_ACT, 0); |
013af227 | 391 | regmap_update_bits(priv->usb_glue_regmap, USB_R4, |
c9999337 NA |
392 | USB_R4_P21_SLEEP_M0, 0); |
393 | } | |
394 | } | |
395 | ||
5b0ba0ca NA |
396 | static int dwc3_meson_g12a_usb_init_glue(struct dwc3_meson_g12a *priv, |
397 | enum phy_mode mode) | |
c9999337 NA |
398 | { |
399 | int ret; | |
400 | ||
5b0ba0ca | 401 | ret = dwc3_meson_g12a_usb2_init(priv, mode); |
c9999337 NA |
402 | if (ret) |
403 | return ret; | |
404 | ||
013af227 | 405 | regmap_update_bits(priv->usb_glue_regmap, USB_R1, |
c9999337 NA |
406 | USB_R1_U3H_FLADJ_30MHZ_REG_MASK, |
407 | FIELD_PREP(USB_R1_U3H_FLADJ_30MHZ_REG_MASK, 0x20)); | |
408 | ||
013af227 | 409 | regmap_update_bits(priv->usb_glue_regmap, USB_R5, |
c9999337 NA |
410 | USB_R5_ID_DIG_EN_0, |
411 | USB_R5_ID_DIG_EN_0); | |
013af227 | 412 | regmap_update_bits(priv->usb_glue_regmap, USB_R5, |
c9999337 NA |
413 | USB_R5_ID_DIG_EN_1, |
414 | USB_R5_ID_DIG_EN_1); | |
013af227 | 415 | regmap_update_bits(priv->usb_glue_regmap, USB_R5, |
c9999337 NA |
416 | USB_R5_ID_DIG_TH_MASK, |
417 | FIELD_PREP(USB_R5_ID_DIG_TH_MASK, 0xff)); | |
418 | ||
419 | /* If we have an actual SuperSpeed port, initialize it */ | |
420 | if (priv->usb3_ports) | |
421 | dwc3_meson_g12a_usb3_init(priv); | |
422 | ||
5b0ba0ca | 423 | dwc3_meson_g12a_usb_otg_apply_mode(priv, mode); |
c9999337 NA |
424 | |
425 | return 0; | |
426 | } | |
427 | ||
013af227 NA |
428 | static const struct regmap_config phy_meson_g12a_usb_glue_regmap_conf = { |
429 | .name = "usb-glue", | |
c9999337 NA |
430 | .reg_bits = 8, |
431 | .val_bits = 32, | |
432 | .reg_stride = 4, | |
433 | .max_register = USB_R5, | |
434 | }; | |
435 | ||
436 | static int dwc3_meson_g12a_get_phys(struct dwc3_meson_g12a *priv) | |
437 | { | |
5174564c | 438 | const char *phy_name; |
c9999337 NA |
439 | int i; |
440 | ||
5174564c NA |
441 | for (i = 0 ; i < priv->drvdata->num_phys ; ++i) { |
442 | phy_name = priv->drvdata->phy_names[i]; | |
443 | priv->phys[i] = devm_phy_optional_get(priv->dev, phy_name); | |
c9999337 NA |
444 | if (!priv->phys[i]) |
445 | continue; | |
446 | ||
447 | if (IS_ERR(priv->phys[i])) | |
448 | return PTR_ERR(priv->phys[i]); | |
449 | ||
5174564c | 450 | if (strstr(phy_name, "usb3")) |
c9999337 NA |
451 | priv->usb3_ports++; |
452 | else | |
453 | priv->usb2_ports++; | |
454 | } | |
455 | ||
456 | dev_info(priv->dev, "USB2 ports: %d\n", priv->usb2_ports); | |
457 | dev_info(priv->dev, "USB3 ports: %d\n", priv->usb3_ports); | |
458 | ||
459 | return 0; | |
460 | } | |
461 | ||
462 | static enum phy_mode dwc3_meson_g12a_get_id(struct dwc3_meson_g12a *priv) | |
463 | { | |
464 | u32 reg; | |
465 | ||
013af227 | 466 | regmap_read(priv->usb_glue_regmap, USB_R5, ®); |
c9999337 NA |
467 | |
468 | if (reg & (USB_R5_ID_DIG_SYNC | USB_R5_ID_DIG_REG)) | |
469 | return PHY_MODE_USB_DEVICE; | |
470 | ||
471 | return PHY_MODE_USB_HOST; | |
472 | } | |
473 | ||
474 | static int dwc3_meson_g12a_otg_mode_set(struct dwc3_meson_g12a *priv, | |
475 | enum phy_mode mode) | |
476 | { | |
477 | int ret; | |
478 | ||
1e355f21 | 479 | if (!priv->drvdata->otg_switch_supported || !priv->phys[USB2_OTG_PHY]) |
c9999337 NA |
480 | return -EINVAL; |
481 | ||
482 | if (mode == PHY_MODE_USB_HOST) | |
483 | dev_info(priv->dev, "switching to Host Mode\n"); | |
484 | else | |
485 | dev_info(priv->dev, "switching to Device Mode\n"); | |
486 | ||
487 | if (priv->vbus) { | |
488 | if (mode == PHY_MODE_USB_DEVICE) | |
489 | ret = regulator_disable(priv->vbus); | |
490 | else | |
491 | ret = regulator_enable(priv->vbus); | |
492 | if (ret) | |
493 | return ret; | |
494 | } | |
495 | ||
496 | priv->otg_phy_mode = mode; | |
497 | ||
31306821 NA |
498 | ret = priv->drvdata->set_phy_mode(priv, USB2_OTG_PHY, mode); |
499 | if (ret) | |
500 | return ret; | |
c9999337 | 501 | |
5b0ba0ca | 502 | dwc3_meson_g12a_usb_otg_apply_mode(priv, mode); |
c9999337 NA |
503 | |
504 | return 0; | |
505 | } | |
506 | ||
bce3052f HK |
507 | static int dwc3_meson_g12a_role_set(struct usb_role_switch *sw, |
508 | enum usb_role role) | |
c9999337 | 509 | { |
bce3052f | 510 | struct dwc3_meson_g12a *priv = usb_role_switch_get_drvdata(sw); |
c9999337 NA |
511 | enum phy_mode mode; |
512 | ||
513 | if (role == USB_ROLE_NONE) | |
514 | return 0; | |
515 | ||
516 | mode = (role == USB_ROLE_HOST) ? PHY_MODE_USB_HOST | |
517 | : PHY_MODE_USB_DEVICE; | |
518 | ||
519 | if (mode == priv->otg_phy_mode) | |
520 | return 0; | |
521 | ||
df7e3745 NA |
522 | if (priv->drvdata->otg_phy_host_port_disable) |
523 | dev_warn_once(priv->dev, "Manual OTG switch is broken on this "\ | |
524 | "SoC, when manual switching from "\ | |
525 | "Host to device, DWC3 controller "\ | |
526 | "will need to be resetted in order "\ | |
527 | "to recover usage of the Host port"); | |
528 | ||
c9999337 NA |
529 | return dwc3_meson_g12a_otg_mode_set(priv, mode); |
530 | } | |
531 | ||
bce3052f | 532 | static enum usb_role dwc3_meson_g12a_role_get(struct usb_role_switch *sw) |
c9999337 | 533 | { |
bce3052f | 534 | struct dwc3_meson_g12a *priv = usb_role_switch_get_drvdata(sw); |
c9999337 NA |
535 | |
536 | return priv->otg_phy_mode == PHY_MODE_USB_HOST ? | |
537 | USB_ROLE_HOST : USB_ROLE_DEVICE; | |
538 | } | |
539 | ||
f90db107 NA |
540 | static irqreturn_t dwc3_meson_g12a_irq_thread(int irq, void *data) |
541 | { | |
542 | struct dwc3_meson_g12a *priv = data; | |
543 | enum phy_mode otg_id; | |
544 | ||
545 | otg_id = dwc3_meson_g12a_get_id(priv); | |
546 | if (otg_id != priv->otg_phy_mode) { | |
547 | if (dwc3_meson_g12a_otg_mode_set(priv, otg_id)) | |
548 | dev_warn(priv->dev, "Failed to switch OTG mode\n"); | |
549 | } | |
550 | ||
013af227 NA |
551 | regmap_update_bits(priv->usb_glue_regmap, USB_R5, |
552 | USB_R5_ID_DIG_IRQ, 0); | |
f90db107 NA |
553 | |
554 | return IRQ_HANDLED; | |
555 | } | |
556 | ||
c9999337 NA |
557 | static struct device *dwc3_meson_g12_find_child(struct device *dev, |
558 | const char *compatible) | |
559 | { | |
560 | struct platform_device *pdev; | |
561 | struct device_node *np; | |
562 | ||
563 | np = of_get_compatible_child(dev->of_node, compatible); | |
564 | if (!np) | |
565 | return NULL; | |
566 | ||
567 | pdev = of_find_device_by_node(np); | |
568 | of_node_put(np); | |
569 | if (!pdev) | |
570 | return NULL; | |
571 | ||
572 | return &pdev->dev; | |
573 | } | |
574 | ||
1e355f21 HL |
575 | static int dwc3_meson_g12a_otg_init(struct platform_device *pdev, |
576 | struct dwc3_meson_g12a *priv) | |
577 | { | |
578 | enum phy_mode otg_id; | |
579 | int ret, irq; | |
580 | struct device *dev = &pdev->dev; | |
581 | ||
582 | if (!priv->drvdata->otg_switch_supported) | |
583 | return 0; | |
584 | ||
585 | if (priv->otg_mode == USB_DR_MODE_OTG) { | |
586 | /* Ack irq before registering */ | |
013af227 | 587 | regmap_update_bits(priv->usb_glue_regmap, USB_R5, |
1e355f21 HL |
588 | USB_R5_ID_DIG_IRQ, 0); |
589 | ||
590 | irq = platform_get_irq(pdev, 0); | |
591 | ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, | |
592 | dwc3_meson_g12a_irq_thread, | |
593 | IRQF_ONESHOT, pdev->name, priv); | |
594 | if (ret) | |
595 | return ret; | |
596 | } | |
597 | ||
598 | /* Setup OTG mode corresponding to the ID pin */ | |
599 | if (priv->otg_mode == USB_DR_MODE_OTG) { | |
600 | otg_id = dwc3_meson_g12a_get_id(priv); | |
601 | if (otg_id != priv->otg_phy_mode) { | |
602 | if (dwc3_meson_g12a_otg_mode_set(priv, otg_id)) | |
603 | dev_warn(dev, "Failed to switch OTG mode\n"); | |
604 | } | |
605 | } | |
606 | ||
607 | /* Setup role switcher */ | |
608 | priv->switch_desc.usb2_port = dwc3_meson_g12_find_child(dev, | |
609 | "snps,dwc3"); | |
610 | priv->switch_desc.udc = dwc3_meson_g12_find_child(dev, "snps,dwc2"); | |
611 | priv->switch_desc.allow_userspace_control = true; | |
612 | priv->switch_desc.set = dwc3_meson_g12a_role_set; | |
613 | priv->switch_desc.get = dwc3_meson_g12a_role_get; | |
a8ab3e76 | 614 | priv->switch_desc.driver_data = priv; |
1e355f21 HL |
615 | |
616 | priv->role_switch = usb_role_switch_register(dev, &priv->switch_desc); | |
617 | if (IS_ERR(priv->role_switch)) | |
618 | dev_warn(dev, "Unable to register Role Switch\n"); | |
619 | ||
238d7602 | 620 | return 0; |
1e355f21 HL |
621 | } |
622 | ||
a9fc15e0 NA |
623 | static int dwc3_meson_gxl_setup_regmaps(struct dwc3_meson_g12a *priv, |
624 | void __iomem *base) | |
625 | { | |
626 | /* GXL controls the PHY mode in the PHY registers unlike G12A */ | |
627 | priv->usb_glue_regmap = devm_regmap_init_mmio(priv->dev, base, | |
628 | &phy_meson_g12a_usb_glue_regmap_conf); | |
629 | if (IS_ERR(priv->usb_glue_regmap)) | |
630 | return PTR_ERR(priv->usb_glue_regmap); | |
631 | ||
632 | return 0; | |
633 | } | |
634 | ||
013af227 NA |
635 | static int dwc3_meson_g12a_setup_regmaps(struct dwc3_meson_g12a *priv, |
636 | void __iomem *base) | |
637 | { | |
638 | int i; | |
639 | ||
640 | priv->usb_glue_regmap = devm_regmap_init_mmio(priv->dev, | |
641 | base + G12A_GLUE_OFFSET, | |
642 | &phy_meson_g12a_usb_glue_regmap_conf); | |
643 | if (IS_ERR(priv->usb_glue_regmap)) | |
644 | return PTR_ERR(priv->usb_glue_regmap); | |
645 | ||
646 | /* Create a regmap for each USB2 PHY control register set */ | |
647 | for (i = 0; i < priv->usb2_ports; i++) { | |
648 | struct regmap_config u2p_regmap_config = { | |
649 | .reg_bits = 8, | |
650 | .val_bits = 32, | |
651 | .reg_stride = 4, | |
652 | .max_register = U2P_R1, | |
653 | }; | |
654 | ||
655 | u2p_regmap_config.name = devm_kasprintf(priv->dev, GFP_KERNEL, | |
656 | "u2p-%d", i); | |
657 | if (!u2p_regmap_config.name) | |
658 | return -ENOMEM; | |
659 | ||
660 | priv->u2p_regmap[i] = devm_regmap_init_mmio(priv->dev, | |
661 | base + (i * U2P_REG_SIZE), | |
662 | &u2p_regmap_config); | |
663 | if (IS_ERR(priv->u2p_regmap[i])) | |
664 | return PTR_ERR(priv->u2p_regmap[i]); | |
665 | } | |
666 | ||
667 | return 0; | |
668 | } | |
669 | ||
5b0ba0ca NA |
670 | static int dwc3_meson_g12a_usb_init(struct dwc3_meson_g12a *priv) |
671 | { | |
672 | return dwc3_meson_g12a_usb_init_glue(priv, priv->otg_phy_mode); | |
673 | } | |
674 | ||
a9fc15e0 NA |
675 | static int dwc3_meson_gxl_usb_init(struct dwc3_meson_g12a *priv) |
676 | { | |
677 | return dwc3_meson_g12a_usb_init_glue(priv, PHY_MODE_USB_DEVICE); | |
678 | } | |
679 | ||
680 | static int dwc3_meson_gxl_usb_post_init(struct dwc3_meson_g12a *priv) | |
681 | { | |
682 | int ret; | |
683 | ||
684 | ret = priv->drvdata->set_phy_mode(priv, USB2_OTG_PHY, | |
685 | priv->otg_phy_mode); | |
686 | if (ret) | |
687 | return ret; | |
688 | ||
689 | dwc3_meson_g12a_usb_otg_apply_mode(priv, priv->otg_phy_mode); | |
690 | ||
691 | return 0; | |
692 | } | |
693 | ||
c9999337 NA |
694 | static int dwc3_meson_g12a_probe(struct platform_device *pdev) |
695 | { | |
696 | struct dwc3_meson_g12a *priv; | |
697 | struct device *dev = &pdev->dev; | |
698 | struct device_node *np = dev->of_node; | |
699 | void __iomem *base; | |
1e355f21 | 700 | int ret, i; |
c9999337 NA |
701 | |
702 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
703 | if (!priv) | |
704 | return -ENOMEM; | |
705 | ||
c6e4999c | 706 | base = devm_platform_ioremap_resource(pdev, 0); |
c9999337 NA |
707 | if (IS_ERR(base)) |
708 | return PTR_ERR(base); | |
709 | ||
013af227 NA |
710 | priv->drvdata = of_device_get_match_data(&pdev->dev); |
711 | ||
712 | priv->dev = dev; | |
713 | ret = priv->drvdata->setup_regmaps(priv, base); | |
714 | if (ret) | |
715 | return ret; | |
c9999337 NA |
716 | |
717 | priv->vbus = devm_regulator_get_optional(dev, "vbus"); | |
718 | if (IS_ERR(priv->vbus)) { | |
719 | if (PTR_ERR(priv->vbus) == -EPROBE_DEFER) | |
720 | return PTR_ERR(priv->vbus); | |
721 | priv->vbus = NULL; | |
722 | } | |
723 | ||
1e355f21 HL |
724 | ret = devm_clk_bulk_get(dev, |
725 | priv->drvdata->num_clks, | |
726 | priv->drvdata->clks); | |
c9999337 NA |
727 | if (ret) |
728 | return ret; | |
729 | ||
1e355f21 HL |
730 | ret = clk_bulk_prepare_enable(priv->drvdata->num_clks, |
731 | priv->drvdata->clks); | |
732 | if (ret) | |
733 | return ret; | |
c9999337 NA |
734 | |
735 | platform_set_drvdata(pdev, priv); | |
c9999337 | 736 | |
6d9fa35a | 737 | priv->reset = devm_reset_control_get_shared(dev, NULL); |
c9999337 NA |
738 | if (IS_ERR(priv->reset)) { |
739 | ret = PTR_ERR(priv->reset); | |
740 | dev_err(dev, "failed to get device reset, err=%d\n", ret); | |
741 | return ret; | |
742 | } | |
743 | ||
744 | ret = reset_control_reset(priv->reset); | |
745 | if (ret) | |
1e355f21 | 746 | goto err_disable_clks; |
c9999337 NA |
747 | |
748 | ret = dwc3_meson_g12a_get_phys(priv); | |
749 | if (ret) | |
1e355f21 | 750 | goto err_disable_clks; |
c9999337 NA |
751 | |
752 | if (priv->vbus) { | |
753 | ret = regulator_enable(priv->vbus); | |
754 | if (ret) | |
1e355f21 | 755 | goto err_disable_clks; |
c9999337 NA |
756 | } |
757 | ||
758 | /* Get dr_mode */ | |
759 | priv->otg_mode = usb_get_dr_mode(dev); | |
760 | ||
5b0ba0ca NA |
761 | if (priv->otg_mode == USB_DR_MODE_PERIPHERAL) |
762 | priv->otg_phy_mode = PHY_MODE_USB_DEVICE; | |
763 | else | |
764 | priv->otg_phy_mode = PHY_MODE_USB_HOST; | |
765 | ||
766 | ret = priv->drvdata->usb_init(priv); | |
8f5bc1ec NA |
767 | if (ret) |
768 | goto err_disable_clks; | |
c9999337 NA |
769 | |
770 | /* Init PHYs */ | |
771 | for (i = 0 ; i < PHY_COUNT ; ++i) { | |
772 | ret = phy_init(priv->phys[i]); | |
773 | if (ret) | |
1e355f21 | 774 | goto err_disable_clks; |
c9999337 NA |
775 | } |
776 | ||
777 | /* Set PHY Power */ | |
778 | for (i = 0 ; i < PHY_COUNT ; ++i) { | |
779 | ret = phy_power_on(priv->phys[i]); | |
780 | if (ret) | |
781 | goto err_phys_exit; | |
782 | } | |
783 | ||
5b0ba0ca NA |
784 | if (priv->drvdata->usb_post_init) { |
785 | ret = priv->drvdata->usb_post_init(priv); | |
786 | if (ret) | |
787 | goto err_phys_power; | |
788 | } | |
789 | ||
c9999337 | 790 | ret = of_platform_populate(np, NULL, NULL, dev); |
1e355f21 | 791 | if (ret) |
c9999337 | 792 | goto err_phys_power; |
c9999337 | 793 | |
1e355f21 HL |
794 | ret = dwc3_meson_g12a_otg_init(pdev, priv); |
795 | if (ret) | |
796 | goto err_phys_power; | |
c9999337 NA |
797 | |
798 | pm_runtime_set_active(dev); | |
799 | pm_runtime_enable(dev); | |
800 | pm_runtime_get_sync(dev); | |
801 | ||
802 | return 0; | |
803 | ||
804 | err_phys_power: | |
805 | for (i = 0 ; i < PHY_COUNT ; ++i) | |
806 | phy_power_off(priv->phys[i]); | |
807 | ||
808 | err_phys_exit: | |
809 | for (i = 0 ; i < PHY_COUNT ; ++i) | |
810 | phy_exit(priv->phys[i]); | |
811 | ||
1e355f21 HL |
812 | err_disable_clks: |
813 | clk_bulk_disable_unprepare(priv->drvdata->num_clks, | |
814 | priv->drvdata->clks); | |
815 | ||
c9999337 NA |
816 | return ret; |
817 | } | |
818 | ||
819 | static int dwc3_meson_g12a_remove(struct platform_device *pdev) | |
820 | { | |
821 | struct dwc3_meson_g12a *priv = platform_get_drvdata(pdev); | |
822 | struct device *dev = &pdev->dev; | |
823 | int i; | |
824 | ||
1e355f21 HL |
825 | if (priv->drvdata->otg_switch_supported) |
826 | usb_role_switch_unregister(priv->role_switch); | |
c9999337 NA |
827 | |
828 | of_platform_depopulate(dev); | |
829 | ||
830 | for (i = 0 ; i < PHY_COUNT ; ++i) { | |
831 | phy_power_off(priv->phys[i]); | |
832 | phy_exit(priv->phys[i]); | |
833 | } | |
834 | ||
835 | pm_runtime_disable(dev); | |
836 | pm_runtime_put_noidle(dev); | |
837 | pm_runtime_set_suspended(dev); | |
838 | ||
1e355f21 HL |
839 | clk_bulk_disable_unprepare(priv->drvdata->num_clks, |
840 | priv->drvdata->clks); | |
841 | ||
c9999337 NA |
842 | return 0; |
843 | } | |
844 | ||
845 | static int __maybe_unused dwc3_meson_g12a_runtime_suspend(struct device *dev) | |
846 | { | |
847 | struct dwc3_meson_g12a *priv = dev_get_drvdata(dev); | |
848 | ||
1e355f21 HL |
849 | clk_bulk_disable_unprepare(priv->drvdata->num_clks, |
850 | priv->drvdata->clks); | |
c9999337 NA |
851 | |
852 | return 0; | |
853 | } | |
854 | ||
855 | static int __maybe_unused dwc3_meson_g12a_runtime_resume(struct device *dev) | |
856 | { | |
857 | struct dwc3_meson_g12a *priv = dev_get_drvdata(dev); | |
858 | ||
1e355f21 HL |
859 | return clk_bulk_prepare_enable(priv->drvdata->num_clks, |
860 | priv->drvdata->clks); | |
c9999337 NA |
861 | } |
862 | ||
863 | static int __maybe_unused dwc3_meson_g12a_suspend(struct device *dev) | |
864 | { | |
865 | struct dwc3_meson_g12a *priv = dev_get_drvdata(dev); | |
1cf084d1 NA |
866 | int i, ret; |
867 | ||
868 | if (priv->vbus && priv->otg_phy_mode == PHY_MODE_USB_HOST) { | |
869 | ret = regulator_disable(priv->vbus); | |
870 | if (ret) | |
871 | return ret; | |
872 | } | |
c9999337 NA |
873 | |
874 | for (i = 0 ; i < PHY_COUNT ; ++i) { | |
875 | phy_power_off(priv->phys[i]); | |
876 | phy_exit(priv->phys[i]); | |
877 | } | |
878 | ||
879 | reset_control_assert(priv->reset); | |
880 | ||
881 | return 0; | |
882 | } | |
883 | ||
884 | static int __maybe_unused dwc3_meson_g12a_resume(struct device *dev) | |
885 | { | |
886 | struct dwc3_meson_g12a *priv = dev_get_drvdata(dev); | |
887 | int i, ret; | |
888 | ||
889 | reset_control_deassert(priv->reset); | |
890 | ||
5b0ba0ca NA |
891 | ret = priv->drvdata->usb_init(priv); |
892 | if (ret) | |
893 | return ret; | |
c9999337 NA |
894 | |
895 | /* Init PHYs */ | |
896 | for (i = 0 ; i < PHY_COUNT ; ++i) { | |
897 | ret = phy_init(priv->phys[i]); | |
898 | if (ret) | |
899 | return ret; | |
900 | } | |
901 | ||
902 | /* Set PHY Power */ | |
903 | for (i = 0 ; i < PHY_COUNT ; ++i) { | |
904 | ret = phy_power_on(priv->phys[i]); | |
905 | if (ret) | |
906 | return ret; | |
907 | } | |
908 | ||
1cf084d1 NA |
909 | if (priv->vbus && priv->otg_phy_mode == PHY_MODE_USB_HOST) { |
910 | ret = regulator_enable(priv->vbus); | |
911 | if (ret) | |
912 | return ret; | |
913 | } | |
914 | ||
c9999337 NA |
915 | return 0; |
916 | } | |
917 | ||
918 | static const struct dev_pm_ops dwc3_meson_g12a_dev_pm_ops = { | |
919 | SET_SYSTEM_SLEEP_PM_OPS(dwc3_meson_g12a_suspend, dwc3_meson_g12a_resume) | |
920 | SET_RUNTIME_PM_OPS(dwc3_meson_g12a_runtime_suspend, | |
921 | dwc3_meson_g12a_runtime_resume, NULL) | |
922 | }; | |
923 | ||
924 | static const struct of_device_id dwc3_meson_g12a_match[] = { | |
a9fc15e0 NA |
925 | { |
926 | .compatible = "amlogic,meson-gxl-usb-ctrl", | |
927 | .data = &gxl_drvdata, | |
928 | }, | |
929 | { | |
930 | .compatible = "amlogic,meson-gxm-usb-ctrl", | |
931 | .data = &gxm_drvdata, | |
932 | }, | |
1e355f21 HL |
933 | { |
934 | .compatible = "amlogic,meson-g12a-usb-ctrl", | |
935 | .data = &g12a_drvdata, | |
936 | }, | |
937 | { | |
938 | .compatible = "amlogic,meson-a1-usb-ctrl", | |
939 | .data = &a1_drvdata, | |
940 | }, | |
c9999337 NA |
941 | { /* Sentinel */ } |
942 | }; | |
943 | MODULE_DEVICE_TABLE(of, dwc3_meson_g12a_match); | |
944 | ||
945 | static struct platform_driver dwc3_meson_g12a_driver = { | |
946 | .probe = dwc3_meson_g12a_probe, | |
947 | .remove = dwc3_meson_g12a_remove, | |
948 | .driver = { | |
949 | .name = "dwc3-meson-g12a", | |
950 | .of_match_table = dwc3_meson_g12a_match, | |
951 | .pm = &dwc3_meson_g12a_dev_pm_ops, | |
952 | }, | |
953 | }; | |
954 | ||
955 | module_platform_driver(dwc3_meson_g12a_driver); | |
956 | MODULE_LICENSE("GPL v2"); | |
957 | MODULE_DESCRIPTION("Amlogic Meson G12A USB Glue Layer"); | |
958 | MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); |