xref: /openbsd/sys/arch/armv7/omap/omehci.c (revision ea05d060)
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