1/*-
2 * Copyright (c) 2012 The NetBSD Foundation, Inc.
3 * All rights reserved.
4 *
5 * This code is derived from software contributed to The NetBSD Foundation
6 * by Matt Thomas of 3am Software Foundry.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 */
29#define USBH_PRIVATE
30
31#include "locators.h"
32
33#include <sys/cdefs.h>
34
35__KERNEL_RCSID(1, "$NetBSD: bcm53xx_usb.c,v 1.10 2021/08/07 16:18:43 thorpej Exp $");
36
37#include <sys/param.h>
38#include <sys/bus.h>
39#include <sys/device.h>
40#include <sys/intr.h>
41#include <sys/systm.h>
42
43#include <arm/broadcom/bcm53xx_reg.h>
44#include <arm/broadcom/bcm53xx_var.h>
45
46#include <dev/usb/usb.h>
47#include <dev/usb/usbdi.h>
48#include <dev/usb/usbdivar.h>
49#include <dev/usb/usb_mem.h>
50
51#include <dev/usb/ohcireg.h>
52#include <dev/usb/ohcivar.h>
53
54#include <dev/usb/ehcireg.h>
55#include <dev/usb/ehcivar.h>
56
57struct bcmusb_softc {
58	device_t usbsc_dev;
59	bus_dma_tag_t usbsc_dmat;
60	bus_space_tag_t usbsc_bst;
61	bus_space_handle_t usbsc_ehci_bsh;
62	bus_space_handle_t usbsc_ohci_bsh;
63
64	device_t usbsc_ohci_dev;
65	device_t usbsc_ehci_dev;
66	void *usbsc_ohci_sc;
67	void *usbsc_ehci_sc;
68	void *usbsc_ih;
69};
70
71struct bcmusb_attach_args {
72	const char *usbaa_name;
73	bus_dma_tag_t usbaa_dmat;
74	bus_space_tag_t usbaa_bst;
75	bus_space_handle_t usbaa_bsh;
76	bus_size_t usbaa_size;
77};
78
79#ifdef OHCI_DEBUG
80#define OHCI_DPRINTF(x)	if (ohcidebug) printf x
81extern int ohcidebug;
82#else
83#define OHCI_DPRINTF(x)
84#endif
85
86static int ohci_bcmusb_match(device_t, cfdata_t, void *);
87static void ohci_bcmusb_attach(device_t, device_t, void *);
88
89CFATTACH_DECL_NEW(ohci_bcmusb, sizeof(struct ohci_softc),
90	ohci_bcmusb_match, ohci_bcmusb_attach, NULL, NULL);
91
92static int
93ohci_bcmusb_match(device_t parent, cfdata_t cf, void *aux)
94{
95	struct bcmusb_attach_args * const usbaa = aux;
96
97	if (strcmp(cf->cf_name, usbaa->usbaa_name))
98		return 0;
99
100	return 1;
101}
102
103static void
104ohci_bcmusb_attach(device_t parent, device_t self, void *aux)
105{
106	struct ohci_softc * const sc = device_private(self);
107	struct bcmusb_attach_args * const usbaa = aux;
108
109	sc->sc_dev = self;
110
111	sc->iot = usbaa->usbaa_bst;
112	sc->ioh = usbaa->usbaa_bsh;
113	sc->sc_size = usbaa->usbaa_size;
114	sc->sc_bus.ub_dmatag = usbaa->usbaa_dmat;
115	sc->sc_bus.ub_hcpriv = sc;
116
117	aprint_naive(": OHCI USB controller\n");
118	aprint_normal(": OHCI USB controller\n");
119
120	int error = ohci_init(sc);
121	if (error) {
122		aprint_error_dev(self, "init failed, error=%d\n", error);
123		return;
124	}
125
126	/* Attach usb device. */
127	sc->sc_child = config_found(self, &sc->sc_bus, usbctlprint, CFARGS_NONE);
128}
129
130#ifdef EHCI_DEBUG
131#define EHCI_DPRINTF(x)	if (ehcidebug) printf x
132extern int ehcidebug;
133#else
134#define EHCI_DPRINTF(x)
135#endif
136
137static int ehci_bcmusb_match(device_t, cfdata_t, void *);
138static void ehci_bcmusb_attach(device_t, device_t, void *);
139
140CFATTACH_DECL_NEW(ehci_bcmusb, sizeof(struct ehci_softc),
141	ehci_bcmusb_match, ehci_bcmusb_attach, NULL, NULL);
142
143static int
144ehci_bcmusb_match(device_t parent, cfdata_t cf, void *aux)
145{
146	struct bcmusb_attach_args * const usbaa = aux;
147
148	if (strcmp(cf->cf_name, usbaa->usbaa_name))
149		return 0;
150
151	return 1;
152}
153
154static void
155ehci_bcmusb_attach(device_t parent, device_t self, void *aux)
156{
157	struct bcmusb_softc * const usbsc = device_private(parent);
158	struct ehci_softc * const sc = device_private(self);
159	struct bcmusb_attach_args * const usbaa = aux;
160
161	sc->sc_dev = self;
162
163	sc->iot = usbaa->usbaa_bst;
164	sc->ioh = usbaa->usbaa_bsh;
165	sc->sc_size = usbaa->usbaa_size;
166	sc->sc_bus.ub_dmatag = usbaa->usbaa_dmat;
167	sc->sc_bus.ub_hcpriv = sc;
168	sc->sc_bus.ub_revision = USBREV_2_0;
169	sc->sc_ncomp = 0;
170	if (usbsc->usbsc_ohci_dev != NULL) {
171		sc->sc_comps[sc->sc_ncomp++] = usbsc->usbsc_ohci_dev;
172	}
173
174	aprint_naive(": EHCI USB controller\n");
175	aprint_normal(": ECHI USB controller\n");
176
177	int error = ehci_init(sc);
178	if (error) {
179		aprint_error_dev(self, "init failed, error=%d\n", error);
180		return;
181	}
182	/* Attach usb device. */
183	sc->sc_child = config_found(self, &sc->sc_bus, usbctlprint, CFARGS_NONE);
184}
185
186/*
187 * There's only IRQ shared between both OCHI and EHCI devices.
188 */
189static int
190bcmusb_intr(void *arg)
191{
192	struct bcmusb_softc * const usbsc = arg;
193	int rv0 = 0, rv1 = 0;
194
195	if (usbsc->usbsc_ohci_sc)
196		rv0 = ohci_intr(usbsc->usbsc_ohci_sc);
197
198	if (usbsc->usbsc_ehci_sc)
199		rv1 = ehci_intr(usbsc->usbsc_ehci_sc);
200
201	return rv0 ? rv0 : rv1;
202}
203
204static int bcmusb_ccb_match(device_t, cfdata_t, void *);
205static void bcmusb_ccb_attach(device_t, device_t, void *);
206
207CFATTACH_DECL_NEW(bcmusb_ccb, sizeof(struct bcmusb_softc),
208	bcmusb_ccb_match, bcmusb_ccb_attach, NULL, NULL);
209
210int
211bcmusb_ccb_match(device_t parent, cfdata_t cf, void *aux)
212{
213	struct bcmccb_attach_args * const ccbaa = aux;
214	const struct bcm_locators * const loc = &ccbaa->ccbaa_loc;
215
216	if (strcmp(cf->cf_name, loc->loc_name) != 0)
217		return 0;
218
219	KASSERT(cf->cf_loc[BCMCCBCF_PORT] == BCMCCBCF_PORT_DEFAULT);
220
221	return 1;
222}
223
224#define	OHCI_OFFSET	(OHCI_BASE - EHCI_BASE)
225
226void
227bcmusb_ccb_attach(device_t parent, device_t self, void *aux)
228{
229	struct bcmusb_softc * const usbsc = device_private(self);
230	const struct bcmccb_attach_args * const ccbaa = aux;
231	const struct bcm_locators * const loc = &ccbaa->ccbaa_loc;
232
233	usbsc->usbsc_bst = ccbaa->ccbaa_ccb_bst;
234	usbsc->usbsc_dmat = ccbaa->ccbaa_dmat;
235
236	bus_space_subregion(usbsc->usbsc_bst, ccbaa->ccbaa_ccb_bsh,
237	    loc->loc_offset, 0x1000, &usbsc->usbsc_ehci_bsh);
238	bus_space_subregion(usbsc->usbsc_bst, ccbaa->ccbaa_ccb_bsh,
239	    loc->loc_offset + OHCI_OFFSET, 0x1000, &usbsc->usbsc_ohci_bsh);
240
241	/*
242	 * Bring the PHYs out of reset.
243	 */
244	bus_space_write_4(usbsc->usbsc_bst, usbsc->usbsc_ehci_bsh,
245	    USBH_PHY_CTRL_P0, USBH_PHY_CTRL_INIT);
246	bus_space_write_4(usbsc->usbsc_bst, usbsc->usbsc_ehci_bsh,
247	    USBH_PHY_CTRL_P1, USBH_PHY_CTRL_INIT);
248
249	/*
250	 * Disable interrupts
251	 */
252	bus_space_write_4(usbsc->usbsc_bst, usbsc->usbsc_ohci_bsh,
253	    OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS);
254	bus_size_t caplength = bus_space_read_1(usbsc->usbsc_bst,
255	    usbsc->usbsc_ehci_bsh, EHCI_CAPLENGTH);
256	bus_space_write_4(usbsc->usbsc_bst, usbsc->usbsc_ehci_bsh,
257	    caplength + EHCI_USBINTR, 0);
258
259	aprint_naive("\n");
260	aprint_normal("\n");
261
262	struct bcmusb_attach_args usbaa_ohci = {
263		.usbaa_name = "ohci",
264		.usbaa_dmat = usbsc->usbsc_dmat,
265		.usbaa_bst = usbsc->usbsc_bst,
266		.usbaa_bsh = usbsc->usbsc_ohci_bsh,
267		.usbaa_size = 0x100,
268	};
269
270	usbsc->usbsc_ohci_dev = config_found(self, &usbaa_ohci, NULL,
271	    CFARGS_NONE);
272	if (usbsc->usbsc_ohci_dev != NULL)
273		usbsc->usbsc_ohci_sc = device_private(usbsc->usbsc_ohci_dev);
274
275	struct bcmusb_attach_args usbaa_ehci = {
276		.usbaa_name = "ehci",
277		.usbaa_dmat = usbsc->usbsc_dmat,
278		.usbaa_bst = usbsc->usbsc_bst,
279		.usbaa_bsh = usbsc->usbsc_ehci_bsh,
280		.usbaa_size = 0x100,
281	};
282
283	usbsc->usbsc_ehci_dev = config_found(self, &usbaa_ehci, NULL,
284	    CFARGS_NONE);
285	if (usbsc->usbsc_ehci_dev != NULL)
286		usbsc->usbsc_ehci_sc = device_private(usbsc->usbsc_ehci_dev);
287
288	usbsc->usbsc_ih = intr_establish(loc->loc_intrs[0], IPL_USB, IST_LEVEL,
289	    bcmusb_intr, usbsc);
290	if (usbsc->usbsc_ih == NULL) {
291		aprint_error_dev(self, "failed to establish interrupt %d\n",
292		     loc->loc_intrs[0]);
293		return;
294	}
295	aprint_normal_dev(self, "interrupting on irq %d\n", loc->loc_intrs[0]);
296}
297