xref: /openbsd-src/sys/dev/fdt/ehci_fdt.c (revision 505ee9ea3b177e2387d907a91ca7da069f3f14d8)
1 /*	$OpenBSD: ehci_fdt.c,v 1.6 2019/08/11 11:16:05 kettenis 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 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-a64-usb-phy", sun4i_phy_init },
178 	{ "allwinner,sun9i-a80-usb-phy", sun9i_phy_init },
179 };
180 
181 uint32_t *
182 ehci_next_phy(uint32_t *cells)
183 {
184 	uint32_t phandle = cells[0];
185 	int node, ncells;
186 
187 	node = OF_getnodebyphandle(phandle);
188 	if (node == 0)
189 		return NULL;
190 
191 	ncells = OF_getpropint(node, "#phy-cells", 0);
192 	return cells + ncells + 1;
193 }
194 
195 void
196 ehci_init_phy(struct ehci_fdt_softc *sc, uint32_t *cells)
197 {
198 	uint32_t phy_supply;
199 	int node;
200 	int i;
201 
202 	node = OF_getnodebyphandle(cells[0]);
203 	if (node == 0)
204 		return;
205 
206 	for (i = 0; i < nitems(ehci_phys); i++) {
207 		if (OF_is_compatible(node, ehci_phys[i].compat)) {
208 			ehci_phys[i].init(sc, cells);
209 			return;
210 		}
211 	}
212 
213 	phy_supply = OF_getpropint(node, "phy-supply", 0);
214 	if (phy_supply)
215 		regulator_enable(phy_supply);
216 }
217 
218 void
219 ehci_init_phys(struct ehci_fdt_softc *sc)
220 {
221 	uint32_t *phys;
222 	uint32_t *phy;
223 	int len;
224 
225 	len = OF_getproplen(sc->sc_node, "phys");
226 	if (len <= 0)
227 		return;
228 
229 	phys = malloc(len, M_TEMP, M_WAITOK);
230 	OF_getpropintarray(sc->sc_node, "phys", phys, len);
231 
232 	phy = phys;
233 	while (phy && phy < phys + (len / sizeof(uint32_t))) {
234 		ehci_init_phy(sc, phy);
235 		phy = ehci_next_phy(phy);
236 	}
237 
238 	free(phys, M_TEMP, len);
239 }
240 
241 /*
242  * Allwinner PHYs.
243  */
244 
245 /* Registers */
246 #define SUNXI_HCI_ICR		0x800
247 #define  SUNXI_ULPI_BYPASS	(1 << 0)
248 #define  SUNXI_AHB_INCRX_ALIGN	(1 << 8)
249 #define  SUNXI_AHB_INCR4	(1 << 9)
250 #define  SUNXI_AHB_INCR8	(1 << 10)
251 #define  SUNXI_AHB_INCR16	(1 << 11)
252 
253 void
254 sun4i_phy_init(struct ehci_fdt_softc *sc, uint32_t *cells)
255 {
256 	uint32_t vbus_supply;
257 	char name[32];
258 	uint32_t val;
259 	int node;
260 
261 	node = OF_getnodebyphandle(cells[0]);
262 	if (node == -1)
263 		return;
264 
265 	val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR);
266 	val |= SUNXI_AHB_INCR8 | SUNXI_AHB_INCR4;
267 	val |= SUNXI_AHB_INCRX_ALIGN;
268 	val |= SUNXI_ULPI_BYPASS;
269 	bus_space_write_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR, val);
270 
271 	/*
272 	 * We need to poke an undocumented register to make the PHY
273 	 * work on Allwinner A64/H3/H5/R40.
274 	 */
275 	if (OF_is_compatible(node, "allwinner,sun8i-h3-usb-phy") ||
276 	    OF_is_compatible(node, "allwinner,sun8i-r40-usb-phy") ||
277 	    OF_is_compatible(node, "allwinner,sun50i-a64-usb-phy")) {
278 		val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, 0x810);
279 		val &= ~(1 << 1);
280 		bus_space_write_4(sc->sc.iot, sc->sc.ioh, 0x810, val);
281 	}
282 
283 	pinctrl_byname(node, "default");
284 
285 	/*
286 	 * On sun4i, sun5i and sun7i, there is a single clock.  The
287 	 * more recent SoCs have a separate clock for each PHY.
288 	 */
289 	if (OF_is_compatible(node, "allwinner,sun4i-a10-usb-phy") ||
290 	    OF_is_compatible(node, "allwinner,sun5i-a13-usb-phy") ||
291 	    OF_is_compatible(node, "allwinner,sun7i-a20-usb-phy")) {
292 		clock_enable(node, "usb_phy");
293 	} else {
294 		snprintf(name, sizeof(name), "usb%d_phy", cells[1]);
295 		clock_enable(node, name);
296 	}
297 
298 	snprintf(name, sizeof(name), "usb%d_reset", cells[1]);
299 	reset_deassert(node, name);
300 
301 	snprintf(name, sizeof(name), "usb%d_vbus-supply", cells[1]);
302 	vbus_supply = OF_getpropint(node, name, 0);
303 	if (vbus_supply)
304 		regulator_enable(vbus_supply);
305 }
306 
307 void
308 sun9i_phy_init(struct ehci_fdt_softc *sc, uint32_t *cells)
309 {
310 	uint32_t phy_supply;
311 	uint32_t val;
312 	int node;
313 
314 	node = OF_getnodebyphandle(cells[0]);
315 	if (node == -1)
316 		return;
317 
318 	pinctrl_byname(node, "default");
319 	clock_enable(node, "phy");
320 	reset_deassert(node, "phy");
321 
322 	val = bus_space_read_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR);
323 	val |= SUNXI_AHB_INCR16 | SUNXI_AHB_INCR8 | SUNXI_AHB_INCR4;
324 	val |= SUNXI_AHB_INCRX_ALIGN;
325 	val |= SUNXI_ULPI_BYPASS;
326 	bus_space_write_4(sc->sc.iot, sc->sc.ioh, SUNXI_HCI_ICR, val);
327 
328 	phy_supply = OF_getpropint(node, "phy-supply", 0);
329 	if (phy_supply)
330 		regulator_enable(phy_supply);
331 }
332