xref: /freebsd/sys/arm/mv/a37x0_spi.c (revision 8cfe2a7ad3b1a0de5cf2414058bb357a7d5b7f58)
1*8cfe2a7aSLuiz Otavio O Souza /*-
2*8cfe2a7aSLuiz Otavio O Souza  * Copyright (c) 2018, 2019 Rubicon Communications, LLC (Netgate)
3*8cfe2a7aSLuiz Otavio O Souza  *
4*8cfe2a7aSLuiz Otavio O Souza  * Redistribution and use in source and binary forms, with or without
5*8cfe2a7aSLuiz Otavio O Souza  * modification, are permitted provided that the following conditions
6*8cfe2a7aSLuiz Otavio O Souza  * are met:
7*8cfe2a7aSLuiz Otavio O Souza  * 1. Redistributions of source code must retain the above copyright
8*8cfe2a7aSLuiz Otavio O Souza  *    notice, this list of conditions and the following disclaimer.
9*8cfe2a7aSLuiz Otavio O Souza  * 2. Redistributions in binary form must reproduce the above copyright
10*8cfe2a7aSLuiz Otavio O Souza  *    notice, this list of conditions and the following disclaimer in the
11*8cfe2a7aSLuiz Otavio O Souza  *    documentation and/or other materials provided with the distribution.
12*8cfe2a7aSLuiz Otavio O Souza  *
13*8cfe2a7aSLuiz Otavio O Souza  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14*8cfe2a7aSLuiz Otavio O Souza  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15*8cfe2a7aSLuiz Otavio O Souza  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16*8cfe2a7aSLuiz Otavio O Souza  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17*8cfe2a7aSLuiz Otavio O Souza  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18*8cfe2a7aSLuiz Otavio O Souza  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19*8cfe2a7aSLuiz Otavio O Souza  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20*8cfe2a7aSLuiz Otavio O Souza  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21*8cfe2a7aSLuiz Otavio O Souza  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22*8cfe2a7aSLuiz Otavio O Souza  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23*8cfe2a7aSLuiz Otavio O Souza  *
24*8cfe2a7aSLuiz Otavio O Souza  */
25*8cfe2a7aSLuiz Otavio O Souza 
26*8cfe2a7aSLuiz Otavio O Souza #include <sys/cdefs.h>
27*8cfe2a7aSLuiz Otavio O Souza __FBSDID("$FreeBSD$");
28*8cfe2a7aSLuiz Otavio O Souza 
29*8cfe2a7aSLuiz Otavio O Souza #include <sys/param.h>
30*8cfe2a7aSLuiz Otavio O Souza #include <sys/systm.h>
31*8cfe2a7aSLuiz Otavio O Souza #include <sys/bus.h>
32*8cfe2a7aSLuiz Otavio O Souza 
33*8cfe2a7aSLuiz Otavio O Souza #include <sys/kernel.h>
34*8cfe2a7aSLuiz Otavio O Souza #include <sys/module.h>
35*8cfe2a7aSLuiz Otavio O Souza #include <sys/rman.h>
36*8cfe2a7aSLuiz Otavio O Souza 
37*8cfe2a7aSLuiz Otavio O Souza #include <machine/bus.h>
38*8cfe2a7aSLuiz Otavio O Souza #include <machine/resource.h>
39*8cfe2a7aSLuiz Otavio O Souza #include <machine/intr.h>
40*8cfe2a7aSLuiz Otavio O Souza 
41*8cfe2a7aSLuiz Otavio O Souza #include <dev/ofw/ofw_bus.h>
42*8cfe2a7aSLuiz Otavio O Souza #include <dev/ofw/ofw_bus_subr.h>
43*8cfe2a7aSLuiz Otavio O Souza #include <dev/spibus/spi.h>
44*8cfe2a7aSLuiz Otavio O Souza #include <dev/spibus/spibusvar.h>
45*8cfe2a7aSLuiz Otavio O Souza 
46*8cfe2a7aSLuiz Otavio O Souza #include "spibus_if.h"
47*8cfe2a7aSLuiz Otavio O Souza 
48*8cfe2a7aSLuiz Otavio O Souza struct a37x0_spi_softc {
49*8cfe2a7aSLuiz Otavio O Souza 	device_t		sc_dev;
50*8cfe2a7aSLuiz Otavio O Souza 	struct mtx		sc_mtx;
51*8cfe2a7aSLuiz Otavio O Souza 	struct resource		*sc_mem_res;
52*8cfe2a7aSLuiz Otavio O Souza 	struct resource		*sc_irq_res;
53*8cfe2a7aSLuiz Otavio O Souza 	struct spi_command	*sc_cmd;
54*8cfe2a7aSLuiz Otavio O Souza 	bus_space_tag_t		sc_bst;
55*8cfe2a7aSLuiz Otavio O Souza 	bus_space_handle_t	sc_bsh;
56*8cfe2a7aSLuiz Otavio O Souza 	uint32_t		sc_len;
57*8cfe2a7aSLuiz Otavio O Souza 	uint32_t		sc_maxfreq;
58*8cfe2a7aSLuiz Otavio O Souza 	uint32_t		sc_read;
59*8cfe2a7aSLuiz Otavio O Souza 	uint32_t		sc_flags;
60*8cfe2a7aSLuiz Otavio O Souza 	uint32_t		sc_written;
61*8cfe2a7aSLuiz Otavio O Souza 	void			*sc_intrhand;
62*8cfe2a7aSLuiz Otavio O Souza };
63*8cfe2a7aSLuiz Otavio O Souza 
64*8cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_WRITE(_sc, _off, _val)		\
65*8cfe2a7aSLuiz Otavio O Souza     bus_space_write_4((_sc)->sc_bst, (_sc)->sc_bsh, (_off), (_val))
66*8cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_READ(_sc, _off)			\
67*8cfe2a7aSLuiz Otavio O Souza     bus_space_read_4((_sc)->sc_bst, (_sc)->sc_bsh, (_off))
68*8cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_LOCK(_sc)	mtx_lock(&(_sc)->sc_mtx)
69*8cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_UNLOCK(_sc)	mtx_unlock(&(_sc)->sc_mtx)
70*8cfe2a7aSLuiz Otavio O Souza 
71*8cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_BUSY			(1 << 0)
72*8cfe2a7aSLuiz Otavio O Souza /*
73*8cfe2a7aSLuiz Otavio O Souza  * While the A3700 utils from Marvell usually sets the QSF clock to 200MHz,
74*8cfe2a7aSLuiz Otavio O Souza  * there is no guarantee that it is correct without the proper clock framework
75*8cfe2a7aSLuiz Otavio O Souza  * to retrieve the actual TBG and PLL settings.
76*8cfe2a7aSLuiz Otavio O Souza  */
77*8cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_CLOCK			200000000	/* QSF Clock 200MHz */
78*8cfe2a7aSLuiz Otavio O Souza 
79*8cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_CONTROL		0x0
80*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_CS_SHIFT		16
81*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_CS_MASK		(0xf << A37X0_SPI_CS_SHIFT)
82*8cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_CONF			0x4
83*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_WFIFO_THRS_SHIFT	28
84*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_RFIFO_THRS_SHIFT	24
85*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_AUTO_CS_EN		(1 << 20)
86*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_DMA_WR_EN		(1 << 19)
87*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_DMA_RD_EN		(1 << 18)
88*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_FIFO_MODE		(1 << 17)
89*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_SRST			(1 << 16)
90*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_XFER_START		(1 << 15)
91*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_XFER_STOP		(1 << 14)
92*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_INSTR_PIN		(1 << 13)
93*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_ADDR_PIN		(1 << 12)
94*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_DATA_PIN_MASK	0x3
95*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_DATA_PIN_SHIFT	10
96*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_FIFO_FLUSH		(1 << 9)
97*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_RW_EN		(1 << 8)
98*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_CLK_POL		(1 << 7)
99*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_CLK_PHASE		(1 << 6)
100*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_BYTE_LEN		(1 << 5)
101*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_PSC_MASK		0x1f
102*8cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_DATA_OUT		0x8
103*8cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_DATA_IN		0xc
104*8cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_INTR_STAT		0x28
105*8cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_INTR_MASK		0x2c
106*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_RDY			(1 << 1)
107*8cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_XFER_DONE		(1 << 0)
108*8cfe2a7aSLuiz Otavio O Souza 
109*8cfe2a7aSLuiz Otavio O Souza static struct ofw_compat_data compat_data[] = {
110*8cfe2a7aSLuiz Otavio O Souza 	{ "marvell,armada-3700-spi",	1 },
111*8cfe2a7aSLuiz Otavio O Souza 	{ NULL, 0 }
112*8cfe2a7aSLuiz Otavio O Souza };
113*8cfe2a7aSLuiz Otavio O Souza 
114*8cfe2a7aSLuiz Otavio O Souza static void a37x0_spi_intr(void *);
115*8cfe2a7aSLuiz Otavio O Souza 
116*8cfe2a7aSLuiz Otavio O Souza static int
117*8cfe2a7aSLuiz Otavio O Souza a37x0_spi_wait(struct a37x0_spi_softc *sc, int timeout, uint32_t reg,
118*8cfe2a7aSLuiz Otavio O Souza     uint32_t mask)
119*8cfe2a7aSLuiz Otavio O Souza {
120*8cfe2a7aSLuiz Otavio O Souza 	int i;
121*8cfe2a7aSLuiz Otavio O Souza 
122*8cfe2a7aSLuiz Otavio O Souza 	for (i = 0; i < timeout; i++) {
123*8cfe2a7aSLuiz Otavio O Souza 		if ((A37X0_SPI_READ(sc, reg) & mask) == 0)
124*8cfe2a7aSLuiz Otavio O Souza 			return (0);
125*8cfe2a7aSLuiz Otavio O Souza 		DELAY(100);
126*8cfe2a7aSLuiz Otavio O Souza 	}
127*8cfe2a7aSLuiz Otavio O Souza 
128*8cfe2a7aSLuiz Otavio O Souza 	return (ETIMEDOUT);
129*8cfe2a7aSLuiz Otavio O Souza }
130*8cfe2a7aSLuiz Otavio O Souza 
131*8cfe2a7aSLuiz Otavio O Souza static int
132*8cfe2a7aSLuiz Otavio O Souza a37x0_spi_probe(device_t dev)
133*8cfe2a7aSLuiz Otavio O Souza {
134*8cfe2a7aSLuiz Otavio O Souza 
135*8cfe2a7aSLuiz Otavio O Souza 	if (!ofw_bus_status_okay(dev))
136*8cfe2a7aSLuiz Otavio O Souza 		return (ENXIO);
137*8cfe2a7aSLuiz Otavio O Souza 	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
138*8cfe2a7aSLuiz Otavio O Souza 		return (ENXIO);
139*8cfe2a7aSLuiz Otavio O Souza 	device_set_desc(dev, "Armada 37x0 SPI controller");
140*8cfe2a7aSLuiz Otavio O Souza 
141*8cfe2a7aSLuiz Otavio O Souza 	return (BUS_PROBE_DEFAULT);
142*8cfe2a7aSLuiz Otavio O Souza }
143*8cfe2a7aSLuiz Otavio O Souza 
144*8cfe2a7aSLuiz Otavio O Souza static int
145*8cfe2a7aSLuiz Otavio O Souza a37x0_spi_attach(device_t dev)
146*8cfe2a7aSLuiz Otavio O Souza {
147*8cfe2a7aSLuiz Otavio O Souza 	int err, rid;
148*8cfe2a7aSLuiz Otavio O Souza 	pcell_t maxfreq;
149*8cfe2a7aSLuiz Otavio O Souza 	struct a37x0_spi_softc *sc;
150*8cfe2a7aSLuiz Otavio O Souza 	uint32_t reg;
151*8cfe2a7aSLuiz Otavio O Souza 
152*8cfe2a7aSLuiz Otavio O Souza 	sc = device_get_softc(dev);
153*8cfe2a7aSLuiz Otavio O Souza 	sc->sc_dev = dev;
154*8cfe2a7aSLuiz Otavio O Souza 
155*8cfe2a7aSLuiz Otavio O Souza 	rid = 0;
156*8cfe2a7aSLuiz Otavio O Souza 	sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
157*8cfe2a7aSLuiz Otavio O Souza 	    RF_ACTIVE);
158*8cfe2a7aSLuiz Otavio O Souza 	if (!sc->sc_mem_res) {
159*8cfe2a7aSLuiz Otavio O Souza 		device_printf(dev, "cannot allocate memory window\n");
160*8cfe2a7aSLuiz Otavio O Souza 		return (ENXIO);
161*8cfe2a7aSLuiz Otavio O Souza 	}
162*8cfe2a7aSLuiz Otavio O Souza 
163*8cfe2a7aSLuiz Otavio O Souza 	sc->sc_bst = rman_get_bustag(sc->sc_mem_res);
164*8cfe2a7aSLuiz Otavio O Souza 	sc->sc_bsh = rman_get_bushandle(sc->sc_mem_res);
165*8cfe2a7aSLuiz Otavio O Souza 
166*8cfe2a7aSLuiz Otavio O Souza 	rid = 0;
167*8cfe2a7aSLuiz Otavio O Souza 	sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
168*8cfe2a7aSLuiz Otavio O Souza 	    RF_ACTIVE);
169*8cfe2a7aSLuiz Otavio O Souza 	if (!sc->sc_irq_res) {
170*8cfe2a7aSLuiz Otavio O Souza 		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
171*8cfe2a7aSLuiz Otavio O Souza 		device_printf(dev, "cannot allocate interrupt\n");
172*8cfe2a7aSLuiz Otavio O Souza 		return (ENXIO);
173*8cfe2a7aSLuiz Otavio O Souza 	}
174*8cfe2a7aSLuiz Otavio O Souza 
175*8cfe2a7aSLuiz Otavio O Souza 	/* Make sure that no CS is asserted. */
176*8cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_CONTROL);
177*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONTROL, reg & ~A37X0_SPI_CS_MASK);
178*8cfe2a7aSLuiz Otavio O Souza 
179*8cfe2a7aSLuiz Otavio O Souza 	/* Reset FIFO. */
180*8cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF);
181*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg | A37X0_SPI_FIFO_FLUSH);
182*8cfe2a7aSLuiz Otavio O Souza 	err = a37x0_spi_wait(sc, 20, A37X0_SPI_CONF, A37X0_SPI_FIFO_FLUSH);
183*8cfe2a7aSLuiz Otavio O Souza 	if (err != 0) {
184*8cfe2a7aSLuiz Otavio O Souza 		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
185*8cfe2a7aSLuiz Otavio O Souza 		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
186*8cfe2a7aSLuiz Otavio O Souza 		device_printf(dev, "cannot flush the controller fifo.\n");
187*8cfe2a7aSLuiz Otavio O Souza 		return (ENXIO);
188*8cfe2a7aSLuiz Otavio O Souza 	}
189*8cfe2a7aSLuiz Otavio O Souza 
190*8cfe2a7aSLuiz Otavio O Souza 	/* Reset the Controller. */
191*8cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF);
192*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg | A37X0_SPI_SRST);
193*8cfe2a7aSLuiz Otavio O Souza 	DELAY(1000);
194*8cfe2a7aSLuiz Otavio O Souza 	/* Enable the single byte IO, disable FIFO. */
195*8cfe2a7aSLuiz Otavio O Souza 	reg &= ~(A37X0_SPI_FIFO_MODE | A37X0_SPI_BYTE_LEN);
196*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg);
197*8cfe2a7aSLuiz Otavio O Souza 
198*8cfe2a7aSLuiz Otavio O Souza 	/* Disable and clear interrupts. */
199*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_MASK, 0);
200*8cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_INTR_STAT);
201*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_STAT, reg);
202*8cfe2a7aSLuiz Otavio O Souza 
203*8cfe2a7aSLuiz Otavio O Souza 	/* Hook up our interrupt handler. */
204*8cfe2a7aSLuiz Otavio O Souza 	if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
205*8cfe2a7aSLuiz Otavio O Souza 	    NULL, a37x0_spi_intr, sc, &sc->sc_intrhand)) {
206*8cfe2a7aSLuiz Otavio O Souza 		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
207*8cfe2a7aSLuiz Otavio O Souza 		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
208*8cfe2a7aSLuiz Otavio O Souza 		device_printf(dev, "cannot setup the interrupt handler\n");
209*8cfe2a7aSLuiz Otavio O Souza 		return (ENXIO);
210*8cfe2a7aSLuiz Otavio O Souza 	}
211*8cfe2a7aSLuiz Otavio O Souza 
212*8cfe2a7aSLuiz Otavio O Souza 	mtx_init(&sc->sc_mtx, "a37x0_spi", NULL, MTX_DEF);
213*8cfe2a7aSLuiz Otavio O Souza 
214*8cfe2a7aSLuiz Otavio O Souza 	/* Read the controller max-frequency. */
215*8cfe2a7aSLuiz Otavio O Souza 	if (OF_getencprop(ofw_bus_get_node(dev), "spi-max-frequency", &maxfreq,
216*8cfe2a7aSLuiz Otavio O Souza 	    sizeof(maxfreq)) == -1)
217*8cfe2a7aSLuiz Otavio O Souza 		maxfreq = 0;
218*8cfe2a7aSLuiz Otavio O Souza 	sc->sc_maxfreq = maxfreq;
219*8cfe2a7aSLuiz Otavio O Souza 
220*8cfe2a7aSLuiz Otavio O Souza 	device_add_child(dev, "spibus", -1);
221*8cfe2a7aSLuiz Otavio O Souza 
222*8cfe2a7aSLuiz Otavio O Souza 	/* Probe and attach the spibus when interrupts are available. */
223*8cfe2a7aSLuiz Otavio O Souza 	config_intrhook_oneshot((ich_func_t)bus_generic_attach, dev);
224*8cfe2a7aSLuiz Otavio O Souza 
225*8cfe2a7aSLuiz Otavio O Souza 	return (0);
226*8cfe2a7aSLuiz Otavio O Souza }
227*8cfe2a7aSLuiz Otavio O Souza 
228*8cfe2a7aSLuiz Otavio O Souza static int
229*8cfe2a7aSLuiz Otavio O Souza a37x0_spi_detach(device_t dev)
230*8cfe2a7aSLuiz Otavio O Souza {
231*8cfe2a7aSLuiz Otavio O Souza 	struct a37x0_spi_softc *sc;
232*8cfe2a7aSLuiz Otavio O Souza 
233*8cfe2a7aSLuiz Otavio O Souza 	bus_generic_detach(dev);
234*8cfe2a7aSLuiz Otavio O Souza 	sc = device_get_softc(dev);
235*8cfe2a7aSLuiz Otavio O Souza 	mtx_destroy(&sc->sc_mtx);
236*8cfe2a7aSLuiz Otavio O Souza 	if (sc->sc_intrhand)
237*8cfe2a7aSLuiz Otavio O Souza 		bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intrhand);
238*8cfe2a7aSLuiz Otavio O Souza 	if (sc->sc_irq_res)
239*8cfe2a7aSLuiz Otavio O Souza 		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
240*8cfe2a7aSLuiz Otavio O Souza 	if (sc->sc_mem_res)
241*8cfe2a7aSLuiz Otavio O Souza 		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
242*8cfe2a7aSLuiz Otavio O Souza 
243*8cfe2a7aSLuiz Otavio O Souza 	return (0);
244*8cfe2a7aSLuiz Otavio O Souza }
245*8cfe2a7aSLuiz Otavio O Souza 
246*8cfe2a7aSLuiz Otavio O Souza static __inline void
247*8cfe2a7aSLuiz Otavio O Souza a37x0_spi_rx_byte(struct a37x0_spi_softc *sc)
248*8cfe2a7aSLuiz Otavio O Souza {
249*8cfe2a7aSLuiz Otavio O Souza 	struct spi_command *cmd;
250*8cfe2a7aSLuiz Otavio O Souza 	uint32_t read;
251*8cfe2a7aSLuiz Otavio O Souza 	uint8_t *p;
252*8cfe2a7aSLuiz Otavio O Souza 
253*8cfe2a7aSLuiz Otavio O Souza 	if (sc->sc_read == sc->sc_len)
254*8cfe2a7aSLuiz Otavio O Souza 		return;
255*8cfe2a7aSLuiz Otavio O Souza 	cmd = sc->sc_cmd;
256*8cfe2a7aSLuiz Otavio O Souza 	p = (uint8_t *)cmd->rx_cmd;
257*8cfe2a7aSLuiz Otavio O Souza 	read = sc->sc_read++;
258*8cfe2a7aSLuiz Otavio O Souza 	if (read >= cmd->rx_cmd_sz) {
259*8cfe2a7aSLuiz Otavio O Souza 		p = (uint8_t *)cmd->rx_data;
260*8cfe2a7aSLuiz Otavio O Souza 		read -= cmd->rx_cmd_sz;
261*8cfe2a7aSLuiz Otavio O Souza 	}
262*8cfe2a7aSLuiz Otavio O Souza 	p[read] = A37X0_SPI_READ(sc, A37X0_SPI_DATA_IN) & 0xff;
263*8cfe2a7aSLuiz Otavio O Souza }
264*8cfe2a7aSLuiz Otavio O Souza 
265*8cfe2a7aSLuiz Otavio O Souza static __inline void
266*8cfe2a7aSLuiz Otavio O Souza a37x0_spi_tx_byte(struct a37x0_spi_softc *sc)
267*8cfe2a7aSLuiz Otavio O Souza {
268*8cfe2a7aSLuiz Otavio O Souza 	struct spi_command *cmd;
269*8cfe2a7aSLuiz Otavio O Souza 	uint32_t written;
270*8cfe2a7aSLuiz Otavio O Souza 	uint8_t *p;
271*8cfe2a7aSLuiz Otavio O Souza 
272*8cfe2a7aSLuiz Otavio O Souza 	if (sc->sc_written == sc->sc_len)
273*8cfe2a7aSLuiz Otavio O Souza 		return;
274*8cfe2a7aSLuiz Otavio O Souza 	cmd = sc->sc_cmd;
275*8cfe2a7aSLuiz Otavio O Souza 	p = (uint8_t *)cmd->tx_cmd;
276*8cfe2a7aSLuiz Otavio O Souza 	written = sc->sc_written++;
277*8cfe2a7aSLuiz Otavio O Souza 	if (written >= cmd->tx_cmd_sz) {
278*8cfe2a7aSLuiz Otavio O Souza 		p = (uint8_t *)cmd->tx_data;
279*8cfe2a7aSLuiz Otavio O Souza 		written -= cmd->tx_cmd_sz;
280*8cfe2a7aSLuiz Otavio O Souza 	}
281*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_DATA_OUT, p[written]);
282*8cfe2a7aSLuiz Otavio O Souza }
283*8cfe2a7aSLuiz Otavio O Souza 
284*8cfe2a7aSLuiz Otavio O Souza static __inline void
285*8cfe2a7aSLuiz Otavio O Souza a37x0_spi_set_clock(struct a37x0_spi_softc *sc, uint32_t clock)
286*8cfe2a7aSLuiz Otavio O Souza {
287*8cfe2a7aSLuiz Otavio O Souza 	uint32_t psc, reg;
288*8cfe2a7aSLuiz Otavio O Souza 
289*8cfe2a7aSLuiz Otavio O Souza 	if (sc->sc_maxfreq > 0 && clock > sc->sc_maxfreq)
290*8cfe2a7aSLuiz Otavio O Souza 		clock = sc->sc_maxfreq;
291*8cfe2a7aSLuiz Otavio O Souza 	psc = A37X0_SPI_CLOCK / clock;
292*8cfe2a7aSLuiz Otavio O Souza 	if ((A37X0_SPI_CLOCK % clock) > 0)
293*8cfe2a7aSLuiz Otavio O Souza 		psc++;
294*8cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF);
295*8cfe2a7aSLuiz Otavio O Souza 	reg &= ~A37X0_SPI_PSC_MASK;
296*8cfe2a7aSLuiz Otavio O Souza 	reg |= psc & A37X0_SPI_PSC_MASK;
297*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg);
298*8cfe2a7aSLuiz Otavio O Souza }
299*8cfe2a7aSLuiz Otavio O Souza 
300*8cfe2a7aSLuiz Otavio O Souza static __inline void
301*8cfe2a7aSLuiz Otavio O Souza a37x0_spi_set_pins(struct a37x0_spi_softc *sc, uint32_t npins)
302*8cfe2a7aSLuiz Otavio O Souza {
303*8cfe2a7aSLuiz Otavio O Souza 	uint32_t reg;
304*8cfe2a7aSLuiz Otavio O Souza 
305*8cfe2a7aSLuiz Otavio O Souza 	/* Sets single, dual or quad SPI mode. */
306*8cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF);
307*8cfe2a7aSLuiz Otavio O Souza 	reg &= ~(A37X0_SPI_DATA_PIN_MASK << A37X0_SPI_DATA_PIN_SHIFT);
308*8cfe2a7aSLuiz Otavio O Souza 	reg |= (npins / 2) << A37X0_SPI_DATA_PIN_SHIFT;
309*8cfe2a7aSLuiz Otavio O Souza 	reg |= A37X0_SPI_INSTR_PIN | A37X0_SPI_ADDR_PIN;
310*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg);
311*8cfe2a7aSLuiz Otavio O Souza }
312*8cfe2a7aSLuiz Otavio O Souza 
313*8cfe2a7aSLuiz Otavio O Souza static __inline void
314*8cfe2a7aSLuiz Otavio O Souza a37x0_spi_set_mode(struct a37x0_spi_softc *sc, uint32_t mode)
315*8cfe2a7aSLuiz Otavio O Souza {
316*8cfe2a7aSLuiz Otavio O Souza 	uint32_t reg;
317*8cfe2a7aSLuiz Otavio O Souza 
318*8cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF);
319*8cfe2a7aSLuiz Otavio O Souza 	switch (mode) {
320*8cfe2a7aSLuiz Otavio O Souza 	case 0:
321*8cfe2a7aSLuiz Otavio O Souza 		reg &= ~(A37X0_SPI_CLK_PHASE | A37X0_SPI_CLK_POL);
322*8cfe2a7aSLuiz Otavio O Souza 		break;
323*8cfe2a7aSLuiz Otavio O Souza 	case 1:
324*8cfe2a7aSLuiz Otavio O Souza 		reg &= ~A37X0_SPI_CLK_POL;
325*8cfe2a7aSLuiz Otavio O Souza 		reg |= A37X0_SPI_CLK_PHASE;
326*8cfe2a7aSLuiz Otavio O Souza 		break;
327*8cfe2a7aSLuiz Otavio O Souza 	case 2:
328*8cfe2a7aSLuiz Otavio O Souza 		reg &= ~A37X0_SPI_CLK_PHASE;
329*8cfe2a7aSLuiz Otavio O Souza 		reg |= A37X0_SPI_CLK_POL;
330*8cfe2a7aSLuiz Otavio O Souza 		break;
331*8cfe2a7aSLuiz Otavio O Souza 	case 3:
332*8cfe2a7aSLuiz Otavio O Souza 		reg |= (A37X0_SPI_CLK_PHASE | A37X0_SPI_CLK_POL);
333*8cfe2a7aSLuiz Otavio O Souza 		break;
334*8cfe2a7aSLuiz Otavio O Souza 	}
335*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg);
336*8cfe2a7aSLuiz Otavio O Souza }
337*8cfe2a7aSLuiz Otavio O Souza 
338*8cfe2a7aSLuiz Otavio O Souza static void
339*8cfe2a7aSLuiz Otavio O Souza a37x0_spi_intr(void *arg)
340*8cfe2a7aSLuiz Otavio O Souza {
341*8cfe2a7aSLuiz Otavio O Souza 	struct a37x0_spi_softc *sc;
342*8cfe2a7aSLuiz Otavio O Souza 	uint32_t status;
343*8cfe2a7aSLuiz Otavio O Souza 
344*8cfe2a7aSLuiz Otavio O Souza 	sc = (struct a37x0_spi_softc *)arg;
345*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_LOCK(sc);
346*8cfe2a7aSLuiz Otavio O Souza 
347*8cfe2a7aSLuiz Otavio O Souza 	/* Filter stray interrupts. */
348*8cfe2a7aSLuiz Otavio O Souza 	if ((sc->sc_flags & A37X0_SPI_BUSY) == 0) {
349*8cfe2a7aSLuiz Otavio O Souza 		A37X0_SPI_UNLOCK(sc);
350*8cfe2a7aSLuiz Otavio O Souza 		return;
351*8cfe2a7aSLuiz Otavio O Souza 	}
352*8cfe2a7aSLuiz Otavio O Souza 
353*8cfe2a7aSLuiz Otavio O Souza 	status = A37X0_SPI_READ(sc, A37X0_SPI_INTR_STAT);
354*8cfe2a7aSLuiz Otavio O Souza 	if (status & A37X0_SPI_XFER_DONE)
355*8cfe2a7aSLuiz Otavio O Souza 		a37x0_spi_rx_byte(sc);
356*8cfe2a7aSLuiz Otavio O Souza 
357*8cfe2a7aSLuiz Otavio O Souza 	/* Clear the interrupt status. */
358*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_STAT, status);
359*8cfe2a7aSLuiz Otavio O Souza 
360*8cfe2a7aSLuiz Otavio O Souza 	/* Check for end of transfer. */
361*8cfe2a7aSLuiz Otavio O Souza 	if (sc->sc_written == sc->sc_len && sc->sc_read == sc->sc_len)
362*8cfe2a7aSLuiz Otavio O Souza 		wakeup(sc->sc_dev);
363*8cfe2a7aSLuiz Otavio O Souza 	else
364*8cfe2a7aSLuiz Otavio O Souza 		a37x0_spi_tx_byte(sc);
365*8cfe2a7aSLuiz Otavio O Souza 
366*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_UNLOCK(sc);
367*8cfe2a7aSLuiz Otavio O Souza }
368*8cfe2a7aSLuiz Otavio O Souza 
369*8cfe2a7aSLuiz Otavio O Souza static int
370*8cfe2a7aSLuiz Otavio O Souza a37x0_spi_transfer(device_t dev, device_t child, struct spi_command *cmd)
371*8cfe2a7aSLuiz Otavio O Souza {
372*8cfe2a7aSLuiz Otavio O Souza 	int timeout;
373*8cfe2a7aSLuiz Otavio O Souza 	struct a37x0_spi_softc *sc;
374*8cfe2a7aSLuiz Otavio O Souza 	uint32_t clock, cs, mode, reg;
375*8cfe2a7aSLuiz Otavio O Souza 
376*8cfe2a7aSLuiz Otavio O Souza 	KASSERT(cmd->tx_cmd_sz == cmd->rx_cmd_sz,
377*8cfe2a7aSLuiz Otavio O Souza 	    ("TX/RX command sizes should be equal"));
378*8cfe2a7aSLuiz Otavio O Souza 	KASSERT(cmd->tx_data_sz == cmd->rx_data_sz,
379*8cfe2a7aSLuiz Otavio O Souza 	    ("TX/RX data sizes should be equal"));
380*8cfe2a7aSLuiz Otavio O Souza 
381*8cfe2a7aSLuiz Otavio O Souza 	/* Get the proper data for this child. */
382*8cfe2a7aSLuiz Otavio O Souza 	spibus_get_cs(child, &cs);
383*8cfe2a7aSLuiz Otavio O Souza 	cs &= ~SPIBUS_CS_HIGH;
384*8cfe2a7aSLuiz Otavio O Souza 	if (cs > 3) {
385*8cfe2a7aSLuiz Otavio O Souza 		device_printf(dev,
386*8cfe2a7aSLuiz Otavio O Souza 		    "Invalid CS %d requested by %s\n", cs,
387*8cfe2a7aSLuiz Otavio O Souza 		    device_get_nameunit(child));
388*8cfe2a7aSLuiz Otavio O Souza 		return (EINVAL);
389*8cfe2a7aSLuiz Otavio O Souza 	}
390*8cfe2a7aSLuiz Otavio O Souza 	spibus_get_clock(child, &clock);
391*8cfe2a7aSLuiz Otavio O Souza 	if (clock == 0) {
392*8cfe2a7aSLuiz Otavio O Souza 		device_printf(dev,
393*8cfe2a7aSLuiz Otavio O Souza 		    "Invalid clock %uHz requested by %s\n", clock,
394*8cfe2a7aSLuiz Otavio O Souza 		    device_get_nameunit(child));
395*8cfe2a7aSLuiz Otavio O Souza 		return (EINVAL);
396*8cfe2a7aSLuiz Otavio O Souza 	}
397*8cfe2a7aSLuiz Otavio O Souza 	spibus_get_mode(child, &mode);
398*8cfe2a7aSLuiz Otavio O Souza 	if (mode > 3) {
399*8cfe2a7aSLuiz Otavio O Souza 		device_printf(dev,
400*8cfe2a7aSLuiz Otavio O Souza 		    "Invalid mode %u requested by %s\n", mode,
401*8cfe2a7aSLuiz Otavio O Souza 		    device_get_nameunit(child));
402*8cfe2a7aSLuiz Otavio O Souza 		return (EINVAL);
403*8cfe2a7aSLuiz Otavio O Souza 	}
404*8cfe2a7aSLuiz Otavio O Souza 
405*8cfe2a7aSLuiz Otavio O Souza 	sc = device_get_softc(dev);
406*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_LOCK(sc);
407*8cfe2a7aSLuiz Otavio O Souza 
408*8cfe2a7aSLuiz Otavio O Souza 	/* Wait until the controller is free. */
409*8cfe2a7aSLuiz Otavio O Souza 	while (sc->sc_flags & A37X0_SPI_BUSY)
410*8cfe2a7aSLuiz Otavio O Souza 		mtx_sleep(dev, &sc->sc_mtx, 0, "a37x0_spi", 0);
411*8cfe2a7aSLuiz Otavio O Souza 
412*8cfe2a7aSLuiz Otavio O Souza 	/* Now we have control over SPI controller. */
413*8cfe2a7aSLuiz Otavio O Souza 	sc->sc_flags = A37X0_SPI_BUSY;
414*8cfe2a7aSLuiz Otavio O Souza 
415*8cfe2a7aSLuiz Otavio O Souza 	/* Set transfer mode and clock. */
416*8cfe2a7aSLuiz Otavio O Souza 	a37x0_spi_set_mode(sc, mode);
417*8cfe2a7aSLuiz Otavio O Souza 	a37x0_spi_set_pins(sc, 1);
418*8cfe2a7aSLuiz Otavio O Souza 	a37x0_spi_set_clock(sc, clock);
419*8cfe2a7aSLuiz Otavio O Souza 
420*8cfe2a7aSLuiz Otavio O Souza 	/* Set CS. */
421*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONTROL, 1 << (A37X0_SPI_CS_SHIFT + cs));
422*8cfe2a7aSLuiz Otavio O Souza 
423*8cfe2a7aSLuiz Otavio O Souza 	/* Save a pointer to the SPI command. */
424*8cfe2a7aSLuiz Otavio O Souza 	sc->sc_cmd = cmd;
425*8cfe2a7aSLuiz Otavio O Souza 	sc->sc_read = 0;
426*8cfe2a7aSLuiz Otavio O Souza 	sc->sc_written = 0;
427*8cfe2a7aSLuiz Otavio O Souza 	sc->sc_len = cmd->tx_cmd_sz + cmd->tx_data_sz;
428*8cfe2a7aSLuiz Otavio O Souza 
429*8cfe2a7aSLuiz Otavio O Souza 	/* Clear interrupts. */
430*8cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_INTR_STAT);
431*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_STAT, reg);
432*8cfe2a7aSLuiz Otavio O Souza 
433*8cfe2a7aSLuiz Otavio O Souza 	while ((sc->sc_len - sc->sc_written) > 0) {
434*8cfe2a7aSLuiz Otavio O Souza 		/*
435*8cfe2a7aSLuiz Otavio O Souza 		 * Write to start the transmission and read the byte
436*8cfe2a7aSLuiz Otavio O Souza 		 * back when ready.
437*8cfe2a7aSLuiz Otavio O Souza 		 */
438*8cfe2a7aSLuiz Otavio O Souza 		a37x0_spi_tx_byte(sc);
439*8cfe2a7aSLuiz Otavio O Souza 		timeout = 1000;
440*8cfe2a7aSLuiz Otavio O Souza 		while (--timeout > 0) {
441*8cfe2a7aSLuiz Otavio O Souza 			reg = A37X0_SPI_READ(sc, A37X0_SPI_CONTROL);
442*8cfe2a7aSLuiz Otavio O Souza 			if (reg & A37X0_SPI_XFER_DONE)
443*8cfe2a7aSLuiz Otavio O Souza 				break;
444*8cfe2a7aSLuiz Otavio O Souza 			DELAY(1);
445*8cfe2a7aSLuiz Otavio O Souza 		}
446*8cfe2a7aSLuiz Otavio O Souza 		if (timeout == 0)
447*8cfe2a7aSLuiz Otavio O Souza 			break;
448*8cfe2a7aSLuiz Otavio O Souza 		a37x0_spi_rx_byte(sc);
449*8cfe2a7aSLuiz Otavio O Souza 	}
450*8cfe2a7aSLuiz Otavio O Souza 
451*8cfe2a7aSLuiz Otavio O Souza 	/* Stop the controller. */
452*8cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_CONTROL);
453*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONTROL, reg & ~A37X0_SPI_CS_MASK);
454*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_MASK, 0);
455*8cfe2a7aSLuiz Otavio O Souza 
456*8cfe2a7aSLuiz Otavio O Souza 	/* Release the controller and wakeup the next thread waiting for it. */
457*8cfe2a7aSLuiz Otavio O Souza 	sc->sc_flags = 0;
458*8cfe2a7aSLuiz Otavio O Souza 	wakeup_one(dev);
459*8cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_UNLOCK(sc);
460*8cfe2a7aSLuiz Otavio O Souza 
461*8cfe2a7aSLuiz Otavio O Souza 	return ((timeout == 0) ? EIO : 0);
462*8cfe2a7aSLuiz Otavio O Souza }
463*8cfe2a7aSLuiz Otavio O Souza 
464*8cfe2a7aSLuiz Otavio O Souza static phandle_t
465*8cfe2a7aSLuiz Otavio O Souza a37x0_spi_get_node(device_t bus, device_t dev)
466*8cfe2a7aSLuiz Otavio O Souza {
467*8cfe2a7aSLuiz Otavio O Souza 
468*8cfe2a7aSLuiz Otavio O Souza 	return (ofw_bus_get_node(bus));
469*8cfe2a7aSLuiz Otavio O Souza }
470*8cfe2a7aSLuiz Otavio O Souza 
471*8cfe2a7aSLuiz Otavio O Souza static device_method_t a37x0_spi_methods[] = {
472*8cfe2a7aSLuiz Otavio O Souza 	/* Device interface */
473*8cfe2a7aSLuiz Otavio O Souza 	DEVMETHOD(device_probe,		a37x0_spi_probe),
474*8cfe2a7aSLuiz Otavio O Souza 	DEVMETHOD(device_attach,	a37x0_spi_attach),
475*8cfe2a7aSLuiz Otavio O Souza 	DEVMETHOD(device_detach,	a37x0_spi_detach),
476*8cfe2a7aSLuiz Otavio O Souza 
477*8cfe2a7aSLuiz Otavio O Souza 	/* SPI interface */
478*8cfe2a7aSLuiz Otavio O Souza 	DEVMETHOD(spibus_transfer,	a37x0_spi_transfer),
479*8cfe2a7aSLuiz Otavio O Souza 
480*8cfe2a7aSLuiz Otavio O Souza 	/* ofw_bus interface */
481*8cfe2a7aSLuiz Otavio O Souza 	DEVMETHOD(ofw_bus_get_node,	a37x0_spi_get_node),
482*8cfe2a7aSLuiz Otavio O Souza 
483*8cfe2a7aSLuiz Otavio O Souza 	DEVMETHOD_END
484*8cfe2a7aSLuiz Otavio O Souza };
485*8cfe2a7aSLuiz Otavio O Souza 
486*8cfe2a7aSLuiz Otavio O Souza static devclass_t a37x0_spi_devclass;
487*8cfe2a7aSLuiz Otavio O Souza 
488*8cfe2a7aSLuiz Otavio O Souza static driver_t a37x0_spi_driver = {
489*8cfe2a7aSLuiz Otavio O Souza 	"spi",
490*8cfe2a7aSLuiz Otavio O Souza 	a37x0_spi_methods,
491*8cfe2a7aSLuiz Otavio O Souza 	sizeof(struct a37x0_spi_softc),
492*8cfe2a7aSLuiz Otavio O Souza };
493*8cfe2a7aSLuiz Otavio O Souza 
494*8cfe2a7aSLuiz Otavio O Souza DRIVER_MODULE(a37x0_spi, simplebus, a37x0_spi_driver, a37x0_spi_devclass, 0, 0);
495