xref: /netbsd-src/sys/arch/mips/atheros/dev/arspi.c (revision c7fb772b85b2b5d4cfb282f868f454b4701534fd)
1*c7fb772bSthorpej /* $NetBSD: arspi.c,v 1.15 2021/08/07 16:18:58 thorpej Exp $ */
25bf8068cSgdamore 
35bf8068cSgdamore /*-
45bf8068cSgdamore  * Copyright (c) 2006 Urbana-Champaign Independent Media Center.
55bf8068cSgdamore  * Copyright (c) 2006 Garrett D'Amore.
65bf8068cSgdamore  * All rights reserved.
75bf8068cSgdamore  *
85bf8068cSgdamore  * Portions of this code were written by Garrett D'Amore for the
95bf8068cSgdamore  * Champaign-Urbana Community Wireless Network Project.
105bf8068cSgdamore  *
115bf8068cSgdamore  * Redistribution and use in source and binary forms, with or
125bf8068cSgdamore  * without modification, are permitted provided that the following
135bf8068cSgdamore  * conditions are met:
145bf8068cSgdamore  * 1. Redistributions of source code must retain the above copyright
155bf8068cSgdamore  *    notice, this list of conditions and the following disclaimer.
165bf8068cSgdamore  * 2. Redistributions in binary form must reproduce the above
175bf8068cSgdamore  *    copyright notice, this list of conditions and the following
185bf8068cSgdamore  *    disclaimer in the documentation and/or other materials provided
195bf8068cSgdamore  *    with the distribution.
205bf8068cSgdamore  * 3. All advertising materials mentioning features or use of this
215bf8068cSgdamore  *    software must display the following acknowledgements:
225bf8068cSgdamore  *      This product includes software developed by the Urbana-Champaign
235bf8068cSgdamore  *      Independent Media Center.
245bf8068cSgdamore  *	This product includes software developed by Garrett D'Amore.
255bf8068cSgdamore  * 4. Urbana-Champaign Independent Media Center's name and Garrett
265bf8068cSgdamore  *    D'Amore's name may not be used to endorse or promote products
275bf8068cSgdamore  *    derived from this software without specific prior written permission.
285bf8068cSgdamore  *
295bf8068cSgdamore  * THIS SOFTWARE IS PROVIDED BY THE URBANA-CHAMPAIGN INDEPENDENT
305bf8068cSgdamore  * MEDIA CENTER AND GARRETT D'AMORE ``AS IS'' AND ANY EXPRESS OR
315bf8068cSgdamore  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
325bf8068cSgdamore  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
335bf8068cSgdamore  * ARE DISCLAIMED.  IN NO EVENT SHALL THE URBANA-CHAMPAIGN INDEPENDENT
345bf8068cSgdamore  * MEDIA CENTER OR GARRETT D'AMORE BE LIABLE FOR ANY DIRECT, INDIRECT,
355bf8068cSgdamore  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
365bf8068cSgdamore  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
375bf8068cSgdamore  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
385bf8068cSgdamore  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
395bf8068cSgdamore  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
405bf8068cSgdamore  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
415bf8068cSgdamore  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
425bf8068cSgdamore  */
435bf8068cSgdamore 
445bf8068cSgdamore #include <sys/cdefs.h>
45*c7fb772bSthorpej __KERNEL_RCSID(0, "$NetBSD: arspi.c,v 1.15 2021/08/07 16:18:58 thorpej Exp $");
465bf8068cSgdamore 
475bf8068cSgdamore #include "locators.h"
485bf8068cSgdamore 
495bf8068cSgdamore #include <sys/param.h>
50fa40faf6Smatt #include <sys/bus.h>
51fa40faf6Smatt #include <sys/cpu.h>
525bf8068cSgdamore #include <sys/device.h>
535bf8068cSgdamore #include <sys/errno.h>
54fa40faf6Smatt #include <sys/kernel.h>
559e26d3f3Sthorpej #include <sys/kmem.h>
565bf8068cSgdamore #include <sys/proc.h>
57fa40faf6Smatt #include <sys/systm.h>
585bf8068cSgdamore 
595bf8068cSgdamore #include <mips/atheros/include/ar5315reg.h>
605bf8068cSgdamore #include <mips/atheros/include/arbusvar.h>
615bf8068cSgdamore 
625bf8068cSgdamore #include <mips/atheros/dev/arspireg.h>
635bf8068cSgdamore 
645bf8068cSgdamore #include <dev/spi/spiflash.h>
655bf8068cSgdamore #include <dev/spi/spivar.h>
665bf8068cSgdamore 
675bf8068cSgdamore /*
685bf8068cSgdamore  * This device is intended only to operate with specific SPI flash
695bf8068cSgdamore  * parts, and is not a general purpose SPI host.  (Or at least if it
705bf8068cSgdamore  * is, the Linux and eCos sources do not show how to use it as such.)
715bf8068cSgdamore  * And lack of documentation on the Atheros SoCs is less than helpful.
725bf8068cSgdamore  *
735bf8068cSgdamore  * So for now we just "emulate" enough of the host bus framework to
745bf8068cSgdamore  * make the SPI flash drivers happy.
755bf8068cSgdamore  */
765bf8068cSgdamore 
775bf8068cSgdamore struct arspi_job {
785bf8068cSgdamore 	uint8_t			job_opcode;
795bf8068cSgdamore 	struct spi_chunk	*job_chunk;
805bf8068cSgdamore 	uint32_t		job_flags;
815bf8068cSgdamore 	uint32_t		job_addr;
825bf8068cSgdamore 	uint32_t		job_data;
835bf8068cSgdamore 	int			job_rxcnt;
845bf8068cSgdamore 	int			job_txcnt;
855bf8068cSgdamore 	int			job_addrcnt;
865bf8068cSgdamore 	int			job_rresid;
875bf8068cSgdamore 	int			job_wresid;
885bf8068cSgdamore };
895bf8068cSgdamore 
905bf8068cSgdamore #define	JOB_READ		0x1
915bf8068cSgdamore #define	JOB_WRITE		0x2
925bf8068cSgdamore #define	JOB_LAST		0x4
935bf8068cSgdamore #define	JOB_WAIT		0x8	/* job must wait for WIP bits */
945bf8068cSgdamore #define	JOB_WREN		0x10	/* WREN needed */
955bf8068cSgdamore 
965bf8068cSgdamore struct arspi_softc {
975bf8068cSgdamore 	struct spi_controller	sc_spi;
985bf8068cSgdamore 	void			*sc_ih;
99712239e3Sthorpej 	bool			sc_interrupts;
1005bf8068cSgdamore 
1015bf8068cSgdamore 	struct spi_transfer	*sc_transfer;
1025bf8068cSgdamore 	struct spi_chunk	*sc_wchunk;	/* for partial writes */
1035bf8068cSgdamore 	struct spi_transq	sc_transq;
1045bf8068cSgdamore 	bus_space_tag_t		sc_st;
1055bf8068cSgdamore 	bus_space_handle_t	sc_sh;
1065bf8068cSgdamore 	bus_size_t		sc_size;
1075bf8068cSgdamore };
1085bf8068cSgdamore 
1095bf8068cSgdamore #define	STATIC
1105bf8068cSgdamore 
111cbab9cadSchs STATIC int arspi_match(device_t, cfdata_t, void *);
112cbab9cadSchs STATIC void arspi_attach(device_t, device_t, void *);
113cbab9cadSchs STATIC void arspi_interrupts(device_t);
1145bf8068cSgdamore STATIC int arspi_intr(void *);
1155bf8068cSgdamore /* SPI service routines */
1165bf8068cSgdamore STATIC int arspi_configure(void *, int, int, int);
1175bf8068cSgdamore STATIC int arspi_transfer(void *, struct spi_transfer *);
1185bf8068cSgdamore /* internal support */
1195bf8068cSgdamore STATIC void arspi_poll(struct arspi_softc *);
1205bf8068cSgdamore STATIC void arspi_done(struct arspi_softc *, int);
1215bf8068cSgdamore STATIC void arspi_sched(struct arspi_softc *);
1225bf8068cSgdamore STATIC int arspi_get_byte(struct spi_chunk **, uint8_t *);
1235bf8068cSgdamore STATIC int arspi_put_byte(struct spi_chunk **, uint8_t);
1245bf8068cSgdamore STATIC int arspi_make_job(struct spi_transfer *);
1255bf8068cSgdamore STATIC void arspi_update_job(struct spi_transfer *);
1265bf8068cSgdamore STATIC void arspi_finish_job(struct spi_transfer *);
1275bf8068cSgdamore 
1285bf8068cSgdamore 
129cbab9cadSchs CFATTACH_DECL_NEW(arspi, sizeof(struct arspi_softc),
1305bf8068cSgdamore     arspi_match, arspi_attach, NULL, NULL);
1315bf8068cSgdamore 
1325bf8068cSgdamore #define	GETREG(sc, o)		bus_space_read_4(sc->sc_st, sc->sc_sh, o)
1335bf8068cSgdamore #define	PUTREG(sc, o, v)	bus_space_write_4(sc->sc_st, sc->sc_sh, o, v)
1345bf8068cSgdamore 
1355bf8068cSgdamore int
arspi_match(device_t parent,cfdata_t cf,void * aux)136cbab9cadSchs arspi_match(device_t parent, cfdata_t cf, void *aux)
1375bf8068cSgdamore {
1385bf8068cSgdamore 	struct arbus_attach_args *aa = aux;
1395bf8068cSgdamore 
1405bf8068cSgdamore 	if (strcmp(aa->aa_name, cf->cf_name) != 0)
1415bf8068cSgdamore 		return 0;
1425bf8068cSgdamore 	return 1;
1435bf8068cSgdamore }
1445bf8068cSgdamore 
1455bf8068cSgdamore void
arspi_attach(device_t parent,device_t self,void * aux)146cbab9cadSchs arspi_attach(device_t parent, device_t self, void *aux)
1475bf8068cSgdamore {
1485bf8068cSgdamore 	struct arspi_softc *sc = device_private(self);
1495bf8068cSgdamore 	struct spibus_attach_args sba;
1505bf8068cSgdamore 	struct arbus_attach_args *aa = aux;
1515bf8068cSgdamore 
1525bf8068cSgdamore 	/*
1535bf8068cSgdamore 	 * Map registers.
1545bf8068cSgdamore 	 */
1555bf8068cSgdamore 	sc->sc_st = aa->aa_bst;
1565bf8068cSgdamore 	sc->sc_size = aa->aa_size;
1575bf8068cSgdamore 	if (bus_space_map(sc->sc_st, aa->aa_addr, sc->sc_size, 0,
1585bf8068cSgdamore 		&sc->sc_sh) != 0) {
1595bf8068cSgdamore 		printf(": unable to map registers!\n");
1605bf8068cSgdamore 		return;
1615bf8068cSgdamore 	}
1625bf8068cSgdamore 
1635bf8068cSgdamore 	aprint_normal(": Atheros SPI controller\n");
1645bf8068cSgdamore 
1655bf8068cSgdamore 	/*
1665bf8068cSgdamore 	 * Initialize SPI controller.
1675bf8068cSgdamore 	 */
1685bf8068cSgdamore 	sc->sc_spi.sct_cookie = sc;
1695bf8068cSgdamore 	sc->sc_spi.sct_configure = arspi_configure;
1705bf8068cSgdamore 	sc->sc_spi.sct_transfer = arspi_transfer;
1715bf8068cSgdamore 	sc->sc_spi.sct_nslaves = 1;
1725bf8068cSgdamore 
1735bf8068cSgdamore 
1745bf8068cSgdamore 	/*
1755bf8068cSgdamore 	 * Initialize the queue.
1765bf8068cSgdamore 	 */
1775bf8068cSgdamore 	spi_transq_init(&sc->sc_transq);
1785bf8068cSgdamore 
1795bf8068cSgdamore 	/*
1805bf8068cSgdamore 	 * Enable device interrupts.
1815bf8068cSgdamore 	 */
1825bf8068cSgdamore 	sc->sc_ih = arbus_intr_establish(aa->aa_cirq, aa->aa_mirq,
1835bf8068cSgdamore 	    arspi_intr, sc);
1845bf8068cSgdamore 	if (sc->sc_ih == NULL) {
1855bf8068cSgdamore 		aprint_error("%s: couldn't establish interrupt\n",
1865bf8068cSgdamore 		    device_xname(self));
1875bf8068cSgdamore 		/* just leave it in polled mode */
1885bf8068cSgdamore 	} else
1895bf8068cSgdamore 		config_interrupts(self, arspi_interrupts);
1905bf8068cSgdamore 
1915bf8068cSgdamore 	/*
1925bf8068cSgdamore 	 * Initialize and attach bus attach.
1935bf8068cSgdamore 	 */
1949555f417Stnn 	memset(&sba, 0, sizeof(sba));
1955bf8068cSgdamore 	sba.sba_controller = &sc->sc_spi;
196*c7fb772bSthorpej 	config_found(self, &sba, spibus_print, CFARGS_NONE);
1975bf8068cSgdamore }
1985bf8068cSgdamore 
1995bf8068cSgdamore void
arspi_interrupts(device_t self)200cbab9cadSchs arspi_interrupts(device_t self)
2015bf8068cSgdamore {
2025bf8068cSgdamore 	/*
2035bf8068cSgdamore 	 * we never leave polling mode, because, apparently, we
2045bf8068cSgdamore 	 * are missing some data about how to drive the SPI in interrupt
2055bf8068cSgdamore 	 * mode.
2065bf8068cSgdamore 	 */
2075bf8068cSgdamore #if 0
2085bf8068cSgdamore 	struct arspi_softc *sc = device_private(self);
2095bf8068cSgdamore 	int	s;
2105bf8068cSgdamore 
211b1d9d10dSrmind 	s = splbio();
21209c5f9ccSthorpej 	sc->sc_interrupts = true;
2135bf8068cSgdamore 	splx(s);
2145bf8068cSgdamore #endif
2155bf8068cSgdamore }
2165bf8068cSgdamore 
2175bf8068cSgdamore int
arspi_intr(void * arg)2185bf8068cSgdamore arspi_intr(void *arg)
2195bf8068cSgdamore {
2205bf8068cSgdamore 	struct arspi_softc *sc = arg;
2215bf8068cSgdamore 
2225bf8068cSgdamore 	while (GETREG(sc, ARSPI_REG_CTL) & ARSPI_CTL_BUSY);
2235bf8068cSgdamore 
2245bf8068cSgdamore 	arspi_done(sc, 0);
2255bf8068cSgdamore 
2265bf8068cSgdamore 	return 1;
2275bf8068cSgdamore }
2285bf8068cSgdamore 
2295bf8068cSgdamore void
arspi_poll(struct arspi_softc * sc)2305bf8068cSgdamore arspi_poll(struct arspi_softc *sc)
2315bf8068cSgdamore {
2325bf8068cSgdamore 
2335bf8068cSgdamore 	while (sc->sc_transfer) {
2345bf8068cSgdamore 		arspi_intr(sc);
2355bf8068cSgdamore 	}
2365bf8068cSgdamore }
2375bf8068cSgdamore 
2385bf8068cSgdamore int
arspi_configure(void * cookie,int slave,int mode,int speed)2395bf8068cSgdamore arspi_configure(void *cookie, int slave, int mode, int speed)
2405bf8068cSgdamore {
2415bf8068cSgdamore 
2425bf8068cSgdamore 	/*
2435bf8068cSgdamore 	 * We don't support the full SPI protocol, and hopefully the
2445bf8068cSgdamore 	 * firmware has programmed a reasonable mode already.  So
2455bf8068cSgdamore 	 * just a couple of quick sanity checks, then bail.
2465bf8068cSgdamore 	 */
2475bf8068cSgdamore 	if ((mode != 0) || (slave != 0))
2485bf8068cSgdamore 		return EINVAL;
2495bf8068cSgdamore 
2505bf8068cSgdamore 	return 0;
2515bf8068cSgdamore }
2525bf8068cSgdamore 
2535bf8068cSgdamore int
arspi_transfer(void * cookie,struct spi_transfer * st)2545bf8068cSgdamore arspi_transfer(void *cookie, struct spi_transfer *st)
2555bf8068cSgdamore {
2565bf8068cSgdamore 	struct arspi_softc *sc = cookie;
2575bf8068cSgdamore 	int rv;
2585bf8068cSgdamore 	int s;
2595bf8068cSgdamore 
2605bf8068cSgdamore 	st->st_busprivate = NULL;
2615bf8068cSgdamore 	if ((rv = arspi_make_job(st)) != 0) {
2625bf8068cSgdamore 		if (st->st_busprivate) {
2639e26d3f3Sthorpej 			struct arspi_job *job = st->st_busprivate;
2645bf8068cSgdamore 			st->st_busprivate = NULL;
2659e26d3f3Sthorpej 			kmem_free(job, sizeof(*job));
2665bf8068cSgdamore 		}
2675bf8068cSgdamore 		spi_done(st, rv);
2685bf8068cSgdamore 		return rv;
2695bf8068cSgdamore 	}
2705bf8068cSgdamore 
271b1d9d10dSrmind 	s = splbio();
2725bf8068cSgdamore 	spi_transq_enqueue(&sc->sc_transq, st);
2735bf8068cSgdamore 	if (sc->sc_transfer == NULL) {
2745bf8068cSgdamore 		arspi_sched(sc);
2755bf8068cSgdamore 		if (!sc->sc_interrupts)
2765bf8068cSgdamore 			arspi_poll(sc);
2775bf8068cSgdamore 	}
2785bf8068cSgdamore 	splx(s);
2795bf8068cSgdamore 	return 0;
2805bf8068cSgdamore }
2815bf8068cSgdamore 
2825bf8068cSgdamore void
arspi_sched(struct arspi_softc * sc)2835bf8068cSgdamore arspi_sched(struct arspi_softc *sc)
2845bf8068cSgdamore {
2855bf8068cSgdamore 	struct spi_transfer *st;
2865bf8068cSgdamore 	struct arspi_job *job;
2875bf8068cSgdamore 	uint32_t ctl, cnt;
2885bf8068cSgdamore 
2895bf8068cSgdamore 	for (;;) {
2905bf8068cSgdamore 		if ((st = sc->sc_transfer) == NULL) {
2915bf8068cSgdamore 			if ((st = spi_transq_first(&sc->sc_transq)) == NULL) {
2925bf8068cSgdamore 				/* no work left to do */
2935bf8068cSgdamore 				break;
2945bf8068cSgdamore 			}
2955bf8068cSgdamore 			spi_transq_dequeue(&sc->sc_transq);
2965bf8068cSgdamore 			sc->sc_transfer = st;
2975bf8068cSgdamore 		}
2985bf8068cSgdamore 
2995bf8068cSgdamore 		arspi_update_job(st);
3005bf8068cSgdamore 		job = st->st_busprivate;
3015bf8068cSgdamore 
3025bf8068cSgdamore 		/* there shouldn't be anything running, but ensure it */
3035bf8068cSgdamore 		do {
3045bf8068cSgdamore 			ctl = GETREG(sc, ARSPI_REG_CTL);
3055bf8068cSgdamore 		}  while (ctl & ARSPI_CTL_BUSY);
3065bf8068cSgdamore 		/* clear all of the tx and rx bits */
3075bf8068cSgdamore 		ctl &= ~(ARSPI_CTL_TXCNT_MASK | ARSPI_CTL_RXCNT_MASK);
3085bf8068cSgdamore 
3095bf8068cSgdamore 		if (job->job_flags & JOB_WAIT) {
3105bf8068cSgdamore 			PUTREG(sc, ARSPI_REG_OPCODE, SPIFLASH_CMD_RDSR);
3115bf8068cSgdamore 			/* only the opcode for tx */
3125bf8068cSgdamore 			ctl |= (1 << ARSPI_CTL_TXCNT_SHIFT);
3135bf8068cSgdamore 			/* and one rx byte */
3145bf8068cSgdamore 			ctl |= (1 << ARSPI_CTL_RXCNT_SHIFT);
3155bf8068cSgdamore 		} else if (job->job_flags & JOB_WREN) {
3165bf8068cSgdamore 			PUTREG(sc, ARSPI_REG_OPCODE, SPIFLASH_CMD_WREN);
3175bf8068cSgdamore 			/* just the opcode */
3185bf8068cSgdamore 			ctl |= (1 << ARSPI_CTL_TXCNT_SHIFT);
3195bf8068cSgdamore 			/* no rx bytes */
3205bf8068cSgdamore 		} else {
3215bf8068cSgdamore 			/* set the data */
3225bf8068cSgdamore 			PUTREG(sc, ARSPI_REG_DATA, job->job_data);
3235bf8068cSgdamore 
3245bf8068cSgdamore 			/* set the opcode and the address */
3255bf8068cSgdamore 			PUTREG(sc, ARSPI_REG_OPCODE, job->job_opcode |
3265bf8068cSgdamore 			    (job->job_addr << 8));
3275bf8068cSgdamore 
3285bf8068cSgdamore 			/* now set txcnt */
3295bf8068cSgdamore 			cnt = 1;	/* opcode */
3305bf8068cSgdamore 			cnt += job->job_addrcnt + job->job_txcnt;
3315bf8068cSgdamore 			ctl |= (cnt << ARSPI_CTL_TXCNT_SHIFT);
3325bf8068cSgdamore 
3335bf8068cSgdamore 			/* now set rxcnt */
3345bf8068cSgdamore 			cnt = job->job_rxcnt;
3355bf8068cSgdamore 			ctl |= (cnt << ARSPI_CTL_RXCNT_SHIFT);
3365bf8068cSgdamore 		}
3375bf8068cSgdamore 
3385bf8068cSgdamore 		/* set the start bit */
3395bf8068cSgdamore 		ctl |= ARSPI_CTL_START;
3405bf8068cSgdamore 
3415bf8068cSgdamore 		PUTREG(sc, ARSPI_REG_CTL, ctl);
3425bf8068cSgdamore 		break;
3435bf8068cSgdamore 	}
3445bf8068cSgdamore }
3455bf8068cSgdamore 
3465bf8068cSgdamore void
arspi_done(struct arspi_softc * sc,int err)3475bf8068cSgdamore arspi_done(struct arspi_softc *sc, int err)
3485bf8068cSgdamore {
3495bf8068cSgdamore 	struct spi_transfer *st;
3505bf8068cSgdamore 	struct arspi_job *job;
3515bf8068cSgdamore 
3525bf8068cSgdamore 	if ((st = sc->sc_transfer) != NULL) {
3535bf8068cSgdamore 		job = st->st_busprivate;
3545bf8068cSgdamore 
3555bf8068cSgdamore 		if (job->job_flags & JOB_WAIT) {
3565bf8068cSgdamore 			if (err == 0) {
3575bf8068cSgdamore 				if ((GETREG(sc, ARSPI_REG_DATA) &
3585bf8068cSgdamore 				    SPIFLASH_SR_BUSY) == 0) {
3595bf8068cSgdamore 					/* intermediate wait done */
3605bf8068cSgdamore 					job->job_flags &= ~JOB_WAIT;
3615bf8068cSgdamore 					goto done;
3625bf8068cSgdamore 				}
3635bf8068cSgdamore 			}
3645bf8068cSgdamore 		} else if (job->job_flags & JOB_WREN) {
3655bf8068cSgdamore 			if (err == 0) {
3665bf8068cSgdamore 				job->job_flags &= ~JOB_WREN;
3675bf8068cSgdamore 				goto done;
3685bf8068cSgdamore 			}
3695bf8068cSgdamore 		} else if (err == 0) {
3705bf8068cSgdamore 			/*
3715bf8068cSgdamore 			 * When breaking up write jobs, we have to wait until
3723ed1aa91Swiz 			 * the WIP bit is clear, and we have to separately
3735bf8068cSgdamore 			 * send WREN for each chunk.  These flags facilitate
3745bf8068cSgdamore 			 * that.
3755bf8068cSgdamore 			 */
3765bf8068cSgdamore 			if (job->job_flags & JOB_WRITE)
3775bf8068cSgdamore 				job->job_flags |= (JOB_WAIT | JOB_WREN);
3785bf8068cSgdamore 			job->job_data = GETREG(sc, ARSPI_REG_DATA);
3795bf8068cSgdamore 			arspi_finish_job(st);
3805bf8068cSgdamore 		}
3815bf8068cSgdamore 
3825bf8068cSgdamore 		if (err || (job->job_flags & JOB_LAST)) {
3835bf8068cSgdamore 			sc->sc_transfer = NULL;
3845bf8068cSgdamore 			st->st_busprivate = NULL;
3855bf8068cSgdamore 			spi_done(st, err);
3869e26d3f3Sthorpej 			kmem_free(job, sizeof(*job));
3875bf8068cSgdamore 		}
3885bf8068cSgdamore 	}
3895bf8068cSgdamore done:
3905bf8068cSgdamore 	arspi_sched(sc);
3915bf8068cSgdamore }
3925bf8068cSgdamore 
3935bf8068cSgdamore int
arspi_get_byte(struct spi_chunk ** chunkp,uint8_t * bytep)3945bf8068cSgdamore arspi_get_byte(struct spi_chunk **chunkp, uint8_t *bytep)
3955bf8068cSgdamore {
3965bf8068cSgdamore 	struct spi_chunk *chunk;
3975bf8068cSgdamore 
3985bf8068cSgdamore 	chunk = *chunkp;
3995bf8068cSgdamore 
4005bf8068cSgdamore 	/* skip leading empty (or already consumed) chunks */
4015bf8068cSgdamore 	while (chunk && chunk->chunk_wresid == 0)
4025bf8068cSgdamore 		chunk = chunk->chunk_next;
4035bf8068cSgdamore 
4045bf8068cSgdamore 	if (chunk == NULL) {
4055bf8068cSgdamore 		return ENODATA;
4065bf8068cSgdamore 	}
4075bf8068cSgdamore 
4085bf8068cSgdamore 	/*
4095bf8068cSgdamore 	 * chunk must be write only.  SPI flash doesn't support
4105bf8068cSgdamore 	 * any full duplex operations.
4115bf8068cSgdamore 	 */
4125bf8068cSgdamore 	if ((chunk->chunk_rptr) || !(chunk->chunk_wptr)) {
4135bf8068cSgdamore 		return EINVAL;
4145bf8068cSgdamore 	}
4155bf8068cSgdamore 
4165bf8068cSgdamore 	*bytep = *chunk->chunk_wptr;
4175bf8068cSgdamore 	chunk->chunk_wptr++;
4185bf8068cSgdamore 	chunk->chunk_wresid--;
4195bf8068cSgdamore 	chunk->chunk_rresid--;
4205bf8068cSgdamore 	/* clearing wptr and rptr makes sanity checks later easier */
4215bf8068cSgdamore 	if (chunk->chunk_wresid == 0)
4225bf8068cSgdamore 		chunk->chunk_wptr = NULL;
4235bf8068cSgdamore 	if (chunk->chunk_rresid == 0)
4245bf8068cSgdamore 		chunk->chunk_rptr = NULL;
4255bf8068cSgdamore 	while (chunk && chunk->chunk_wresid == 0)
4265bf8068cSgdamore 		chunk = chunk->chunk_next;
4275bf8068cSgdamore 
4285bf8068cSgdamore 	*chunkp = chunk;
4295bf8068cSgdamore 	return 0;
4305bf8068cSgdamore }
4315bf8068cSgdamore 
4325bf8068cSgdamore int
arspi_put_byte(struct spi_chunk ** chunkp,uint8_t byte)4335bf8068cSgdamore arspi_put_byte(struct spi_chunk **chunkp, uint8_t byte)
4345bf8068cSgdamore {
4355bf8068cSgdamore 	struct spi_chunk *chunk;
4365bf8068cSgdamore 
4375bf8068cSgdamore 	chunk = *chunkp;
4385bf8068cSgdamore 
4395bf8068cSgdamore 	/* skip leading empty (or already consumed) chunks */
4405bf8068cSgdamore 	while (chunk && chunk->chunk_rresid == 0)
4415bf8068cSgdamore 		chunk = chunk->chunk_next;
4425bf8068cSgdamore 
4435bf8068cSgdamore 	if (chunk == NULL) {
4445bf8068cSgdamore 		return EOVERFLOW;
4455bf8068cSgdamore 	}
4465bf8068cSgdamore 
4475bf8068cSgdamore 	/*
4485bf8068cSgdamore 	 * chunk must be read only.  SPI flash doesn't support
4495bf8068cSgdamore 	 * any full duplex operations.
4505bf8068cSgdamore 	 */
4515bf8068cSgdamore 	if ((chunk->chunk_wptr) || !(chunk->chunk_rptr)) {
4525bf8068cSgdamore 		return EINVAL;
4535bf8068cSgdamore 	}
4545bf8068cSgdamore 
4555bf8068cSgdamore 	*chunk->chunk_rptr = byte;
4565bf8068cSgdamore 	chunk->chunk_rptr++;
4575bf8068cSgdamore 	chunk->chunk_wresid--;	/* technically this was done at send time */
4585bf8068cSgdamore 	chunk->chunk_rresid--;
4595bf8068cSgdamore 	while (chunk && chunk->chunk_rresid == 0)
4605bf8068cSgdamore 		chunk = chunk->chunk_next;
4615bf8068cSgdamore 
4625bf8068cSgdamore 	*chunkp = chunk;
4635bf8068cSgdamore 	return 0;
4645bf8068cSgdamore }
4655bf8068cSgdamore 
4665bf8068cSgdamore int
arspi_make_job(struct spi_transfer * st)4675bf8068cSgdamore arspi_make_job(struct spi_transfer *st)
4685bf8068cSgdamore {
4695bf8068cSgdamore 	struct arspi_job *job;
4705bf8068cSgdamore 	struct spi_chunk *chunk;
4715bf8068cSgdamore 	uint8_t byte;
4725bf8068cSgdamore 	int i, rv;
4735bf8068cSgdamore 
4749e26d3f3Sthorpej 	job = kmem_zalloc(sizeof (struct arspi_job), KM_SLEEP);
4755bf8068cSgdamore 
4765bf8068cSgdamore 	st->st_busprivate = job;
4775bf8068cSgdamore 
4785bf8068cSgdamore 	/* skip any leading empty chunks (should not be any!) */
4795bf8068cSgdamore 	chunk = st->st_chunks;
4805bf8068cSgdamore 
4815bf8068cSgdamore 	/* get transfer opcode */
4825bf8068cSgdamore 	if ((rv = arspi_get_byte(&chunk, &byte)) != 0)
4835bf8068cSgdamore 		return rv;
4845bf8068cSgdamore 
4855bf8068cSgdamore 	job->job_opcode = byte;
4865bf8068cSgdamore 	switch (job->job_opcode) {
4875bf8068cSgdamore 	case SPIFLASH_CMD_WREN:
4885bf8068cSgdamore 	case SPIFLASH_CMD_WRDI:
4895bf8068cSgdamore 	case SPIFLASH_CMD_CHIPERASE:
4905bf8068cSgdamore 		break;
4915bf8068cSgdamore 	case SPIFLASH_CMD_RDJI:
4925bf8068cSgdamore 		job->job_rxcnt = 3;
4935bf8068cSgdamore 		break;
4945bf8068cSgdamore 	case SPIFLASH_CMD_RDSR:
4955bf8068cSgdamore 		job->job_rxcnt = 1;
4965bf8068cSgdamore 		break;
4975bf8068cSgdamore 	case SPIFLASH_CMD_WRSR:
4985bf8068cSgdamore 		/*
4995bf8068cSgdamore 		 * is this in data, or in address?  stick it in data
5005bf8068cSgdamore 		 * for now.
5015bf8068cSgdamore 		 */
5025bf8068cSgdamore 		job->job_txcnt = 1;
5035bf8068cSgdamore 		break;
5045bf8068cSgdamore 	case SPIFLASH_CMD_RDID:
5055bf8068cSgdamore 		job->job_addrcnt = 3;	/* 3 dummy bytes */
5065bf8068cSgdamore 		job->job_rxcnt = 1;
5075bf8068cSgdamore 		break;
5085bf8068cSgdamore 	case SPIFLASH_CMD_ERASE:
5095bf8068cSgdamore 		job->job_addrcnt = 3;
5105bf8068cSgdamore 		break;
5115bf8068cSgdamore 	case SPIFLASH_CMD_READ:
5125bf8068cSgdamore 		job->job_addrcnt = 3;
5135bf8068cSgdamore 		job->job_flags |= JOB_READ;
5145bf8068cSgdamore 		break;
5155bf8068cSgdamore 	case SPIFLASH_CMD_PROGRAM:
5165bf8068cSgdamore 		job->job_addrcnt = 3;
5175bf8068cSgdamore 		job->job_flags |= JOB_WRITE;
5185bf8068cSgdamore 		break;
5195bf8068cSgdamore 	case SPIFLASH_CMD_READFAST:
5205bf8068cSgdamore 		/*
5215bf8068cSgdamore 		 * This is a pain in the arse to support, so we will
5225bf8068cSgdamore 		 * rewrite as an ordinary read.  But later, after we
5235bf8068cSgdamore 		 * obtain the address.
5245bf8068cSgdamore 		 */
5255bf8068cSgdamore 		job->job_addrcnt = 3;	/* 3 address */
5265bf8068cSgdamore 		job->job_flags |= JOB_READ;
5275bf8068cSgdamore 		break;
5285bf8068cSgdamore 	default:
5295bf8068cSgdamore 		return EINVAL;
5305bf8068cSgdamore 	}
5315bf8068cSgdamore 
5325bf8068cSgdamore 	for (i = 0; i < job->job_addrcnt; i++) {
5335bf8068cSgdamore 		if ((rv = arspi_get_byte(&chunk, &byte)) != 0)
5345bf8068cSgdamore 			return rv;
5355bf8068cSgdamore 		job->job_addr <<= 8;
5365bf8068cSgdamore 		job->job_addr |= byte;
5375bf8068cSgdamore 	}
5385bf8068cSgdamore 
5395bf8068cSgdamore 
5405bf8068cSgdamore 	if (job->job_opcode == SPIFLASH_CMD_READFAST) {
5415bf8068cSgdamore 		/* eat the dummy timing byte */
5425bf8068cSgdamore 		if ((rv = arspi_get_byte(&chunk, &byte)) != 0)
5435bf8068cSgdamore 			return rv;
5445bf8068cSgdamore 		/* rewrite this as a read */
5455bf8068cSgdamore 		job->job_opcode = SPIFLASH_CMD_READ;
5465bf8068cSgdamore 	}
5475bf8068cSgdamore 
5485bf8068cSgdamore 	job->job_chunk = chunk;
5495bf8068cSgdamore 
5505bf8068cSgdamore 	/*
5515bf8068cSgdamore 	 * Now quickly check a few other things.   Namely, we are not
5525bf8068cSgdamore 	 * allowed to have both READ and WRITE.
5535bf8068cSgdamore 	 */
5545bf8068cSgdamore 	for (chunk = job->job_chunk; chunk; chunk = chunk->chunk_next) {
5555bf8068cSgdamore 		if (chunk->chunk_wptr) {
5565bf8068cSgdamore 			job->job_wresid += chunk->chunk_wresid;
5575bf8068cSgdamore 		}
5585bf8068cSgdamore 		if (chunk->chunk_rptr) {
5595bf8068cSgdamore 			job->job_rresid += chunk->chunk_rresid;
5605bf8068cSgdamore 		}
5615bf8068cSgdamore 	}
5625bf8068cSgdamore 
5635bf8068cSgdamore 	if (job->job_rresid && job->job_wresid) {
5645bf8068cSgdamore 		return EINVAL;
5655bf8068cSgdamore 	}
5665bf8068cSgdamore 
5675bf8068cSgdamore 	return 0;
5685bf8068cSgdamore }
5695bf8068cSgdamore 
570e8ac1cadSgdamore /*
571e8ac1cadSgdamore  * NB: The Atheros SPI controller runs in little endian mode. So all
572e8ac1cadSgdamore  * data accesses must be swapped appropriately.
573e8ac1cadSgdamore  *
574e8ac1cadSgdamore  * The controller auto-swaps read accesses done through the mapped memory
575e8ac1cadSgdamore  * region, but when using SPI directly, we have to do the right thing to
576e8ac1cadSgdamore  * swap to or from little endian.
577e8ac1cadSgdamore  */
578e8ac1cadSgdamore 
5795bf8068cSgdamore void
arspi_update_job(struct spi_transfer * st)5805bf8068cSgdamore arspi_update_job(struct spi_transfer *st)
5815bf8068cSgdamore {
5825bf8068cSgdamore 	struct arspi_job *job = st->st_busprivate;
5835bf8068cSgdamore 	uint8_t byte;
5845bf8068cSgdamore 	int i;
5855bf8068cSgdamore 
5865bf8068cSgdamore 	if (job->job_flags & (JOB_WAIT|JOB_WREN))
5875bf8068cSgdamore 		return;
5885bf8068cSgdamore 
5895bf8068cSgdamore 	job->job_rxcnt = 0;
5905bf8068cSgdamore 	job->job_txcnt = 0;
5915bf8068cSgdamore 	job->job_data = 0;
5925bf8068cSgdamore 
593d1579b2dSriastradh 	job->job_txcnt = uimin(job->job_wresid, 4);
594d1579b2dSriastradh 	job->job_rxcnt = uimin(job->job_rresid, 4);
5955bf8068cSgdamore 
5965bf8068cSgdamore 	job->job_wresid -= job->job_txcnt;
5975bf8068cSgdamore 	job->job_rresid -= job->job_rxcnt;
5985bf8068cSgdamore 
5995bf8068cSgdamore 	for (i = 0; i < job->job_txcnt; i++) {
6005bf8068cSgdamore 		arspi_get_byte(&job->job_chunk, &byte);
601e8ac1cadSgdamore 		job->job_data |= (byte << (i * 8));
6025bf8068cSgdamore 	}
6035bf8068cSgdamore 
6045bf8068cSgdamore 	if ((!job->job_wresid) && (!job->job_rresid)) {
6055bf8068cSgdamore 		job->job_flags |= JOB_LAST;
6065bf8068cSgdamore 	}
6075bf8068cSgdamore }
6085bf8068cSgdamore 
6095bf8068cSgdamore void
arspi_finish_job(struct spi_transfer * st)6105bf8068cSgdamore arspi_finish_job(struct spi_transfer *st)
6115bf8068cSgdamore {
6125bf8068cSgdamore 	struct arspi_job *job = st->st_busprivate;
6135bf8068cSgdamore 	uint8_t	byte;
6145bf8068cSgdamore 	int i;
6155bf8068cSgdamore 
6165bf8068cSgdamore 	job->job_addr += job->job_rxcnt;
6175bf8068cSgdamore 	job->job_addr += job->job_txcnt;
6185bf8068cSgdamore 	for (i = 0; i < job->job_rxcnt; i++) {
6195bf8068cSgdamore 		byte = job->job_data & 0xff;
6205bf8068cSgdamore 		job->job_data >>= 8;
6215bf8068cSgdamore 		arspi_put_byte(&job->job_chunk, byte);
6225bf8068cSgdamore 	}
6235bf8068cSgdamore }
6245bf8068cSgdamore 
625