1d27ba308SAdrian Chadd /*- 24d846d26SWarner Losh * SPDX-License-Identifier: BSD-2-Clause 3d27ba308SAdrian Chadd * 4d27ba308SAdrian Chadd * Copyright (c) 2021, Adrian Chadd <adrian@FreeBSD.org> 5d27ba308SAdrian Chadd * 6d27ba308SAdrian Chadd * Redistribution and use in source and binary forms, with or without 7d27ba308SAdrian Chadd * modification, are permitted provided that the following conditions 8d27ba308SAdrian Chadd * are met: 9d27ba308SAdrian Chadd * 1. Redistributions of source code must retain the above copyright 10d27ba308SAdrian Chadd * notice unmodified, this list of conditions, and the following 11d27ba308SAdrian Chadd * disclaimer. 12d27ba308SAdrian Chadd * 2. Redistributions in binary form must reproduce the above copyright 13d27ba308SAdrian Chadd * notice, this list of conditions and the following disclaimer in the 14d27ba308SAdrian Chadd * documentation and/or other materials provided with the distribution. 15d27ba308SAdrian Chadd * 16d27ba308SAdrian Chadd * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17d27ba308SAdrian Chadd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18d27ba308SAdrian Chadd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19d27ba308SAdrian Chadd * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20d27ba308SAdrian Chadd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21d27ba308SAdrian Chadd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22d27ba308SAdrian Chadd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23d27ba308SAdrian Chadd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24d27ba308SAdrian Chadd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25d27ba308SAdrian Chadd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26d27ba308SAdrian Chadd * SUCH DAMAGE. 27d27ba308SAdrian Chadd */ 28d27ba308SAdrian Chadd 29d27ba308SAdrian Chadd #include <sys/param.h> 30d27ba308SAdrian Chadd #include <sys/systm.h> 31d27ba308SAdrian Chadd 32d27ba308SAdrian Chadd #include <sys/bus.h> 33d27ba308SAdrian Chadd #include <sys/interrupt.h> 34d27ba308SAdrian Chadd #include <sys/malloc.h> 35d27ba308SAdrian Chadd #include <sys/lock.h> 36d27ba308SAdrian Chadd #include <sys/mutex.h> 37d27ba308SAdrian Chadd #include <sys/kernel.h> 38d27ba308SAdrian Chadd #include <sys/module.h> 39d27ba308SAdrian Chadd #include <sys/rman.h> 40d27ba308SAdrian Chadd #include <sys/gpio.h> 41d27ba308SAdrian Chadd 42d27ba308SAdrian Chadd #include <vm/vm.h> 43d27ba308SAdrian Chadd #include <vm/pmap.h> 44d27ba308SAdrian Chadd #include <vm/vm_extern.h> 45d27ba308SAdrian Chadd 46d27ba308SAdrian Chadd #include <machine/bus.h> 47d27ba308SAdrian Chadd #include <machine/cpu.h> 48d27ba308SAdrian Chadd 49d27ba308SAdrian Chadd #include <dev/fdt/fdt_common.h> 50d27ba308SAdrian Chadd #include <dev/fdt/fdt_pinctrl.h> 51d27ba308SAdrian Chadd 52d27ba308SAdrian Chadd #include <dev/gpio/gpiobusvar.h> 53d27ba308SAdrian Chadd #include <dev/ofw/ofw_bus.h> 54d27ba308SAdrian Chadd #include <dev/ofw/ofw_bus_subr.h> 55d27ba308SAdrian Chadd 56be82b3a0SEmmanuel Vadot #include <dev/clk/clk.h> 571f469a9fSEmmanuel Vadot #include <dev/hwreset/hwreset.h> 58d27ba308SAdrian Chadd 59d27ba308SAdrian Chadd #include <dev/spibus/spi.h> 60d27ba308SAdrian Chadd #include <dev/spibus/spibusvar.h> 61d27ba308SAdrian Chadd #include "spibus_if.h" 62d27ba308SAdrian Chadd 63d27ba308SAdrian Chadd #include <dev/qcom_qup/qcom_spi_var.h> 64d27ba308SAdrian Chadd #include <dev/qcom_qup/qcom_qup_reg.h> 65d27ba308SAdrian Chadd #include <dev/qcom_qup/qcom_spi_reg.h> 66d27ba308SAdrian Chadd #include <dev/qcom_qup/qcom_spi_debug.h> 67d27ba308SAdrian Chadd 68d27ba308SAdrian Chadd static struct ofw_compat_data compat_data[] = { 69d27ba308SAdrian Chadd { "qcom,spi-qup-v1.1.1", QCOM_SPI_HW_QPI_V1_1 }, 70d27ba308SAdrian Chadd { "qcom,spi-qup-v2.1.1", QCOM_SPI_HW_QPI_V2_1 }, 71d27ba308SAdrian Chadd { "qcom,spi-qup-v2.2.1", QCOM_SPI_HW_QPI_V2_2 }, 72d27ba308SAdrian Chadd { NULL, 0 } 73d27ba308SAdrian Chadd }; 74d27ba308SAdrian Chadd 75d27ba308SAdrian Chadd /* 76d27ba308SAdrian Chadd * Flip the CS GPIO line either active or inactive. 77d27ba308SAdrian Chadd * 78d27ba308SAdrian Chadd * Actually listen to the CS polarity. 79d27ba308SAdrian Chadd */ 80d27ba308SAdrian Chadd static void 81d27ba308SAdrian Chadd qcom_spi_set_chipsel(struct qcom_spi_softc *sc, int cs, bool active) 82d27ba308SAdrian Chadd { 83d27ba308SAdrian Chadd bool pinactive; 84d27ba308SAdrian Chadd bool invert = !! (cs & SPIBUS_CS_HIGH); 85d27ba308SAdrian Chadd 86d27ba308SAdrian Chadd cs = cs & ~SPIBUS_CS_HIGH; 87d27ba308SAdrian Chadd 88d27ba308SAdrian Chadd if (sc->cs_pins[cs] == NULL) { 89d27ba308SAdrian Chadd device_printf(sc->sc_dev, 90d27ba308SAdrian Chadd "%s: cs=%u, active=%u, invert=%u, no gpio?\n", 91d27ba308SAdrian Chadd __func__, cs, active, invert); 92d27ba308SAdrian Chadd return; 93d27ba308SAdrian Chadd } 94d27ba308SAdrian Chadd 95d27ba308SAdrian Chadd QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_CHIPSELECT, 96d27ba308SAdrian Chadd "%s: cs=%u active=%u\n", __func__, cs, active); 97d27ba308SAdrian Chadd 98d27ba308SAdrian Chadd /* 99d27ba308SAdrian Chadd * Default rule here is CS is active low. 100d27ba308SAdrian Chadd */ 101d27ba308SAdrian Chadd if (active) 102d27ba308SAdrian Chadd pinactive = false; 103d27ba308SAdrian Chadd else 104d27ba308SAdrian Chadd pinactive = true; 105d27ba308SAdrian Chadd 106d27ba308SAdrian Chadd /* 107d27ba308SAdrian Chadd * Invert the CS line if required. 108d27ba308SAdrian Chadd */ 109d27ba308SAdrian Chadd if (invert) 110d27ba308SAdrian Chadd pinactive = !! pinactive; 111d27ba308SAdrian Chadd 112d27ba308SAdrian Chadd gpio_pin_set_active(sc->cs_pins[cs], pinactive); 113d27ba308SAdrian Chadd gpio_pin_is_active(sc->cs_pins[cs], &pinactive); 114d27ba308SAdrian Chadd } 115d27ba308SAdrian Chadd 116d27ba308SAdrian Chadd static void 117d27ba308SAdrian Chadd qcom_spi_intr(void *arg) 118d27ba308SAdrian Chadd { 119d27ba308SAdrian Chadd struct qcom_spi_softc *sc = arg; 120d27ba308SAdrian Chadd int ret; 121d27ba308SAdrian Chadd 122d27ba308SAdrian Chadd QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_INTR, "%s: called\n", __func__); 123d27ba308SAdrian Chadd 124d27ba308SAdrian Chadd 125d27ba308SAdrian Chadd QCOM_SPI_LOCK(sc); 126d27ba308SAdrian Chadd ret = qcom_spi_hw_interrupt_handle(sc); 127d27ba308SAdrian Chadd if (ret != 0) { 128d27ba308SAdrian Chadd device_printf(sc->sc_dev, 129d27ba308SAdrian Chadd "ERROR: failed to read intr status\n"); 130d27ba308SAdrian Chadd goto done; 131d27ba308SAdrian Chadd } 132d27ba308SAdrian Chadd 133d27ba308SAdrian Chadd /* 134d27ba308SAdrian Chadd * Handle spurious interrupts outside of an actual 135d27ba308SAdrian Chadd * transfer. 136d27ba308SAdrian Chadd */ 137d27ba308SAdrian Chadd if (sc->transfer.active == false) { 138d27ba308SAdrian Chadd device_printf(sc->sc_dev, 139d27ba308SAdrian Chadd "ERROR: spurious interrupt\n"); 140d27ba308SAdrian Chadd qcom_spi_hw_ack_opmode(sc); 141d27ba308SAdrian Chadd goto done; 142d27ba308SAdrian Chadd } 143d27ba308SAdrian Chadd 144d27ba308SAdrian Chadd /* Now, handle interrupts */ 145d27ba308SAdrian Chadd if (sc->intr.error) { 146d27ba308SAdrian Chadd sc->intr.error = false; 147d27ba308SAdrian Chadd device_printf(sc->sc_dev, "ERROR: intr\n"); 148d27ba308SAdrian Chadd } 149d27ba308SAdrian Chadd 150d27ba308SAdrian Chadd if (sc->intr.do_rx) { 151d27ba308SAdrian Chadd sc->intr.do_rx = false; 152d27ba308SAdrian Chadd QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_INTR, 153d27ba308SAdrian Chadd "%s: PIO_READ\n", __func__); 154d27ba308SAdrian Chadd if (sc->state.transfer_mode == QUP_IO_M_MODE_FIFO) 155d27ba308SAdrian Chadd ret = qcom_spi_hw_read_pio_fifo(sc); 156d27ba308SAdrian Chadd else 157d27ba308SAdrian Chadd ret = qcom_spi_hw_read_pio_block(sc); 158d27ba308SAdrian Chadd if (ret != 0) { 159d27ba308SAdrian Chadd device_printf(sc->sc_dev, 160d27ba308SAdrian Chadd "ERROR: qcom_spi_hw_read failed (%u)\n", ret); 161d27ba308SAdrian Chadd goto done; 162d27ba308SAdrian Chadd } 163d27ba308SAdrian Chadd } 164d27ba308SAdrian Chadd if (sc->intr.do_tx) { 165d27ba308SAdrian Chadd sc->intr.do_tx = false; 166d27ba308SAdrian Chadd QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_INTR, 167d27ba308SAdrian Chadd "%s: PIO_WRITE\n", __func__); 168d27ba308SAdrian Chadd /* 169d27ba308SAdrian Chadd * For FIFO operations we do not do a write here, we did 170d27ba308SAdrian Chadd * it at the beginning of the transfer. 171d27ba308SAdrian Chadd * 172d27ba308SAdrian Chadd * For BLOCK operations yes, we call the routine. 173d27ba308SAdrian Chadd */ 174d27ba308SAdrian Chadd 175d27ba308SAdrian Chadd if (sc->state.transfer_mode == QUP_IO_M_MODE_FIFO) 176d27ba308SAdrian Chadd ret = qcom_spi_hw_ack_write_pio_fifo(sc); 177d27ba308SAdrian Chadd else 178d27ba308SAdrian Chadd ret = qcom_spi_hw_write_pio_block(sc); 179d27ba308SAdrian Chadd if (ret != 0) { 180d27ba308SAdrian Chadd device_printf(sc->sc_dev, 181d27ba308SAdrian Chadd "ERROR: qcom_spi_hw_write failed (%u)\n", ret); 182d27ba308SAdrian Chadd goto done; 183d27ba308SAdrian Chadd } 184d27ba308SAdrian Chadd } 185d27ba308SAdrian Chadd 186d27ba308SAdrian Chadd /* 187d27ba308SAdrian Chadd * Do this last. We may actually have completed the 188d27ba308SAdrian Chadd * transfer in the PIO receive path above and it will 189d27ba308SAdrian Chadd * set the done flag here. 190d27ba308SAdrian Chadd */ 191d27ba308SAdrian Chadd if (sc->intr.done) { 192d27ba308SAdrian Chadd sc->intr.done = false; 193d27ba308SAdrian Chadd sc->transfer.done = true; 194d27ba308SAdrian Chadd QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_INTR, 195d27ba308SAdrian Chadd "%s: transfer done\n", __func__); 196d27ba308SAdrian Chadd wakeup(sc); 197d27ba308SAdrian Chadd } 198d27ba308SAdrian Chadd 199d27ba308SAdrian Chadd done: 200d27ba308SAdrian Chadd QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_INTR, 201d27ba308SAdrian Chadd "%s: done\n", __func__); 202d27ba308SAdrian Chadd QCOM_SPI_UNLOCK(sc); 203d27ba308SAdrian Chadd } 204d27ba308SAdrian Chadd 205d27ba308SAdrian Chadd static int 206d27ba308SAdrian Chadd qcom_spi_probe(device_t dev) 207d27ba308SAdrian Chadd { 208d27ba308SAdrian Chadd 209d27ba308SAdrian Chadd if (!ofw_bus_status_okay(dev)) 210d27ba308SAdrian Chadd return (ENXIO); 211d27ba308SAdrian Chadd 212d27ba308SAdrian Chadd if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) 213d27ba308SAdrian Chadd return (ENXIO); 214d27ba308SAdrian Chadd 215d27ba308SAdrian Chadd device_set_desc(dev, "Qualcomm SPI Interface"); 216d27ba308SAdrian Chadd return (BUS_PROBE_DEFAULT); 217d27ba308SAdrian Chadd } 218d27ba308SAdrian Chadd 219d27ba308SAdrian Chadd /* 220d27ba308SAdrian Chadd * Allocate GPIOs if provided in the SPI controller block. 221d27ba308SAdrian Chadd * 222d27ba308SAdrian Chadd * Some devices will use GPIO lines for chip select. 223d27ba308SAdrian Chadd * It's also quite annoying because some devices will want to use 224d27ba308SAdrian Chadd * the hardware provided CS gating for say, the first chipselect block, 225d27ba308SAdrian Chadd * and then use GPIOs for the later ones. 226d27ba308SAdrian Chadd * 227d27ba308SAdrian Chadd * So here we just assume for now that SPI index 0 uses the hardware 228d27ba308SAdrian Chadd * lines, and >0 use GPIO lines. Revisit this if better hardware 229d27ba308SAdrian Chadd * shows up. 230d27ba308SAdrian Chadd * 231d27ba308SAdrian Chadd * And finally, iterating over the cs-gpios list to allocate GPIOs 232d27ba308SAdrian Chadd * doesn't actually tell us what the polarity is. For that we need 233d27ba308SAdrian Chadd * to actually iterate over the list of child nodes and check what 234d27ba308SAdrian Chadd * their properties are (and look for "spi-cs-high".) 235d27ba308SAdrian Chadd */ 236d27ba308SAdrian Chadd static void 237d27ba308SAdrian Chadd qcom_spi_attach_gpios(struct qcom_spi_softc *sc) 238d27ba308SAdrian Chadd { 239d27ba308SAdrian Chadd phandle_t node; 240d27ba308SAdrian Chadd int idx, err; 241d27ba308SAdrian Chadd 242d27ba308SAdrian Chadd /* Allocate gpio pins for configured chip selects. */ 243d27ba308SAdrian Chadd node = ofw_bus_get_node(sc->sc_dev); 244d27ba308SAdrian Chadd for (idx = 0; idx < nitems(sc->cs_pins); idx++) { 245d27ba308SAdrian Chadd err = gpio_pin_get_by_ofw_propidx(sc->sc_dev, node, 246d27ba308SAdrian Chadd "cs-gpios", idx, &sc->cs_pins[idx]); 247d27ba308SAdrian Chadd if (err == 0) { 248d27ba308SAdrian Chadd err = gpio_pin_setflags(sc->cs_pins[idx], 249d27ba308SAdrian Chadd GPIO_PIN_OUTPUT); 250d27ba308SAdrian Chadd if (err != 0) { 251d27ba308SAdrian Chadd device_printf(sc->sc_dev, 252d27ba308SAdrian Chadd "error configuring gpio for" 253d27ba308SAdrian Chadd " cs %u (%d)\n", idx, err); 254d27ba308SAdrian Chadd } 255d27ba308SAdrian Chadd /* 256d27ba308SAdrian Chadd * We can't set this HIGH right now because 257d27ba308SAdrian Chadd * we don't know if it needs to be set to 258d27ba308SAdrian Chadd * high for inactive or low for inactive 259d27ba308SAdrian Chadd * based on the child SPI device flags. 260d27ba308SAdrian Chadd */ 261d27ba308SAdrian Chadd #if 0 262d27ba308SAdrian Chadd gpio_pin_set_active(sc->cs_pins[idx], 1); 263d27ba308SAdrian Chadd gpio_pin_is_active(sc->cs_pins[idx], &tmp); 264d27ba308SAdrian Chadd #endif 265d27ba308SAdrian Chadd } else { 266d27ba308SAdrian Chadd device_printf(sc->sc_dev, 267d27ba308SAdrian Chadd "cannot configure gpio for chip select %u\n", idx); 268d27ba308SAdrian Chadd sc->cs_pins[idx] = NULL; 269d27ba308SAdrian Chadd } 270d27ba308SAdrian Chadd } 271d27ba308SAdrian Chadd } 272d27ba308SAdrian Chadd 273d27ba308SAdrian Chadd static void 274d27ba308SAdrian Chadd qcom_spi_sysctl_attach(struct qcom_spi_softc *sc) 275d27ba308SAdrian Chadd { 276d27ba308SAdrian Chadd struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev); 277d27ba308SAdrian Chadd struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev); 278d27ba308SAdrian Chadd 279d27ba308SAdrian Chadd SYSCTL_ADD_UINT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, 280d27ba308SAdrian Chadd "debug", CTLFLAG_RW, &sc->sc_debug, 0, 281d27ba308SAdrian Chadd "control debugging printfs"); 282d27ba308SAdrian Chadd } 283d27ba308SAdrian Chadd 284d27ba308SAdrian Chadd static int 285d27ba308SAdrian Chadd qcom_spi_attach(device_t dev) 286d27ba308SAdrian Chadd { 287d27ba308SAdrian Chadd struct qcom_spi_softc *sc = device_get_softc(dev); 288d27ba308SAdrian Chadd int rid, ret, i, val; 289d27ba308SAdrian Chadd 290d27ba308SAdrian Chadd sc->sc_dev = dev; 291d27ba308SAdrian Chadd 292d27ba308SAdrian Chadd /* 293d27ba308SAdrian Chadd * Hardware version is stored in the ofw_compat_data table. 294d27ba308SAdrian Chadd */ 295d27ba308SAdrian Chadd sc->hw_version = 296d27ba308SAdrian Chadd ofw_bus_search_compatible(dev, compat_data)->ocd_data; 297d27ba308SAdrian Chadd 298d27ba308SAdrian Chadd mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); 299d27ba308SAdrian Chadd 300d27ba308SAdrian Chadd rid = 0; 301d27ba308SAdrian Chadd sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, 302d27ba308SAdrian Chadd RF_ACTIVE); 303d27ba308SAdrian Chadd if (!sc->sc_mem_res) { 304d27ba308SAdrian Chadd device_printf(dev, "ERROR: Could not map memory\n"); 305d27ba308SAdrian Chadd ret = ENXIO; 306d27ba308SAdrian Chadd goto error; 307d27ba308SAdrian Chadd } 308d27ba308SAdrian Chadd 309d27ba308SAdrian Chadd rid = 0; 310d27ba308SAdrian Chadd sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, 311d27ba308SAdrian Chadd RF_ACTIVE | RF_SHAREABLE); 312d27ba308SAdrian Chadd if (!sc->sc_irq_res) { 313d27ba308SAdrian Chadd device_printf(dev, "ERROR: Could not map interrupt\n"); 314d27ba308SAdrian Chadd ret = ENXIO; 315d27ba308SAdrian Chadd goto error; 316d27ba308SAdrian Chadd } 317d27ba308SAdrian Chadd 318d27ba308SAdrian Chadd ret = bus_setup_intr(dev, sc->sc_irq_res, 319d27ba308SAdrian Chadd INTR_TYPE_MISC | INTR_MPSAFE, 320d27ba308SAdrian Chadd NULL, qcom_spi_intr, sc, &sc->sc_irq_h); 321d27ba308SAdrian Chadd if (ret != 0) { 322d27ba308SAdrian Chadd device_printf(dev, "ERROR: could not configure interrupt " 323d27ba308SAdrian Chadd "(%d)\n", 324d27ba308SAdrian Chadd ret); 325d27ba308SAdrian Chadd goto error; 326d27ba308SAdrian Chadd } 327d27ba308SAdrian Chadd 328d27ba308SAdrian Chadd qcom_spi_attach_gpios(sc); 329d27ba308SAdrian Chadd 330d27ba308SAdrian Chadd ret = clk_get_by_ofw_name(dev, 0, "core", &sc->clk_core); 331d27ba308SAdrian Chadd if (ret != 0) { 332d27ba308SAdrian Chadd device_printf(dev, "ERROR: could not get %s clock (%d)\n", 333d27ba308SAdrian Chadd "core", ret); 334d27ba308SAdrian Chadd goto error; 335d27ba308SAdrian Chadd } 336d27ba308SAdrian Chadd ret = clk_get_by_ofw_name(dev, 0, "iface", &sc->clk_iface); 337d27ba308SAdrian Chadd if (ret != 0) { 338d27ba308SAdrian Chadd device_printf(dev, "ERROR: could not get %s clock (%d)\n", 339d27ba308SAdrian Chadd "iface", ret); 340d27ba308SAdrian Chadd goto error; 341d27ba308SAdrian Chadd } 342d27ba308SAdrian Chadd 343d27ba308SAdrian Chadd /* Bring up initial clocks if they're off */ 344d27ba308SAdrian Chadd ret = clk_enable(sc->clk_core); 345d27ba308SAdrian Chadd if (ret != 0) { 346d27ba308SAdrian Chadd device_printf(dev, "ERROR: couldn't enable core clock (%u)\n", 347d27ba308SAdrian Chadd ret); 348d27ba308SAdrian Chadd goto error; 349d27ba308SAdrian Chadd } 350d27ba308SAdrian Chadd ret = clk_enable(sc->clk_iface); 351d27ba308SAdrian Chadd if (ret != 0) { 352d27ba308SAdrian Chadd device_printf(dev, "ERROR: couldn't enable iface clock (%u)\n", 353d27ba308SAdrian Chadd ret); 354d27ba308SAdrian Chadd goto error; 355d27ba308SAdrian Chadd } 356d27ba308SAdrian Chadd 357d27ba308SAdrian Chadd /* 358d27ba308SAdrian Chadd * Read optional spi-max-frequency 359d27ba308SAdrian Chadd */ 360d27ba308SAdrian Chadd if (OF_getencprop(ofw_bus_get_node(dev), "spi-max-frequency", 361d27ba308SAdrian Chadd &val, sizeof(val)) > 0) 362d27ba308SAdrian Chadd sc->config.max_frequency = val; 363d27ba308SAdrian Chadd else 364d27ba308SAdrian Chadd sc->config.max_frequency = SPI_MAX_RATE; 365d27ba308SAdrian Chadd 366d27ba308SAdrian Chadd /* 367d27ba308SAdrian Chadd * Read optional cs-select 368d27ba308SAdrian Chadd */ 369d27ba308SAdrian Chadd if (OF_getencprop(ofw_bus_get_node(dev), "cs-select", 370d27ba308SAdrian Chadd &val, sizeof(val)) > 0) 371d27ba308SAdrian Chadd sc->config.cs_select = val; 372d27ba308SAdrian Chadd else 373d27ba308SAdrian Chadd sc->config.cs_select = 0; 374d27ba308SAdrian Chadd 375d27ba308SAdrian Chadd /* 376d27ba308SAdrian Chadd * Read optional num-cs 377d27ba308SAdrian Chadd */ 378d27ba308SAdrian Chadd if (OF_getencprop(ofw_bus_get_node(dev), "num-cs", 379d27ba308SAdrian Chadd &val, sizeof(val)) > 0) 380d27ba308SAdrian Chadd sc->config.num_cs = val; 381d27ba308SAdrian Chadd else 382d27ba308SAdrian Chadd sc->config.num_cs = SPI_NUM_CHIPSELECTS; 383d27ba308SAdrian Chadd 384d27ba308SAdrian Chadd ret = fdt_pinctrl_configure_by_name(dev, "default"); 385d27ba308SAdrian Chadd if (ret != 0) { 386d27ba308SAdrian Chadd device_printf(dev, 387d27ba308SAdrian Chadd "ERROR: could not configure default pinmux\n"); 388d27ba308SAdrian Chadd goto error; 389d27ba308SAdrian Chadd } 390d27ba308SAdrian Chadd 391d27ba308SAdrian Chadd ret = qcom_spi_hw_read_controller_transfer_sizes(sc); 392d27ba308SAdrian Chadd if (ret != 0) { 393d27ba308SAdrian Chadd device_printf(dev, "ERROR: Could not read transfer config\n"); 394d27ba308SAdrian Chadd goto error; 395d27ba308SAdrian Chadd } 396d27ba308SAdrian Chadd 397d27ba308SAdrian Chadd 398d27ba308SAdrian Chadd device_printf(dev, "BLOCK: input=%u bytes, output=%u bytes\n", 399d27ba308SAdrian Chadd sc->config.input_block_size, 400d27ba308SAdrian Chadd sc->config.output_block_size); 401d27ba308SAdrian Chadd device_printf(dev, "FIFO: input=%u bytes, output=%u bytes\n", 402d27ba308SAdrian Chadd sc->config.input_fifo_size, 403d27ba308SAdrian Chadd sc->config.output_fifo_size); 404d27ba308SAdrian Chadd 405d27ba308SAdrian Chadd /* QUP config */ 406d27ba308SAdrian Chadd QCOM_SPI_LOCK(sc); 407d27ba308SAdrian Chadd ret = qcom_spi_hw_qup_init_locked(sc); 408d27ba308SAdrian Chadd if (ret != 0) { 409d27ba308SAdrian Chadd device_printf(dev, "ERROR: QUP init failed (%d)\n", ret); 410d27ba308SAdrian Chadd QCOM_SPI_UNLOCK(sc); 411d27ba308SAdrian Chadd goto error; 412d27ba308SAdrian Chadd } 413d27ba308SAdrian Chadd 414d27ba308SAdrian Chadd /* Initial SPI config */ 415d27ba308SAdrian Chadd ret = qcom_spi_hw_spi_init_locked(sc); 416d27ba308SAdrian Chadd if (ret != 0) { 417d27ba308SAdrian Chadd device_printf(dev, "ERROR: SPI init failed (%d)\n", ret); 418d27ba308SAdrian Chadd QCOM_SPI_UNLOCK(sc); 419d27ba308SAdrian Chadd goto error; 420d27ba308SAdrian Chadd } 421d27ba308SAdrian Chadd QCOM_SPI_UNLOCK(sc); 422d27ba308SAdrian Chadd 423d27ba308SAdrian Chadd sc->spibus = device_add_child(dev, "spibus", -1); 424d27ba308SAdrian Chadd 425d27ba308SAdrian Chadd /* We're done, so shut down the interface clock for now */ 426d27ba308SAdrian Chadd device_printf(dev, "DONE: shutting down interface clock for now\n"); 427d27ba308SAdrian Chadd clk_disable(sc->clk_iface); 428d27ba308SAdrian Chadd 429d27ba308SAdrian Chadd /* Register for debug sysctl */ 430d27ba308SAdrian Chadd qcom_spi_sysctl_attach(sc); 431d27ba308SAdrian Chadd 432*18250ec6SJohn Baldwin bus_attach_children(dev); 433*18250ec6SJohn Baldwin return (0); 434d27ba308SAdrian Chadd error: 435d27ba308SAdrian Chadd if (sc->sc_irq_h) 436d27ba308SAdrian Chadd bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_h); 437d27ba308SAdrian Chadd if (sc->sc_mem_res) 438d27ba308SAdrian Chadd bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); 439d27ba308SAdrian Chadd if (sc->sc_irq_res) 440d27ba308SAdrian Chadd bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); 441d27ba308SAdrian Chadd if (sc->clk_core) { 442d27ba308SAdrian Chadd clk_disable(sc->clk_core); 443d27ba308SAdrian Chadd clk_release(sc->clk_core); 444d27ba308SAdrian Chadd } 445d27ba308SAdrian Chadd if (sc->clk_iface) { 446d27ba308SAdrian Chadd clk_disable(sc->clk_iface); 447d27ba308SAdrian Chadd clk_release(sc->clk_iface); 448d27ba308SAdrian Chadd } 449d27ba308SAdrian Chadd for (i = 0; i < CS_MAX; i++) { 450d27ba308SAdrian Chadd if (sc->cs_pins[i] != NULL) 451d27ba308SAdrian Chadd gpio_pin_release(sc->cs_pins[i]); 452d27ba308SAdrian Chadd } 453d27ba308SAdrian Chadd mtx_destroy(&sc->sc_mtx); 454d27ba308SAdrian Chadd return (ret); 455d27ba308SAdrian Chadd } 456d27ba308SAdrian Chadd 457d27ba308SAdrian Chadd /* 458d27ba308SAdrian Chadd * Do a PIO transfer. 459d27ba308SAdrian Chadd * 460d27ba308SAdrian Chadd * Note that right now the TX/RX lens need to match, I'm not doing 461d27ba308SAdrian Chadd * dummy reads / dummy writes as required if they're not the same 462d27ba308SAdrian Chadd * size. The QUP hardware supports doing multi-phase transactions 463d27ba308SAdrian Chadd * where the FIFO isn't engaged for transmit or receive, but it's 464d27ba308SAdrian Chadd * not yet being done here. 465d27ba308SAdrian Chadd */ 466d27ba308SAdrian Chadd static int 467d27ba308SAdrian Chadd qcom_spi_transfer_pio_block(struct qcom_spi_softc *sc, int mode, 468d27ba308SAdrian Chadd char *tx_buf, int tx_len, char *rx_buf, int rx_len) 469d27ba308SAdrian Chadd { 470d27ba308SAdrian Chadd int ret = 0; 471d27ba308SAdrian Chadd 472d27ba308SAdrian Chadd QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_TRANSFER, "%s: start\n", 473d27ba308SAdrian Chadd __func__); 474d27ba308SAdrian Chadd 475d27ba308SAdrian Chadd if (rx_len != tx_len) { 476d27ba308SAdrian Chadd device_printf(sc->sc_dev, 477d27ba308SAdrian Chadd "ERROR: tx/rx len doesn't match (%d/%d)\n", 478d27ba308SAdrian Chadd tx_len, rx_len); 479d27ba308SAdrian Chadd return (ENXIO); 480d27ba308SAdrian Chadd } 481d27ba308SAdrian Chadd 482d27ba308SAdrian Chadd QCOM_SPI_ASSERT_LOCKED(sc); 483d27ba308SAdrian Chadd 484d27ba308SAdrian Chadd /* 485d27ba308SAdrian Chadd * Make initial choices for transfer configuration. 486d27ba308SAdrian Chadd */ 487d27ba308SAdrian Chadd ret = qcom_spi_hw_setup_transfer_selection(sc, tx_len); 488d27ba308SAdrian Chadd if (ret != 0) { 489d27ba308SAdrian Chadd device_printf(sc->sc_dev, 490d27ba308SAdrian Chadd "ERROR: failed to setup transfer selection (%d)\n", 491d27ba308SAdrian Chadd ret); 492d27ba308SAdrian Chadd return (ret); 493d27ba308SAdrian Chadd } 494d27ba308SAdrian Chadd 495d27ba308SAdrian Chadd /* Now set suitable buffer/lengths */ 496d27ba308SAdrian Chadd sc->transfer.tx_buf = tx_buf; 497d27ba308SAdrian Chadd sc->transfer.tx_len = tx_len; 498d27ba308SAdrian Chadd sc->transfer.rx_buf = rx_buf; 499d27ba308SAdrian Chadd sc->transfer.rx_len = rx_len; 500d27ba308SAdrian Chadd sc->transfer.done = false; 501d27ba308SAdrian Chadd sc->transfer.active = false; 502d27ba308SAdrian Chadd 503d27ba308SAdrian Chadd /* 504d27ba308SAdrian Chadd * Loop until the full transfer set is done. 505d27ba308SAdrian Chadd * 506d27ba308SAdrian Chadd * qcom_spi_hw_setup_current_transfer() will take care of 507d27ba308SAdrian Chadd * setting a maximum transfer size for the hardware and choose 508d27ba308SAdrian Chadd * a suitable operating mode. 509d27ba308SAdrian Chadd */ 510d27ba308SAdrian Chadd while (sc->transfer.tx_offset < sc->transfer.tx_len) { 511d27ba308SAdrian Chadd /* 512d27ba308SAdrian Chadd * Set transfer to false early; this covers 513d27ba308SAdrian Chadd * it also finishing a sub-transfer and we're 514d27ba308SAdrian Chadd * about the put the block into RESET state before 515d27ba308SAdrian Chadd * starting a new transfer. 516d27ba308SAdrian Chadd */ 517d27ba308SAdrian Chadd sc->transfer.active = false; 518d27ba308SAdrian Chadd 519d27ba308SAdrian Chadd QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_TRANSFER, 520d27ba308SAdrian Chadd "%s: tx=%d of %d bytes, rx=%d of %d bytes\n", 521d27ba308SAdrian Chadd __func__, 522d27ba308SAdrian Chadd sc->transfer.tx_offset, sc->transfer.tx_len, 523d27ba308SAdrian Chadd sc->transfer.rx_offset, sc->transfer.rx_len); 524d27ba308SAdrian Chadd 525d27ba308SAdrian Chadd /* 526d27ba308SAdrian Chadd * Set state to RESET before doing anything. 527d27ba308SAdrian Chadd * 528d27ba308SAdrian Chadd * Otherwise the second sub-transfer that we queue up 529d27ba308SAdrian Chadd * will generate interrupts immediately when we start 530d27ba308SAdrian Chadd * configuring it here and it'll start underflowing. 531d27ba308SAdrian Chadd */ 532d27ba308SAdrian Chadd ret = qcom_spi_hw_qup_set_state_locked(sc, QUP_STATE_RESET); 533d27ba308SAdrian Chadd if (ret != 0) { 534d27ba308SAdrian Chadd device_printf(sc->sc_dev, 535d27ba308SAdrian Chadd "ERROR: can't transition to RESET (%u)\n", ret); 536d27ba308SAdrian Chadd goto done; 537d27ba308SAdrian Chadd } 538d27ba308SAdrian Chadd /* blank interrupt state; we'll do a RESET below */ 539d27ba308SAdrian Chadd bzero(&sc->intr, sizeof(sc->intr)); 540d27ba308SAdrian Chadd sc->transfer.done = false; 541d27ba308SAdrian Chadd 542d27ba308SAdrian Chadd /* 543d27ba308SAdrian Chadd * Configure what the transfer configuration for this 544d27ba308SAdrian Chadd * sub-transfer will be. 545d27ba308SAdrian Chadd */ 546d27ba308SAdrian Chadd ret = qcom_spi_hw_setup_current_transfer(sc); 547d27ba308SAdrian Chadd if (ret != 0) { 548d27ba308SAdrian Chadd device_printf(sc->sc_dev, 549d27ba308SAdrian Chadd "ERROR: failed to setup sub transfer (%d)\n", 550d27ba308SAdrian Chadd ret); 551d27ba308SAdrian Chadd goto done; 552d27ba308SAdrian Chadd } 553d27ba308SAdrian Chadd 554d27ba308SAdrian Chadd /* 555d27ba308SAdrian Chadd * For now since we're configuring up PIO, we only setup 556d27ba308SAdrian Chadd * the PIO transfer size. 557d27ba308SAdrian Chadd */ 558d27ba308SAdrian Chadd ret = qcom_spi_hw_setup_pio_transfer_cnt(sc); 559d27ba308SAdrian Chadd if (ret != 0) { 560d27ba308SAdrian Chadd device_printf(sc->sc_dev, 561d27ba308SAdrian Chadd "ERROR: qcom_spi_hw_setup_pio_transfer_cnt failed" 562d27ba308SAdrian Chadd " (%u)\n", ret); 563d27ba308SAdrian Chadd goto done; 564d27ba308SAdrian Chadd } 565d27ba308SAdrian Chadd 566d27ba308SAdrian Chadd #if 0 567d27ba308SAdrian Chadd /* 568d27ba308SAdrian Chadd * This is what we'd do to setup the block transfer sizes. 569d27ba308SAdrian Chadd */ 570d27ba308SAdrian Chadd ret = qcom_spi_hw_setup_block_transfer_cnt(sc); 571d27ba308SAdrian Chadd if (ret != 0) { 572d27ba308SAdrian Chadd device_printf(sc->sc_dev, 573d27ba308SAdrian Chadd "ERROR: qcom_spi_hw_setup_block_transfer_cnt failed" 574d27ba308SAdrian Chadd " (%u)\n", ret); 575d27ba308SAdrian Chadd goto done; 576d27ba308SAdrian Chadd } 577d27ba308SAdrian Chadd #endif 578d27ba308SAdrian Chadd 579d27ba308SAdrian Chadd ret = qcom_spi_hw_setup_io_modes(sc); 580d27ba308SAdrian Chadd if (ret != 0) { 581d27ba308SAdrian Chadd device_printf(sc->sc_dev, 582d27ba308SAdrian Chadd "ERROR: qcom_spi_hw_setup_io_modes failed" 583d27ba308SAdrian Chadd " (%u)\n", ret); 584d27ba308SAdrian Chadd goto done; 585d27ba308SAdrian Chadd } 586d27ba308SAdrian Chadd 587d27ba308SAdrian Chadd ret = qcom_spi_hw_setup_spi_io_clock_polarity(sc, 588d27ba308SAdrian Chadd !! (mode & SPIBUS_MODE_CPOL)); 589d27ba308SAdrian Chadd if (ret != 0) { 590d27ba308SAdrian Chadd device_printf(sc->sc_dev, 591d27ba308SAdrian Chadd "ERROR: qcom_spi_hw_setup_spi_io_clock_polarity" 592d27ba308SAdrian Chadd " failed (%u)\n", ret); 593d27ba308SAdrian Chadd goto done; 594d27ba308SAdrian Chadd } 595d27ba308SAdrian Chadd 596d27ba308SAdrian Chadd ret = qcom_spi_hw_setup_spi_config(sc, sc->state.frequency, 597d27ba308SAdrian Chadd !! (mode & SPIBUS_MODE_CPHA)); 598d27ba308SAdrian Chadd if (ret != 0) { 599d27ba308SAdrian Chadd device_printf(sc->sc_dev, 600d27ba308SAdrian Chadd "ERROR: qcom_spi_hw_setup_spi_config failed" 601d27ba308SAdrian Chadd " (%u)\n", ret); 602d27ba308SAdrian Chadd goto done; 603d27ba308SAdrian Chadd } 604d27ba308SAdrian Chadd 605d27ba308SAdrian Chadd ret = qcom_spi_hw_setup_qup_config(sc, !! (tx_len > 0), 606d27ba308SAdrian Chadd !! (rx_len > 0)); 607d27ba308SAdrian Chadd if (ret != 0) { 608d27ba308SAdrian Chadd device_printf(sc->sc_dev, 609d27ba308SAdrian Chadd "ERROR: qcom_spi_hw_setup_qup_config failed" 610d27ba308SAdrian Chadd " (%u)\n", ret); 611d27ba308SAdrian Chadd goto done; 612d27ba308SAdrian Chadd } 613d27ba308SAdrian Chadd 614d27ba308SAdrian Chadd ret = qcom_spi_hw_setup_operational_mask(sc); 615d27ba308SAdrian Chadd if (ret != 0) { 616d27ba308SAdrian Chadd device_printf(sc->sc_dev, 617d27ba308SAdrian Chadd "ERROR: qcom_spi_hw_setup_operational_mask failed" 618d27ba308SAdrian Chadd " (%u)\n", ret); 619d27ba308SAdrian Chadd goto done; 620d27ba308SAdrian Chadd } 621d27ba308SAdrian Chadd 622d27ba308SAdrian Chadd /* 623d27ba308SAdrian Chadd * Setup is done; reset the controller and start the PIO 624d27ba308SAdrian Chadd * write. 625d27ba308SAdrian Chadd */ 626d27ba308SAdrian Chadd 627d27ba308SAdrian Chadd /* 628d27ba308SAdrian Chadd * Set state to RUN; we may start getting interrupts that 629d27ba308SAdrian Chadd * are valid and we need to handle. 630d27ba308SAdrian Chadd */ 631d27ba308SAdrian Chadd sc->transfer.active = true; 632d27ba308SAdrian Chadd ret = qcom_spi_hw_qup_set_state_locked(sc, QUP_STATE_RUN); 633d27ba308SAdrian Chadd if (ret != 0) { 634d27ba308SAdrian Chadd device_printf(sc->sc_dev, 635d27ba308SAdrian Chadd "ERROR: can't transition to RUN (%u)\n", ret); 636d27ba308SAdrian Chadd goto done; 637d27ba308SAdrian Chadd } 638d27ba308SAdrian Chadd 639d27ba308SAdrian Chadd /* 640d27ba308SAdrian Chadd * Set state to PAUSE 641d27ba308SAdrian Chadd */ 642d27ba308SAdrian Chadd ret = qcom_spi_hw_qup_set_state_locked(sc, QUP_STATE_PAUSE); 643d27ba308SAdrian Chadd if (ret != 0) { 644d27ba308SAdrian Chadd device_printf(sc->sc_dev, 645d27ba308SAdrian Chadd "ERROR: can't transition to PAUSE (%u)\n", ret); 646d27ba308SAdrian Chadd goto done; 647d27ba308SAdrian Chadd } 648d27ba308SAdrian Chadd 649d27ba308SAdrian Chadd /* 650d27ba308SAdrian Chadd * If FIFO mode, write data now. Else, we'll get an 651d27ba308SAdrian Chadd * interrupt when it's time to populate more data 652d27ba308SAdrian Chadd * in BLOCK mode. 653d27ba308SAdrian Chadd */ 654d27ba308SAdrian Chadd if (sc->state.transfer_mode == QUP_IO_M_MODE_FIFO) 655d27ba308SAdrian Chadd ret = qcom_spi_hw_write_pio_fifo(sc); 656d27ba308SAdrian Chadd else 657d27ba308SAdrian Chadd ret = qcom_spi_hw_write_pio_block(sc); 658d27ba308SAdrian Chadd if (ret != 0) { 659d27ba308SAdrian Chadd device_printf(sc->sc_dev, 660d27ba308SAdrian Chadd "ERROR: qcom_spi_hw_write failed (%u)\n", ret); 661d27ba308SAdrian Chadd goto done; 662d27ba308SAdrian Chadd } 663d27ba308SAdrian Chadd 664d27ba308SAdrian Chadd /* 665d27ba308SAdrian Chadd * Set state to RUN 666d27ba308SAdrian Chadd */ 667d27ba308SAdrian Chadd ret = qcom_spi_hw_qup_set_state_locked(sc, QUP_STATE_RUN); 668d27ba308SAdrian Chadd if (ret != 0) { 669d27ba308SAdrian Chadd device_printf(sc->sc_dev, 670d27ba308SAdrian Chadd "ERROR: can't transition to RUN (%u)\n", ret); 671d27ba308SAdrian Chadd goto done; 672d27ba308SAdrian Chadd } 673d27ba308SAdrian Chadd 674d27ba308SAdrian Chadd /* 675d27ba308SAdrian Chadd * Wait for an interrupt notification (which will 676d27ba308SAdrian Chadd * continue to drive the state machine for this 677d27ba308SAdrian Chadd * sub-transfer) or timeout. 678d27ba308SAdrian Chadd */ 679d27ba308SAdrian Chadd ret = 0; 680d27ba308SAdrian Chadd while (ret == 0 && sc->transfer.done == false) { 681d27ba308SAdrian Chadd QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_TRANSFER, 682d27ba308SAdrian Chadd "%s: waiting\n", __func__); 683d27ba308SAdrian Chadd ret = msleep(sc, &sc->sc_mtx, 0, "qcom_spi", 0); 684d27ba308SAdrian Chadd } 685d27ba308SAdrian Chadd } 686d27ba308SAdrian Chadd done: 687d27ba308SAdrian Chadd /* 688d27ba308SAdrian Chadd * Complete; put controller into reset. 689d27ba308SAdrian Chadd * 690d27ba308SAdrian Chadd * Don't worry about return value here; if we errored out above then 691d27ba308SAdrian Chadd * we want to communicate that value to the caller. 692d27ba308SAdrian Chadd */ 693d27ba308SAdrian Chadd (void) qcom_spi_hw_qup_set_state_locked(sc, QUP_STATE_RESET); 694d27ba308SAdrian Chadd QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_TRANSFER, 695d27ba308SAdrian Chadd "%s: completed\n", __func__); 696d27ba308SAdrian Chadd 697d27ba308SAdrian Chadd /* 698d27ba308SAdrian Chadd * Blank the transfer state so we don't use an old transfer 699d27ba308SAdrian Chadd * state in a subsequent interrupt. 700d27ba308SAdrian Chadd */ 701d27ba308SAdrian Chadd (void) qcom_spi_hw_complete_transfer(sc); 702d27ba308SAdrian Chadd sc->transfer.active = false; 703d27ba308SAdrian Chadd 704d27ba308SAdrian Chadd return (ret); 705d27ba308SAdrian Chadd } 706d27ba308SAdrian Chadd 707d27ba308SAdrian Chadd static int 708d27ba308SAdrian Chadd qcom_spi_transfer(device_t dev, device_t child, struct spi_command *cmd) 709d27ba308SAdrian Chadd { 710d27ba308SAdrian Chadd struct qcom_spi_softc *sc = device_get_softc(dev); 711d27ba308SAdrian Chadd uint32_t cs_val, mode_val, clock_val; 712d27ba308SAdrian Chadd uint32_t ret = 0; 713d27ba308SAdrian Chadd 714d27ba308SAdrian Chadd spibus_get_cs(child, &cs_val); 715d27ba308SAdrian Chadd spibus_get_clock(child, &clock_val); 716d27ba308SAdrian Chadd spibus_get_mode(child, &mode_val); 717d27ba308SAdrian Chadd 718d27ba308SAdrian Chadd QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_TRANSFER, 719d27ba308SAdrian Chadd "%s: called; child cs=0x%08x, clock=%u, mode=0x%08x, " 720d27ba308SAdrian Chadd "cmd=%u/%u bytes; data=%u/%u bytes\n", 721d27ba308SAdrian Chadd __func__, 722d27ba308SAdrian Chadd cs_val, 723d27ba308SAdrian Chadd clock_val, 724d27ba308SAdrian Chadd mode_val, 725d27ba308SAdrian Chadd cmd->tx_cmd_sz, cmd->rx_cmd_sz, 726d27ba308SAdrian Chadd cmd->tx_data_sz, cmd->rx_data_sz); 727d27ba308SAdrian Chadd 728d27ba308SAdrian Chadd QCOM_SPI_LOCK(sc); 729d27ba308SAdrian Chadd 730d27ba308SAdrian Chadd /* 731d27ba308SAdrian Chadd * wait until the controller isn't busy 732d27ba308SAdrian Chadd */ 733d27ba308SAdrian Chadd while (sc->sc_busy == true) 734d27ba308SAdrian Chadd mtx_sleep(sc, &sc->sc_mtx, 0, "qcom_spi_wait", 0); 735d27ba308SAdrian Chadd 736d27ba308SAdrian Chadd /* 737d27ba308SAdrian Chadd * it's ours now! 738d27ba308SAdrian Chadd */ 739d27ba308SAdrian Chadd sc->sc_busy = true; 740d27ba308SAdrian Chadd 741d27ba308SAdrian Chadd sc->state.cs_high = !! (cs_val & SPIBUS_CS_HIGH); 742d27ba308SAdrian Chadd sc->state.frequency = clock_val; 743d27ba308SAdrian Chadd 744d27ba308SAdrian Chadd /* 745d27ba308SAdrian Chadd * We can't set the clock frequency and enable it 746d27ba308SAdrian Chadd * with the driver lock held, as the SPI lock is non-sleepable 747d27ba308SAdrian Chadd * and the clock framework is sleepable. 748d27ba308SAdrian Chadd * 749d27ba308SAdrian Chadd * No other transaction is going on right now, so we can 750d27ba308SAdrian Chadd * unlock here and do the clock related work. 751d27ba308SAdrian Chadd */ 752d27ba308SAdrian Chadd QCOM_SPI_UNLOCK(sc); 753d27ba308SAdrian Chadd 754d27ba308SAdrian Chadd /* 755d27ba308SAdrian Chadd * Set the clock frequency 756d27ba308SAdrian Chadd */ 757d27ba308SAdrian Chadd ret = clk_set_freq(sc->clk_iface, sc->state.frequency, 0); 758d27ba308SAdrian Chadd if (ret != 0) { 759d27ba308SAdrian Chadd device_printf(sc->sc_dev, 760d27ba308SAdrian Chadd "ERROR: failed to set frequency to %u\n", 761d27ba308SAdrian Chadd sc->state.frequency); 762d27ba308SAdrian Chadd goto done2; 763d27ba308SAdrian Chadd } 764d27ba308SAdrian Chadd clk_enable(sc->clk_iface); 765d27ba308SAdrian Chadd 766d27ba308SAdrian Chadd QCOM_SPI_LOCK(sc); 767d27ba308SAdrian Chadd 768d27ba308SAdrian Chadd /* 769d27ba308SAdrian Chadd * Set state to RESET 770d27ba308SAdrian Chadd */ 771d27ba308SAdrian Chadd ret = qcom_spi_hw_qup_set_state_locked(sc, QUP_STATE_RESET); 772d27ba308SAdrian Chadd if (ret != 0) { 773d27ba308SAdrian Chadd device_printf(sc->sc_dev, 774d27ba308SAdrian Chadd "ERROR: can't transition to RESET (%u)\n", ret); 775d27ba308SAdrian Chadd goto done; 776d27ba308SAdrian Chadd } 777d27ba308SAdrian Chadd 778d27ba308SAdrian Chadd /* Assert hardware CS if set, else GPIO */ 779d27ba308SAdrian Chadd if (sc->cs_pins[cs_val & ~SPIBUS_CS_HIGH] == NULL) 780d27ba308SAdrian Chadd qcom_spi_hw_spi_cs_force(sc, cs_val & SPIBUS_CS_HIGH, true); 781d27ba308SAdrian Chadd else 782d27ba308SAdrian Chadd qcom_spi_set_chipsel(sc, cs_val & ~SPIBUS_CS_HIGH, true); 783d27ba308SAdrian Chadd 784d27ba308SAdrian Chadd /* 785d27ba308SAdrian Chadd * cmd buffer transfer 786d27ba308SAdrian Chadd */ 787d27ba308SAdrian Chadd ret = qcom_spi_transfer_pio_block(sc, mode_val, cmd->tx_cmd, 788d27ba308SAdrian Chadd cmd->tx_cmd_sz, cmd->rx_cmd, cmd->rx_cmd_sz); 789d27ba308SAdrian Chadd if (ret != 0) { 790d27ba308SAdrian Chadd device_printf(sc->sc_dev, 791d27ba308SAdrian Chadd "ERROR: failed to transfer cmd payload (%u)\n", ret); 792d27ba308SAdrian Chadd goto done; 793d27ba308SAdrian Chadd } 794d27ba308SAdrian Chadd 795d27ba308SAdrian Chadd /* 796d27ba308SAdrian Chadd * data buffer transfer 797d27ba308SAdrian Chadd */ 798d27ba308SAdrian Chadd if (cmd->tx_data_sz > 0) { 799d27ba308SAdrian Chadd ret = qcom_spi_transfer_pio_block(sc, mode_val, cmd->tx_data, 800d27ba308SAdrian Chadd cmd->tx_data_sz, cmd->rx_data, cmd->rx_data_sz); 801d27ba308SAdrian Chadd if (ret != 0) { 802d27ba308SAdrian Chadd device_printf(sc->sc_dev, 803d27ba308SAdrian Chadd "ERROR: failed to transfer data payload (%u)\n", 804d27ba308SAdrian Chadd ret); 805d27ba308SAdrian Chadd goto done; 806d27ba308SAdrian Chadd } 807d27ba308SAdrian Chadd } 808d27ba308SAdrian Chadd 809d27ba308SAdrian Chadd done: 810d27ba308SAdrian Chadd /* De-assert GPIO/CS */ 811d27ba308SAdrian Chadd if (sc->cs_pins[cs_val & ~SPIBUS_CS_HIGH] == NULL) 812d27ba308SAdrian Chadd qcom_spi_hw_spi_cs_force(sc, cs_val & ~SPIBUS_CS_HIGH, false); 813d27ba308SAdrian Chadd else 814d27ba308SAdrian Chadd qcom_spi_set_chipsel(sc, cs_val & ~SPIBUS_CS_HIGH, false); 815d27ba308SAdrian Chadd 816d27ba308SAdrian Chadd /* 817d27ba308SAdrian Chadd * Similarly to when we enabled the clock, we can't hold it here 818d27ba308SAdrian Chadd * across a clk API as that's a sleep lock and we're non-sleepable. 819d27ba308SAdrian Chadd * So instead we unlock/relock here, but we still hold the busy flag. 820d27ba308SAdrian Chadd */ 821d27ba308SAdrian Chadd 822d27ba308SAdrian Chadd QCOM_SPI_UNLOCK(sc); 823d27ba308SAdrian Chadd clk_disable(sc->clk_iface); 824d27ba308SAdrian Chadd QCOM_SPI_LOCK(sc); 825d27ba308SAdrian Chadd done2: 826d27ba308SAdrian Chadd /* 827d27ba308SAdrian Chadd * We're done; so mark the bus as not busy and wakeup 828d27ba308SAdrian Chadd * the next caller. 829d27ba308SAdrian Chadd */ 830d27ba308SAdrian Chadd sc->sc_busy = false; 831d27ba308SAdrian Chadd wakeup_one(sc); 832d27ba308SAdrian Chadd QCOM_SPI_UNLOCK(sc); 833d27ba308SAdrian Chadd return (ret); 834d27ba308SAdrian Chadd } 835d27ba308SAdrian Chadd 836d27ba308SAdrian Chadd static int 837d27ba308SAdrian Chadd qcom_spi_detach(device_t dev) 838d27ba308SAdrian Chadd { 839d27ba308SAdrian Chadd struct qcom_spi_softc *sc = device_get_softc(dev); 840d27ba308SAdrian Chadd int i; 841d27ba308SAdrian Chadd 842d27ba308SAdrian Chadd bus_generic_detach(sc->sc_dev); 843d27ba308SAdrian Chadd 844d27ba308SAdrian Chadd if (sc->sc_irq_h) 845d27ba308SAdrian Chadd bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_h); 846d27ba308SAdrian Chadd 847d27ba308SAdrian Chadd if (sc->clk_iface) { 848d27ba308SAdrian Chadd clk_disable(sc->clk_iface); 849d27ba308SAdrian Chadd clk_release(sc->clk_iface); 850d27ba308SAdrian Chadd } 851d27ba308SAdrian Chadd if (sc->clk_core) { 852d27ba308SAdrian Chadd clk_disable(sc->clk_core); 853d27ba308SAdrian Chadd clk_release(sc->clk_core); 854d27ba308SAdrian Chadd } 855d27ba308SAdrian Chadd 856d27ba308SAdrian Chadd for (i = 0; i < CS_MAX; i++) { 857d27ba308SAdrian Chadd if (sc->cs_pins[i] != NULL) 858d27ba308SAdrian Chadd gpio_pin_release(sc->cs_pins[i]); 859d27ba308SAdrian Chadd } 860d27ba308SAdrian Chadd 861d27ba308SAdrian Chadd if (sc->sc_mem_res) 862d27ba308SAdrian Chadd bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); 863d27ba308SAdrian Chadd if (sc->sc_irq_res) 864d27ba308SAdrian Chadd bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); 865d27ba308SAdrian Chadd 866d27ba308SAdrian Chadd mtx_destroy(&sc->sc_mtx); 867d27ba308SAdrian Chadd 868d27ba308SAdrian Chadd return (0); 869d27ba308SAdrian Chadd } 870d27ba308SAdrian Chadd 871d27ba308SAdrian Chadd static phandle_t 872d27ba308SAdrian Chadd qcom_spi_get_node(device_t bus, device_t dev) 873d27ba308SAdrian Chadd { 874d27ba308SAdrian Chadd 875d27ba308SAdrian Chadd return ofw_bus_get_node(bus); 876d27ba308SAdrian Chadd } 877d27ba308SAdrian Chadd 878d27ba308SAdrian Chadd 879d27ba308SAdrian Chadd static device_method_t qcom_spi_methods[] = { 880d27ba308SAdrian Chadd /* Device interface */ 881d27ba308SAdrian Chadd DEVMETHOD(device_probe, qcom_spi_probe), 882d27ba308SAdrian Chadd DEVMETHOD(device_attach, qcom_spi_attach), 883d27ba308SAdrian Chadd DEVMETHOD(device_detach, qcom_spi_detach), 884d27ba308SAdrian Chadd /* TODO: suspend */ 885d27ba308SAdrian Chadd /* TODO: resume */ 886d27ba308SAdrian Chadd 887d27ba308SAdrian Chadd DEVMETHOD(spibus_transfer, qcom_spi_transfer), 888d27ba308SAdrian Chadd 889d27ba308SAdrian Chadd /* ofw_bus_if */ 890d27ba308SAdrian Chadd DEVMETHOD(ofw_bus_get_node, qcom_spi_get_node), 891d27ba308SAdrian Chadd 892d27ba308SAdrian Chadd DEVMETHOD_END 893d27ba308SAdrian Chadd }; 894d27ba308SAdrian Chadd 895d27ba308SAdrian Chadd static driver_t qcom_spi_driver = { 896d27ba308SAdrian Chadd "qcom_spi", 897d27ba308SAdrian Chadd qcom_spi_methods, 898d27ba308SAdrian Chadd sizeof(struct qcom_spi_softc), 899d27ba308SAdrian Chadd }; 900d27ba308SAdrian Chadd 90153e1cbefSJohn Baldwin DRIVER_MODULE(qcom_spi, simplebus, qcom_spi_driver, 0, 0); 9025f31d14aSJohn Baldwin DRIVER_MODULE(ofw_spibus, qcom_spi, ofw_spibus_driver, 0, 0); 903d27ba308SAdrian Chadd MODULE_DEPEND(qcom_spi, ofw_spibus, 1, 1, 1); 904d27ba308SAdrian Chadd SIMPLEBUS_PNP_INFO(compat_data); 905