/*-
* 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.
*/
/*
* 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;
};
/*
* 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.
*/
memset(&sba, 0, sizeof(sba));
sba.sba_controller = &sc->sc_spi;
config_found(self, &sba, spibus_print, CFARGS_NONE);
}
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;
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));
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);
}
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;
/* 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.
*/