/*
 * spi.c
 *
 * Copyright (C) 2019-2020  Sylvain Munaut <tnt@246tNt.com>
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#include <stdbool.h>
#include <stdint.h>

#include "config.h"
#include "spi.h"


struct wb_qpi {
	uint32_t csr;
	uint32_t _rsvd0;
	uint32_t rf_nw;
	uint32_t rf_wt;
	uint32_t _rsvd1[12];
	uint32_t cf[16];
} __attribute__((packed,aligned(4)));

#define CSR_RF_EMPTY	(1 << 15)
#define CSR_RF_FULL	(1 << 14)
#define CSR_RF_OVFL	(1 << 13)
#define CSR_CF_EMPTY	(1 << 11)
#define CSR_CF_FUL	(1 << 10)
#define CSR_CS(n)	((n) << 4)
#define CSR_GRANT	(1 <<  2)
#define CSR_REL		(1 <<  2)
#define CSR_REQ		(1 <<  1)
#define CSR_IDLE	(1 <<  0)

#define CMD_SPI		(0 << 2)
#define CMD_QPI_RD	(1 << 2)
#define CMD_QPI_WR	(2 << 2)
#define CMD_QPI_CMD	(3 << 2)
#define CMD_LEN(n)	((n)-1)

static volatile struct wb_qpi * const qpi_regs = (void*)QPI_BASE;


void
spi_init(void)
{
	/* Nothing to do */
}

void
spi_xfer(unsigned cs, const struct spi_xfer_chunk *xfer, unsigned n)
{
	uint8_t rxd;

	/* Request external control & Setup CS */
	qpi_regs->csr = CSR_CS(cs) | CSR_REQ;
	while (!(qpi_regs->csr & CSR_GRANT));

	/* Run the chunks */
	while (n--) {
		for (unsigned int i=0; i<xfer->len; i++)
		{
			qpi_regs->cf[CMD_SPI | CMD_LEN(1)] = xfer->write ? (xfer->data[i] << 24) : 0x00;
			rxd = qpi_regs->rf_wt;
			if (xfer->read)
				xfer->data[i] = rxd;
		}
		xfer++;
	}

	/* Release external control */
	qpi_regs->csr = CSR_REL;
	while (qpi_regs->csr & CSR_GRANT);
}


#define FLASH_CMD_DEEP_POWER_DOWN	0xb9
#define FLASH_CMD_WAKE_UP		0xab
#define FLASH_CMD_WRITE_ENABLE		0x06
#define FLASH_CMD_WRITE_ENABLE_VOLATILE	0x50
#define FLASH_CMD_WRITE_DISABLE		0x04

#define FLASH_CMD_READ_MANUF_ID		0x9f
#define FLASH_CMD_READ_UNIQUE_ID	0x4b

#define FLASH_CMD_READ_SR1		0x05
#define FLASH_CMD_READ_SR2		0x35
#define FLASH_CMD_WRITE_SR1		0x01

#define FLASH_CMD_READ_DATA		0x03
#define FLASH_CMD_PAGE_PROGRAM		0x02
#define FLASH_CMD_CHIP_ERASE		0x60
#define FLASH_CMD_SECTOR_ERASE		0x20

void
flash_cmd(uint8_t cmd)
{
	struct spi_xfer_chunk xfer[1] = {
		{ .data = (void*)&cmd, .len = 1, .read = false, .write = true,  },
	};
	spi_xfer(SPI_CS_FLASH, xfer, 1);
}

void
flash_deep_power_down(void)
{
	flash_cmd(FLASH_CMD_DEEP_POWER_DOWN);
}

void
flash_wake_up(void)
{
	flash_cmd(FLASH_CMD_WAKE_UP);
}

void
flash_write_enable(void)
{
	flash_cmd(FLASH_CMD_WRITE_ENABLE);
}

void
flash_write_enable_volatile(void)
{
	flash_cmd(FLASH_CMD_WRITE_ENABLE_VOLATILE);
}

void
flash_write_disable(void)
{
	flash_cmd(FLASH_CMD_WRITE_DISABLE);
}

void
flash_manuf_id(void *manuf)
{
	uint8_t cmd = FLASH_CMD_READ_MANUF_ID;
	struct spi_xfer_chunk xfer[2] = {
		{ .data = (void*)&cmd,  .len = 1, .read = false, .write = true,  },
		{ .data = (void*)manuf, .len = 3, .read = true,  .write = false, },
	};
	spi_xfer(SPI_CS_FLASH, xfer, 2);
}

void
flash_unique_id(void *id)
{
	uint8_t cmd = FLASH_CMD_READ_UNIQUE_ID;
	struct spi_xfer_chunk xfer[3] = {
		{ .data = (void*)&cmd, .len = 1, .read = false, .write = true,  },
		{ .data = (void*)0,    .len = 4, .read = false, .write = false, },
		{ .data = (void*)id,   .len = 8, .read = true,  .write = false, },
	};
	spi_xfer(SPI_CS_FLASH, xfer, 3);
}

uint8_t
flash_read_sr(int n)
{
	uint8_t cmd = (n == 2) ? FLASH_CMD_READ_SR2 : FLASH_CMD_READ_SR1;
	uint8_t rv;
	struct spi_xfer_chunk xfer[2] = {
		{ .data = (void*)&cmd, .len = 1, .read = false, .write = true,  },
		{ .data = (void*)&rv,  .len = 1, .read = true,  .write = false, },
	};
	spi_xfer(SPI_CS_FLASH, xfer, 2);
	return rv;
}

void
flash_write_sr(uint8_t sr)
{
	uint8_t cmd[2] = { FLASH_CMD_WRITE_SR1, sr };
	struct spi_xfer_chunk xfer[1] = {
		{ .data = (void*)cmd, .len = 2, .read = false, .write = true,  },
	};
	spi_xfer(SPI_CS_FLASH, xfer, 1);
}

void
flash_read(void *dst, uint32_t addr, unsigned len)
{
	uint8_t cmd[4] = { FLASH_CMD_READ_DATA, ((addr >> 16) & 0xff), ((addr >> 8) & 0xff), (addr & 0xff)  };
	struct spi_xfer_chunk xfer[2] = {
		{ .data = (void*)cmd, .len = 4,   .read = false, .write = true,  },
		{ .data = (void*)dst, .len = len, .read = true,  .write = false, },
	};
	spi_xfer(SPI_CS_FLASH, xfer, 2);
}

void
flash_page_program(const void *src, uint32_t addr, unsigned len)
{
	uint8_t cmd[4] = { FLASH_CMD_PAGE_PROGRAM, ((addr >> 16) & 0xff), ((addr >> 8) & 0xff), (addr & 0xff)  };
	struct spi_xfer_chunk xfer[2] = {
		{ .data = (void*)cmd, .len = 4,   .read = false, .write = true, },
		{ .data = (void*)src, .len = len, .read = false, .write = true, },
	};
	spi_xfer(SPI_CS_FLASH, xfer, 2);
}

void
flash_sector_erase(uint32_t addr)
{
	uint8_t cmd[4] = { FLASH_CMD_SECTOR_ERASE, ((addr >> 16) & 0xff), ((addr >> 8) & 0xff), (addr & 0xff)  };
	struct spi_xfer_chunk xfer[1] = {
		{ .data = (void*)cmd, .len = 4,   .read = false, .write = true,  },
	};
	spi_xfer(SPI_CS_FLASH, xfer, 1);
}