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