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