xref: /freebsd/sys/arm/mv/a37x0_spi.c (revision 16f4a8ea9e3b8b3c2081c8035577dba2109d3fca)
18cfe2a7aSLuiz Otavio O Souza /*-
28cfe2a7aSLuiz Otavio O Souza  * Copyright (c) 2018, 2019 Rubicon Communications, LLC (Netgate)
38cfe2a7aSLuiz Otavio O Souza  *
48cfe2a7aSLuiz Otavio O Souza  * Redistribution and use in source and binary forms, with or without
58cfe2a7aSLuiz Otavio O Souza  * modification, are permitted provided that the following conditions
68cfe2a7aSLuiz Otavio O Souza  * are met:
78cfe2a7aSLuiz Otavio O Souza  * 1. Redistributions of source code must retain the above copyright
88cfe2a7aSLuiz Otavio O Souza  *    notice, this list of conditions and the following disclaimer.
98cfe2a7aSLuiz Otavio O Souza  * 2. Redistributions in binary form must reproduce the above copyright
108cfe2a7aSLuiz Otavio O Souza  *    notice, this list of conditions and the following disclaimer in the
118cfe2a7aSLuiz Otavio O Souza  *    documentation and/or other materials provided with the distribution.
128cfe2a7aSLuiz Otavio O Souza  *
138cfe2a7aSLuiz Otavio O Souza  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
148cfe2a7aSLuiz Otavio O Souza  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
158cfe2a7aSLuiz Otavio O Souza  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
168cfe2a7aSLuiz Otavio O Souza  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
178cfe2a7aSLuiz Otavio O Souza  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
188cfe2a7aSLuiz Otavio O Souza  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
198cfe2a7aSLuiz Otavio O Souza  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
208cfe2a7aSLuiz Otavio O Souza  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
218cfe2a7aSLuiz Otavio O Souza  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
228cfe2a7aSLuiz Otavio O Souza  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
238cfe2a7aSLuiz Otavio O Souza  *
248cfe2a7aSLuiz Otavio O Souza  */
258cfe2a7aSLuiz Otavio O Souza 
268cfe2a7aSLuiz Otavio O Souza #include <sys/cdefs.h>
278cfe2a7aSLuiz Otavio O Souza __FBSDID("$FreeBSD$");
288cfe2a7aSLuiz Otavio O Souza 
298cfe2a7aSLuiz Otavio O Souza #include <sys/param.h>
308cfe2a7aSLuiz Otavio O Souza #include <sys/systm.h>
318cfe2a7aSLuiz Otavio O Souza #include <sys/bus.h>
328cfe2a7aSLuiz Otavio O Souza #include <sys/kernel.h>
338cfe2a7aSLuiz Otavio O Souza #include <sys/module.h>
34*16f4a8eaSLuiz Otavio O Souza #include <sys/mutex.h>
358cfe2a7aSLuiz Otavio O Souza #include <sys/rman.h>
368cfe2a7aSLuiz Otavio O Souza 
378cfe2a7aSLuiz Otavio O Souza #include <machine/bus.h>
388cfe2a7aSLuiz Otavio O Souza #include <machine/resource.h>
398cfe2a7aSLuiz Otavio O Souza #include <machine/intr.h>
408cfe2a7aSLuiz Otavio O Souza 
418cfe2a7aSLuiz Otavio O Souza #include <dev/ofw/ofw_bus.h>
428cfe2a7aSLuiz Otavio O Souza #include <dev/ofw/ofw_bus_subr.h>
438cfe2a7aSLuiz Otavio O Souza #include <dev/spibus/spi.h>
448cfe2a7aSLuiz Otavio O Souza #include <dev/spibus/spibusvar.h>
458cfe2a7aSLuiz Otavio O Souza 
468cfe2a7aSLuiz Otavio O Souza #include "spibus_if.h"
478cfe2a7aSLuiz Otavio O Souza 
488cfe2a7aSLuiz Otavio O Souza struct a37x0_spi_softc {
498cfe2a7aSLuiz Otavio O Souza 	device_t		sc_dev;
508cfe2a7aSLuiz Otavio O Souza 	struct mtx		sc_mtx;
518cfe2a7aSLuiz Otavio O Souza 	struct resource		*sc_mem_res;
528cfe2a7aSLuiz Otavio O Souza 	struct resource		*sc_irq_res;
538cfe2a7aSLuiz Otavio O Souza 	struct spi_command	*sc_cmd;
548cfe2a7aSLuiz Otavio O Souza 	bus_space_tag_t		sc_bst;
558cfe2a7aSLuiz Otavio O Souza 	bus_space_handle_t	sc_bsh;
568cfe2a7aSLuiz Otavio O Souza 	uint32_t		sc_len;
578cfe2a7aSLuiz Otavio O Souza 	uint32_t		sc_maxfreq;
588cfe2a7aSLuiz Otavio O Souza 	uint32_t		sc_read;
598cfe2a7aSLuiz Otavio O Souza 	uint32_t		sc_flags;
608cfe2a7aSLuiz Otavio O Souza 	uint32_t		sc_written;
618cfe2a7aSLuiz Otavio O Souza 	void			*sc_intrhand;
628cfe2a7aSLuiz Otavio O Souza };
638cfe2a7aSLuiz Otavio O Souza 
648cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_WRITE(_sc, _off, _val)		\
658cfe2a7aSLuiz Otavio O Souza     bus_space_write_4((_sc)->sc_bst, (_sc)->sc_bsh, (_off), (_val))
668cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_READ(_sc, _off)			\
678cfe2a7aSLuiz Otavio O Souza     bus_space_read_4((_sc)->sc_bst, (_sc)->sc_bsh, (_off))
688cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_LOCK(_sc)	mtx_lock(&(_sc)->sc_mtx)
698cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_UNLOCK(_sc)	mtx_unlock(&(_sc)->sc_mtx)
708cfe2a7aSLuiz Otavio O Souza 
718cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_BUSY			(1 << 0)
728cfe2a7aSLuiz Otavio O Souza /*
738cfe2a7aSLuiz Otavio O Souza  * While the A3700 utils from Marvell usually sets the QSF clock to 200MHz,
748cfe2a7aSLuiz Otavio O Souza  * there is no guarantee that it is correct without the proper clock framework
758cfe2a7aSLuiz Otavio O Souza  * to retrieve the actual TBG and PLL settings.
768cfe2a7aSLuiz Otavio O Souza  */
778cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_CLOCK			200000000	/* QSF Clock 200MHz */
788cfe2a7aSLuiz Otavio O Souza 
798cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_CONTROL		0x0
808cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_CS_SHIFT		16
818cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_CS_MASK		(0xf << A37X0_SPI_CS_SHIFT)
828cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_CONF			0x4
838cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_WFIFO_THRS_SHIFT	28
848cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_RFIFO_THRS_SHIFT	24
858cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_AUTO_CS_EN		(1 << 20)
868cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_DMA_WR_EN		(1 << 19)
878cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_DMA_RD_EN		(1 << 18)
888cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_FIFO_MODE		(1 << 17)
898cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_SRST			(1 << 16)
908cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_XFER_START		(1 << 15)
918cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_XFER_STOP		(1 << 14)
928cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_INSTR_PIN		(1 << 13)
938cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_ADDR_PIN		(1 << 12)
948cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_DATA_PIN_MASK	0x3
958cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_DATA_PIN_SHIFT	10
968cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_FIFO_FLUSH		(1 << 9)
978cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_RW_EN		(1 << 8)
988cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_CLK_POL		(1 << 7)
998cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_CLK_PHASE		(1 << 6)
1008cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_BYTE_LEN		(1 << 5)
1018cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_PSC_MASK		0x1f
1028cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_DATA_OUT		0x8
1038cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_DATA_IN		0xc
1048cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_INTR_STAT		0x28
1058cfe2a7aSLuiz Otavio O Souza #define	A37X0_SPI_INTR_MASK		0x2c
1068cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_RDY			(1 << 1)
1078cfe2a7aSLuiz Otavio O Souza #define	 A37X0_SPI_XFER_DONE		(1 << 0)
1088cfe2a7aSLuiz Otavio O Souza 
1098cfe2a7aSLuiz Otavio O Souza static struct ofw_compat_data compat_data[] = {
1108cfe2a7aSLuiz Otavio O Souza 	{ "marvell,armada-3700-spi",	1 },
1118cfe2a7aSLuiz Otavio O Souza 	{ NULL, 0 }
1128cfe2a7aSLuiz Otavio O Souza };
1138cfe2a7aSLuiz Otavio O Souza 
1148cfe2a7aSLuiz Otavio O Souza static void a37x0_spi_intr(void *);
1158cfe2a7aSLuiz Otavio O Souza 
1168cfe2a7aSLuiz Otavio O Souza static int
1178cfe2a7aSLuiz Otavio O Souza a37x0_spi_wait(struct a37x0_spi_softc *sc, int timeout, uint32_t reg,
1188cfe2a7aSLuiz Otavio O Souza     uint32_t mask)
1198cfe2a7aSLuiz Otavio O Souza {
1208cfe2a7aSLuiz Otavio O Souza 	int i;
1218cfe2a7aSLuiz Otavio O Souza 
1228cfe2a7aSLuiz Otavio O Souza 	for (i = 0; i < timeout; i++) {
1238cfe2a7aSLuiz Otavio O Souza 		if ((A37X0_SPI_READ(sc, reg) & mask) == 0)
1248cfe2a7aSLuiz Otavio O Souza 			return (0);
1258cfe2a7aSLuiz Otavio O Souza 		DELAY(100);
1268cfe2a7aSLuiz Otavio O Souza 	}
1278cfe2a7aSLuiz Otavio O Souza 
1288cfe2a7aSLuiz Otavio O Souza 	return (ETIMEDOUT);
1298cfe2a7aSLuiz Otavio O Souza }
1308cfe2a7aSLuiz Otavio O Souza 
1318cfe2a7aSLuiz Otavio O Souza static int
1328cfe2a7aSLuiz Otavio O Souza a37x0_spi_probe(device_t dev)
1338cfe2a7aSLuiz Otavio O Souza {
1348cfe2a7aSLuiz Otavio O Souza 
1358cfe2a7aSLuiz Otavio O Souza 	if (!ofw_bus_status_okay(dev))
1368cfe2a7aSLuiz Otavio O Souza 		return (ENXIO);
1378cfe2a7aSLuiz Otavio O Souza 	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
1388cfe2a7aSLuiz Otavio O Souza 		return (ENXIO);
1398cfe2a7aSLuiz Otavio O Souza 	device_set_desc(dev, "Armada 37x0 SPI controller");
1408cfe2a7aSLuiz Otavio O Souza 
1418cfe2a7aSLuiz Otavio O Souza 	return (BUS_PROBE_DEFAULT);
1428cfe2a7aSLuiz Otavio O Souza }
1438cfe2a7aSLuiz Otavio O Souza 
1448cfe2a7aSLuiz Otavio O Souza static int
1458cfe2a7aSLuiz Otavio O Souza a37x0_spi_attach(device_t dev)
1468cfe2a7aSLuiz Otavio O Souza {
1478cfe2a7aSLuiz Otavio O Souza 	int err, rid;
1488cfe2a7aSLuiz Otavio O Souza 	pcell_t maxfreq;
1498cfe2a7aSLuiz Otavio O Souza 	struct a37x0_spi_softc *sc;
1508cfe2a7aSLuiz Otavio O Souza 	uint32_t reg;
1518cfe2a7aSLuiz Otavio O Souza 
1528cfe2a7aSLuiz Otavio O Souza 	sc = device_get_softc(dev);
1538cfe2a7aSLuiz Otavio O Souza 	sc->sc_dev = dev;
1548cfe2a7aSLuiz Otavio O Souza 
1558cfe2a7aSLuiz Otavio O Souza 	rid = 0;
1568cfe2a7aSLuiz Otavio O Souza 	sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
1578cfe2a7aSLuiz Otavio O Souza 	    RF_ACTIVE);
1588cfe2a7aSLuiz Otavio O Souza 	if (!sc->sc_mem_res) {
1598cfe2a7aSLuiz Otavio O Souza 		device_printf(dev, "cannot allocate memory window\n");
1608cfe2a7aSLuiz Otavio O Souza 		return (ENXIO);
1618cfe2a7aSLuiz Otavio O Souza 	}
1628cfe2a7aSLuiz Otavio O Souza 
1638cfe2a7aSLuiz Otavio O Souza 	sc->sc_bst = rman_get_bustag(sc->sc_mem_res);
1648cfe2a7aSLuiz Otavio O Souza 	sc->sc_bsh = rman_get_bushandle(sc->sc_mem_res);
1658cfe2a7aSLuiz Otavio O Souza 
1668cfe2a7aSLuiz Otavio O Souza 	rid = 0;
1678cfe2a7aSLuiz Otavio O Souza 	sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
1688cfe2a7aSLuiz Otavio O Souza 	    RF_ACTIVE);
1698cfe2a7aSLuiz Otavio O Souza 	if (!sc->sc_irq_res) {
1708cfe2a7aSLuiz Otavio O Souza 		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
1718cfe2a7aSLuiz Otavio O Souza 		device_printf(dev, "cannot allocate interrupt\n");
1728cfe2a7aSLuiz Otavio O Souza 		return (ENXIO);
1738cfe2a7aSLuiz Otavio O Souza 	}
1748cfe2a7aSLuiz Otavio O Souza 
1758cfe2a7aSLuiz Otavio O Souza 	/* Make sure that no CS is asserted. */
1768cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_CONTROL);
1778cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONTROL, reg & ~A37X0_SPI_CS_MASK);
1788cfe2a7aSLuiz Otavio O Souza 
1798cfe2a7aSLuiz Otavio O Souza 	/* Reset FIFO. */
1808cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF);
1818cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg | A37X0_SPI_FIFO_FLUSH);
1828cfe2a7aSLuiz Otavio O Souza 	err = a37x0_spi_wait(sc, 20, A37X0_SPI_CONF, A37X0_SPI_FIFO_FLUSH);
1838cfe2a7aSLuiz Otavio O Souza 	if (err != 0) {
1848cfe2a7aSLuiz Otavio O Souza 		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
1858cfe2a7aSLuiz Otavio O Souza 		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
1868cfe2a7aSLuiz Otavio O Souza 		device_printf(dev, "cannot flush the controller fifo.\n");
1878cfe2a7aSLuiz Otavio O Souza 		return (ENXIO);
1888cfe2a7aSLuiz Otavio O Souza 	}
1898cfe2a7aSLuiz Otavio O Souza 
1908cfe2a7aSLuiz Otavio O Souza 	/* Reset the Controller. */
1918cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF);
1928cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg | A37X0_SPI_SRST);
1938cfe2a7aSLuiz Otavio O Souza 	DELAY(1000);
1948cfe2a7aSLuiz Otavio O Souza 	/* Enable the single byte IO, disable FIFO. */
1958cfe2a7aSLuiz Otavio O Souza 	reg &= ~(A37X0_SPI_FIFO_MODE | A37X0_SPI_BYTE_LEN);
1968cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg);
1978cfe2a7aSLuiz Otavio O Souza 
1988cfe2a7aSLuiz Otavio O Souza 	/* Disable and clear interrupts. */
1998cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_MASK, 0);
2008cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_INTR_STAT);
2018cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_STAT, reg);
2028cfe2a7aSLuiz Otavio O Souza 
2038cfe2a7aSLuiz Otavio O Souza 	/* Hook up our interrupt handler. */
2048cfe2a7aSLuiz Otavio O Souza 	if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
2058cfe2a7aSLuiz Otavio O Souza 	    NULL, a37x0_spi_intr, sc, &sc->sc_intrhand)) {
2068cfe2a7aSLuiz Otavio O Souza 		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
2078cfe2a7aSLuiz Otavio O Souza 		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
2088cfe2a7aSLuiz Otavio O Souza 		device_printf(dev, "cannot setup the interrupt handler\n");
2098cfe2a7aSLuiz Otavio O Souza 		return (ENXIO);
2108cfe2a7aSLuiz Otavio O Souza 	}
2118cfe2a7aSLuiz Otavio O Souza 
2128cfe2a7aSLuiz Otavio O Souza 	mtx_init(&sc->sc_mtx, "a37x0_spi", NULL, MTX_DEF);
2138cfe2a7aSLuiz Otavio O Souza 
2148cfe2a7aSLuiz Otavio O Souza 	/* Read the controller max-frequency. */
2158cfe2a7aSLuiz Otavio O Souza 	if (OF_getencprop(ofw_bus_get_node(dev), "spi-max-frequency", &maxfreq,
2168cfe2a7aSLuiz Otavio O Souza 	    sizeof(maxfreq)) == -1)
2178cfe2a7aSLuiz Otavio O Souza 		maxfreq = 0;
2188cfe2a7aSLuiz Otavio O Souza 	sc->sc_maxfreq = maxfreq;
2198cfe2a7aSLuiz Otavio O Souza 
2208cfe2a7aSLuiz Otavio O Souza 	device_add_child(dev, "spibus", -1);
2218cfe2a7aSLuiz Otavio O Souza 
2228cfe2a7aSLuiz Otavio O Souza 	/* Probe and attach the spibus when interrupts are available. */
2238cfe2a7aSLuiz Otavio O Souza 	config_intrhook_oneshot((ich_func_t)bus_generic_attach, dev);
2248cfe2a7aSLuiz Otavio O Souza 
2258cfe2a7aSLuiz Otavio O Souza 	return (0);
2268cfe2a7aSLuiz Otavio O Souza }
2278cfe2a7aSLuiz Otavio O Souza 
2288cfe2a7aSLuiz Otavio O Souza static int
2298cfe2a7aSLuiz Otavio O Souza a37x0_spi_detach(device_t dev)
2308cfe2a7aSLuiz Otavio O Souza {
231*16f4a8eaSLuiz Otavio O Souza 	int err;
2328cfe2a7aSLuiz Otavio O Souza 	struct a37x0_spi_softc *sc;
2338cfe2a7aSLuiz Otavio O Souza 
234*16f4a8eaSLuiz Otavio O Souza 	if ((err = device_delete_children(dev)) != 0)
235*16f4a8eaSLuiz Otavio O Souza 		return (err);
2368cfe2a7aSLuiz Otavio O Souza 	sc = device_get_softc(dev);
2378cfe2a7aSLuiz Otavio O Souza 	mtx_destroy(&sc->sc_mtx);
2388cfe2a7aSLuiz Otavio O Souza 	if (sc->sc_intrhand)
2398cfe2a7aSLuiz Otavio O Souza 		bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intrhand);
2408cfe2a7aSLuiz Otavio O Souza 	if (sc->sc_irq_res)
2418cfe2a7aSLuiz Otavio O Souza 		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
2428cfe2a7aSLuiz Otavio O Souza 	if (sc->sc_mem_res)
2438cfe2a7aSLuiz Otavio O Souza 		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
2448cfe2a7aSLuiz Otavio O Souza 
2458cfe2a7aSLuiz Otavio O Souza 	return (0);
2468cfe2a7aSLuiz Otavio O Souza }
2478cfe2a7aSLuiz Otavio O Souza 
2488cfe2a7aSLuiz Otavio O Souza static __inline void
2498cfe2a7aSLuiz Otavio O Souza a37x0_spi_rx_byte(struct a37x0_spi_softc *sc)
2508cfe2a7aSLuiz Otavio O Souza {
2518cfe2a7aSLuiz Otavio O Souza 	struct spi_command *cmd;
2528cfe2a7aSLuiz Otavio O Souza 	uint32_t read;
2538cfe2a7aSLuiz Otavio O Souza 	uint8_t *p;
2548cfe2a7aSLuiz Otavio O Souza 
2558cfe2a7aSLuiz Otavio O Souza 	if (sc->sc_read == sc->sc_len)
2568cfe2a7aSLuiz Otavio O Souza 		return;
2578cfe2a7aSLuiz Otavio O Souza 	cmd = sc->sc_cmd;
2588cfe2a7aSLuiz Otavio O Souza 	p = (uint8_t *)cmd->rx_cmd;
2598cfe2a7aSLuiz Otavio O Souza 	read = sc->sc_read++;
2608cfe2a7aSLuiz Otavio O Souza 	if (read >= cmd->rx_cmd_sz) {
2618cfe2a7aSLuiz Otavio O Souza 		p = (uint8_t *)cmd->rx_data;
2628cfe2a7aSLuiz Otavio O Souza 		read -= cmd->rx_cmd_sz;
2638cfe2a7aSLuiz Otavio O Souza 	}
2648cfe2a7aSLuiz Otavio O Souza 	p[read] = A37X0_SPI_READ(sc, A37X0_SPI_DATA_IN) & 0xff;
2658cfe2a7aSLuiz Otavio O Souza }
2668cfe2a7aSLuiz Otavio O Souza 
2678cfe2a7aSLuiz Otavio O Souza static __inline void
2688cfe2a7aSLuiz Otavio O Souza a37x0_spi_tx_byte(struct a37x0_spi_softc *sc)
2698cfe2a7aSLuiz Otavio O Souza {
2708cfe2a7aSLuiz Otavio O Souza 	struct spi_command *cmd;
2718cfe2a7aSLuiz Otavio O Souza 	uint32_t written;
2728cfe2a7aSLuiz Otavio O Souza 	uint8_t *p;
2738cfe2a7aSLuiz Otavio O Souza 
2748cfe2a7aSLuiz Otavio O Souza 	if (sc->sc_written == sc->sc_len)
2758cfe2a7aSLuiz Otavio O Souza 		return;
2768cfe2a7aSLuiz Otavio O Souza 	cmd = sc->sc_cmd;
2778cfe2a7aSLuiz Otavio O Souza 	p = (uint8_t *)cmd->tx_cmd;
2788cfe2a7aSLuiz Otavio O Souza 	written = sc->sc_written++;
2798cfe2a7aSLuiz Otavio O Souza 	if (written >= cmd->tx_cmd_sz) {
2808cfe2a7aSLuiz Otavio O Souza 		p = (uint8_t *)cmd->tx_data;
2818cfe2a7aSLuiz Otavio O Souza 		written -= cmd->tx_cmd_sz;
2828cfe2a7aSLuiz Otavio O Souza 	}
2838cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_DATA_OUT, p[written]);
2848cfe2a7aSLuiz Otavio O Souza }
2858cfe2a7aSLuiz Otavio O Souza 
2868cfe2a7aSLuiz Otavio O Souza static __inline void
2878cfe2a7aSLuiz Otavio O Souza a37x0_spi_set_clock(struct a37x0_spi_softc *sc, uint32_t clock)
2888cfe2a7aSLuiz Otavio O Souza {
2898cfe2a7aSLuiz Otavio O Souza 	uint32_t psc, reg;
2908cfe2a7aSLuiz Otavio O Souza 
2918cfe2a7aSLuiz Otavio O Souza 	if (sc->sc_maxfreq > 0 && clock > sc->sc_maxfreq)
2928cfe2a7aSLuiz Otavio O Souza 		clock = sc->sc_maxfreq;
2938cfe2a7aSLuiz Otavio O Souza 	psc = A37X0_SPI_CLOCK / clock;
2948cfe2a7aSLuiz Otavio O Souza 	if ((A37X0_SPI_CLOCK % clock) > 0)
2958cfe2a7aSLuiz Otavio O Souza 		psc++;
2968cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF);
2978cfe2a7aSLuiz Otavio O Souza 	reg &= ~A37X0_SPI_PSC_MASK;
2988cfe2a7aSLuiz Otavio O Souza 	reg |= psc & A37X0_SPI_PSC_MASK;
2998cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg);
3008cfe2a7aSLuiz Otavio O Souza }
3018cfe2a7aSLuiz Otavio O Souza 
3028cfe2a7aSLuiz Otavio O Souza static __inline void
3038cfe2a7aSLuiz Otavio O Souza a37x0_spi_set_pins(struct a37x0_spi_softc *sc, uint32_t npins)
3048cfe2a7aSLuiz Otavio O Souza {
3058cfe2a7aSLuiz Otavio O Souza 	uint32_t reg;
3068cfe2a7aSLuiz Otavio O Souza 
3078cfe2a7aSLuiz Otavio O Souza 	/* Sets single, dual or quad SPI mode. */
3088cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF);
3098cfe2a7aSLuiz Otavio O Souza 	reg &= ~(A37X0_SPI_DATA_PIN_MASK << A37X0_SPI_DATA_PIN_SHIFT);
3108cfe2a7aSLuiz Otavio O Souza 	reg |= (npins / 2) << A37X0_SPI_DATA_PIN_SHIFT;
3118cfe2a7aSLuiz Otavio O Souza 	reg |= A37X0_SPI_INSTR_PIN | A37X0_SPI_ADDR_PIN;
3128cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg);
3138cfe2a7aSLuiz Otavio O Souza }
3148cfe2a7aSLuiz Otavio O Souza 
3158cfe2a7aSLuiz Otavio O Souza static __inline void
3168cfe2a7aSLuiz Otavio O Souza a37x0_spi_set_mode(struct a37x0_spi_softc *sc, uint32_t mode)
3178cfe2a7aSLuiz Otavio O Souza {
3188cfe2a7aSLuiz Otavio O Souza 	uint32_t reg;
3198cfe2a7aSLuiz Otavio O Souza 
3208cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF);
3218cfe2a7aSLuiz Otavio O Souza 	switch (mode) {
3228cfe2a7aSLuiz Otavio O Souza 	case 0:
3238cfe2a7aSLuiz Otavio O Souza 		reg &= ~(A37X0_SPI_CLK_PHASE | A37X0_SPI_CLK_POL);
3248cfe2a7aSLuiz Otavio O Souza 		break;
3258cfe2a7aSLuiz Otavio O Souza 	case 1:
3268cfe2a7aSLuiz Otavio O Souza 		reg &= ~A37X0_SPI_CLK_POL;
3278cfe2a7aSLuiz Otavio O Souza 		reg |= A37X0_SPI_CLK_PHASE;
3288cfe2a7aSLuiz Otavio O Souza 		break;
3298cfe2a7aSLuiz Otavio O Souza 	case 2:
3308cfe2a7aSLuiz Otavio O Souza 		reg &= ~A37X0_SPI_CLK_PHASE;
3318cfe2a7aSLuiz Otavio O Souza 		reg |= A37X0_SPI_CLK_POL;
3328cfe2a7aSLuiz Otavio O Souza 		break;
3338cfe2a7aSLuiz Otavio O Souza 	case 3:
3348cfe2a7aSLuiz Otavio O Souza 		reg |= (A37X0_SPI_CLK_PHASE | A37X0_SPI_CLK_POL);
3358cfe2a7aSLuiz Otavio O Souza 		break;
3368cfe2a7aSLuiz Otavio O Souza 	}
3378cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg);
3388cfe2a7aSLuiz Otavio O Souza }
3398cfe2a7aSLuiz Otavio O Souza 
3408cfe2a7aSLuiz Otavio O Souza static void
3418cfe2a7aSLuiz Otavio O Souza a37x0_spi_intr(void *arg)
3428cfe2a7aSLuiz Otavio O Souza {
3438cfe2a7aSLuiz Otavio O Souza 	struct a37x0_spi_softc *sc;
3448cfe2a7aSLuiz Otavio O Souza 	uint32_t status;
3458cfe2a7aSLuiz Otavio O Souza 
3468cfe2a7aSLuiz Otavio O Souza 	sc = (struct a37x0_spi_softc *)arg;
3478cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_LOCK(sc);
3488cfe2a7aSLuiz Otavio O Souza 
3498cfe2a7aSLuiz Otavio O Souza 	/* Filter stray interrupts. */
3508cfe2a7aSLuiz Otavio O Souza 	if ((sc->sc_flags & A37X0_SPI_BUSY) == 0) {
3518cfe2a7aSLuiz Otavio O Souza 		A37X0_SPI_UNLOCK(sc);
3528cfe2a7aSLuiz Otavio O Souza 		return;
3538cfe2a7aSLuiz Otavio O Souza 	}
3548cfe2a7aSLuiz Otavio O Souza 
3558cfe2a7aSLuiz Otavio O Souza 	status = A37X0_SPI_READ(sc, A37X0_SPI_INTR_STAT);
3568cfe2a7aSLuiz Otavio O Souza 	if (status & A37X0_SPI_XFER_DONE)
3578cfe2a7aSLuiz Otavio O Souza 		a37x0_spi_rx_byte(sc);
3588cfe2a7aSLuiz Otavio O Souza 
3598cfe2a7aSLuiz Otavio O Souza 	/* Clear the interrupt status. */
3608cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_STAT, status);
3618cfe2a7aSLuiz Otavio O Souza 
3628cfe2a7aSLuiz Otavio O Souza 	/* Check for end of transfer. */
3638cfe2a7aSLuiz Otavio O Souza 	if (sc->sc_written == sc->sc_len && sc->sc_read == sc->sc_len)
3648cfe2a7aSLuiz Otavio O Souza 		wakeup(sc->sc_dev);
3658cfe2a7aSLuiz Otavio O Souza 	else
3668cfe2a7aSLuiz Otavio O Souza 		a37x0_spi_tx_byte(sc);
3678cfe2a7aSLuiz Otavio O Souza 
3688cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_UNLOCK(sc);
3698cfe2a7aSLuiz Otavio O Souza }
3708cfe2a7aSLuiz Otavio O Souza 
3718cfe2a7aSLuiz Otavio O Souza static int
3728cfe2a7aSLuiz Otavio O Souza a37x0_spi_transfer(device_t dev, device_t child, struct spi_command *cmd)
3738cfe2a7aSLuiz Otavio O Souza {
3748cfe2a7aSLuiz Otavio O Souza 	int timeout;
3758cfe2a7aSLuiz Otavio O Souza 	struct a37x0_spi_softc *sc;
3768cfe2a7aSLuiz Otavio O Souza 	uint32_t clock, cs, mode, reg;
3778cfe2a7aSLuiz Otavio O Souza 
3788cfe2a7aSLuiz Otavio O Souza 	KASSERT(cmd->tx_cmd_sz == cmd->rx_cmd_sz,
3798cfe2a7aSLuiz Otavio O Souza 	    ("TX/RX command sizes should be equal"));
3808cfe2a7aSLuiz Otavio O Souza 	KASSERT(cmd->tx_data_sz == cmd->rx_data_sz,
3818cfe2a7aSLuiz Otavio O Souza 	    ("TX/RX data sizes should be equal"));
3828cfe2a7aSLuiz Otavio O Souza 
3838cfe2a7aSLuiz Otavio O Souza 	/* Get the proper data for this child. */
3848cfe2a7aSLuiz Otavio O Souza 	spibus_get_cs(child, &cs);
3858cfe2a7aSLuiz Otavio O Souza 	cs &= ~SPIBUS_CS_HIGH;
3868cfe2a7aSLuiz Otavio O Souza 	if (cs > 3) {
3878cfe2a7aSLuiz Otavio O Souza 		device_printf(dev,
3888cfe2a7aSLuiz Otavio O Souza 		    "Invalid CS %d requested by %s\n", cs,
3898cfe2a7aSLuiz Otavio O Souza 		    device_get_nameunit(child));
3908cfe2a7aSLuiz Otavio O Souza 		return (EINVAL);
3918cfe2a7aSLuiz Otavio O Souza 	}
3928cfe2a7aSLuiz Otavio O Souza 	spibus_get_clock(child, &clock);
3938cfe2a7aSLuiz Otavio O Souza 	if (clock == 0) {
3948cfe2a7aSLuiz Otavio O Souza 		device_printf(dev,
3958cfe2a7aSLuiz Otavio O Souza 		    "Invalid clock %uHz requested by %s\n", clock,
3968cfe2a7aSLuiz Otavio O Souza 		    device_get_nameunit(child));
3978cfe2a7aSLuiz Otavio O Souza 		return (EINVAL);
3988cfe2a7aSLuiz Otavio O Souza 	}
3998cfe2a7aSLuiz Otavio O Souza 	spibus_get_mode(child, &mode);
4008cfe2a7aSLuiz Otavio O Souza 	if (mode > 3) {
4018cfe2a7aSLuiz Otavio O Souza 		device_printf(dev,
4028cfe2a7aSLuiz Otavio O Souza 		    "Invalid mode %u requested by %s\n", mode,
4038cfe2a7aSLuiz Otavio O Souza 		    device_get_nameunit(child));
4048cfe2a7aSLuiz Otavio O Souza 		return (EINVAL);
4058cfe2a7aSLuiz Otavio O Souza 	}
4068cfe2a7aSLuiz Otavio O Souza 
4078cfe2a7aSLuiz Otavio O Souza 	sc = device_get_softc(dev);
4088cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_LOCK(sc);
4098cfe2a7aSLuiz Otavio O Souza 
4108cfe2a7aSLuiz Otavio O Souza 	/* Wait until the controller is free. */
4118cfe2a7aSLuiz Otavio O Souza 	while (sc->sc_flags & A37X0_SPI_BUSY)
4128cfe2a7aSLuiz Otavio O Souza 		mtx_sleep(dev, &sc->sc_mtx, 0, "a37x0_spi", 0);
4138cfe2a7aSLuiz Otavio O Souza 
4148cfe2a7aSLuiz Otavio O Souza 	/* Now we have control over SPI controller. */
4158cfe2a7aSLuiz Otavio O Souza 	sc->sc_flags = A37X0_SPI_BUSY;
4168cfe2a7aSLuiz Otavio O Souza 
4178cfe2a7aSLuiz Otavio O Souza 	/* Set transfer mode and clock. */
4188cfe2a7aSLuiz Otavio O Souza 	a37x0_spi_set_mode(sc, mode);
4198cfe2a7aSLuiz Otavio O Souza 	a37x0_spi_set_pins(sc, 1);
4208cfe2a7aSLuiz Otavio O Souza 	a37x0_spi_set_clock(sc, clock);
4218cfe2a7aSLuiz Otavio O Souza 
4228cfe2a7aSLuiz Otavio O Souza 	/* Set CS. */
4238cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONTROL, 1 << (A37X0_SPI_CS_SHIFT + cs));
4248cfe2a7aSLuiz Otavio O Souza 
4258cfe2a7aSLuiz Otavio O Souza 	/* Save a pointer to the SPI command. */
4268cfe2a7aSLuiz Otavio O Souza 	sc->sc_cmd = cmd;
4278cfe2a7aSLuiz Otavio O Souza 	sc->sc_read = 0;
4288cfe2a7aSLuiz Otavio O Souza 	sc->sc_written = 0;
4298cfe2a7aSLuiz Otavio O Souza 	sc->sc_len = cmd->tx_cmd_sz + cmd->tx_data_sz;
4308cfe2a7aSLuiz Otavio O Souza 
4318cfe2a7aSLuiz Otavio O Souza 	/* Clear interrupts. */
4328cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_INTR_STAT);
4338cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_STAT, reg);
4348cfe2a7aSLuiz Otavio O Souza 
4358cfe2a7aSLuiz Otavio O Souza 	while ((sc->sc_len - sc->sc_written) > 0) {
4368cfe2a7aSLuiz Otavio O Souza 		/*
4378cfe2a7aSLuiz Otavio O Souza 		 * Write to start the transmission and read the byte
4388cfe2a7aSLuiz Otavio O Souza 		 * back when ready.
4398cfe2a7aSLuiz Otavio O Souza 		 */
4408cfe2a7aSLuiz Otavio O Souza 		a37x0_spi_tx_byte(sc);
4418cfe2a7aSLuiz Otavio O Souza 		timeout = 1000;
4428cfe2a7aSLuiz Otavio O Souza 		while (--timeout > 0) {
4438cfe2a7aSLuiz Otavio O Souza 			reg = A37X0_SPI_READ(sc, A37X0_SPI_CONTROL);
4448cfe2a7aSLuiz Otavio O Souza 			if (reg & A37X0_SPI_XFER_DONE)
4458cfe2a7aSLuiz Otavio O Souza 				break;
4468cfe2a7aSLuiz Otavio O Souza 			DELAY(1);
4478cfe2a7aSLuiz Otavio O Souza 		}
4488cfe2a7aSLuiz Otavio O Souza 		if (timeout == 0)
4498cfe2a7aSLuiz Otavio O Souza 			break;
4508cfe2a7aSLuiz Otavio O Souza 		a37x0_spi_rx_byte(sc);
4518cfe2a7aSLuiz Otavio O Souza 	}
4528cfe2a7aSLuiz Otavio O Souza 
4538cfe2a7aSLuiz Otavio O Souza 	/* Stop the controller. */
4548cfe2a7aSLuiz Otavio O Souza 	reg = A37X0_SPI_READ(sc, A37X0_SPI_CONTROL);
4558cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_CONTROL, reg & ~A37X0_SPI_CS_MASK);
4568cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_MASK, 0);
4578cfe2a7aSLuiz Otavio O Souza 
4588cfe2a7aSLuiz Otavio O Souza 	/* Release the controller and wakeup the next thread waiting for it. */
4598cfe2a7aSLuiz Otavio O Souza 	sc->sc_flags = 0;
4608cfe2a7aSLuiz Otavio O Souza 	wakeup_one(dev);
4618cfe2a7aSLuiz Otavio O Souza 	A37X0_SPI_UNLOCK(sc);
4628cfe2a7aSLuiz Otavio O Souza 
4638cfe2a7aSLuiz Otavio O Souza 	return ((timeout == 0) ? EIO : 0);
4648cfe2a7aSLuiz Otavio O Souza }
4658cfe2a7aSLuiz Otavio O Souza 
4668cfe2a7aSLuiz Otavio O Souza static phandle_t
4678cfe2a7aSLuiz Otavio O Souza a37x0_spi_get_node(device_t bus, device_t dev)
4688cfe2a7aSLuiz Otavio O Souza {
4698cfe2a7aSLuiz Otavio O Souza 
4708cfe2a7aSLuiz Otavio O Souza 	return (ofw_bus_get_node(bus));
4718cfe2a7aSLuiz Otavio O Souza }
4728cfe2a7aSLuiz Otavio O Souza 
4738cfe2a7aSLuiz Otavio O Souza static device_method_t a37x0_spi_methods[] = {
4748cfe2a7aSLuiz Otavio O Souza 	/* Device interface */
4758cfe2a7aSLuiz Otavio O Souza 	DEVMETHOD(device_probe,		a37x0_spi_probe),
4768cfe2a7aSLuiz Otavio O Souza 	DEVMETHOD(device_attach,	a37x0_spi_attach),
4778cfe2a7aSLuiz Otavio O Souza 	DEVMETHOD(device_detach,	a37x0_spi_detach),
4788cfe2a7aSLuiz Otavio O Souza 
4798cfe2a7aSLuiz Otavio O Souza 	/* SPI interface */
4808cfe2a7aSLuiz Otavio O Souza 	DEVMETHOD(spibus_transfer,	a37x0_spi_transfer),
4818cfe2a7aSLuiz Otavio O Souza 
4828cfe2a7aSLuiz Otavio O Souza 	/* ofw_bus interface */
4838cfe2a7aSLuiz Otavio O Souza 	DEVMETHOD(ofw_bus_get_node,	a37x0_spi_get_node),
4848cfe2a7aSLuiz Otavio O Souza 
4858cfe2a7aSLuiz Otavio O Souza 	DEVMETHOD_END
4868cfe2a7aSLuiz Otavio O Souza };
4878cfe2a7aSLuiz Otavio O Souza 
4888cfe2a7aSLuiz Otavio O Souza static devclass_t a37x0_spi_devclass;
4898cfe2a7aSLuiz Otavio O Souza 
4908cfe2a7aSLuiz Otavio O Souza static driver_t a37x0_spi_driver = {
4918cfe2a7aSLuiz Otavio O Souza 	"spi",
4928cfe2a7aSLuiz Otavio O Souza 	a37x0_spi_methods,
4938cfe2a7aSLuiz Otavio O Souza 	sizeof(struct a37x0_spi_softc),
4948cfe2a7aSLuiz Otavio O Souza };
4958cfe2a7aSLuiz Otavio O Souza 
4968cfe2a7aSLuiz Otavio O Souza DRIVER_MODULE(a37x0_spi, simplebus, a37x0_spi_driver, a37x0_spi_devclass, 0, 0);
497