/* $NetBSD: arspi.c,v 1.15 2021/08/07 16:18:58 thorpej 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.15 2021/08/07 16:18:58 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/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.
	 */
	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;

	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) {
				/* 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);
			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;

	/* 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 = kmem_zalloc(sizeof (struct arspi_job), KM_SLEEP);

	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 = 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);
	}
}

