// SPDX-License-Identifier: GPL-2.0-or-later

/***************************************************************************
 *   Copyright (C) 2019 by Andreas Bolsch <andreas.bolsch@mni.thm.de	   *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "imp.h"
#include "spi.h"
#include "sfdp.h"

#define SFDP_MAGIC			0x50444653
#define SFDP_ACCESS_PROT	0xFF
#define SFDP_BASIC_FLASH	0xFF00
#define SFDP_4BYTE_ADDR		0xFF84

static const char *sfdp_name = "sfdp";

struct sfdp_hdr {
	uint32_t			signature;
	uint32_t			revision;
};

struct sfdp_phdr {
	uint32_t			revision;
	uint32_t			ptr;
};

struct sfdp_basic_flash_param {
	uint32_t			fast_addr;		/* 01: fast read and 3/4 address bytes */
	uint32_t			density;		/* 02: memory density */
	uint32_t			fast_1x4;		/* 03: 1-1-4 and 1-4-4 fast read */
	uint32_t			fast_1x2;		/* 04: 1-2-2 and 1-1-2 fast read */
	uint32_t			fast_444;		/* 05: 4-4-4 and 2-2-2 fast read */
	uint32_t			read_222;		/* 06: 2-2-2 fast read instr and dummy */
	uint32_t			read_444;		/* 07: 4-4-4 fast read instr and dummy */
	uint32_t			erase_t12;		/* 08: erase types 1, 2 */
	uint32_t			erase_t34;		/* 09: erase types 3, 4 */
	uint32_t			erase_time;		/* 10: erase times for types 1 - 4 */
	uint32_t			chip_byte;		/* 11: chip erase time, byte prog time, page prog */
	uint32_t			susp_time;		/* 12: suspend and resume times */
	uint32_t			susp_instr;		/* 13: suspend and resume instr */
	uint32_t			pwrd_instr;		/* 14: powerdown instr */
	uint32_t			quad_req;		/* 15: quad enable requirements */
	uint32_t			addr_reset;		/* 16: 3-/4-byte addressing and reset */
	uint32_t			read_1x8;		/* 17: 1-1-8 and 1-8-8 fast read instr and dummy */
	uint32_t			dtr_drive;		/* 18: dtr modes and drive strength */
	uint32_t			octal_req;		/* 19: octal enable requirements */
	uint32_t			speed_888;		/* 20: speed in 8-8-8 modes */
};

struct sfdp_4byte_addr_param {
	uint32_t			flags;			/* 01: various flags */
	uint32_t			erase_t1234;	/* 02: erase commands */
};

/* Try to get parameters from flash via SFDP */
int spi_sfdp(struct flash_bank *bank, struct flash_device *dev,
	read_sfdp_block_t read_sfdp_block)
{
	struct sfdp_hdr header;
	struct sfdp_phdr *pheaders = NULL;
	uint32_t *ptable = NULL;
	unsigned int j, k, nph;
	int retval, erase_type = 0;

	memset(dev, 0, sizeof(struct flash_device));

	/* retrieve SFDP header */
	memset(&header, 0, sizeof(header));
	retval = read_sfdp_block(bank, 0x0, sizeof(header) >> 2, (uint32_t *)&header);
	if (retval != ERROR_OK)
		return retval;
	LOG_DEBUG("header 0x%08" PRIx32 " 0x%08" PRIx32, header.signature, header.revision);
	if (header.signature != SFDP_MAGIC) {
		LOG_INFO("no SDFP found");
		return ERROR_FLASH_BANK_NOT_PROBED;
	}
	if (((header.revision >> 24) & 0xFF) != SFDP_ACCESS_PROT) {
		LOG_ERROR("access protocol 0x%02x not implemented",
			(header.revision >> 24) & 0xFFU);
		return ERROR_FLASH_BANK_NOT_PROBED;
	}

	/* retrieve table of parameter headers */
	nph = ((header.revision >> 16) & 0xFF) + 1;
	LOG_DEBUG("parameter headers: %d", nph);
	pheaders = malloc(sizeof(struct sfdp_phdr) * nph);
	if (!pheaders) {
		LOG_ERROR("not enough memory");
		return ERROR_FAIL;
	}
	memset(pheaders, 0, sizeof(struct sfdp_phdr) * nph);
	retval = read_sfdp_block(bank, sizeof(header),
		(sizeof(struct sfdp_phdr) >> 2) * nph, (uint32_t *)pheaders);
	if (retval != ERROR_OK)
		goto err;

	for (k = 0; k < nph; k++) {
		uint8_t words = (pheaders[k].revision >> 24) & 0xFF;
		uint16_t id = (((pheaders[k].ptr) >> 16) & 0xFF00) | (pheaders[k].revision & 0xFF);
		uint32_t ptr = pheaders[k].ptr & 0xFFFFFF;

		LOG_DEBUG("pheader %d len=0x%02" PRIx8 " id=0x%04" PRIx16
			" ptr=0x%06" PRIx32, k, words, id, ptr);

		/* retrieve parameter table */
		ptable = malloc(words << 2);
		if (!ptable) {
			LOG_ERROR("not enough memory");
			retval = ERROR_FAIL;
			goto err;
		}
		retval = read_sfdp_block(bank, ptr, words, ptable);
		if (retval != ERROR_OK)
			goto err;

		for (j = 0; j < words; j++)
			LOG_DEBUG("word %02d 0x%08X", j + 1, ptable[j]);

		if (id == SFDP_BASIC_FLASH) {
			struct sfdp_basic_flash_param *table = (struct sfdp_basic_flash_param *)ptable;
			uint16_t erase;

			if (words < 9) {
				LOG_ERROR("id=0x%04" PRIx16 " invalid length %d", id, words);
				retval = ERROR_FLASH_BANK_NOT_PROBED;
				goto err;
			}

			LOG_DEBUG("basic flash parameter table");
			/* dummy device name */
			dev->name = sfdp_name;

			/* default instructions */
			dev->read_cmd = SPIFLASH_READ;
			dev->pprog_cmd = SPIFLASH_PAGE_PROGRAM;
			dev->chip_erase_cmd = SPIFLASH_MASS_ERASE;

			/* get device size */
			if (table->density & (1UL << 31))
				dev->size_in_bytes = 1UL << ((table->density & ~(1UL << 31)) - 3);
			else
				dev->size_in_bytes = (table->density + 1) >> 3;

			/* 2-2-2 read instruction, not used */
			if (table->fast_444 & (1UL << 0))
				dev->qread_cmd = (table->read_222 >> 24) & 0xFF;

			/* 4-4-4 read instruction */
			if (table->fast_444 & (1UL << 4))
				dev->qread_cmd = (table->read_444 >> 24) & 0xFF;

			/* find the largest erase block size and instruction */
			erase = (table->erase_t12 >> 0) & 0xFFFF;
			erase_type = 1;
			if (((table->erase_t12 >> 16) & 0xFF) > (erase & 0xFF)) {
				erase = (table->erase_t12 >> 16) & 0xFFFF;
				erase_type = 2;
			}
			if (((table->erase_t34 >> 0) & 0xFF) > (erase & 0xFF)) {
				erase = (table->erase_t34 >> 0) & 0xFFFF;
				erase_type = 3;
			}
			if (((table->erase_t34 >> 16) & 0xFF) > (erase & 0xFF)) {
				erase = (table->erase_t34 >> 16) & 0xFFFF;
				erase_type = 4;
			}
			dev->erase_cmd = (erase >> 8) & 0xFF;
			dev->sectorsize = 1UL << (erase & 0xFF);

			if ((offsetof(struct sfdp_basic_flash_param, chip_byte) >> 2) < words) {
				/* get Program Page Size, if chip_byte present, that's optional */
				dev->pagesize = 1UL << ((table->chip_byte >> 4) & 0x0F);
			} else {
				/* no explicit page size specified ... */
				if (table->fast_addr & (1UL << 2)) {
					/* Write Granularity = 1, use 64 bytes */
					dev->pagesize = 1UL << 6;
				} else {
					/* Write Granularity = 0, use 16 bytes */
					dev->pagesize = 1UL << 4;
				}
			}

			if (dev->size_in_bytes > (1UL << 24)) {
				if (((table->fast_addr >> 17) & 0x3) == 0x0)
					LOG_ERROR("device needs paging - not implemented");

				/* 4-byte addresses needed if more than 16 MBytes */
				if (((offsetof(struct sfdp_basic_flash_param, addr_reset) >> 2) < words) &&
					(table->addr_reset & (1UL << 29))) {
					/* dedicated 4-byte-address instructions, hopefully these ...
					 * this entry is unfortunately optional as well
					 * a subsequent 4-byte address table may overwrite this */
					dev->read_cmd = 0x13;
					dev->pprog_cmd = 0x12;
					dev->erase_cmd = 0xDC;
					if (dev->qread_cmd != 0)
						dev->qread_cmd = 0xEC;
				} else if (((table->fast_addr >> 17) & 0x3) == 0x1)
					LOG_INFO("device has to be switched to 4-byte addresses");
			}
		} else if (id == SFDP_4BYTE_ADDR) {
			struct sfdp_4byte_addr_param *table = (struct sfdp_4byte_addr_param *)ptable;

			if (words >= (offsetof(struct sfdp_4byte_addr_param, erase_t1234)
				+ sizeof(table->erase_t1234)) >> 2) {
				LOG_INFO("4-byte address parameter table");

				/* read and page program instructions */
				if (table->flags & (1UL << 0))
					dev->read_cmd = 0x13;
				if (table->flags & (1UL << 5))
					dev->qread_cmd = 0xEC;
				if (table->flags & (1UL << 6))
					dev->pprog_cmd = 0x12;

				/* erase instructions */
				if ((erase_type == 1) && (table->flags & (1UL << 9)))
					dev->erase_cmd = (table->erase_t1234 >> 0) & 0xFF;
				else if ((erase_type == 2) && (table->flags & (1UL << 10)))
					dev->erase_cmd = (table->erase_t1234 >> 8) & 0xFF;
				else if ((erase_type == 3) && (table->flags & (1UL << 11)))
					dev->erase_cmd = (table->erase_t1234 >> 16) & 0xFF;
				else if ((erase_type == 4) && (table->flags & (1UL << 12)))
					dev->erase_cmd = (table->erase_t1234 >> 24) & 0xFF;
			} else
				LOG_ERROR("parameter table id=0x%04" PRIx16 " invalid length %d", id, words);
		} else
			LOG_DEBUG("unimplemented parameter table id=0x%04" PRIx16, id);

		free(ptable);
		ptable = NULL;
	}

	if (erase_type != 0) {
		LOG_INFO("valid SFDP detected");
		retval = ERROR_OK;
	} else {
		LOG_ERROR("incomplete/invalid SFDP");
		retval = ERROR_FLASH_BANK_NOT_PROBED;
	}

err:
	free(pheaders);
	free(ptable);

	return retval;
}
