xref: /openbsd/sys/dev/fdt/ehci_fdt.c (revision 623540ca)
1 /*	$OpenBSD: ehci_fdt.c,v 1.12 2024/02/12 21:37:25 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/ofw_misc.h>
34 #include <dev/ofw/fdt.h>
35 
36 #include <dev/usb/usb.h>
37 #include <dev/usb/usbdi.h>
38 #include <dev/usb/usbdivar.h>
39 #include <dev/usb/usb_mem.h>
40 
41 #include <dev/usb/ehcireg.h>
42 #include <dev/usb/ehcivar.h>
43 
44 #define MARVELL_EHCI_HOST_OFFSET	0x0100
45 
46 struct ehci_fdt_softc {
47 	struct ehci_softc	sc;
48 
49 	bus_space_handle_t	sc_ioh;
50 	bus_size_t		sc_size;
51 	void			*sc_ih;
52 
53 	int			sc_node;
54 };
55 
56 int	ehci_fdt_match(struct device *, void *, void *);
57 void	ehci_fdt_attach(struct device *, struct device *, void *);
58 int	ehci_fdt_detach(struct device *, int);
59 
60 const struct cfattach ehci_fdt_ca = {
61 	sizeof(struct ehci_fdt_softc), ehci_fdt_match, ehci_fdt_attach,
62 	ehci_fdt_detach, ehci_activate
63 };
64 
65 void	ehci_init_phys(struct ehci_fdt_softc *);
66 
67 int
ehci_fdt_match(struct device * parent,void * match,void * aux)68 ehci_fdt_match(struct device *parent, void *match, void *aux)
69 {
70 	struct fdt_attach_args *faa = aux;
71 
72 	return OF_is_compatible(faa->fa_node, "generic-ehci") ||
73 	    OF_is_compatible(faa->fa_node, "marvell,armada-3700-ehci");
74 }
75 
76 void
ehci_fdt_attach(struct device * parent,struct device * self,void * aux)77 ehci_fdt_attach(struct device *parent, struct device *self, void *aux)
78 {
79 	struct ehci_fdt_softc *sc = (struct ehci_fdt_softc *)self;
80 	struct fdt_attach_args *faa = aux;
81 	char *devname = sc->sc.sc_bus.bdev.dv_xname;
82 	bus_size_t offset = 0;
83 	usbd_status r;
84 
85 	if (faa->fa_nreg < 1) {
86 		printf(": no registers\n");
87 		return;
88 	}
89 
90 	sc->sc_node = faa->fa_node;
91 	sc->sc.iot = faa->fa_iot;
92 	sc->sc.sc_bus.dmatag = faa->fa_dmat;
93 	sc->sc_size = faa->fa_reg[0].size;
94 
95 	if (bus_space_map(sc->sc.iot, faa->fa_reg[0].addr,
96 	    sc->sc_size, 0, &sc->sc_ioh)) {
97 		printf(": can't map registers\n");
98 		goto out;
99 	}
100 
101 	if (OF_is_compatible(faa->fa_node, "marvell,armada-3700-ehci"))
102 		offset = MARVELL_EHCI_HOST_OFFSET;
103 
104 	sc->sc.sc_size = sc->sc_size - offset;
105 	if (bus_space_subregion(sc->sc.iot, sc->sc_ioh, offset,
106 	    sc->sc.sc_size, &sc->sc.ioh)) {
107 		printf(": can't map ehci registers\n");
108 		goto unmap;
109 	}
110 
111 	pinctrl_byname(sc->sc_node, "default");
112 
113 	clock_enable_all(sc->sc_node);
114 	reset_deassert_all(sc->sc_node);
115 
116 	/* Disable interrupts, so we don't get any spurious ones. */
117 	sc->sc.sc_offs = EREAD1(&sc->sc, EHCI_CAPLENGTH);
118 	EOWRITE2(&sc->sc, EHCI_USBINTR, 0);
119 
120 	sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_USB,
121 	    ehci_intr, &sc->sc, devname);
122 	if (sc->sc_ih == NULL) {
123 		printf(": can't establish interrupt\n");
124 		clock_disable_all(sc->sc_node);
125 		goto unmap;
126 	}
127 
128 	printf("\n");
129 
130 	if (OF_is_compatible(faa->fa_node, "marvell,armada-3700-ehci")) {
131 		uint32_t usbmode;
132 
133 		/* force HOST mode */
134 		sc->sc.sc_flags = EHCIF_USBMODE;
135 
136 		usbmode = EOREAD4(&sc->sc, EHCI_USBMODE);
137 		CLR(usbmode, EHCI_USBMODE_CM_M);
138 		SET(usbmode, EHCI_USBMODE_CM_HOST);
139 		EOWRITE4(&sc->sc, EHCI_USBMODE, usbmode);
140 	}
141 
142 	ehci_init_phys(sc);
143 
144 	strlcpy(sc->sc.sc_vendor, "Generic", sizeof(sc->sc.sc_vendor));
145 	r = ehci_init(&sc->sc);
146 	if (r != USBD_NORMAL_COMPLETION) {
147 		printf("%s: init failed, error=%d\n", devname, r);
148 		clock_disable_all(sc->sc_node);
149 		goto disestablish_intr;
150 	}
151 
152 	/* Attach usb device. */
153 	config_found(self, &sc->sc.sc_bus, usbctlprint);
154 	return;
155 
156 disestablish_intr:
157 	fdt_intr_disestablish(sc->sc_ih);
158 	sc->sc_ih = NULL;
159 unmap:
160 	bus_space_unmap(sc->sc.iot, sc->sc_ioh, sc->sc_size);
161 	sc->sc.sc_size = 0;
162 out:
163 	return;
164 }
165 
166 int
ehci_fdt_detach(struct device * self,int flags)167 ehci_fdt_detach(struct device *self, int flags)
168 {
169 	struct ehci_fdt_softc *sc = (struct ehci_fdt_softc *)self;
170 	int rv;
171 
172 	rv = ehci_detach(self, flags);
173 	if (rv)
174 		return rv;
175 
176 	if (sc->sc_ih != NULL) {
177 		fdt_intr_disestablish(sc->sc_ih);
178 		sc->sc_ih = NULL;
179 	}
180 
181 	if (sc->sc.sc_size) {
182 		bus_space_unmap(sc->sc.iot, sc->sc_ioh, sc->sc_size);
183 		sc->sc.sc_size = 0;
184 	}
185 
186 	clock_disable_all(sc->sc_node);
187 	return 0;
188 }
189 
190 struct ehci_phy {
191 	const char *compat;
192 	void (*init)(struct ehci_fdt_softc *, uint32_t *);
193 };
194 
195 void sun4i_phy_init(struct ehci_fdt_softc *, uint32_t *);
196 void sun9i_phy_init(struct ehci_fdt_softc *, uint32_t *);
197 
198 struct ehci_phy ehci_phys[] = {
199 	{ "allwinner,sun4i-a10-usb-phy", sun4i_phy_init },
200 	{ "allwinner,sun5i-a13-usb-phy", sun4i_phy_init },
201 	{ "allwinner,sun6i-a31-usb-phy", sun4i_phy_init },
202 	{ "allwinner,sun7i-a20-usb-phy", sun4i_phy_init },
203 	{ "allwinner,sun8i-a23-usb-phy", sun4i_phy_init },
204 	{ "allwinner,sun8i-a33-usb-phy", sun4i_phy_init },
205 	{ "allwinner,sun8i-h3-usb-phy", sun4i_phy_init },
206 	{ "allwinner,sun8i-r40-usb-phy", sun4i_phy_init },
207 	{ "allwinner,sun8i-v3s-usb-phy", sun4i_phy_init },
208 	{ "allwinner,sun20i-d1-usb-phy", sun4i_phy_init },
209 	{ "allwinner,sun50i-h6-usb-phy", sun4i_phy_init },
210 	{ "allwinner,sun50i-h616-usb-phy", sun4i_phy_init },
211 	{ "allwinner,sun50i-a64-usb-phy", sun4i_phy_init },
212 	{ "allwinner,sun9i-a80-usb-phy", sun9i_phy_init },
213 };
214 
215 uint32_t *
ehci_next_phy(uint32_t * cells)216 ehci_next_phy(uint32_t *cells)
217 {
218 	uint32_t phandle = cells[0];
219 	int node, ncells;
220 
221 	node = OF_getnodebyphandle(phandle);
222 	if (node == 0)
223 		return NULL;
224 
225 	ncells = OF_getpropint(node, "#phy-cells", 0);
226 	return cells + ncells + 1;
227 }
228 
229 void
ehci_init_phy(struct ehci_fdt_softc * sc,uint32_t * cells)230 ehci_init_phy(struct ehci_fdt_softc *sc, uint32_t *cells)
231 {
232 	uint32_t phy_supply;
233 	int node;
234 	int i;
235 
236 	node = OF_getnodebyphandle(cells[0]);
237 	if (node == 0)
238 		return;
239 
240 	for (i = 0; i < nitems(ehci_phys); i++) {
241 		if (OF_is_compatible(node, ehci_phys[i].compat)) {
242 			ehci_phys[i].init(sc, cells);
243 			return;
244 		}
245 	}
246 
247 	phy_supply = OF_getpropint(node, "phy-supply", 0);
248 	if (phy_supply)
249 		regulator_enable(phy_supply);
250 }
251 
252 void
ehci_init_phys(struct ehci_fdt_softc * sc)253 ehci_init_phys(struct ehci_fdt_softc *sc)
254 {
255 	uint32_t *phys;
256 	uint32_t *phy;
257 	int len;
258 
259 	if (phy_enable(sc->sc_node, "usb") == 0)
260 		return;
261 
262 	len = OF_getproplen(sc->sc_node, "phys");
263 	if (len <= 0)
264 		return;
265 
266 	phys = malloc(len, M_TEMP, M_WAITOK);
267 	OF_getpropintarray(sc->sc_node, "phys", phys, len);
268 
269 	phy = phys;
270 	while (phy && phy < phys + (len / sizeof(uint32_t))) {
271 		ehci_init_phy(sc, phy);
272 		phy = ehci_next_phy(phy);
273 	}
274 
275 	free(phys, M_TEMP, len);
276 }
277 
278 /*
279  * Allwinner PHYs.
280  */
281 
282 /* Registers */
283 #define SUNXI_HCI_ICR		0x800
284 #define  SUNXI_ULPI_BYPASS	(1 << 0)
285 #define  SUNXI_AHB_INCRX_ALIGN	(1 << 8)
286 #define  SUNXI_AHB_INCR4	(1 << 9)
287 #define  SUNXI_AHB_INCR8	(1 << 10)
288 #define  SUNXI_AHB_INCR16	(1 << 11)
289 
290 void
sun50i_h616_phy2_init(struct ehci_fdt_softc * sc,int node)291 sun50i_h616_phy2_init(struct ehci_fdt_softc *sc, int node)
292 {
293 	int len, idx;
294 	uint32_t *reg, val;
295 	bus_size_t size;
296 	bus_space_handle_t ioh;
297 
298 	/*
299 	 * to access USB2-PHY register, get address from "reg" property of
300 	 * current "allwinner,...-usb-phy" node
301 	 */
302 	len = OF_getproplen(node, "reg");
303 	if (len <= 0)
304 		goto out;
305 
306 	reg = malloc(len, M_TEMP, M_WAITOK);
307 	OF_getpropintarray(node, "reg", reg, len);
308 
309 	idx = OF_getindex(node, "pmu2", "reg-names");
310 	if (idx < 0 || (idx + 1) > (len / (sizeof(uint32_t) * 2))) {
311 		printf(": no phy2 register\n");
312 		goto free;
313 	}
314 
315 	/* convert "reg-names" index to "reg" (address-size pair) index */
316 	idx *= 2;
317 
318 	size = reg[idx + 1];
319 	if (bus_space_map(sc->sc.iot, reg[idx], size, 0, &ioh)) {
320 		printf(": can't map phy2 registers\n");
321 		goto free;
322 	}
323 
324 	clock_enable(node, "usb2_phy");
325 	reset_deassert(node, "usb2_reset");
326 	clock_enable(node, "pmu2_clk");
327 
328 	/*
329 	 * address is offset from "pmu2", not EHCI2 base address
330 	 * (normally it points EHCI2 base address + 0x810)
331 	 */
332 	val = bus_space_read_4(sc->sc.iot, ioh, 0x10);
333 	val &= ~(1 << 3);	/* clear SIDDQ */
334 	bus_space_write_4(sc->sc.iot, ioh, 0x10, val);
335 
336 	clock_disable(node, "pmu2_clk");
337 	/* "usb2_reset" and "usb2_phy" unchanged */
338 
339 	bus_space_unmap(sc->sc.iot, ioh, size);
340 free:
341 	free(reg, M_TEMP, len);
342 out:
343 	return;
344 }
345 
346 void
sun4i_phy_init(struct ehci_fdt_softc * sc,uint32_t * cells)347 sun4i_phy_init(struct ehci_fdt_softc *sc, uint32_t *cells)
348 {
349 	uint32_t vbus_supply;
350 	char name[32];
351 	uint32_t val;
352 	int node;
353 
354 	node = OF_getnodebyphandle(cells[0]);
355 	if (node == -1)
356 		return;
357 
358 	/* Allwinner H616 needs to clear PHY2's SIDDQ flag */
359 	if (OF_is_compatible(node, "allwinner,sun50i-h616-usb-phy") &&
360 	    cells[1] != 2)
361 		sun50i_h616_phy2_init(sc, node);
362 
363 	val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR);
364 	val |= SUNXI_AHB_INCR8 | SUNXI_AHB_INCR4;
365 	val |= SUNXI_AHB_INCRX_ALIGN;
366 	val |= SUNXI_ULPI_BYPASS;
367 	bus_space_write_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR, val);
368 
369 	/*
370 	 * We need to poke an undocumented register to make the PHY
371 	 * work on Allwinner A64/D1/H3/H5/R40.
372 	 */
373 	if (OF_is_compatible(node, "allwinner,sun8i-h3-usb-phy") ||
374 	    OF_is_compatible(node, "allwinner,sun8i-r40-usb-phy") ||
375 	    OF_is_compatible(node, "allwinner,sun50i-h6-usb-phy") ||
376 	    OF_is_compatible(node, "allwinner,sun50i-a64-usb-phy")) {
377 		val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, 0x810);
378 		val &= ~(1 << 1);
379 		bus_space_write_4(sc->sc.iot, sc->sc.ioh, 0x810, val);
380 	} else if (OF_is_compatible(node, "allwinner,sun8i-a83t-usb-phy") ||
381 		   OF_is_compatible(node, "allwinner,sun20i-d1-usb-phy") ||
382 		   OF_is_compatible(node, "allwinner,sun50i-h616-usb-phy")) {
383 		val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, 0x810);
384 		val &= ~(1 << 3);
385 		bus_space_write_4(sc->sc.iot, sc->sc.ioh, 0x810, val);
386 	}
387 	if (OF_is_compatible(node, "allwinner,sun8i-a83t-usb-phy") ||
388 	    OF_is_compatible(node, "allwinner,sun50i-h616-usb-phy")) {
389 		val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, 0x810);
390 		val |= 1 << 5;
391 		bus_space_write_4(sc->sc.iot, sc->sc.ioh, 0x810, val);
392 	}
393 
394 	pinctrl_byname(node, "default");
395 
396 	/*
397 	 * On sun4i, sun5i and sun7i, there is a single clock.  The
398 	 * more recent SoCs have a separate clock for each PHY.
399 	 */
400 	if (OF_is_compatible(node, "allwinner,sun4i-a10-usb-phy") ||
401 	    OF_is_compatible(node, "allwinner,sun5i-a13-usb-phy") ||
402 	    OF_is_compatible(node, "allwinner,sun7i-a20-usb-phy")) {
403 		clock_enable(node, "usb_phy");
404 	} else {
405 		snprintf(name, sizeof(name), "usb%d_phy", cells[1]);
406 		clock_enable(node, name);
407 	}
408 
409 	snprintf(name, sizeof(name), "usb%d_reset", cells[1]);
410 	reset_deassert(node, name);
411 
412 	snprintf(name, sizeof(name), "usb%d_vbus-supply", cells[1]);
413 	vbus_supply = OF_getpropint(node, name, 0);
414 	if (vbus_supply)
415 		regulator_enable(vbus_supply);
416 }
417 
418 void
sun9i_phy_init(struct ehci_fdt_softc * sc,uint32_t * cells)419 sun9i_phy_init(struct ehci_fdt_softc *sc, uint32_t *cells)
420 {
421 	uint32_t phy_supply;
422 	uint32_t val;
423 	int node;
424 
425 	node = OF_getnodebyphandle(cells[0]);
426 	if (node == -1)
427 		return;
428 
429 	pinctrl_byname(node, "default");
430 	clock_enable(node, "phy");
431 	reset_deassert(node, "phy");
432 
433 	val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR);
434 	val |= SUNXI_AHB_INCR16 | SUNXI_AHB_INCR8 | SUNXI_AHB_INCR4;
435 	val |= SUNXI_AHB_INCRX_ALIGN;
436 	val |= SUNXI_ULPI_BYPASS;
437 	bus_space_write_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR, val);
438 
439 	phy_supply = OF_getpropint(node, "phy-supply", 0);
440 	if (phy_supply)
441 		regulator_enable(phy_supply);
442 }
443