xref: /freebsd-src/sys/dev/qcom_qup/qcom_spi.c (revision b196276c20b577b364372f1aa1a646b9ce34bf5c)
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