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