/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 Oleksandr Tymoshenko * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "spibus_if.h" #define RK_SPI_CTRLR0 0x0000 #define CTRLR0_OPM_MASTER (0 << 20) #define CTRLR0_XFM_TR (0 << 18) #define CTRLR0_FRF_MOTO (0 << 16) #define CTRLR0_BHT_8BIT (1 << 13) #define CTRLR0_EM_BIG (1 << 11) #define CTRLR0_SSD_ONE (1 << 10) #define CTRLR0_SCPOL (1 << 7) #define CTRLR0_SCPH (1 << 6) #define CTRLR0_DFS_8BIT (1 << 0) #define RK_SPI_CTRLR1 0x0004 #define RK_SPI_ENR 0x0008 #define RK_SPI_SER 0x000c #define RK_SPI_BAUDR 0x0010 #define RK_SPI_TXFTLR 0x0014 #define RK_SPI_RXFTLR 0x0018 #define RK_SPI_TXFLR 0x001c #define RK_SPI_RXFLR 0x0020 #define RK_SPI_SR 0x0024 #define SR_BUSY (1 << 0) #define RK_SPI_IPR 0x0028 #define RK_SPI_IMR 0x002c #define IMR_RFFIM (1 << 4) #define IMR_TFEIM (1 << 0) #define RK_SPI_ISR 0x0030 #define ISR_RFFIS (1 << 4) #define ISR_TFEIS (1 << 0) #define RK_SPI_RISR 0x0034 #define RK_SPI_ICR 0x0038 #define RK_SPI_DMACR 0x003c #define RK_SPI_DMATDLR 0x0040 #define RK_SPI_DMARDLR 0x0044 #define RK_SPI_TXDR 0x0400 #define RK_SPI_RXDR 0x0800 #define CS_MAX 1 static struct ofw_compat_data compat_data[] = { { "rockchip,rk3328-spi", 1 }, { "rockchip,rk3399-spi", 1 }, { "rockchip,rk3568-spi", 1 }, { NULL, 0 } }; static struct resource_spec rk_spi_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0 } }; struct rk_spi_softc { device_t dev; device_t spibus; struct resource *res[2]; struct mtx mtx; clk_t clk_apb; clk_t clk_spi; void * intrhand; int transfer; uint32_t fifo_size; uint64_t max_freq; uint32_t intreg; uint8_t *rxbuf; uint32_t rxidx; uint8_t *txbuf; uint32_t txidx; uint32_t txlen; uint32_t rxlen; }; #define RK_SPI_LOCK(sc) mtx_lock(&(sc)->mtx) #define RK_SPI_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define RK_SPI_READ_4(sc, reg) bus_read_4((sc)->res[0], (reg)) #define RK_SPI_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) static int rk_spi_probe(device_t dev); static int rk_spi_attach(device_t dev); static int rk_spi_detach(device_t dev); static void rk_spi_intr(void *arg); static void rk_spi_enable_chip(struct rk_spi_softc *sc, int enable) { RK_SPI_WRITE_4(sc, RK_SPI_ENR, enable ? 1 : 0); } static int rk_spi_set_cs(struct rk_spi_softc *sc, uint32_t cs, bool active) { uint32_t reg; if (cs & SPIBUS_CS_HIGH) { device_printf(sc->dev, "SPIBUS_CS_HIGH is not supported\n"); return (EINVAL); } if (cs > CS_MAX) return (EINVAL); reg = RK_SPI_READ_4(sc, RK_SPI_SER); if (active) reg |= (1 << cs); else reg &= ~(1 << cs); RK_SPI_WRITE_4(sc, RK_SPI_SER, reg); return (0); } static void rk_spi_hw_setup(struct rk_spi_softc *sc, uint32_t mode, uint32_t freq) { uint32_t cr0; uint32_t div; cr0 = CTRLR0_OPM_MASTER | CTRLR0_XFM_TR | CTRLR0_FRF_MOTO | CTRLR0_BHT_8BIT | CTRLR0_EM_BIG | CTRLR0_SSD_ONE | CTRLR0_DFS_8BIT; if (mode & SPIBUS_MODE_CPHA) cr0 |= CTRLR0_SCPH; if (mode & SPIBUS_MODE_CPOL) cr0 |= CTRLR0_SCPOL; /* minimum divider is 2 */ if (sc->max_freq < freq*2) { clk_set_freq(sc->clk_spi, 2 * freq, CLK_SET_ROUND_DOWN); clk_get_freq(sc->clk_spi, &sc->max_freq); } div = ((sc->max_freq + freq - 1) / freq); div = (div + 1) & 0xfffe; RK_SPI_WRITE_4(sc, RK_SPI_BAUDR, div); RK_SPI_WRITE_4(sc, RK_SPI_CTRLR0, cr0); } static uint32_t rk_spi_fifo_size(struct rk_spi_softc *sc) { uint32_t txftlr, reg; for (txftlr = 2; txftlr < 32; txftlr++) { RK_SPI_WRITE_4(sc, RK_SPI_TXFTLR, txftlr); reg = RK_SPI_READ_4(sc, RK_SPI_TXFTLR); if (reg != txftlr) break; } RK_SPI_WRITE_4(sc, RK_SPI_TXFTLR, 0); if (txftlr == 31) return 0; return txftlr; } static void rk_spi_empty_rxfifo(struct rk_spi_softc *sc) { uint32_t rxlevel; rxlevel = RK_SPI_READ_4(sc, RK_SPI_RXFLR); while (sc->rxidx < sc->rxlen && (rxlevel-- > 0)) { sc->rxbuf[sc->rxidx++] = (uint8_t)RK_SPI_READ_4(sc, RK_SPI_RXDR); } } static void rk_spi_fill_txfifo(struct rk_spi_softc *sc) { uint32_t txlevel; txlevel = RK_SPI_READ_4(sc, RK_SPI_TXFLR); while (sc->txidx < sc->txlen && txlevel < sc->fifo_size) { RK_SPI_WRITE_4(sc, RK_SPI_TXDR, sc->txbuf[sc->txidx++]); txlevel++; } if (sc->txidx != sc->txlen) sc->intreg |= (IMR_TFEIM | IMR_RFFIM); } static int rk_spi_xfer_buf(struct rk_spi_softc *sc, void *rxbuf, void *txbuf, uint32_t len) { int err; if (len == 0) return (0); sc->rxbuf = rxbuf; sc->rxlen = len; sc->rxidx = 0; sc->txbuf = txbuf; sc->txlen = len; sc->txidx = 0; sc->intreg = 0; rk_spi_fill_txfifo(sc); RK_SPI_WRITE_4(sc, RK_SPI_IMR, sc->intreg); err = 0; while (err == 0 && sc->intreg != 0) err = msleep(sc, &sc->mtx, 0, "rk_spi", 10 * hz); while (err == 0 && sc->rxidx != sc->txidx) { /* read residual data from RX fifo */ rk_spi_empty_rxfifo(sc); } if (sc->rxidx != sc->rxlen || sc->txidx != sc->txlen) err = EIO; return (err); } static int rk_spi_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); device_set_desc(dev, "Rockchip SPI"); return (BUS_PROBE_DEFAULT); } static int rk_spi_attach(device_t dev) { struct rk_spi_softc *sc; int error; sc = device_get_softc(dev); sc->dev = dev; mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); if (bus_alloc_resources(dev, rk_spi_spec, sc->res) != 0) { device_printf(dev, "cannot allocate resources for device\n"); error = ENXIO; goto fail; } if (bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE, NULL, rk_spi_intr, sc, &sc->intrhand)) { bus_release_resources(dev, rk_spi_spec, sc->res); device_printf(dev, "cannot setup interrupt handler\n"); return (ENXIO); } /* Activate the module clock. */ error = clk_get_by_ofw_name(dev, 0, "apb_pclk", &sc->clk_apb); if (error != 0) { device_printf(dev, "cannot get apb_pclk clock\n"); goto fail; } error = clk_get_by_ofw_name(dev, 0, "spiclk", &sc->clk_spi); if (error != 0) { device_printf(dev, "cannot get spiclk clock\n"); goto fail; } error = clk_enable(sc->clk_apb); if (error != 0) { device_printf(dev, "cannot enable ahb clock\n"); goto fail; } error = clk_enable(sc->clk_spi); if (error != 0) { device_printf(dev, "cannot enable spiclk clock\n"); goto fail; } clk_get_freq(sc->clk_spi, &sc->max_freq); sc->fifo_size = rk_spi_fifo_size(sc); if (sc->fifo_size == 0) { device_printf(dev, "failed to get fifo size\n"); goto fail; } sc->spibus = device_add_child(dev, "spibus", DEVICE_UNIT_ANY); RK_SPI_WRITE_4(sc, RK_SPI_IMR, 0); RK_SPI_WRITE_4(sc, RK_SPI_TXFTLR, sc->fifo_size/2 - 1); RK_SPI_WRITE_4(sc, RK_SPI_RXFTLR, sc->fifo_size/2 - 1); bus_attach_children(dev); return (0); fail: rk_spi_detach(dev); return (error); } static int rk_spi_detach(device_t dev) { struct rk_spi_softc *sc; sc = device_get_softc(dev); bus_generic_detach(sc->dev); if (sc->spibus != NULL) device_delete_child(dev, sc->spibus); if (sc->clk_spi != NULL) clk_release(sc->clk_spi); if (sc->clk_apb) clk_release(sc->clk_apb); if (sc->intrhand != NULL) bus_teardown_intr(sc->dev, sc->res[1], sc->intrhand); bus_release_resources(dev, rk_spi_spec, sc->res); mtx_destroy(&sc->mtx); return (0); } static void rk_spi_intr(void *arg) { struct rk_spi_softc *sc; uint32_t intreg, isr; sc = arg; RK_SPI_LOCK(sc); intreg = RK_SPI_READ_4(sc, RK_SPI_IMR); isr = RK_SPI_READ_4(sc, RK_SPI_ISR); RK_SPI_WRITE_4(sc, RK_SPI_ICR, isr); if (isr & ISR_RFFIS) rk_spi_empty_rxfifo(sc); if (isr & ISR_TFEIS) rk_spi_fill_txfifo(sc); /* no bytes left, disable interrupt */ if (sc->txidx == sc->txlen) { sc->intreg = 0; wakeup(sc); } if (sc->intreg != intreg) { (void)RK_SPI_WRITE_4(sc, RK_SPI_IMR, sc->intreg); (void)RK_SPI_READ_4(sc, RK_SPI_IMR); } RK_SPI_UNLOCK(sc); } static phandle_t rk_spi_get_node(device_t bus, device_t dev) { return ofw_bus_get_node(bus); } static int rk_spi_transfer(device_t dev, device_t child, struct spi_command *cmd) { struct rk_spi_softc *sc; uint32_t cs, mode, clock; int err = 0; sc = device_get_softc(dev); spibus_get_cs(child, &cs); spibus_get_clock(child, &clock); spibus_get_mode(child, &mode); RK_SPI_LOCK(sc); rk_spi_hw_setup(sc, mode, clock); rk_spi_enable_chip(sc, 1); err = rk_spi_set_cs(sc, cs, true); if (err != 0) { rk_spi_enable_chip(sc, 0); RK_SPI_UNLOCK(sc); return (err); } /* Transfer command then data bytes. */ err = 0; if (cmd->tx_cmd_sz > 0) err = rk_spi_xfer_buf(sc, cmd->rx_cmd, cmd->tx_cmd, cmd->tx_cmd_sz); if (cmd->tx_data_sz > 0 && err == 0) err = rk_spi_xfer_buf(sc, cmd->rx_data, cmd->tx_data, cmd->tx_data_sz); rk_spi_set_cs(sc, cs, false); rk_spi_enable_chip(sc, 0); RK_SPI_UNLOCK(sc); return (err); } static device_method_t rk_spi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, rk_spi_probe), DEVMETHOD(device_attach, rk_spi_attach), DEVMETHOD(device_detach, rk_spi_detach), /* spibus_if */ DEVMETHOD(spibus_transfer, rk_spi_transfer), /* ofw_bus_if */ DEVMETHOD(ofw_bus_get_node, rk_spi_get_node), DEVMETHOD_END }; static driver_t rk_spi_driver = { "spi", rk_spi_methods, sizeof(struct rk_spi_softc), }; DRIVER_MODULE(rk_spi, simplebus, rk_spi_driver, 0, 0); DRIVER_MODULE(ofw_spibus, rk_spi, ofw_spibus_driver, 0, 0); MODULE_DEPEND(rk_spi, ofw_spibus, 1, 1, 1); OFWBUS_PNP_INFO(compat_data);