xref: /openbsd-src/sys/dev/fdt/ehci_fdt.c (revision 6ca44032e7be0d795b9f13c99fbce059e942c15d)
1 /*	$OpenBSD: ehci_fdt.c,v 1.10 2023/04/03 01:57:41 dlg 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
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
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
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,sun50i-h6-usb-phy", sun4i_phy_init },
209 	{ "allwinner,sun50i-a64-usb-phy", sun4i_phy_init },
210 	{ "allwinner,sun9i-a80-usb-phy", sun9i_phy_init },
211 };
212 
213 uint32_t *
214 ehci_next_phy(uint32_t *cells)
215 {
216 	uint32_t phandle = cells[0];
217 	int node, ncells;
218 
219 	node = OF_getnodebyphandle(phandle);
220 	if (node == 0)
221 		return NULL;
222 
223 	ncells = OF_getpropint(node, "#phy-cells", 0);
224 	return cells + ncells + 1;
225 }
226 
227 void
228 ehci_init_phy(struct ehci_fdt_softc *sc, uint32_t *cells)
229 {
230 	uint32_t phy_supply;
231 	int node;
232 	int i;
233 
234 	node = OF_getnodebyphandle(cells[0]);
235 	if (node == 0)
236 		return;
237 
238 	for (i = 0; i < nitems(ehci_phys); i++) {
239 		if (OF_is_compatible(node, ehci_phys[i].compat)) {
240 			ehci_phys[i].init(sc, cells);
241 			return;
242 		}
243 	}
244 
245 	phy_supply = OF_getpropint(node, "phy-supply", 0);
246 	if (phy_supply)
247 		regulator_enable(phy_supply);
248 }
249 
250 void
251 ehci_init_phys(struct ehci_fdt_softc *sc)
252 {
253 	uint32_t *phys;
254 	uint32_t *phy;
255 	int len;
256 
257 	if (phy_enable(sc->sc_node, "usb") == 0)
258 		return;
259 
260 	len = OF_getproplen(sc->sc_node, "phys");
261 	if (len <= 0)
262 		return;
263 
264 	phys = malloc(len, M_TEMP, M_WAITOK);
265 	OF_getpropintarray(sc->sc_node, "phys", phys, len);
266 
267 	phy = phys;
268 	while (phy && phy < phys + (len / sizeof(uint32_t))) {
269 		ehci_init_phy(sc, phy);
270 		phy = ehci_next_phy(phy);
271 	}
272 
273 	free(phys, M_TEMP, len);
274 }
275 
276 /*
277  * Allwinner PHYs.
278  */
279 
280 /* Registers */
281 #define SUNXI_HCI_ICR		0x800
282 #define  SUNXI_ULPI_BYPASS	(1 << 0)
283 #define  SUNXI_AHB_INCRX_ALIGN	(1 << 8)
284 #define  SUNXI_AHB_INCR4	(1 << 9)
285 #define  SUNXI_AHB_INCR8	(1 << 10)
286 #define  SUNXI_AHB_INCR16	(1 << 11)
287 
288 void
289 sun4i_phy_init(struct ehci_fdt_softc *sc, uint32_t *cells)
290 {
291 	uint32_t vbus_supply;
292 	char name[32];
293 	uint32_t val;
294 	int node;
295 
296 	node = OF_getnodebyphandle(cells[0]);
297 	if (node == -1)
298 		return;
299 
300 	val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR);
301 	val |= SUNXI_AHB_INCR8 | SUNXI_AHB_INCR4;
302 	val |= SUNXI_AHB_INCRX_ALIGN;
303 	val |= SUNXI_ULPI_BYPASS;
304 	bus_space_write_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR, val);
305 
306 	/*
307 	 * We need to poke an undocumented register to make the PHY
308 	 * work on Allwinner A64/H3/H5/R40.
309 	 */
310 	if (OF_is_compatible(node, "allwinner,sun8i-h3-usb-phy") ||
311 	    OF_is_compatible(node, "allwinner,sun8i-r40-usb-phy") ||
312 	    OF_is_compatible(node, "allwinner,sun50i-h6-usb-phy") ||
313 	    OF_is_compatible(node, "allwinner,sun50i-a64-usb-phy")) {
314 		val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, 0x810);
315 		val &= ~(1 << 1);
316 		bus_space_write_4(sc->sc.iot, sc->sc.ioh, 0x810, val);
317 	}
318 
319 	pinctrl_byname(node, "default");
320 
321 	/*
322 	 * On sun4i, sun5i and sun7i, there is a single clock.  The
323 	 * more recent SoCs have a separate clock for each PHY.
324 	 */
325 	if (OF_is_compatible(node, "allwinner,sun4i-a10-usb-phy") ||
326 	    OF_is_compatible(node, "allwinner,sun5i-a13-usb-phy") ||
327 	    OF_is_compatible(node, "allwinner,sun7i-a20-usb-phy")) {
328 		clock_enable(node, "usb_phy");
329 	} else {
330 		snprintf(name, sizeof(name), "usb%d_phy", cells[1]);
331 		clock_enable(node, name);
332 	}
333 
334 	snprintf(name, sizeof(name), "usb%d_reset", cells[1]);
335 	reset_deassert(node, name);
336 
337 	snprintf(name, sizeof(name), "usb%d_vbus-supply", cells[1]);
338 	vbus_supply = OF_getpropint(node, name, 0);
339 	if (vbus_supply)
340 		regulator_enable(vbus_supply);
341 }
342 
343 void
344 sun9i_phy_init(struct ehci_fdt_softc *sc, uint32_t *cells)
345 {
346 	uint32_t phy_supply;
347 	uint32_t val;
348 	int node;
349 
350 	node = OF_getnodebyphandle(cells[0]);
351 	if (node == -1)
352 		return;
353 
354 	pinctrl_byname(node, "default");
355 	clock_enable(node, "phy");
356 	reset_deassert(node, "phy");
357 
358 	val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR);
359 	val |= SUNXI_AHB_INCR16 | SUNXI_AHB_INCR8 | SUNXI_AHB_INCR4;
360 	val |= SUNXI_AHB_INCRX_ALIGN;
361 	val |= SUNXI_ULPI_BYPASS;
362 	bus_space_write_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR, val);
363 
364 	phy_supply = OF_getpropint(node, "phy-supply", 0);
365 	if (phy_supply)
366 		regulator_enable(phy_supply);
367 }
368