1257062Sloos/*-
2257062Sloos * Copyright (c) 2012 Oleksandr Tymoshenko <gonzo@freebsd.org>
3257062Sloos * Copyright (c) 2013 Luiz Otavio O Souza <loos@freebsd.org>
4257062Sloos * All rights reserved.
5257062Sloos *
6257062Sloos * Redistribution and use in source and binary forms, with or without
7257062Sloos * modification, are permitted provided that the following conditions
8257062Sloos * are met:
9257062Sloos * 1. Redistributions of source code must retain the above copyright
10257062Sloos *    notice, this list of conditions and the following disclaimer.
11257062Sloos * 2. Redistributions in binary form must reproduce the above copyright
12257062Sloos *    notice, this list of conditions and the following disclaimer in the
13257062Sloos *    documentation and/or other materials provided with the distribution.
14257062Sloos *
15257062Sloos * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16257062Sloos * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17257062Sloos * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18257062Sloos * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19257062Sloos * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20257062Sloos * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21257062Sloos * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22257062Sloos * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23257062Sloos * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24257062Sloos * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25257062Sloos * SUCH DAMAGE.
26257062Sloos *
27257062Sloos */
28257062Sloos#include <sys/cdefs.h>
29257062Sloos__FBSDID("$FreeBSD: stable/10/sys/arm/broadcom/bcm2835/bcm2835_spi.c 322724 2017-08-20 16:52:27Z marius $");
30257062Sloos
31257062Sloos#include <sys/param.h>
32257062Sloos#include <sys/systm.h>
33257062Sloos#include <sys/bus.h>
34257062Sloos
35257062Sloos#include <sys/kernel.h>
36257062Sloos#include <sys/module.h>
37257062Sloos#include <sys/rman.h>
38257062Sloos#include <sys/lock.h>
39257062Sloos#include <sys/mutex.h>
40257062Sloos#include <sys/sysctl.h>
41257062Sloos
42257062Sloos#include <machine/bus.h>
43257062Sloos#include <machine/cpu.h>
44257062Sloos#include <machine/cpufunc.h>
45257062Sloos#include <machine/resource.h>
46257062Sloos#include <machine/fdt.h>
47257062Sloos#include <machine/intr.h>
48257062Sloos
49257062Sloos#include <dev/fdt/fdt_common.h>
50257062Sloos#include <dev/ofw/ofw_bus.h>
51257062Sloos#include <dev/ofw/ofw_bus_subr.h>
52257062Sloos
53257062Sloos#include <dev/spibus/spi.h>
54257062Sloos#include <dev/spibus/spibusvar.h>
55257062Sloos
56257062Sloos#include <arm/broadcom/bcm2835/bcm2835_gpio.h>
57257062Sloos#include <arm/broadcom/bcm2835/bcm2835_spireg.h>
58257062Sloos#include <arm/broadcom/bcm2835/bcm2835_spivar.h>
59257062Sloos
60257062Sloos#include "spibus_if.h"
61257062Sloos
62322724Smariusstatic struct ofw_compat_data compat_data[] = {
63322724Smarius	{"broadcom,bcm2835-spi",	1},
64322724Smarius	{"brcm,bcm2835-spi",		1},
65322724Smarius	{NULL,				0}
66322724Smarius};
67322724Smarius
68257062Sloosstatic void bcm_spi_intr(void *);
69257062Sloos
70257062Sloos#ifdef	BCM_SPI_DEBUG
71257062Sloosstatic void
72257062Sloosbcm_spi_printr(device_t dev)
73257062Sloos{
74257062Sloos	struct bcm_spi_softc *sc;
75257062Sloos	uint32_t reg;
76257062Sloos
77257062Sloos	sc = device_get_softc(dev);
78257062Sloos	reg = BCM_SPI_READ(sc, SPI_CS);
79257062Sloos	device_printf(dev, "CS=%b\n", reg,
80257062Sloos	    "\20\1CS0\2CS1\3CPHA\4CPOL\7CSPOL"
81257062Sloos	    "\10TA\11DMAEN\12INTD\13INTR\14ADCS\15REN\16LEN"
82257062Sloos	    "\21DONE\22RXD\23TXD\24RXR\25RXF\26CSPOL0\27CSPOL1"
83257062Sloos	    "\30CSPOL2\31DMA_LEN\32LEN_LONG");
84257062Sloos	reg = BCM_SPI_READ(sc, SPI_CLK) & SPI_CLK_MASK;
85257062Sloos	if (reg % 2)
86257062Sloos		reg--;
87257062Sloos	if (reg == 0)
88257062Sloos		reg = 65536;
89257062Sloos	device_printf(dev, "CLK=%uMhz/%d=%luhz\n",
90257062Sloos	    SPI_CORE_CLK / 1000000, reg, SPI_CORE_CLK / reg);
91257062Sloos	reg = BCM_SPI_READ(sc, SPI_DLEN) & SPI_DLEN_MASK;
92257062Sloos	device_printf(dev, "DLEN=%d\n", reg);
93257062Sloos	reg = BCM_SPI_READ(sc, SPI_LTOH) & SPI_LTOH_MASK;
94257062Sloos	device_printf(dev, "LTOH=%d\n", reg);
95257062Sloos	reg = BCM_SPI_READ(sc, SPI_DC);
96257062Sloos	device_printf(dev, "DC=RPANIC=%#x RDREQ=%#x TPANIC=%#x TDREQ=%#x\n",
97257062Sloos	    (reg & SPI_DC_RPANIC_MASK) >> SPI_DC_RPANIC_SHIFT,
98257062Sloos	    (reg & SPI_DC_RDREQ_MASK) >> SPI_DC_RDREQ_SHIFT,
99257062Sloos	    (reg & SPI_DC_TPANIC_MASK) >> SPI_DC_TPANIC_SHIFT,
100257062Sloos	    (reg & SPI_DC_TDREQ_MASK) >> SPI_DC_TDREQ_SHIFT);
101257062Sloos}
102257062Sloos#endif
103257062Sloos
104257062Sloosstatic void
105257062Sloosbcm_spi_modifyreg(struct bcm_spi_softc *sc, uint32_t off, uint32_t mask,
106257062Sloos	uint32_t value)
107257062Sloos{
108257062Sloos	uint32_t reg;
109257062Sloos
110257062Sloos	mtx_assert(&sc->sc_mtx, MA_OWNED);
111257062Sloos	reg = BCM_SPI_READ(sc, off);
112257062Sloos	reg &= ~mask;
113257062Sloos	reg |= value;
114257062Sloos	BCM_SPI_WRITE(sc, off, reg);
115257062Sloos}
116257062Sloos
117257062Sloosstatic int
118257062Sloosbcm_spi_clock_proc(SYSCTL_HANDLER_ARGS)
119257062Sloos{
120257062Sloos	struct bcm_spi_softc *sc;
121257062Sloos	uint32_t clk;
122257062Sloos	int error;
123257062Sloos
124257062Sloos	sc = (struct bcm_spi_softc *)arg1;
125257062Sloos
126257062Sloos	BCM_SPI_LOCK(sc);
127257062Sloos	clk = BCM_SPI_READ(sc, SPI_CLK);
128257062Sloos	BCM_SPI_UNLOCK(sc);
129257062Sloos	clk &= 0xffff;
130257062Sloos	if (clk == 0)
131257062Sloos		clk = 65536;
132257062Sloos	clk = SPI_CORE_CLK / clk;
133257062Sloos
134257062Sloos	error = sysctl_handle_int(oidp, &clk, sizeof(clk), req);
135257062Sloos	if (error != 0 || req->newptr == NULL)
136257062Sloos		return (error);
137257062Sloos
138257062Sloos	clk = SPI_CORE_CLK / clk;
139257062Sloos	if (clk <= 1)
140257062Sloos		clk = 2;
141257062Sloos	else if (clk % 2)
142257062Sloos		clk--;
143257062Sloos	if (clk > 0xffff)
144257062Sloos		clk = 0;
145257062Sloos	BCM_SPI_LOCK(sc);
146257062Sloos	BCM_SPI_WRITE(sc, SPI_CLK, clk);
147257062Sloos	BCM_SPI_UNLOCK(sc);
148257062Sloos
149257062Sloos	return (0);
150257062Sloos}
151257062Sloos
152257062Sloosstatic int
153257062Sloosbcm_spi_cs_bit_proc(SYSCTL_HANDLER_ARGS, uint32_t bit)
154257062Sloos{
155257062Sloos	struct bcm_spi_softc *sc;
156257062Sloos	uint32_t reg;
157257062Sloos	int error;
158257062Sloos
159257062Sloos	sc = (struct bcm_spi_softc *)arg1;
160257062Sloos	BCM_SPI_LOCK(sc);
161257062Sloos	reg = BCM_SPI_READ(sc, SPI_CS);
162257062Sloos	BCM_SPI_UNLOCK(sc);
163257062Sloos	reg = (reg & bit) ? 1 : 0;
164257062Sloos
165257062Sloos	error = sysctl_handle_int(oidp, &reg, sizeof(reg), req);
166257062Sloos	if (error != 0 || req->newptr == NULL)
167257062Sloos		return (error);
168257062Sloos
169257062Sloos	if (reg)
170257062Sloos		reg = bit;
171257062Sloos	BCM_SPI_LOCK(sc);
172257062Sloos	bcm_spi_modifyreg(sc, SPI_CS, bit, reg);
173257062Sloos	BCM_SPI_UNLOCK(sc);
174257062Sloos
175257062Sloos	return (0);
176257062Sloos}
177257062Sloos
178257062Sloosstatic int
179257062Sloosbcm_spi_cpol_proc(SYSCTL_HANDLER_ARGS)
180257062Sloos{
181257062Sloos
182257062Sloos	return (bcm_spi_cs_bit_proc(oidp, arg1, arg2, req, SPI_CS_CPOL));
183257062Sloos}
184257062Sloos
185257062Sloosstatic int
186257062Sloosbcm_spi_cpha_proc(SYSCTL_HANDLER_ARGS)
187257062Sloos{
188257062Sloos
189257062Sloos	return (bcm_spi_cs_bit_proc(oidp, arg1, arg2, req, SPI_CS_CPHA));
190257062Sloos}
191257062Sloos
192257062Sloosstatic int
193257062Sloosbcm_spi_cspol0_proc(SYSCTL_HANDLER_ARGS)
194257062Sloos{
195257062Sloos
196257062Sloos	return (bcm_spi_cs_bit_proc(oidp, arg1, arg2, req, SPI_CS_CSPOL0));
197257062Sloos}
198257062Sloos
199257062Sloosstatic int
200257062Sloosbcm_spi_cspol1_proc(SYSCTL_HANDLER_ARGS)
201257062Sloos{
202257062Sloos
203257062Sloos	return (bcm_spi_cs_bit_proc(oidp, arg1, arg2, req, SPI_CS_CSPOL1));
204257062Sloos}
205257062Sloos
206257062Sloosstatic void
207257062Sloosbcm_spi_sysctl_init(struct bcm_spi_softc *sc)
208257062Sloos{
209257062Sloos	struct sysctl_ctx_list *ctx;
210257062Sloos	struct sysctl_oid *tree_node;
211257062Sloos	struct sysctl_oid_list *tree;
212257062Sloos
213257062Sloos	/*
214257062Sloos	 * Add system sysctl tree/handlers.
215257062Sloos	 */
216257062Sloos	ctx = device_get_sysctl_ctx(sc->sc_dev);
217257062Sloos	tree_node = device_get_sysctl_tree(sc->sc_dev);
218257062Sloos	tree = SYSCTL_CHILDREN(tree_node);
219257062Sloos	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "clock",
220257062Sloos	    CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
221257062Sloos	    bcm_spi_clock_proc, "IU", "SPI BUS clock frequency");
222257062Sloos	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "cpol",
223257062Sloos	    CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
224257062Sloos	    bcm_spi_cpol_proc, "IU", "SPI BUS clock polarity");
225257062Sloos	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "cpha",
226257062Sloos	    CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
227257062Sloos	    bcm_spi_cpha_proc, "IU", "SPI BUS clock phase");
228257062Sloos	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "cspol0",
229257062Sloos	    CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
230257062Sloos	    bcm_spi_cspol0_proc, "IU", "SPI BUS chip select 0 polarity");
231257062Sloos	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "cspol1",
232257062Sloos	    CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
233257062Sloos	    bcm_spi_cspol1_proc, "IU", "SPI BUS chip select 1 polarity");
234257062Sloos}
235257062Sloos
236257062Sloosstatic int
237257062Sloosbcm_spi_probe(device_t dev)
238257062Sloos{
239257062Sloos
240266152Sian	if (!ofw_bus_status_okay(dev))
241266152Sian		return (ENXIO);
242266152Sian
243322724Smarius	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
244257062Sloos		return (ENXIO);
245257062Sloos
246257062Sloos	device_set_desc(dev, "BCM2708/2835 SPI controller");
247257062Sloos
248257062Sloos	return (BUS_PROBE_DEFAULT);
249257062Sloos}
250257062Sloos
251257062Sloosstatic int
252257062Sloosbcm_spi_attach(device_t dev)
253257062Sloos{
254257062Sloos	struct bcm_spi_softc *sc;
255257062Sloos	device_t gpio;
256257062Sloos	int i, rid;
257257062Sloos
258257062Sloos	if (device_get_unit(dev) != 0) {
259257062Sloos		device_printf(dev, "only one SPI controller supported\n");
260257062Sloos		return (ENXIO);
261257062Sloos	}
262257062Sloos
263257062Sloos	sc = device_get_softc(dev);
264257062Sloos	sc->sc_dev = dev;
265257062Sloos
266257062Sloos	/* Configure the GPIO pins to ALT0 function to enable SPI the pins. */
267257062Sloos	gpio = devclass_get_device(devclass_find("gpio"), 0);
268257062Sloos	if (!gpio) {
269257062Sloos		device_printf(dev, "cannot find gpio0\n");
270257062Sloos		return (ENXIO);
271257062Sloos	}
272257062Sloos	for (i = 0; i < nitems(bcm_spi_pins); i++)
273257062Sloos		bcm_gpio_set_alternate(gpio, bcm_spi_pins[i], BCM_GPIO_ALT0);
274257062Sloos
275257062Sloos	rid = 0;
276257062Sloos	sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
277257062Sloos	    RF_ACTIVE);
278257062Sloos	if (!sc->sc_mem_res) {
279257062Sloos		device_printf(dev, "cannot allocate memory window\n");
280257062Sloos		return (ENXIO);
281257062Sloos	}
282257062Sloos
283257062Sloos	sc->sc_bst = rman_get_bustag(sc->sc_mem_res);
284257062Sloos	sc->sc_bsh = rman_get_bushandle(sc->sc_mem_res);
285257062Sloos
286257062Sloos	rid = 0;
287257062Sloos	sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
288257062Sloos	    RF_ACTIVE);
289257062Sloos	if (!sc->sc_irq_res) {
290257062Sloos		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
291257062Sloos		device_printf(dev, "cannot allocate interrupt\n");
292257062Sloos		return (ENXIO);
293257062Sloos	}
294257062Sloos
295257062Sloos	/* Hook up our interrupt handler. */
296257062Sloos	if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
297257062Sloos	    NULL, bcm_spi_intr, sc, &sc->sc_intrhand)) {
298257062Sloos		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
299257062Sloos		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
300257062Sloos		device_printf(dev, "cannot setup the interrupt handler\n");
301257062Sloos		return (ENXIO);
302257062Sloos	}
303257062Sloos
304257062Sloos	mtx_init(&sc->sc_mtx, "bcm_spi", NULL, MTX_DEF);
305257062Sloos
306257062Sloos	/* Add sysctl nodes. */
307257062Sloos	bcm_spi_sysctl_init(sc);
308257062Sloos
309257062Sloos#ifdef	BCM_SPI_DEBUG
310257062Sloos	bcm_spi_printr(dev);
311257062Sloos#endif
312257062Sloos
313257062Sloos	/*
314257062Sloos	 * Enable the SPI controller.  Clear the rx and tx FIFO.
315257062Sloos	 * Defaults to SPI mode 0.
316257062Sloos	 */
317257062Sloos	BCM_SPI_WRITE(sc, SPI_CS, SPI_CS_CLEAR_RXFIFO | SPI_CS_CLEAR_TXFIFO);
318257062Sloos
319257062Sloos	/* Set the SPI clock to 500Khz. */
320257062Sloos	BCM_SPI_WRITE(sc, SPI_CLK, SPI_CORE_CLK / 500000);
321257062Sloos
322257062Sloos#ifdef	BCM_SPI_DEBUG
323257062Sloos	bcm_spi_printr(dev);
324257062Sloos#endif
325257062Sloos
326257062Sloos	device_add_child(dev, "spibus", -1);
327257062Sloos
328257062Sloos	return (bus_generic_attach(dev));
329257062Sloos}
330257062Sloos
331257062Sloosstatic int
332257062Sloosbcm_spi_detach(device_t dev)
333257062Sloos{
334257062Sloos	struct bcm_spi_softc *sc;
335257062Sloos
336257062Sloos	bus_generic_detach(dev);
337257062Sloos
338257062Sloos	sc = device_get_softc(dev);
339257062Sloos	mtx_destroy(&sc->sc_mtx);
340257062Sloos	if (sc->sc_intrhand)
341257062Sloos		bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intrhand);
342257062Sloos	if (sc->sc_irq_res)
343257062Sloos		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
344257062Sloos	if (sc->sc_mem_res)
345257062Sloos		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
346257062Sloos
347257062Sloos	return (0);
348257062Sloos}
349257062Sloos
350257062Sloosstatic void
351257062Sloosbcm_spi_fill_fifo(struct bcm_spi_softc *sc)
352257062Sloos{
353257062Sloos	struct spi_command *cmd;
354257062Sloos	uint32_t cs, written;
355257062Sloos	uint8_t *data;
356257062Sloos
357257062Sloos	cmd = sc->sc_cmd;
358257062Sloos	cs = BCM_SPI_READ(sc, SPI_CS) & (SPI_CS_TA | SPI_CS_TXD);
359257062Sloos	while (sc->sc_written < sc->sc_len &&
360257062Sloos	    cs == (SPI_CS_TA | SPI_CS_TXD)) {
361257062Sloos		data = (uint8_t *)cmd->tx_cmd;
362257062Sloos		written = sc->sc_written++;
363257062Sloos		if (written >= cmd->tx_cmd_sz) {
364257062Sloos			data = (uint8_t *)cmd->tx_data;
365257062Sloos			written -= cmd->tx_cmd_sz;
366257062Sloos		}
367257062Sloos		BCM_SPI_WRITE(sc, SPI_FIFO, data[written]);
368257062Sloos		cs = BCM_SPI_READ(sc, SPI_CS) & (SPI_CS_TA | SPI_CS_TXD);
369257062Sloos	}
370257062Sloos}
371257062Sloos
372257062Sloosstatic void
373257062Sloosbcm_spi_drain_fifo(struct bcm_spi_softc *sc)
374257062Sloos{
375257062Sloos	struct spi_command *cmd;
376257062Sloos	uint32_t cs, read;
377257062Sloos	uint8_t *data;
378257062Sloos
379257062Sloos	cmd = sc->sc_cmd;
380257062Sloos	cs = BCM_SPI_READ(sc, SPI_CS) & SPI_CS_RXD;
381257062Sloos	while (sc->sc_read < sc->sc_len && cs == SPI_CS_RXD) {
382257062Sloos		data = (uint8_t *)cmd->rx_cmd;
383257062Sloos		read = sc->sc_read++;
384257062Sloos		if (read >= cmd->rx_cmd_sz) {
385257062Sloos			data = (uint8_t *)cmd->rx_data;
386257062Sloos			read -= cmd->rx_cmd_sz;
387257062Sloos		}
388257062Sloos		data[read] = BCM_SPI_READ(sc, SPI_FIFO) & 0xff;
389257062Sloos		cs = BCM_SPI_READ(sc, SPI_CS) & SPI_CS_RXD;
390257062Sloos	}
391257062Sloos}
392257062Sloos
393257062Sloosstatic void
394257062Sloosbcm_spi_intr(void *arg)
395257062Sloos{
396257062Sloos	struct bcm_spi_softc *sc;
397257062Sloos
398257062Sloos	sc = (struct bcm_spi_softc *)arg;
399257062Sloos	BCM_SPI_LOCK(sc);
400257062Sloos
401257062Sloos	/* Filter stray interrupts. */
402257062Sloos	if ((sc->sc_flags & BCM_SPI_BUSY) == 0) {
403257062Sloos		BCM_SPI_UNLOCK(sc);
404257062Sloos		return;
405257062Sloos	}
406257062Sloos
407257062Sloos	/* TX - Fill up the FIFO. */
408257062Sloos	bcm_spi_fill_fifo(sc);
409257062Sloos
410257062Sloos	/* RX - Drain the FIFO. */
411257062Sloos	bcm_spi_drain_fifo(sc);
412257062Sloos
413257062Sloos	/* Check for end of transfer. */
414257062Sloos	if (sc->sc_written == sc->sc_len && sc->sc_read == sc->sc_len) {
415257062Sloos		/* Disable interrupts and the SPI engine. */
416257062Sloos		bcm_spi_modifyreg(sc, SPI_CS,
417257062Sloos		    SPI_CS_TA | SPI_CS_INTR | SPI_CS_INTD, 0);
418257062Sloos		wakeup(sc->sc_dev);
419257062Sloos	}
420257062Sloos
421257062Sloos	BCM_SPI_UNLOCK(sc);
422257062Sloos}
423257062Sloos
424257062Sloosstatic int
425257062Sloosbcm_spi_transfer(device_t dev, device_t child, struct spi_command *cmd)
426257062Sloos{
427257062Sloos	struct bcm_spi_softc *sc;
428257062Sloos	int cs, err;
429257062Sloos
430257062Sloos	sc = device_get_softc(dev);
431257062Sloos
432257062Sloos	KASSERT(cmd->tx_cmd_sz == cmd->rx_cmd_sz,
433257062Sloos	    ("TX/RX command sizes should be equal"));
434257062Sloos	KASSERT(cmd->tx_data_sz == cmd->rx_data_sz,
435257062Sloos	    ("TX/RX data sizes should be equal"));
436257062Sloos
437322724Smarius	/* Get the proper chip select for this child. */
438322724Smarius	spibus_get_cs(child, &cs);
439322724Smarius	if (cs < 0 || cs > 2) {
440322724Smarius		device_printf(dev,
441322724Smarius		    "Invalid chip select %d requested by %s\n", cs,
442322724Smarius		    device_get_nameunit(child));
443322724Smarius		return (EINVAL);
444322724Smarius	}
445322724Smarius
446257062Sloos	BCM_SPI_LOCK(sc);
447257062Sloos
448257062Sloos	/* If the controller is in use wait until it is available. */
449257062Sloos	while (sc->sc_flags & BCM_SPI_BUSY)
450257062Sloos		mtx_sleep(dev, &sc->sc_mtx, 0, "bcm_spi", 0);
451257062Sloos
452257062Sloos	/* Now we have control over SPI controller. */
453257062Sloos	sc->sc_flags = BCM_SPI_BUSY;
454257062Sloos
455257062Sloos	/* Clear the FIFO. */
456257062Sloos	bcm_spi_modifyreg(sc, SPI_CS,
457257062Sloos	    SPI_CS_CLEAR_RXFIFO | SPI_CS_CLEAR_TXFIFO,
458257062Sloos	    SPI_CS_CLEAR_RXFIFO | SPI_CS_CLEAR_TXFIFO);
459257062Sloos
460257062Sloos	/* Save a pointer to the SPI command. */
461257062Sloos	sc->sc_cmd = cmd;
462257062Sloos	sc->sc_read = 0;
463257062Sloos	sc->sc_written = 0;
464257062Sloos	sc->sc_len = cmd->tx_cmd_sz + cmd->tx_data_sz;
465257062Sloos
466257062Sloos	/*
467257062Sloos	 * Set the CS for this transaction, enable interrupts and announce
468257062Sloos	 * we're ready to tx.  This will kick off the first interrupt.
469257062Sloos	 */
470257062Sloos	bcm_spi_modifyreg(sc, SPI_CS,
471257062Sloos	    SPI_CS_MASK | SPI_CS_TA | SPI_CS_INTR | SPI_CS_INTD,
472257062Sloos	    cs | SPI_CS_TA | SPI_CS_INTR | SPI_CS_INTD);
473257062Sloos
474257062Sloos	/* Wait for the transaction to complete. */
475257062Sloos	err = mtx_sleep(dev, &sc->sc_mtx, 0, "bcm_spi", hz * 2);
476257062Sloos
477257062Sloos	/* Make sure the SPI engine and interrupts are disabled. */
478257062Sloos	bcm_spi_modifyreg(sc, SPI_CS, SPI_CS_TA | SPI_CS_INTR | SPI_CS_INTD, 0);
479257062Sloos
480322724Smarius	/* Release the controller and wakeup the next thread waiting for it. */
481257062Sloos	sc->sc_flags = 0;
482322724Smarius	wakeup_one(dev);
483322724Smarius	BCM_SPI_UNLOCK(sc);
484257062Sloos
485257062Sloos	/*
486257062Sloos	 * Check for transfer timeout.  The SPI controller doesn't
487257062Sloos	 * return errors.
488257062Sloos	 */
489257062Sloos	if (err == EWOULDBLOCK) {
490257062Sloos		device_printf(sc->sc_dev, "SPI error\n");
491257062Sloos		err = EIO;
492257062Sloos	}
493257062Sloos
494257062Sloos	return (err);
495257062Sloos}
496257062Sloos
497257062Sloosstatic phandle_t
498257062Sloosbcm_spi_get_node(device_t bus, device_t dev)
499257062Sloos{
500257062Sloos
501257062Sloos	/* We only have one child, the SPI bus, which needs our own node. */
502257062Sloos	return (ofw_bus_get_node(bus));
503257062Sloos}
504257062Sloos
505257062Sloosstatic device_method_t bcm_spi_methods[] = {
506257062Sloos	/* Device interface */
507257062Sloos	DEVMETHOD(device_probe,		bcm_spi_probe),
508257062Sloos	DEVMETHOD(device_attach,	bcm_spi_attach),
509257062Sloos	DEVMETHOD(device_detach,	bcm_spi_detach),
510257062Sloos
511257062Sloos	/* SPI interface */
512257062Sloos	DEVMETHOD(spibus_transfer,	bcm_spi_transfer),
513257062Sloos
514257062Sloos	/* ofw_bus interface */
515257062Sloos	DEVMETHOD(ofw_bus_get_node,	bcm_spi_get_node),
516257062Sloos
517257062Sloos	DEVMETHOD_END
518257062Sloos};
519257062Sloos
520257062Sloosstatic devclass_t bcm_spi_devclass;
521257062Sloos
522257062Sloosstatic driver_t bcm_spi_driver = {
523257062Sloos	"spi",
524257062Sloos	bcm_spi_methods,
525257062Sloos	sizeof(struct bcm_spi_softc),
526257062Sloos};
527257062Sloos
528257062SloosDRIVER_MODULE(bcm2835_spi, simplebus, bcm_spi_driver, bcm_spi_devclass, 0, 0);
529