1 /* $OpenBSD: omehci.c,v 1.11 2024/08/20 16:24:50 deraadt Exp $ */
2
3 /*
4 * Copyright (c) 2005 David Gwynne <dlg@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 /*-
20 * Copyright (c) 2011
21 * Ben Gray <ben.r.gray@gmail.com>.
22 * All rights reserved.
23 *
24 * Redistribution and use in source and binary forms, with or without
25 * modification, are permitted provided that the following conditions
26 * are met:
27 * 1. Redistributions of source code must retain the above copyright
28 * notice, this list of conditions and the following disclaimer.
29 * 2. Redistributions in binary form must reproduce the above copyright
30 * notice, this list of conditions and the following disclaimer in the
31 * documentation and/or other materials provided with the distribution.
32 *
33 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
34 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
35 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
36 * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
37 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
38 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
39 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
41 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
42 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
43 * SUCH DAMAGE.
44 */
45
46 #include <sys/param.h>
47 #include <sys/systm.h>
48 #include <sys/device.h>
49 #include <sys/kernel.h>
50
51 #include <machine/intr.h>
52 #include <machine/bus.h>
53 #include <machine/fdt.h>
54
55 #include <dev/usb/usb.h>
56 #include <dev/usb/usbdi.h>
57 #include <dev/usb/usbdivar.h>
58
59 #include <armv7/omap/prcmvar.h>
60 #include <armv7/omap/omehcivar.h>
61
62 #include <dev/ofw/openfirm.h>
63 #include <dev/ofw/ofw_misc.h>
64 #include <dev/ofw/fdt.h>
65
66 #include <dev/usb/ehcireg.h>
67 #include <dev/usb/ehcivar.h>
68
69 int omehci_match(struct device *, void *, void *);
70 void omehci_attach(struct device *, struct device *, void *);
71 int omehci_detach(struct device *, int);
72 int omehci_activate(struct device *, int);
73
74 struct omehci_softc {
75 struct ehci_softc sc;
76 void *sc_ih;
77 bus_space_handle_t uhh_ioh;
78 bus_space_handle_t tll_ioh;
79
80 uint32_t ehci_rev;
81 uint32_t tll_avail;
82
83 uint32_t port_mode[OMAP_HS_USB_PORTS];
84 };
85
86 int omehci_init(struct omehci_softc *);
87 void omehci_soft_phy_reset(struct omehci_softc *sc, unsigned int port);
88
89 const struct cfattach omehci_ca = {
90 sizeof (struct omehci_softc), omehci_match, omehci_attach,
91 omehci_detach, omehci_activate
92 };
93
94 struct cfdriver omehci_cd = {
95 NULL, "omehci", DV_DULL
96 };
97
98 int
omehci_match(struct device * parent,void * match,void * aux)99 omehci_match(struct device *parent, void *match, void *aux)
100 {
101 struct fdt_attach_args *faa = aux;
102
103 return OF_is_compatible(faa->fa_node, "ti,usbhs-host");
104 }
105
106 void
omehci_attach(struct device * parent,struct device * self,void * aux)107 omehci_attach(struct device *parent, struct device *self, void *aux)
108 {
109 struct omehci_softc *sc = (struct omehci_softc *)self;
110 struct fdt_attach_args *faa = aux;
111 usbd_status r;
112 char *devname = sc->sc.sc_bus.bdev.dv_xname;
113 uint32_t i;
114 char port_mode[16];
115 char name[32];
116 int node;
117 uint32_t reg[2];
118
119 if (faa->fa_nreg < 1)
120 return;
121
122 sc->sc.iot = faa->fa_iot;
123 sc->sc.sc_bus.dmatag = faa->fa_dmat;
124
125 /* set defaults */
126 for (i = 0; i < OMAP_HS_USB_PORTS; i++)
127 sc->port_mode[i] = EHCI_HCD_OMAP_MODE_UNKNOWN;
128
129 strlcpy(name, "portX-mode", sizeof(name));
130
131 for (i = 0; i < OMAP_HS_USB_PORTS; i++) {
132 name[4] = '1' + i;
133 memset(port_mode, 0, sizeof(port_mode));
134
135 if (OF_getprop(faa->fa_node, name, port_mode,
136 sizeof(port_mode)) == -1)
137 continue;
138
139 if (strcmp(port_mode, "ehci-phy") == 0)
140 sc->port_mode[i] = EHCI_HCD_OMAP_MODE_PHY;
141 if (strcmp(port_mode, "ehci-hsic") == 0)
142 sc->port_mode[i] = EHCI_HCD_OMAP_MODE_HSIC;
143 if (strcmp(port_mode, "ehci-tll") == 0)
144 sc->port_mode[i] = EHCI_HCD_OMAP_MODE_TLL ;
145 }
146
147 for (node = OF_child(faa->fa_node); node; node = OF_peer(node)) {
148 if (OF_is_compatible(node, "ti,ehci-omap"))
149 break;
150 }
151
152 if (node == 0)
153 panic("could not find ehci child node");
154
155 if (OF_getpropintarray(node, "reg", reg, sizeof(reg)) != sizeof(reg))
156 return;
157
158 /* Map I/O space */
159 if (bus_space_map(sc->sc.iot, reg[0], reg[1], 0, &sc->sc.ioh)) {
160 printf(": cannot map mem space\n");
161 goto out;
162 }
163 sc->sc.sc_size = reg[1];
164
165 if (bus_space_map(sc->sc.iot, faa->fa_reg[0].addr, faa->fa_reg[0].size,
166 0, &sc->uhh_ioh)) {
167 printf(": cannot map mem space\n");
168 goto mem0;
169 }
170
171 #if 0
172 if (sc->tll_avail &&
173 bus_space_map(sc->sc.iot, aa->aa_dev->mem[2].addr,
174 aa->aa_dev->mem[2].size, 0, &sc->tll_ioh)) {
175 printf(": cannot map mem space\n");
176 goto mem1;
177 }
178 #endif
179
180 printf("\n");
181
182 phy_enable_idx(node, 0);
183
184 if (omehci_init(sc))
185 return;
186
187 /* Disable interrupts, so we don't get any spurious ones. */
188 sc->sc.sc_offs = EREAD1(&sc->sc, EHCI_CAPLENGTH);
189 EOWRITE2(&sc->sc, EHCI_USBINTR, 0);
190
191 sc->sc_ih = arm_intr_establish_fdt(node, IPL_USB,
192 ehci_intr, &sc->sc, devname);
193 if (sc->sc_ih == NULL) {
194 printf(": unable to establish interrupt\n");
195 printf("XXX - disable ehci and prcm");
196 goto mem2;
197 }
198
199 strlcpy(sc->sc.sc_vendor, "TI OMAP", sizeof(sc->sc.sc_vendor));
200 r = ehci_init(&sc->sc);
201 if (r != USBD_NORMAL_COMPLETION) {
202 printf("%s: init failed, error=%d\n", devname, r);
203 printf("XXX - disable ehci and prcm");
204 goto intr;
205 }
206
207 config_found(self, &sc->sc.sc_bus, usbctlprint);
208
209 goto out;
210
211 intr:
212 arm_intr_disestablish(sc->sc_ih);
213 sc->sc_ih = NULL;
214 mem2:
215 #if 0
216 bus_space_unmap(sc->sc.iot, sc->tll_ioh, aa->aa_dev->mem[2].size);
217 mem1:
218 #endif
219 bus_space_unmap(sc->sc.iot, sc->uhh_ioh, faa->fa_reg[0].size);
220 mem0:
221 bus_space_unmap(sc->sc.iot, sc->sc.ioh, sc->sc.sc_size);
222 sc->sc.sc_size = 0;
223 out:
224 return;
225 }
226
227 int
omehci_init(struct omehci_softc * sc)228 omehci_init(struct omehci_softc *sc)
229 {
230 uint32_t i = 0, reg;
231 uint32_t reset_performed = 0;
232 uint32_t timeout = 0;
233 uint32_t tll_ch_mask = 0;
234
235 /* enable high speed usb host clock */
236 prcm_enablemodule(PRCM_USB);
237
238 /* Hold the PHY in RESET for enough time till DIR is high */
239 if (reset_performed)
240 delay(10);
241
242 /* Read the UHH revision */
243 sc->ehci_rev = bus_space_read_4(sc->sc.iot, sc->uhh_ioh,
244 OMAP_USBHOST_UHH_REVISION);
245
246 /* Initialise the low level interface module(s) */
247 if (sc->ehci_rev == OMAP_EHCI_REV1) {
248 /* Enable the USB TLL */
249 prcm_enablemodule(PRCM_USBTLL);
250
251 /* Perform TLL soft reset, and wait until reset is complete */
252 bus_space_write_4(sc->sc.iot, sc->tll_ioh,
253 OMAP_USBTLL_SYSCONFIG, TLL_SYSCONFIG_SOFTRESET);
254
255 /* Set the timeout to 100ms*/
256 timeout = (hz < 10) ? 1 : ((100 * hz) / 1000);
257
258 /* Wait for TLL reset to complete */
259 while ((bus_space_read_4(sc->sc.iot, sc->tll_ioh,
260 OMAP_USBTLL_SYSSTATUS) & TLL_SYSSTATUS_RESETDONE)
261 == 0x00) {
262
263 /* Sleep for a tick */
264 delay(10);
265
266 if (timeout-- == 0) {
267 return 1;
268 }
269 }
270
271 bus_space_write_4(sc->sc.iot, sc->tll_ioh,
272 OMAP_USBTLL_SYSCONFIG,
273 TLL_SYSCONFIG_ENAWAKEUP | TLL_SYSCONFIG_AUTOIDLE |
274 TLL_SYSCONFIG_SIDLE_SMART_IDLE | TLL_SYSCONFIG_CACTIVITY);
275 } else if (sc->ehci_rev == OMAP_EHCI_REV2) {
276 /* For OMAP44xx devices you have to enable the per-port clocks:
277 * PHY_MODE - External ULPI clock
278 * TTL_MODE - Internal UTMI clock
279 * HSIC_MODE - Internal 480Mhz and 60Mhz clocks
280 */
281 if (sc->port_mode[0] == EHCI_HCD_OMAP_MODE_PHY) {
282 //ti_prcm_clk_set_source(USBP1_PHY_CLK, EXT_CLK);
283 prcm_enablemodule(PRCM_USBP1_PHY);
284 } else if (sc->port_mode[0] == EHCI_HCD_OMAP_MODE_TLL)
285 prcm_enablemodule(PRCM_USBP1_UTMI);
286 else if (sc->port_mode[0] == EHCI_HCD_OMAP_MODE_HSIC)
287 prcm_enablemodule(PRCM_USBP1_HSIC);
288
289 if (sc->port_mode[1] == EHCI_HCD_OMAP_MODE_PHY) {
290 //ti_prcm_clk_set_source(USBP2_PHY_CLK, EXT_CLK);
291 prcm_enablemodule(PRCM_USBP2_PHY);
292 } else if (sc->port_mode[1] == EHCI_HCD_OMAP_MODE_TLL)
293 prcm_enablemodule(PRCM_USBP2_UTMI);
294 else if (sc->port_mode[1] == EHCI_HCD_OMAP_MODE_HSIC)
295 prcm_enablemodule(PRCM_USBP2_HSIC);
296 }
297
298 /* Put UHH in SmartIdle/SmartStandby mode */
299 reg = bus_space_read_4(sc->sc.iot, sc->uhh_ioh,
300 OMAP_USBHOST_UHH_SYSCONFIG);
301 if (sc->ehci_rev == OMAP_EHCI_REV1) {
302 reg &= ~(UHH_SYSCONFIG_SIDLEMODE_MASK |
303 UHH_SYSCONFIG_MIDLEMODE_MASK);
304 reg |= (UHH_SYSCONFIG_ENAWAKEUP |
305 UHH_SYSCONFIG_AUTOIDLE |
306 UHH_SYSCONFIG_CLOCKACTIVITY |
307 UHH_SYSCONFIG_SIDLEMODE_SMARTIDLE |
308 UHH_SYSCONFIG_MIDLEMODE_SMARTSTANDBY);
309 } else if (sc->ehci_rev == OMAP_EHCI_REV2) {
310 reg &= ~UHH_SYSCONFIG_IDLEMODE_MASK;
311 reg |= UHH_SYSCONFIG_IDLEMODE_NOIDLE;
312 reg &= ~UHH_SYSCONFIG_STANDBYMODE_MASK;
313 reg |= UHH_SYSCONFIG_STANDBYMODE_NOSTDBY;
314 }
315 bus_space_write_4(sc->sc.iot, sc->uhh_ioh, OMAP_USBHOST_UHH_SYSCONFIG,
316 reg);
317
318 reg = bus_space_read_4(sc->sc.iot, sc->uhh_ioh,
319 OMAP_USBHOST_UHH_HOSTCONFIG);
320
321 /* Setup ULPI bypass and burst configurations */
322 reg |= (UHH_HOSTCONFIG_ENA_INCR4 |
323 UHH_HOSTCONFIG_ENA_INCR8 |
324 UHH_HOSTCONFIG_ENA_INCR16);
325 reg &= ~UHH_HOSTCONFIG_ENA_INCR_ALIGN;
326
327 if (sc->ehci_rev == OMAP_EHCI_REV1) {
328 if (sc->port_mode[0] == EHCI_HCD_OMAP_MODE_UNKNOWN)
329 reg &= ~UHH_HOSTCONFIG_P1_CONNECT_STATUS;
330 if (sc->port_mode[1] == EHCI_HCD_OMAP_MODE_UNKNOWN)
331 reg &= ~UHH_HOSTCONFIG_P2_CONNECT_STATUS;
332 if (sc->port_mode[2] == EHCI_HCD_OMAP_MODE_UNKNOWN)
333 reg &= ~UHH_HOSTCONFIG_P3_CONNECT_STATUS;
334
335 /* Bypass the TLL module for PHY mode operation */
336 if ((sc->port_mode[0] == EHCI_HCD_OMAP_MODE_PHY) ||
337 (sc->port_mode[1] == EHCI_HCD_OMAP_MODE_PHY) ||
338 (sc->port_mode[2] == EHCI_HCD_OMAP_MODE_PHY))
339 reg &= ~UHH_HOSTCONFIG_P1_ULPI_BYPASS;
340 else
341 reg |= UHH_HOSTCONFIG_P1_ULPI_BYPASS;
342 } else if (sc->ehci_rev == OMAP_EHCI_REV2) {
343 reg |= UHH_HOSTCONFIG_APP_START_CLK;
344
345 /* Clear port mode fields for PHY mode*/
346 reg &= ~UHH_HOSTCONFIG_P1_MODE_MASK;
347 reg &= ~UHH_HOSTCONFIG_P2_MODE_MASK;
348
349 if (sc->port_mode[0] == EHCI_HCD_OMAP_MODE_TLL)
350 reg |= UHH_HOSTCONFIG_P1_MODE_UTMI_PHY;
351 else if (sc->port_mode[0] == EHCI_HCD_OMAP_MODE_HSIC)
352 reg |= UHH_HOSTCONFIG_P1_MODE_HSIC;
353
354 if (sc->port_mode[1] == EHCI_HCD_OMAP_MODE_TLL)
355 reg |= UHH_HOSTCONFIG_P2_MODE_UTMI_PHY;
356 else if (sc->port_mode[1] == EHCI_HCD_OMAP_MODE_HSIC)
357 reg |= UHH_HOSTCONFIG_P2_MODE_HSIC;
358 }
359
360 bus_space_write_4(sc->sc.iot, sc->uhh_ioh, OMAP_USBHOST_UHH_HOSTCONFIG, reg);
361
362 /* If any of the ports are configured in TLL mode, enable them */
363 for (i = 0; i < OMAP_HS_USB_PORTS; i++)
364 if (sc->port_mode[i] == EHCI_HCD_OMAP_MODE_PHY)
365 tll_ch_mask |= 1 << i;
366
367 /* Enable UTMI mode for required TLL channels */
368 #ifdef notyet
369 if (tll_ch_mask)
370 omap_ehci_utmi_init(sc, tll_ch_mask);
371 #endif
372
373 /* Set the interrupt threshold control, it controls the maximum rate at
374 * which the host controller issues interrupts. We set it to 1 microframe
375 * at startup - the default is 8 mircoframes (equates to 1ms).
376 */
377 reg = bus_space_read_4(sc->sc.iot, sc->sc.ioh, OMAP_USBHOST_USBCMD);
378 reg &= 0xff00ffff;
379 reg |= (1 << 16);
380 bus_space_write_4(sc->sc.iot, sc->sc.ioh, OMAP_USBHOST_USBCMD, reg);
381
382 /* Soft reset the PHY using PHY reset command over ULPI */
383 for (i = 0; i < OMAP_HS_USB_PORTS; i++)
384 if (sc->port_mode[i] == EHCI_HCD_OMAP_MODE_PHY)
385 omehci_soft_phy_reset(sc, i);
386
387 return(0);
388 }
389
390 void
omehci_soft_phy_reset(struct omehci_softc * sc,unsigned int port)391 omehci_soft_phy_reset(struct omehci_softc *sc, unsigned int port)
392 {
393 unsigned long timeout = (hz < 10) ? 1 : ((100 * hz) / 1000);
394 uint32_t reg;
395
396 reg = ULPI_FUNC_CTRL_RESET
397 /* FUNCTION_CTRL_SET register */
398 | (ULPI_SET(ULPI_FUNC_CTRL) << OMAP_USBHOST_INSNREG05_ULPI_REGADD_SHIFT)
399 /* Write */
400 | (2 << OMAP_USBHOST_INSNREG05_ULPI_OPSEL_SHIFT)
401 /* PORTn */
402 | ((port + 1) << OMAP_USBHOST_INSNREG05_ULPI_PORTSEL_SHIFT)
403 /* start ULPI access*/
404 | (1 << OMAP_USBHOST_INSNREG05_ULPI_CONTROL_SHIFT);
405
406 bus_space_write_4(sc->sc.iot, sc->sc.ioh, OMAP_USBHOST_INSNREG05_ULPI, reg);
407
408 timeout += 1000000;
409 /* Wait for ULPI access completion */
410 while ((bus_space_read_4(sc->sc.iot, sc->sc.ioh, OMAP_USBHOST_INSNREG05_ULPI)
411 & (1 << OMAP_USBHOST_INSNREG05_ULPI_CONTROL_SHIFT))) {
412
413 /* Sleep for a tick */
414 delay(10);
415
416 if (timeout-- == 0) {
417 printf("PHY reset operation timed out\n");
418 break;
419 }
420 }
421 }
422
423 int
omehci_detach(struct device * self,int flags)424 omehci_detach(struct device *self, int flags)
425 {
426 struct omehci_softc *sc = (struct omehci_softc *)self;
427 int rv;
428
429 rv = ehci_detach(self, flags);
430 if (rv)
431 return (rv);
432
433 if (sc->sc_ih != NULL) {
434 arm_intr_disestablish(sc->sc_ih);
435 sc->sc_ih = NULL;
436 }
437
438 if (sc->sc.sc_size) {
439 bus_space_unmap(sc->sc.iot, sc->sc.ioh, sc->sc.sc_size);
440 sc->sc.sc_size = 0;
441 }
442
443 /* XXX: stop clock */
444
445 return (0);
446 }
447
448 int
omehci_activate(struct device * self,int act)449 omehci_activate(struct device *self, int act)
450 {
451 struct omehci_softc *sc = (struct omehci_softc *)self;
452 int rv;
453
454 switch (act) {
455 case DVACT_SUSPEND:
456 rv = config_activate_children(self, act);
457 sc->sc.sc_bus.use_polling++;
458 /* FIXME */
459 sc->sc.sc_bus.use_polling--;
460 break;
461 case DVACT_RESUME:
462 sc->sc.sc_bus.use_polling++;
463 /* FIXME */
464 sc->sc.sc_bus.use_polling--;
465 rv = config_activate_children(self, act);
466 break;
467 case DVACT_POWERDOWN:
468 rv = config_activate_children(self, act);
469 ehci_reset(&sc->sc);
470 break;
471 }
472 return rv;
473 }
474