/* Copyright 2013-2014 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <lpc.h>
#include <sfc-ctrl.h>

#include <libflash/libflash.h>
#include <libflash/libflash-priv.h>

/* Offset of SFC registers in FW space */
#define SFC_CMDREG_OFFSET	0x00000c00
/* Offset of SFC command buffer in FW space */
#define	SFC_CMDBUF_OFFSET	0x00000d00
/* Offset of flash MMIO mapping in FW space */
#define SFC_MMIO_OFFSET		0x0c000000


/*
 * Register definitions
 */
#define SFC_REG_CONF      0x10 /* CONF: Direct Access Configuration */
#define SFC_REG_CONF_FRZE		(1 << 3)
#define SFC_REG_CONF_ECCEN		(1 << 2)
#define SFC_REG_CONF_DRCD		(1 << 1)
#define SFC_REG_CONF_FLRLD		(1 << 0)

#define SFC_REG_STATUS    0x0C /* STATUS : Status Reg */
#define SFC_REG_STATUS_NX_ON_SHFT	28
#define SFC_REG_STATUS_RWP		(1 << 27)
#define SFC_REG_STATUS_FOURBYTEAD	(1 << 26)
#define SFC_REG_STATUS_ILLEGAL		(1 << 4)
#define SFC_REG_STATUS_ECCERRCNTN	(1 << 3)
#define SFC_REG_STATUS_ECCUEN		(1 << 2)
#define SFC_REG_STATUS_DONE		(1 << 0)

#define SFC_REG_CMD       0x40 /* CMD : Command */
#define SFC_REG_CMD_OPCODE_SHFT		9
#define SFC_REG_CMD_LENGTH_SHFT		0

#define SFC_REG_SPICLK    0x3C /* SPICLK: SPI clock rate config */
#define SFC_REG_SPICLK_OUTDLY_SHFT	24
#define SFC_REG_SPICLK_INSAMPDLY_SHFT	16
#define SFC_REG_SPICLK_CLKHI_SHFT	8
#define SFC_REG_SPICLK_CLKLO_SHFT	0

#define SFC_REG_ADR       0x44 /* ADR : Address */
#define SFC_REG_ERASMS    0x48 /* ERASMS : Small Erase Block Size */
#define SFC_REG_ERASLGS   0x4C /* ERALGS : Large Erase Block Size */
#define SFC_REG_CONF4     0x54 /* CONF4  : SPI Op Code for Small Erase */
#define SFC_REG_CONF5     0x58 /* CONF5  : Small Erase Size config reg */

#define SFC_REG_CONF8     0x64 /* CONF8  : Read Command */
#define SFC_REG_CONF8_CSINACTIVERD_SHFT	18
#define SFC_REG_CONF8_DUMMY_SHFT	8
#define SFC_REG_CONF8_READOP_SHFT	0

#define SFC_REG_ADRCBF    0x80 /* ADRCBF : First Intf NOR Addr Offset */
#define SFC_REG_ADRCMF    0x84 /* ADRCMF : First Intf NOR Allocation */
#define SFC_REG_ADRCBS    0x88 /* ADRCBS : Second Intf NOR Addr Offset */
#define SFC_REG_ADRCMS    0x8C /* ADRCMS : Second Intf NOR Allocation */
#define SFC_REG_OADRNB    0x90 /* OADRNB : Direct Access OBP Window Base Address */
#define SFC_REG_OADRNS    0x94 /* OADRNS : DIrect Access OPB Window Size */

#define SFC_REG_CHIPIDCONF    0x9C /* CHIPIDCONF : config ChipId CMD */
#define SFC_REG_CHIPIDCONF_OPCODE_SHFT	24
#define SFC_REG_CHIPIDCONF_READ		(1 << 23)
#define SFC_REG_CHIPIDCONF_WRITE	(1 << 22)
#define SFC_REG_CHIPIDCONF_USE_ADDR	(1 << 21)
#define SFC_REG_CHIPIDCONF_DUMMY_SHFT	16
#define SFC_REG_CHIPIDCONF_LEN_SHFT	0

/*
 * SFC Opcodes
 */
#define SFC_OP_READRAW      0x03 /* Read Raw */
#define SFC_OP_WRITERAW     0x02 /* Write Raw */
#define SFC_OP_ERASM        0x32 /* Erase Small */
#define SFC_OP_ERALG        0x34 /* Erase Large */
#define SFC_OP_ENWRITPROT   0x53 /* Enable WRite Protect */
#define SFC_OP_CHIPID       0x1F /* Get Chip ID */
#define SFC_OP_STATUS       0x05 /* Get Status */
#define SFC_OP_TURNOFF      0x5E /* Turn Off */
#define SFC_OP_TURNON       0x50 /* Turn On */
#define SFC_OP_ABORT        0x6F /* Super-Abort */
#define SFC_OP_START4BA     0x37 /* Start 4BA */
#define SFC_OP_END4BA       0x69 /* End 4BA */

/* Command buffer size */
#define SFC_CMDBUF_SIZE     256

struct sfc_ctrl {
	/* Erase sizes */
	uint32_t		small_er_size;
	uint32_t		large_er_size;

	/* Current 4b mode */
	bool			mode_4b;

	/* Callbacks */
	struct spi_flash_ctrl	ops;
};

/* Command register support */
static inline int sfc_reg_read(uint8_t reg, uint32_t *val)
{
	uint32_t tmp;
	int rc;

	*val = 0xffffffff;
	rc = lpc_fw_read32(&tmp, SFC_CMDREG_OFFSET + reg);
	if (rc)
		return rc;
	*val = be32_to_cpu(tmp);
	return 0;
}

static inline int sfc_reg_write(uint8_t reg, uint32_t val)
{
	return lpc_fw_write32(cpu_to_be32(val), SFC_CMDREG_OFFSET + reg);
}

static int sfc_buf_write(uint32_t len, const void *data)
{
	uint32_t tmp, off = 0;
	int rc;

	if (len > SFC_CMDBUF_SIZE)
		return FLASH_ERR_PARM_ERROR;

	while (len >= 4) {
		tmp = *(const uint32_t *)data;
		rc = lpc_fw_write32(tmp, SFC_CMDBUF_OFFSET + off);
		if (rc)
			return rc;
		off += 4;
		len -= 4;
		data += 4;
	}
	if (!len)
		return 0;

	/* lpc_fw_write operates on BE values so that's what we layout
	 * in memory with memcpy. The swap in the register on LE doesn't
	 * matter, the result in memory will be in the right order.
	 */
	tmp = -1;
	memcpy(&tmp, data, len);
	return lpc_fw_write32(tmp, SFC_CMDBUF_OFFSET + off);
}

static int sfc_buf_read(uint32_t len, void *data)
{
	uint32_t tmp, off = 0;
	int rc;

	if (len > SFC_CMDBUF_SIZE)
		return FLASH_ERR_PARM_ERROR;

	while (len >= 4) {
		rc = lpc_fw_read32(data, SFC_CMDBUF_OFFSET + off);
		if (rc)
			return rc;
		off += 4;
		len -= 4;
		data += 4;
	}
	if (!len)
		return 0;

	rc = lpc_fw_read32(&tmp, SFC_CMDBUF_OFFSET + off);
	if (rc)
		return rc;
	/* We know tmp contains a big endian value, so memcpy is
	 * our friend here
	 */
	memcpy(data, &tmp, len);
	return 0;
}

/* Polls until SFC indicates command is complete */
static int sfc_poll_complete(void)
{
	uint32_t status, timeout;
	struct timespec ts;

	/*
	 * A full 256 bytes read/write command will take at least
	 * 126us. Smaller commands are faster but we use less of
	 * them. So let's sleep in increments of 100us
	 */
	ts.tv_sec = 0;
	ts.tv_nsec = 100000;

	/*
	 * Use a 1s timeout which should be sufficient for the
	 * commands we use
	 */
	timeout = 10000;

	do {
		int rc;

		rc = sfc_reg_read(SFC_REG_STATUS, &status);
		if (rc)
			return rc;
		if (status & SFC_REG_STATUS_DONE)
			break;
		if (--timeout == 0)
			return FLASH_ERR_CTRL_TIMEOUT;
		nanosleep(&ts, NULL);
	} while (true);

	return 0;
}

static int sfc_exec_command(uint8_t opcode, uint32_t length)
{
	int rc = 0;
	uint32_t cmd_reg = 0;

	if (opcode > 0x7f || length > 0x1ff)
		return FLASH_ERR_PARM_ERROR;

	/* Write command register to start execution */
	cmd_reg |= (opcode << SFC_REG_CMD_OPCODE_SHFT);
	cmd_reg |= (length << SFC_REG_CMD_LENGTH_SHFT);
	rc = sfc_reg_write(SFC_REG_CMD, cmd_reg);
	if (rc)
		return rc;

	/* Wait for command to complete */
	return sfc_poll_complete();
}

static int sfc_chip_id(struct spi_flash_ctrl *ctrl, uint8_t *id_buf,
		       uint32_t *id_size)
{
	uint32_t idconf;
	int rc;

	(void)ctrl;

	if ((*id_size) < 3)
		return FLASH_ERR_PARM_ERROR;

	/*
	 * XXX This will not work in locked down mode but we assume that
	 * in this case, the chip ID command is already properly programmed
	 * and the SFC will ignore this. However I haven't verified...
	 */
	idconf = ((uint64_t)CMD_RDID) << SFC_REG_CHIPIDCONF_OPCODE_SHFT;
	idconf |= SFC_REG_CHIPIDCONF_READ;
        idconf |= (3ul << SFC_REG_CHIPIDCONF_LEN_SHFT);
	(void)sfc_reg_write(SFC_REG_CHIPIDCONF, idconf);

	/* Perform command */
	rc = sfc_exec_command(SFC_OP_CHIPID, 0);
	if (rc)
		return rc;

	/* Read chip ID */
        rc = sfc_buf_read(3, id_buf);
	if (rc)
		return rc;
	*id_size = 3;

	return 0;
}


static int sfc_read(struct spi_flash_ctrl *ctrl, uint32_t pos,
		    void *buf, uint32_t len)
{
	(void)ctrl;

	while(len) {
		uint32_t chunk = len;
		int rc;

		if (chunk > SFC_CMDBUF_SIZE)
			chunk = SFC_CMDBUF_SIZE;
		rc = sfc_reg_write(SFC_REG_ADR, pos);
		if (rc)
			return rc;
		rc = sfc_exec_command(SFC_OP_READRAW, chunk);
		if (rc)
			return rc;
		rc = sfc_buf_read(chunk, buf);
		if (rc)
			return rc;
		len -= chunk;
		pos += chunk;
		buf += chunk;
	}
	return 0;
}

static int sfc_write(struct spi_flash_ctrl *ctrl, uint32_t addr,
		     const void *buf, uint32_t size)
{
	uint32_t chunk;
	int rc;

	(void)ctrl;

	while(size) {
		/* We shall not cross a page boundary */
		chunk = 0x100 - (addr & 0xff);
		if (chunk > size)
			chunk = size;

		/* Write to SFC write buffer */
		rc = sfc_buf_write(chunk, buf);
		if (rc)
			return rc;

		/* Program address */
		rc = sfc_reg_write(SFC_REG_ADR, addr);
		if (rc)
			return rc;

		/* Send command */
		rc = sfc_exec_command(SFC_OP_WRITERAW, chunk);
		if (rc)
			return rc;

		addr += chunk;
		buf += chunk;
		size -= chunk;
	}
	return 0;
}

static int sfc_erase(struct spi_flash_ctrl *ctrl, uint32_t addr,
		     uint32_t size)
{
	struct sfc_ctrl *ct = container_of(ctrl, struct sfc_ctrl, ops);
	uint32_t sm_mask = ct->small_er_size - 1;
	uint32_t lg_mask = ct->large_er_size - 1;
	uint32_t chunk;
	uint8_t cmd;
	int rc;

	while(size) {
		/* Choose erase size for this chunk */
		if (((addr | size) & lg_mask) == 0) {
			chunk = ct->large_er_size;
			cmd = SFC_OP_ERALG;
		} else if (((addr | size) & sm_mask) == 0) {
			chunk = ct->small_er_size;
			cmd = SFC_OP_ERASM;
		} else
			return FLASH_ERR_ERASE_BOUNDARY;

		rc = sfc_reg_write(SFC_REG_ADR, addr);
		if (rc)
			return rc;
		rc = sfc_exec_command(cmd, 0);
		if (rc)
			return rc;
		addr += chunk;
		size -= chunk;
	}
	return 0;
}

static int sfc_setup(struct spi_flash_ctrl *ctrl, uint32_t *tsize)
{
	struct sfc_ctrl *ct = container_of(ctrl, struct sfc_ctrl, ops);
	struct flash_info *info = ctrl->finfo;
	uint32_t er_flags;

	(void)tsize;

	/* Keep non-erase related flags */
	er_flags = ~FL_ERASE_ALL;

	/* Add supported erase sizes */
	if (ct->small_er_size == 0x1000 || ct->large_er_size == 0x1000)
		er_flags |= FL_ERASE_4K;
	if (ct->small_er_size == 0x8000 || ct->large_er_size == 0x8000)
		er_flags |= FL_ERASE_32K;
	if (ct->small_er_size == 0x10000 || ct->large_er_size == 0x10000)
		er_flags |= FL_ERASE_64K;

	/* Mask the flags out */
	info->flags &= er_flags;

	return 0;
}

static int sfc_set_4b(struct spi_flash_ctrl *ctrl, bool enable)
{
	struct sfc_ctrl *ct = container_of(ctrl, struct sfc_ctrl, ops);
	int rc;

	rc = sfc_exec_command(enable ? SFC_OP_START4BA : SFC_OP_END4BA, 0);
	if (rc)
		return rc;
	ct->mode_4b = enable;
	return 0;
}

static void sfc_validate_er_size(uint32_t *size)
{
	if (*size == 0)
		return;

	/* We only support 4k, 32k and 64k */
	if (*size != 0x1000 && *size != 0x8000 && *size != 0x10000) {
		FL_ERR("SFC: Erase size %d bytes unsupported\n", *size);
		*size = 0;
	}
}

static int sfc_init(struct sfc_ctrl *ct)
{
	int rc;
	uint32_t status;

	/*
	 * Assumptions: The controller has been fully initialized
	 * by an earlier FW layer setting the chip ID command, the
	 * erase sizes, and configuring the timings for reads and
	 * writes.
	 *
	 * This driver is meant to be usable if the configuration
	 * is in lock down.
	 *
	 * If that wasn't the case, we could configure some sane
	 * defaults here and tuned values in setup() after the
	 * chip has been identified.
	 */

	/* Read erase sizes from flash */
	rc = sfc_reg_read(SFC_REG_ERASMS, &ct->small_er_size);
	if (rc)
		return rc;
	sfc_validate_er_size(&ct->small_er_size);
	rc = sfc_reg_read(SFC_REG_ERASLGS, &ct->large_er_size);
	if (rc)
		return rc;
	sfc_validate_er_size(&ct->large_er_size);

	/* No erase sizes we can cope with ? Ouch... */
	if ((ct->small_er_size == 0 && ct->large_er_size == 0) ||
	    (ct->large_er_size && (ct->small_er_size > ct->large_er_size))) {
		FL_ERR("SFC: No supported erase sizes !\n");
		return FLASH_ERR_CTRL_CONFIG_MISMATCH;
	}

	FL_INF("SFC: Suppored erase sizes:");
	if (ct->small_er_size)
		FL_INF(" %dKB", ct->small_er_size >> 10);
	if (ct->large_er_size)
		FL_INF(" %dKB", ct->large_er_size >> 10);
	FL_INF("\n");

	/* Read current state of 4 byte addressing */
	rc = sfc_reg_read(SFC_REG_STATUS, &status);
	if (rc)
		return rc;
	ct->mode_4b = !!(status & SFC_REG_STATUS_FOURBYTEAD);

	return 0;
}

int sfc_open(struct spi_flash_ctrl **ctrl)
{
	struct sfc_ctrl *ct;
	int rc;

	*ctrl = NULL;
	ct = malloc(sizeof(*ct));
	if (!ct) {
		FL_ERR("SFC: Failed to allocate\n");
		return FLASH_ERR_MALLOC_FAILED;
	}
	memset(ct, 0, sizeof(*ct));
	ct->ops.chip_id = sfc_chip_id;
	ct->ops.setup = sfc_setup;
	ct->ops.set_4b = sfc_set_4b;
	ct->ops.read = sfc_read;
	ct->ops.write = sfc_write;
	ct->ops.erase = sfc_erase;

	rc = sfc_init(ct);
	if (rc)
		goto fail;
	*ctrl = &ct->ops;
	return 0;
 fail:
	free(ct);
	return rc;
}

void sfc_close(struct spi_flash_ctrl *ctrl)
{
	struct sfc_ctrl *ct = container_of(ctrl, struct sfc_ctrl, ops);

	/* Free the whole lot */
	free(ct);
}

