mirror of
https://github.com/Stichting-MINIX-Research-Foundation/netbsd.git
synced 2025-08-12 07:31:12 -04:00
626 lines
15 KiB
C
626 lines
15 KiB
C
/* $NetBSD: arspi.c,v 1.10 2012/10/27 17:18:02 chs Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2006 Urbana-Champaign Independent Media Center.
|
|
* Copyright (c) 2006 Garrett D'Amore.
|
|
* All rights reserved.
|
|
*
|
|
* Portions of this code were written by Garrett D'Amore for the
|
|
* Champaign-Urbana Community Wireless Network Project.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or
|
|
* without modification, are permitted provided that the following
|
|
* conditions are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following
|
|
* disclaimer in the documentation and/or other materials provided
|
|
* with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this
|
|
* software must display the following acknowledgements:
|
|
* This product includes software developed by the Urbana-Champaign
|
|
* Independent Media Center.
|
|
* This product includes software developed by Garrett D'Amore.
|
|
* 4. Urbana-Champaign Independent Media Center's name and Garrett
|
|
* D'Amore's name may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE URBANA-CHAMPAIGN INDEPENDENT
|
|
* MEDIA CENTER AND GARRETT D'AMORE ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE URBANA-CHAMPAIGN INDEPENDENT
|
|
* MEDIA CENTER OR GARRETT D'AMORE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__KERNEL_RCSID(0, "$NetBSD: arspi.c,v 1.10 2012/10/27 17:18:02 chs Exp $");
|
|
|
|
#include "locators.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/cpu.h>
|
|
#include <sys/device.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/systm.h>
|
|
|
|
#include <mips/atheros/include/ar5315reg.h>
|
|
#include <mips/atheros/include/arbusvar.h>
|
|
|
|
#include <mips/atheros/dev/arspireg.h>
|
|
|
|
#include <dev/spi/spiflash.h>
|
|
#include <dev/spi/spivar.h>
|
|
|
|
/*
|
|
* This device is intended only to operate with specific SPI flash
|
|
* parts, and is not a general purpose SPI host. (Or at least if it
|
|
* is, the Linux and eCos sources do not show how to use it as such.)
|
|
* And lack of documentation on the Atheros SoCs is less than helpful.
|
|
*
|
|
* So for now we just "emulate" enough of the host bus framework to
|
|
* make the SPI flash drivers happy.
|
|
*/
|
|
|
|
struct arspi_job {
|
|
uint8_t job_opcode;
|
|
struct spi_chunk *job_chunk;
|
|
uint32_t job_flags;
|
|
uint32_t job_addr;
|
|
uint32_t job_data;
|
|
int job_rxcnt;
|
|
int job_txcnt;
|
|
int job_addrcnt;
|
|
int job_rresid;
|
|
int job_wresid;
|
|
};
|
|
|
|
#define JOB_READ 0x1
|
|
#define JOB_WRITE 0x2
|
|
#define JOB_LAST 0x4
|
|
#define JOB_WAIT 0x8 /* job must wait for WIP bits */
|
|
#define JOB_WREN 0x10 /* WREN needed */
|
|
|
|
struct arspi_softc {
|
|
struct spi_controller sc_spi;
|
|
void *sc_ih;
|
|
bool sc_interrupts;
|
|
|
|
struct spi_transfer *sc_transfer;
|
|
struct spi_chunk *sc_wchunk; /* for partial writes */
|
|
struct spi_transq sc_transq;
|
|
bus_space_tag_t sc_st;
|
|
bus_space_handle_t sc_sh;
|
|
bus_size_t sc_size;
|
|
};
|
|
|
|
#define STATIC
|
|
|
|
STATIC int arspi_match(device_t, cfdata_t, void *);
|
|
STATIC void arspi_attach(device_t, device_t, void *);
|
|
STATIC void arspi_interrupts(device_t);
|
|
STATIC int arspi_intr(void *);
|
|
/* SPI service routines */
|
|
STATIC int arspi_configure(void *, int, int, int);
|
|
STATIC int arspi_transfer(void *, struct spi_transfer *);
|
|
/* internal support */
|
|
STATIC void arspi_poll(struct arspi_softc *);
|
|
STATIC void arspi_done(struct arspi_softc *, int);
|
|
STATIC void arspi_sched(struct arspi_softc *);
|
|
STATIC int arspi_get_byte(struct spi_chunk **, uint8_t *);
|
|
STATIC int arspi_put_byte(struct spi_chunk **, uint8_t);
|
|
STATIC int arspi_make_job(struct spi_transfer *);
|
|
STATIC void arspi_update_job(struct spi_transfer *);
|
|
STATIC void arspi_finish_job(struct spi_transfer *);
|
|
|
|
|
|
CFATTACH_DECL_NEW(arspi, sizeof(struct arspi_softc),
|
|
arspi_match, arspi_attach, NULL, NULL);
|
|
|
|
#define GETREG(sc, o) bus_space_read_4(sc->sc_st, sc->sc_sh, o)
|
|
#define PUTREG(sc, o, v) bus_space_write_4(sc->sc_st, sc->sc_sh, o, v)
|
|
|
|
int
|
|
arspi_match(device_t parent, cfdata_t cf, void *aux)
|
|
{
|
|
struct arbus_attach_args *aa = aux;
|
|
|
|
if (strcmp(aa->aa_name, cf->cf_name) != 0)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
arspi_attach(device_t parent, device_t self, void *aux)
|
|
{
|
|
struct arspi_softc *sc = device_private(self);
|
|
struct spibus_attach_args sba;
|
|
struct arbus_attach_args *aa = aux;
|
|
|
|
/*
|
|
* Map registers.
|
|
*/
|
|
sc->sc_st = aa->aa_bst;
|
|
sc->sc_size = aa->aa_size;
|
|
if (bus_space_map(sc->sc_st, aa->aa_addr, sc->sc_size, 0,
|
|
&sc->sc_sh) != 0) {
|
|
printf(": unable to map registers!\n");
|
|
return;
|
|
}
|
|
|
|
aprint_normal(": Atheros SPI controller\n");
|
|
|
|
/*
|
|
* Initialize SPI controller.
|
|
*/
|
|
sc->sc_spi.sct_cookie = sc;
|
|
sc->sc_spi.sct_configure = arspi_configure;
|
|
sc->sc_spi.sct_transfer = arspi_transfer;
|
|
sc->sc_spi.sct_nslaves = 1;
|
|
|
|
|
|
/*
|
|
* Initialize the queue.
|
|
*/
|
|
spi_transq_init(&sc->sc_transq);
|
|
|
|
/*
|
|
* Enable device interrupts.
|
|
*/
|
|
sc->sc_ih = arbus_intr_establish(aa->aa_cirq, aa->aa_mirq,
|
|
arspi_intr, sc);
|
|
if (sc->sc_ih == NULL) {
|
|
aprint_error("%s: couldn't establish interrupt\n",
|
|
device_xname(self));
|
|
/* just leave it in polled mode */
|
|
} else
|
|
config_interrupts(self, arspi_interrupts);
|
|
|
|
/*
|
|
* Initialize and attach bus attach.
|
|
*/
|
|
sba.sba_controller = &sc->sc_spi;
|
|
(void) config_found_ia(self, "spibus", &sba, spibus_print);
|
|
}
|
|
|
|
void
|
|
arspi_interrupts(device_t self)
|
|
{
|
|
/*
|
|
* we never leave polling mode, because, apparently, we
|
|
* are missing some data about how to drive the SPI in interrupt
|
|
* mode.
|
|
*/
|
|
#if 0
|
|
struct arspi_softc *sc = device_private(self);
|
|
int s;
|
|
|
|
s = splbio();
|
|
sc->sc_interrupts = true;
|
|
splx(s);
|
|
#endif
|
|
}
|
|
|
|
int
|
|
arspi_intr(void *arg)
|
|
{
|
|
struct arspi_softc *sc = arg;
|
|
|
|
while (GETREG(sc, ARSPI_REG_CTL) & ARSPI_CTL_BUSY);
|
|
|
|
arspi_done(sc, 0);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
arspi_poll(struct arspi_softc *sc)
|
|
{
|
|
|
|
while (sc->sc_transfer) {
|
|
arspi_intr(sc);
|
|
}
|
|
}
|
|
|
|
int
|
|
arspi_configure(void *cookie, int slave, int mode, int speed)
|
|
{
|
|
|
|
/*
|
|
* We don't support the full SPI protocol, and hopefully the
|
|
* firmware has programmed a reasonable mode already. So
|
|
* just a couple of quick sanity checks, then bail.
|
|
*/
|
|
if ((mode != 0) || (slave != 0))
|
|
return EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
arspi_transfer(void *cookie, struct spi_transfer *st)
|
|
{
|
|
struct arspi_softc *sc = cookie;
|
|
int rv;
|
|
int s;
|
|
|
|
st->st_busprivate = NULL;
|
|
if ((rv = arspi_make_job(st)) != 0) {
|
|
if (st->st_busprivate) {
|
|
free(st->st_busprivate, M_DEVBUF);
|
|
st->st_busprivate = NULL;
|
|
}
|
|
spi_done(st, rv);
|
|
return rv;
|
|
}
|
|
|
|
s = splbio();
|
|
spi_transq_enqueue(&sc->sc_transq, st);
|
|
if (sc->sc_transfer == NULL) {
|
|
arspi_sched(sc);
|
|
if (!sc->sc_interrupts)
|
|
arspi_poll(sc);
|
|
}
|
|
splx(s);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
arspi_sched(struct arspi_softc *sc)
|
|
{
|
|
struct spi_transfer *st;
|
|
struct arspi_job *job;
|
|
uint32_t ctl, cnt;
|
|
|
|
for (;;) {
|
|
if ((st = sc->sc_transfer) == NULL) {
|
|
if ((st = spi_transq_first(&sc->sc_transq)) == NULL) {
|
|
/* no work left to do */
|
|
break;
|
|
}
|
|
spi_transq_dequeue(&sc->sc_transq);
|
|
sc->sc_transfer = st;
|
|
}
|
|
|
|
arspi_update_job(st);
|
|
job = st->st_busprivate;
|
|
|
|
/* there shouldn't be anything running, but ensure it */
|
|
do {
|
|
ctl = GETREG(sc, ARSPI_REG_CTL);
|
|
} while (ctl & ARSPI_CTL_BUSY);
|
|
/* clear all of the tx and rx bits */
|
|
ctl &= ~(ARSPI_CTL_TXCNT_MASK | ARSPI_CTL_RXCNT_MASK);
|
|
|
|
if (job->job_flags & JOB_WAIT) {
|
|
PUTREG(sc, ARSPI_REG_OPCODE, SPIFLASH_CMD_RDSR);
|
|
/* only the opcode for tx */
|
|
ctl |= (1 << ARSPI_CTL_TXCNT_SHIFT);
|
|
/* and one rx byte */
|
|
ctl |= (1 << ARSPI_CTL_RXCNT_SHIFT);
|
|
} else if (job->job_flags & JOB_WREN) {
|
|
PUTREG(sc, ARSPI_REG_OPCODE, SPIFLASH_CMD_WREN);
|
|
/* just the opcode */
|
|
ctl |= (1 << ARSPI_CTL_TXCNT_SHIFT);
|
|
/* no rx bytes */
|
|
} else {
|
|
/* set the data */
|
|
PUTREG(sc, ARSPI_REG_DATA, job->job_data);
|
|
|
|
/* set the opcode and the address */
|
|
PUTREG(sc, ARSPI_REG_OPCODE, job->job_opcode |
|
|
(job->job_addr << 8));
|
|
|
|
/* now set txcnt */
|
|
cnt = 1; /* opcode */
|
|
cnt += job->job_addrcnt + job->job_txcnt;
|
|
ctl |= (cnt << ARSPI_CTL_TXCNT_SHIFT);
|
|
|
|
/* now set rxcnt */
|
|
cnt = job->job_rxcnt;
|
|
ctl |= (cnt << ARSPI_CTL_RXCNT_SHIFT);
|
|
}
|
|
|
|
/* set the start bit */
|
|
ctl |= ARSPI_CTL_START;
|
|
|
|
PUTREG(sc, ARSPI_REG_CTL, ctl);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
arspi_done(struct arspi_softc *sc, int err)
|
|
{
|
|
struct spi_transfer *st;
|
|
struct arspi_job *job;
|
|
|
|
if ((st = sc->sc_transfer) != NULL) {
|
|
job = st->st_busprivate;
|
|
|
|
if (job->job_flags & JOB_WAIT) {
|
|
if (err == 0) {
|
|
if ((GETREG(sc, ARSPI_REG_DATA) &
|
|
SPIFLASH_SR_BUSY) == 0) {
|
|
/* intermediate wait done */
|
|
job->job_flags &= ~JOB_WAIT;
|
|
goto done;
|
|
}
|
|
}
|
|
} else if (job->job_flags & JOB_WREN) {
|
|
if (err == 0) {
|
|
job->job_flags &= ~JOB_WREN;
|
|
goto done;
|
|
}
|
|
} else if (err == 0) {
|
|
/*
|
|
* When breaking up write jobs, we have to wait until
|
|
* the WIP bit is clear, and we have to separately
|
|
* send WREN for each chunk. These flags facilitate
|
|
* that.
|
|
*/
|
|
if (job->job_flags & JOB_WRITE)
|
|
job->job_flags |= (JOB_WAIT | JOB_WREN);
|
|
job->job_data = GETREG(sc, ARSPI_REG_DATA);
|
|
arspi_finish_job(st);
|
|
}
|
|
|
|
if (err || (job->job_flags & JOB_LAST)) {
|
|
sc->sc_transfer = NULL;
|
|
st->st_busprivate = NULL;
|
|
spi_done(st, err);
|
|
free(job, M_DEVBUF);
|
|
}
|
|
}
|
|
done:
|
|
arspi_sched(sc);
|
|
}
|
|
|
|
int
|
|
arspi_get_byte(struct spi_chunk **chunkp, uint8_t *bytep)
|
|
{
|
|
struct spi_chunk *chunk;
|
|
|
|
chunk = *chunkp;
|
|
|
|
/* skip leading empty (or already consumed) chunks */
|
|
while (chunk && chunk->chunk_wresid == 0)
|
|
chunk = chunk->chunk_next;
|
|
|
|
if (chunk == NULL) {
|
|
return ENODATA;
|
|
}
|
|
|
|
/*
|
|
* chunk must be write only. SPI flash doesn't support
|
|
* any full duplex operations.
|
|
*/
|
|
if ((chunk->chunk_rptr) || !(chunk->chunk_wptr)) {
|
|
return EINVAL;
|
|
}
|
|
|
|
*bytep = *chunk->chunk_wptr;
|
|
chunk->chunk_wptr++;
|
|
chunk->chunk_wresid--;
|
|
chunk->chunk_rresid--;
|
|
/* clearing wptr and rptr makes sanity checks later easier */
|
|
if (chunk->chunk_wresid == 0)
|
|
chunk->chunk_wptr = NULL;
|
|
if (chunk->chunk_rresid == 0)
|
|
chunk->chunk_rptr = NULL;
|
|
while (chunk && chunk->chunk_wresid == 0)
|
|
chunk = chunk->chunk_next;
|
|
|
|
*chunkp = chunk;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
arspi_put_byte(struct spi_chunk **chunkp, uint8_t byte)
|
|
{
|
|
struct spi_chunk *chunk;
|
|
|
|
chunk = *chunkp;
|
|
|
|
/* skip leading empty (or already consumed) chunks */
|
|
while (chunk && chunk->chunk_rresid == 0)
|
|
chunk = chunk->chunk_next;
|
|
|
|
if (chunk == NULL) {
|
|
return EOVERFLOW;
|
|
}
|
|
|
|
/*
|
|
* chunk must be read only. SPI flash doesn't support
|
|
* any full duplex operations.
|
|
*/
|
|
if ((chunk->chunk_wptr) || !(chunk->chunk_rptr)) {
|
|
return EINVAL;
|
|
}
|
|
|
|
*chunk->chunk_rptr = byte;
|
|
chunk->chunk_rptr++;
|
|
chunk->chunk_wresid--; /* technically this was done at send time */
|
|
chunk->chunk_rresid--;
|
|
while (chunk && chunk->chunk_rresid == 0)
|
|
chunk = chunk->chunk_next;
|
|
|
|
*chunkp = chunk;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
arspi_make_job(struct spi_transfer *st)
|
|
{
|
|
struct arspi_job *job;
|
|
struct spi_chunk *chunk;
|
|
uint8_t byte;
|
|
int i, rv;
|
|
|
|
job = malloc(sizeof (struct arspi_job), M_DEVBUF, M_ZERO);
|
|
if (job == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
st->st_busprivate = job;
|
|
|
|
/* skip any leading empty chunks (should not be any!) */
|
|
chunk = st->st_chunks;
|
|
|
|
/* get transfer opcode */
|
|
if ((rv = arspi_get_byte(&chunk, &byte)) != 0)
|
|
return rv;
|
|
|
|
job->job_opcode = byte;
|
|
switch (job->job_opcode) {
|
|
case SPIFLASH_CMD_WREN:
|
|
case SPIFLASH_CMD_WRDI:
|
|
case SPIFLASH_CMD_CHIPERASE:
|
|
break;
|
|
case SPIFLASH_CMD_RDJI:
|
|
job->job_rxcnt = 3;
|
|
break;
|
|
case SPIFLASH_CMD_RDSR:
|
|
job->job_rxcnt = 1;
|
|
break;
|
|
case SPIFLASH_CMD_WRSR:
|
|
/*
|
|
* is this in data, or in address? stick it in data
|
|
* for now.
|
|
*/
|
|
job->job_txcnt = 1;
|
|
break;
|
|
case SPIFLASH_CMD_RDID:
|
|
job->job_addrcnt = 3; /* 3 dummy bytes */
|
|
job->job_rxcnt = 1;
|
|
break;
|
|
case SPIFLASH_CMD_ERASE:
|
|
job->job_addrcnt = 3;
|
|
break;
|
|
case SPIFLASH_CMD_READ:
|
|
job->job_addrcnt = 3;
|
|
job->job_flags |= JOB_READ;
|
|
break;
|
|
case SPIFLASH_CMD_PROGRAM:
|
|
job->job_addrcnt = 3;
|
|
job->job_flags |= JOB_WRITE;
|
|
break;
|
|
case SPIFLASH_CMD_READFAST:
|
|
/*
|
|
* This is a pain in the arse to support, so we will
|
|
* rewrite as an ordinary read. But later, after we
|
|
* obtain the address.
|
|
*/
|
|
job->job_addrcnt = 3; /* 3 address */
|
|
job->job_flags |= JOB_READ;
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < job->job_addrcnt; i++) {
|
|
if ((rv = arspi_get_byte(&chunk, &byte)) != 0)
|
|
return rv;
|
|
job->job_addr <<= 8;
|
|
job->job_addr |= byte;
|
|
}
|
|
|
|
|
|
if (job->job_opcode == SPIFLASH_CMD_READFAST) {
|
|
/* eat the dummy timing byte */
|
|
if ((rv = arspi_get_byte(&chunk, &byte)) != 0)
|
|
return rv;
|
|
/* rewrite this as a read */
|
|
job->job_opcode = SPIFLASH_CMD_READ;
|
|
}
|
|
|
|
job->job_chunk = chunk;
|
|
|
|
/*
|
|
* Now quickly check a few other things. Namely, we are not
|
|
* allowed to have both READ and WRITE.
|
|
*/
|
|
for (chunk = job->job_chunk; chunk; chunk = chunk->chunk_next) {
|
|
if (chunk->chunk_wptr) {
|
|
job->job_wresid += chunk->chunk_wresid;
|
|
}
|
|
if (chunk->chunk_rptr) {
|
|
job->job_rresid += chunk->chunk_rresid;
|
|
}
|
|
}
|
|
|
|
if (job->job_rresid && job->job_wresid) {
|
|
return EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* NB: The Atheros SPI controller runs in little endian mode. So all
|
|
* data accesses must be swapped appropriately.
|
|
*
|
|
* The controller auto-swaps read accesses done through the mapped memory
|
|
* region, but when using SPI directly, we have to do the right thing to
|
|
* swap to or from little endian.
|
|
*/
|
|
|
|
void
|
|
arspi_update_job(struct spi_transfer *st)
|
|
{
|
|
struct arspi_job *job = st->st_busprivate;
|
|
uint8_t byte;
|
|
int i;
|
|
|
|
if (job->job_flags & (JOB_WAIT|JOB_WREN))
|
|
return;
|
|
|
|
job->job_rxcnt = 0;
|
|
job->job_txcnt = 0;
|
|
job->job_data = 0;
|
|
|
|
job->job_txcnt = min(job->job_wresid, 4);
|
|
job->job_rxcnt = min(job->job_rresid, 4);
|
|
|
|
job->job_wresid -= job->job_txcnt;
|
|
job->job_rresid -= job->job_rxcnt;
|
|
|
|
for (i = 0; i < job->job_txcnt; i++) {
|
|
arspi_get_byte(&job->job_chunk, &byte);
|
|
job->job_data |= (byte << (i * 8));
|
|
}
|
|
|
|
if ((!job->job_wresid) && (!job->job_rresid)) {
|
|
job->job_flags |= JOB_LAST;
|
|
}
|
|
}
|
|
|
|
void
|
|
arspi_finish_job(struct spi_transfer *st)
|
|
{
|
|
struct arspi_job *job = st->st_busprivate;
|
|
uint8_t byte;
|
|
int i;
|
|
|
|
job->job_addr += job->job_rxcnt;
|
|
job->job_addr += job->job_txcnt;
|
|
for (i = 0; i < job->job_rxcnt; i++) {
|
|
byte = job->job_data & 0xff;
|
|
job->job_data >>= 8;
|
|
arspi_put_byte(&job->job_chunk, byte);
|
|
}
|
|
}
|
|
|