xref: /openbsd-src/sys/dev/fdt/ehci_fdt.c (revision 5a38ef86d0b61900239c7913d24a05e7b88a58f0)
1 /*	$OpenBSD: ehci_fdt.c,v 1.8 2021/12/03 19:22:42 uaa Exp $ */
2 
3 /*
4  * Copyright (c) 2005 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/ehcireg.h>
41 #include <dev/usb/ehcivar.h>
42 
43 struct ehci_fdt_softc {
44 	struct ehci_softc	sc;
45 	int			sc_node;
46 	void			*sc_ih;
47 };
48 
49 int	ehci_fdt_match(struct device *, void *, void *);
50 void	ehci_fdt_attach(struct device *, struct device *, void *);
51 int	ehci_fdt_detach(struct device *, int);
52 
53 const struct cfattach ehci_fdt_ca = {
54 	sizeof(struct ehci_fdt_softc), ehci_fdt_match, ehci_fdt_attach,
55 	ehci_fdt_detach, ehci_activate
56 };
57 
58 void	ehci_init_phys(struct ehci_fdt_softc *);
59 
60 int
61 ehci_fdt_match(struct device *parent, void *match, void *aux)
62 {
63 	struct fdt_attach_args *faa = aux;
64 
65 	return OF_is_compatible(faa->fa_node, "generic-ehci");
66 }
67 
68 void
69 ehci_fdt_attach(struct device *parent, struct device *self, void *aux)
70 {
71 	struct ehci_fdt_softc *sc = (struct ehci_fdt_softc *)self;
72 	struct fdt_attach_args *faa = aux;
73 	char *devname = sc->sc.sc_bus.bdev.dv_xname;
74 	usbd_status r;
75 
76 	if (faa->fa_nreg < 1) {
77 		printf(": no registers\n");
78 		return;
79 	}
80 
81 	sc->sc_node = faa->fa_node;
82 	sc->sc.iot = faa->fa_iot;
83 	sc->sc.sc_bus.dmatag = faa->fa_dmat;
84 	sc->sc.sc_size = faa->fa_reg[0].size;
85 
86 	if (bus_space_map(sc->sc.iot, faa->fa_reg[0].addr,
87 	    faa->fa_reg[0].size, 0, &sc->sc.ioh)) {
88 		printf(": can't map registers\n");
89 		goto out;
90 	}
91 
92 	pinctrl_byname(sc->sc_node, "default");
93 
94 	clock_enable_all(sc->sc_node);
95 	reset_deassert_all(sc->sc_node);
96 
97 	/* Disable interrupts, so we don't get any spurious ones. */
98 	sc->sc.sc_offs = EREAD1(&sc->sc, EHCI_CAPLENGTH);
99 	EOWRITE2(&sc->sc, EHCI_USBINTR, 0);
100 
101 	sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_USB,
102 	    ehci_intr, &sc->sc, devname);
103 	if (sc->sc_ih == NULL) {
104 		printf(": can't establish interrupt\n");
105 		clock_disable_all(sc->sc_node);
106 		goto unmap;
107 	}
108 
109 	printf("\n");
110 
111 	ehci_init_phys(sc);
112 
113 	strlcpy(sc->sc.sc_vendor, "Generic", sizeof(sc->sc.sc_vendor));
114 	r = ehci_init(&sc->sc);
115 	if (r != USBD_NORMAL_COMPLETION) {
116 		printf("%s: init failed, error=%d\n", devname, r);
117 		clock_disable_all(sc->sc_node);
118 		goto disestablish_intr;
119 	}
120 
121 	/* Attach usb device. */
122 	config_found(self, &sc->sc.sc_bus, usbctlprint);
123 	return;
124 
125 disestablish_intr:
126 	fdt_intr_disestablish(sc->sc_ih);
127 	sc->sc_ih = NULL;
128 unmap:
129 	bus_space_unmap(sc->sc.iot, sc->sc.ioh, sc->sc.sc_size);
130 	sc->sc.sc_size = 0;
131 out:
132 	return;
133 }
134 
135 int
136 ehci_fdt_detach(struct device *self, int flags)
137 {
138 	struct ehci_fdt_softc *sc = (struct ehci_fdt_softc *)self;
139 	int rv;
140 
141 	rv = ehci_detach(self, flags);
142 	if (rv)
143 		return rv;
144 
145 	if (sc->sc_ih != NULL) {
146 		fdt_intr_disestablish(sc->sc_ih);
147 		sc->sc_ih = NULL;
148 	}
149 
150 	if (sc->sc.sc_size) {
151 		bus_space_unmap(sc->sc.iot, sc->sc.ioh, sc->sc.sc_size);
152 		sc->sc.sc_size = 0;
153 	}
154 
155 	clock_disable_all(sc->sc_node);
156 	return 0;
157 }
158 
159 struct ehci_phy {
160 	const char *compat;
161 	void (*init)(struct ehci_fdt_softc *, uint32_t *);
162 };
163 
164 void sun4i_phy_init(struct ehci_fdt_softc *, uint32_t *);
165 void sun9i_phy_init(struct ehci_fdt_softc *, uint32_t *);
166 
167 struct ehci_phy ehci_phys[] = {
168 	{ "allwinner,sun4i-a10-usb-phy", sun4i_phy_init },
169 	{ "allwinner,sun5i-a13-usb-phy", sun4i_phy_init },
170 	{ "allwinner,sun6i-a31-usb-phy", sun4i_phy_init },
171 	{ "allwinner,sun7i-a20-usb-phy", sun4i_phy_init },
172 	{ "allwinner,sun8i-a23-usb-phy", sun4i_phy_init },
173 	{ "allwinner,sun8i-a33-usb-phy", sun4i_phy_init },
174 	{ "allwinner,sun8i-h3-usb-phy", sun4i_phy_init },
175 	{ "allwinner,sun8i-r40-usb-phy", sun4i_phy_init },
176 	{ "allwinner,sun8i-v3s-usb-phy", sun4i_phy_init },
177 	{ "allwinner,sun50i-h6-usb-phy", sun4i_phy_init },
178 	{ "allwinner,sun50i-a64-usb-phy", sun4i_phy_init },
179 	{ "allwinner,sun9i-a80-usb-phy", sun9i_phy_init },
180 };
181 
182 uint32_t *
183 ehci_next_phy(uint32_t *cells)
184 {
185 	uint32_t phandle = cells[0];
186 	int node, ncells;
187 
188 	node = OF_getnodebyphandle(phandle);
189 	if (node == 0)
190 		return NULL;
191 
192 	ncells = OF_getpropint(node, "#phy-cells", 0);
193 	return cells + ncells + 1;
194 }
195 
196 void
197 ehci_init_phy(struct ehci_fdt_softc *sc, uint32_t *cells)
198 {
199 	uint32_t phy_supply;
200 	int node;
201 	int i;
202 
203 	node = OF_getnodebyphandle(cells[0]);
204 	if (node == 0)
205 		return;
206 
207 	for (i = 0; i < nitems(ehci_phys); i++) {
208 		if (OF_is_compatible(node, ehci_phys[i].compat)) {
209 			ehci_phys[i].init(sc, cells);
210 			return;
211 		}
212 	}
213 
214 	phy_supply = OF_getpropint(node, "phy-supply", 0);
215 	if (phy_supply)
216 		regulator_enable(phy_supply);
217 }
218 
219 void
220 ehci_init_phys(struct ehci_fdt_softc *sc)
221 {
222 	uint32_t *phys;
223 	uint32_t *phy;
224 	int len;
225 
226 	len = OF_getproplen(sc->sc_node, "phys");
227 	if (len <= 0)
228 		return;
229 
230 	phys = malloc(len, M_TEMP, M_WAITOK);
231 	OF_getpropintarray(sc->sc_node, "phys", phys, len);
232 
233 	phy = phys;
234 	while (phy && phy < phys + (len / sizeof(uint32_t))) {
235 		ehci_init_phy(sc, phy);
236 		phy = ehci_next_phy(phy);
237 	}
238 
239 	free(phys, M_TEMP, len);
240 }
241 
242 /*
243  * Allwinner PHYs.
244  */
245 
246 /* Registers */
247 #define SUNXI_HCI_ICR		0x800
248 #define  SUNXI_ULPI_BYPASS	(1 << 0)
249 #define  SUNXI_AHB_INCRX_ALIGN	(1 << 8)
250 #define  SUNXI_AHB_INCR4	(1 << 9)
251 #define  SUNXI_AHB_INCR8	(1 << 10)
252 #define  SUNXI_AHB_INCR16	(1 << 11)
253 
254 void
255 sun4i_phy_init(struct ehci_fdt_softc *sc, uint32_t *cells)
256 {
257 	uint32_t vbus_supply;
258 	char name[32];
259 	uint32_t val;
260 	int node;
261 
262 	node = OF_getnodebyphandle(cells[0]);
263 	if (node == -1)
264 		return;
265 
266 	val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR);
267 	val |= SUNXI_AHB_INCR8 | SUNXI_AHB_INCR4;
268 	val |= SUNXI_AHB_INCRX_ALIGN;
269 	val |= SUNXI_ULPI_BYPASS;
270 	bus_space_write_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR, val);
271 
272 	/*
273 	 * We need to poke an undocumented register to make the PHY
274 	 * work on Allwinner A64/H3/H5/R40.
275 	 */
276 	if (OF_is_compatible(node, "allwinner,sun8i-h3-usb-phy") ||
277 	    OF_is_compatible(node, "allwinner,sun8i-r40-usb-phy") ||
278 	    OF_is_compatible(node, "allwinner,sun50i-h6-usb-phy") ||
279 	    OF_is_compatible(node, "allwinner,sun50i-a64-usb-phy")) {
280 		val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, 0x810);
281 		val &= ~(1 << 1);
282 		bus_space_write_4(sc->sc.iot, sc->sc.ioh, 0x810, val);
283 	}
284 
285 	pinctrl_byname(node, "default");
286 
287 	/*
288 	 * On sun4i, sun5i and sun7i, there is a single clock.  The
289 	 * more recent SoCs have a separate clock for each PHY.
290 	 */
291 	if (OF_is_compatible(node, "allwinner,sun4i-a10-usb-phy") ||
292 	    OF_is_compatible(node, "allwinner,sun5i-a13-usb-phy") ||
293 	    OF_is_compatible(node, "allwinner,sun7i-a20-usb-phy")) {
294 		clock_enable(node, "usb_phy");
295 	} else {
296 		snprintf(name, sizeof(name), "usb%d_phy", cells[1]);
297 		clock_enable(node, name);
298 	}
299 
300 	snprintf(name, sizeof(name), "usb%d_reset", cells[1]);
301 	reset_deassert(node, name);
302 
303 	snprintf(name, sizeof(name), "usb%d_vbus-supply", cells[1]);
304 	vbus_supply = OF_getpropint(node, name, 0);
305 	if (vbus_supply)
306 		regulator_enable(vbus_supply);
307 }
308 
309 void
310 sun9i_phy_init(struct ehci_fdt_softc *sc, uint32_t *cells)
311 {
312 	uint32_t phy_supply;
313 	uint32_t val;
314 	int node;
315 
316 	node = OF_getnodebyphandle(cells[0]);
317 	if (node == -1)
318 		return;
319 
320 	pinctrl_byname(node, "default");
321 	clock_enable(node, "phy");
322 	reset_deassert(node, "phy");
323 
324 	val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR);
325 	val |= SUNXI_AHB_INCR16 | SUNXI_AHB_INCR8 | SUNXI_AHB_INCR4;
326 	val |= SUNXI_AHB_INCRX_ALIGN;
327 	val |= SUNXI_ULPI_BYPASS;
328 	bus_space_write_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR, val);
329 
330 	phy_supply = OF_getpropint(node, "phy-supply", 0);
331 	if (phy_supply)
332 		regulator_enable(phy_supply);
333 }
334