1 /* $OpenBSD: ohci_fdt.c,v 1.3 2021/10/24 17:52:26 mpi Exp $ */
2
3 /*
4 * Copyright (c) 2005, 2019 David Gwynne <dlg@openbsd.org>
5 * Copyright (c) 2017 Mark Kettenis <kettenis@openbsd.org>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 #include <sys/types.h>
21 #include <sys/systm.h>
22 #include <sys/device.h>
23 #include <sys/malloc.h>
24
25 #include <machine/intr.h>
26 #include <machine/bus.h>
27 #include <machine/fdt.h>
28
29 #include <dev/ofw/openfirm.h>
30 #include <dev/ofw/ofw_clock.h>
31 #include <dev/ofw/ofw_pinctrl.h>
32 #include <dev/ofw/ofw_regulator.h>
33 #include <dev/ofw/fdt.h>
34
35 #include <dev/usb/usb.h>
36 #include <dev/usb/usbdi.h>
37 #include <dev/usb/usbdivar.h>
38 #include <dev/usb/usb_mem.h>
39
40 #include <dev/usb/ohcireg.h>
41 #include <dev/usb/ohcivar.h>
42
43 struct ohci_fdt_softc {
44 struct ohci_softc sc;
45 int sc_node;
46 void *sc_ih;
47 };
48
49 int ohci_fdt_match(struct device *, void *, void *);
50 void ohci_fdt_attach(struct device *, struct device *, void *);
51 int ohci_fdt_detach(struct device *, int);
52
53 const struct cfattach ohci_fdt_ca = {
54 sizeof(struct ohci_fdt_softc),
55 ohci_fdt_match,
56 ohci_fdt_attach,
57 ohci_fdt_detach,
58 ohci_activate
59 };
60
61 void ohci_fdt_attach_deferred(struct device *);
62
63 int
ohci_fdt_match(struct device * parent,void * match,void * aux)64 ohci_fdt_match(struct device *parent, void *match, void *aux)
65 {
66 struct fdt_attach_args *faa = aux;
67
68 return OF_is_compatible(faa->fa_node, "generic-ohci");
69 }
70
71 void
ohci_fdt_attach(struct device * parent,struct device * self,void * aux)72 ohci_fdt_attach(struct device *parent, struct device *self, void *aux)
73 {
74 struct ohci_fdt_softc *sc = (struct ohci_fdt_softc *)self;
75 struct fdt_attach_args *faa = aux;
76 char *devname = sc->sc.sc_bus.bdev.dv_xname;
77
78 if (faa->fa_nreg < 1) {
79 printf(": no registers\n");
80 return;
81 }
82
83 sc->sc_node = faa->fa_node;
84 sc->sc.iot = faa->fa_iot;
85 sc->sc.sc_bus.dmatag = faa->fa_dmat;
86 sc->sc.sc_size = faa->fa_reg[0].size;
87
88 if (bus_space_map(sc->sc.iot, faa->fa_reg[0].addr,
89 faa->fa_reg[0].size, 0, &sc->sc.ioh)) {
90 printf(": can't map registers\n");
91 goto out;
92 }
93
94 pinctrl_byname(sc->sc_node, "default");
95
96 clock_enable_all(sc->sc_node);
97 reset_deassert_all(sc->sc_node);
98
99 /* Record what interrupts were enabled by SMM/BIOS. */
100 sc->sc.sc_intre = bus_space_read_4(sc->sc.iot, sc->sc.ioh,
101 OHCI_INTERRUPT_ENABLE);
102
103 /* Disable interrupts, so we don't get any spurious ones. */
104 bus_space_write_4(sc->sc.iot, sc->sc.ioh, OHCI_INTERRUPT_DISABLE,
105 OHCI_MIE);
106
107 bus_space_barrier(sc->sc.iot, sc->sc.ioh, 0, sc->sc.sc_size,
108 BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE);
109 bus_space_write_4(sc->sc.iot, sc->sc.ioh,
110 OHCI_INTERRUPT_DISABLE, OHCI_MIE);
111
112 /* Map and establish the interrupt. */
113 splassert(IPL_USB);
114 sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_USB,
115 ohci_intr, &sc->sc, devname);
116 if (sc->sc_ih == NULL) {
117 printf(": can't establish interrupt\n");
118 goto disable_clocks;
119 }
120 printf(": ");
121
122 strlcpy(sc->sc.sc_vendor, "Generic", sizeof(sc->sc.sc_vendor));
123
124 /* Display revision. Legacy handover isn't needed as there's no smm */
125 if (ohci_checkrev(&sc->sc) != USBD_NORMAL_COMPLETION) {
126 goto disestablish_intr;
127 }
128
129 /* Ignore interrupts for now */
130 sc->sc.sc_bus.dying = 1;
131
132 config_defer(self, ohci_fdt_attach_deferred);
133
134 return;
135
136 disestablish_intr:
137 fdt_intr_disestablish(sc->sc_ih);
138 sc->sc_ih = NULL;
139 disable_clocks:
140 clock_disable_all(sc->sc_node);
141
142 bus_space_unmap(sc->sc.iot, sc->sc.ioh, sc->sc.sc_size);
143 sc->sc.sc_size = 0;
144 out:
145 return;
146 }
147
148 void
ohci_fdt_attach_deferred(struct device * self)149 ohci_fdt_attach_deferred(struct device *self)
150 {
151 struct ohci_fdt_softc *sc = (struct ohci_fdt_softc *)self;
152 usbd_status r;
153 int s;
154
155 s = splusb();
156
157 sc->sc.sc_bus.dying = 0;
158
159 r = ohci_init(&sc->sc);
160
161 splx(s);
162 if (r != USBD_NORMAL_COMPLETION) {
163 printf("%s: init failed, error=%d\n",
164 sc->sc.sc_bus.bdev.dv_xname, r);
165 fdt_intr_disestablish(sc->sc_ih);
166 sc->sc_ih = NULL;
167 clock_disable_all(sc->sc_node);
168 bus_space_unmap(sc->sc.iot, sc->sc.ioh, sc->sc.sc_size);
169 sc->sc.sc_size = 0;
170 return;
171 }
172
173 /* Attach usb device. */
174 config_found(self, &sc->sc.sc_bus, usbctlprint);
175 }
176
177 int
ohci_fdt_detach(struct device * self,int flags)178 ohci_fdt_detach(struct device *self, int flags)
179 {
180 struct ohci_fdt_softc *sc = (struct ohci_fdt_softc *)self;
181 int rv;
182
183 rv = ohci_detach(self, flags);
184 if (rv)
185 return rv;
186
187 if (sc->sc_ih != NULL) {
188 fdt_intr_disestablish(sc->sc_ih);
189 sc->sc_ih = NULL;
190 }
191
192 if (sc->sc.sc_size) {
193 bus_space_unmap(sc->sc.iot, sc->sc.ioh, sc->sc.sc_size);
194 sc->sc.sc_size = 0;
195 }
196
197 clock_disable_all(sc->sc_node);
198 return 0;
199 }
200