#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: arspi.c,v 1.17 2025/10/03 13:38:35 thorpej 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/kmem.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/spivar.h>
#include <dev/spi/spiflash.h>
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
#define JOB_WREN 0x10
struct arspi_softc {
struct spi_controller sc_spi;
void *sc_ih;
bool sc_interrupts;
struct spi_transfer *sc_transfer;
struct spi_chunk *sc_wchunk;
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 *);
STATIC int arspi_configure(void *, int, int, int);
STATIC int arspi_transfer(void *, struct spi_transfer *);
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 arbus_attach_args *aa = aux;
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");
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;
spi_transq_init(&sc->sc_transq);
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));
} else
config_interrupts(self, arspi_interrupts);
spibus_attach(self, &sc->sc_spi);
}
void
arspi_interrupts(device_t self)
{
#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)
{
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) {
struct arspi_job *job = st->st_busprivate;
st->st_busprivate = NULL;
kmem_free(job, sizeof(*job));
}
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) {
break;
}
spi_transq_dequeue(&sc->sc_transq);
sc->sc_transfer = st;
}
arspi_update_job(st);
job = st->st_busprivate;
do {
ctl = GETREG(sc, ARSPI_REG_CTL);
} while (ctl & ARSPI_CTL_BUSY);
ctl &= ~(ARSPI_CTL_TXCNT_MASK | ARSPI_CTL_RXCNT_MASK);
if (job->job_flags & JOB_WAIT) {
PUTREG(sc, ARSPI_REG_OPCODE, SPIFLASH_CMD_RDSR);
ctl |= (1 << ARSPI_CTL_TXCNT_SHIFT);
ctl |= (1 << ARSPI_CTL_RXCNT_SHIFT);
} else if (job->job_flags & JOB_WREN) {
PUTREG(sc, ARSPI_REG_OPCODE, SPIFLASH_CMD_WREN);
ctl |= (1 << ARSPI_CTL_TXCNT_SHIFT);
} else {
PUTREG(sc, ARSPI_REG_DATA, job->job_data);
PUTREG(sc, ARSPI_REG_OPCODE, job->job_opcode |
(job->job_addr << 8));
cnt = 1;
cnt += job->job_addrcnt + job->job_txcnt;
ctl |= (cnt << ARSPI_CTL_TXCNT_SHIFT);
cnt = job->job_rxcnt;
ctl |= (cnt << ARSPI_CTL_RXCNT_SHIFT);
}
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) {
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) {
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);
kmem_free(job, sizeof(*job));
}
}
done:
arspi_sched(sc);
}
int
arspi_get_byte(struct spi_chunk **chunkp, uint8_t *bytep)
{
struct spi_chunk *chunk;
chunk = *chunkp;
while (chunk && chunk->chunk_wresid == 0)
chunk = chunk->chunk_next;
if (chunk == NULL) {
return ENODATA;
}
if ((chunk->chunk_rptr) || !(chunk->chunk_wptr)) {
return EINVAL;
}
*bytep = *chunk->chunk_wptr;
chunk->chunk_wptr++;
chunk->chunk_wresid--;
chunk->chunk_rresid--;
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;
while (chunk && chunk->chunk_rresid == 0)
chunk = chunk->chunk_next;
if (chunk == NULL) {
return EOVERFLOW;
}
if ((chunk->chunk_wptr) || !(chunk->chunk_rptr)) {
return EINVAL;
}
*chunk->chunk_rptr = byte;
chunk->chunk_rptr++;
chunk->chunk_wresid--;
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 = kmem_zalloc(sizeof (struct arspi_job), KM_SLEEP);
st->st_busprivate = job;
chunk = st->st_chunks;
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:
job->job_txcnt = 1;
break;
case SPIFLASH_CMD_RDID:
job->job_addrcnt = 3;
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:
job->job_addrcnt = 3;
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) {
if ((rv = arspi_get_byte(&chunk, &byte)) != 0)
return rv;
job->job_opcode = SPIFLASH_CMD_READ;
}
job->job_chunk = chunk;
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;
}
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 = uimin(job->job_wresid, 4);
job->job_rxcnt = uimin(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);
}
}