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