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