/***************************************************************************
 *	 Copyright (C) 2023 by Christopher Are								   *
 *	 c.are@jcsmith.fr													   *
 ***************************************************************************/

/***************************************************************************
 *	This version for ADuCM36x is largely based on the following flash	   *
 *	drivers:															   *
 *	  - aducm360.c														   *
 *			Copyright (C) 2015 by Ivan Buliev							   *
 *			i.buliev@mikrosistemi.com									   *
 *	  - aduc702x.c														   *
 *			Copyright (C) 2008 by Kevin McGuire							   *
 *			Copyright (C) 2008 by Marcel Wijlaars						   *
 *			Copyright (C) 2009 by Michael Ashton						   *
 *	 and																   *
 *	  - stm32f1x.c														   *
 *			Copyright (C) 2005 by Dominic Rath							   *
 *			Dominic.Rath@gmx.de											   *
 *																		   *
 *			Copyright (C) 2008 by Spencer Oliver						   *
 *			spen@spen-soft.co.uk										   *
 *																		   *
 *			Copyright (C) 2011 by Andreas Fritiofson					   *
 *			andreas.fritiofson@gmail.com								   *
 ***************************************************************************/

#include "general.h"
#include "target.h"
#include "target_internal.h"
#include "cortexm.h"
#include "adiv5.h"


#define ADUCM36x_CPUID_ID				0x412FC231	// 41 = ARM; 2 = Revision; F = Always 0xF; C23 = Cortex-M3; 1 = Patch

#define ADUCM36x_SRAM_BASE				0x20000000
#define ADUCM36x_SRAM_SIZE				0x5FFFU		// 8kB SRAM
#define ADUCM36x_FLASH_BASE				0x00000000
#define ADUCM36x_FLASH_SIZE				0x3FFFFU	// 256kB

#define ADUCM36x_FLASH_CTRL				0x40002800
#define ADUCM36x_FLASH_FEESTA			0x0000
#define ADUCM36x_FLASH_FEECON0			0x0004
#define ADUCM36x_FLASH_FEECMD			0x0008
#define ADUCM36x_FLASH_FEEADR0L			0x0010
#define ADUCM36x_FLASH_FEEADR0H			0x0014
#define ADUCM36x_FLASH_FEEADR1L			0x0018
#define ADUCM36x_FLASH_FEEADR1H			0x001C
#define ADUCM36x_FLASH_FEEKEY			0x0020
#define ADUCM36x_FLASH_FEEPROL			0x0028
#define ADUCM36x_FLASH_FEEPROH			0x002C
#define ADUCM36x_FLASH_FEESIGL			0x0030
#define ADUCM36x_FLASH_FEESIGH			0x0034
#define ADUCM36x_FLASH_FEECON1			0x0038
#define ADUCM36x_FLASH_FEEADRAL			0x0048
#define ADUCM36x_FLASH_FEEADRAH			0x004C
#define ADUCM36x_FLASH_FEEAEN0			0x0078
#define ADUCM36x_FLASH_FEEAEN1			0x007C
#define ADUCM36x_FLASH_FEEAEN2			0x0080

#define ADUCM36x_FLASH_BLOCKSIZE		16384U
#define ADUCM36x_FLASH_PAGESIZE			2048U
#define ADUCM36x_FLASH_PAGE_MSK			(0xFFFFFFFFu & ~(ADUCM36x_FLASH_PAGESIZE - 1U))


static bool aducm36x_attach(target *t);
static int aducm36x_flash_erase(struct target_flash *f, long unsigned int addr, unsigned int len);
static int aducm36x_flash_write(struct target_flash *f, long unsigned int dest, const void *src, unsigned int len);

static bool aducm36x_flash_mass_erase(target *t);
static bool aducm36x_page_erase(target *t, uint32_t paddr);

static bool aducm36x_check_flash_completion(target *t, unsigned int timeout_ms);
static bool aducm36x_set_write_enable(target *t, int enable);
static void aducm36x_flash_unlock(target *t);


static void aducm36x_add_flash(target *t) {
	struct target_flash *f = calloc(1, sizeof(*f));
	if (!f) { /* calloc failed: heap exhaustion */
		DEBUG_WARN("calloc: failed in %s\n", __func__);
		return;
	}

	f->start = ADUCM36x_FLASH_BASE;
	f->length = ADUCM36x_FLASH_SIZE;
	f->blocksize = ADUCM36x_FLASH_BLOCKSIZE;
	f->erase = aducm36x_flash_erase;
	f->write = aducm36x_flash_write;
	f->erased = 0xffU;
	target_add_flash(t, f);
}

bool aducm36x_probe(target *t) {
	/* Positively identify the target device somehow */
	if (target_mem_read32(t, CORTEXM_CPUID) != ADUCM36x_CPUID_ID)
		return false;

	t->driver = "Analog Device - ADuCM36x";
	//t->mass_erase = aducm36x_flash_mass_erase;
	t->attach = aducm36x_attach;
	
	return true;
}

static bool aducm36x_attach(target *t) {
	if (!cortexm_attach(t))
		return false;

	/*
	 * Because we are in attach, which can be called multiple times for a device, we *must*
	 * free any existing map before rebuilding it. Failure to do so will result in unpredictable behaviour.
	 */
	target_mem_map_free(t);
	aducm36x_add_flash(t);
	target_add_ram(t, ADUCM36x_SRAM_BASE, ADUCM36x_SRAM_SIZE);

	return true;
}

/* ----------------------------------------------------------------------- */
/*
 * chip erase          2.4/ 21 ms
 * page erase          2.4/ 21 ms
 * page programm       0.9/ 12 ms
 */
static int aducm36x_flash_erase(struct target_flash *f, long unsigned int addr, unsigned int len) {
	int32_t result = true;
	target *t = f->t;
	DEBUG_INFO("Erase addr 0x%08" PRIx32 " len 0x%" PRIx32 "\n", addr, (uint32_t)len);
	
	if (addr & (f->blocksize - 1U)) {
		DEBUG_WARN("Unaligned erase\n");
		return false;
	}
	if ((addr < f->start) || (addr >= f->start + f->length)) {
		DEBUG_WARN("Address is invalid\n");
		return false;
	}
	addr -= f->start;
	len = ALIGN(len, f->blocksize);
	len = MIN(len, f->length - addr);
	const bool full_erase = addr == f->start && len == f->length;
	
	if (full_erase) {
		aducm36x_flash_mass_erase(t);
	} else {
		while (len) {
			result = aducm36x_page_erase(t, addr);
			if (!result) {
				DEBUG_WARN("Erase failed!\n");
				break;
			}
			
			if (len >= ADUCM36x_FLASH_PAGESIZE) {
				const uint32_t chunk = len & ADUCM36x_FLASH_PAGE_MSK;
				len -= chunk;
				addr += chunk;
			} else {
				len = 0;
			}
		}
	}
	
	return result;
}

static int aducm36x_flash_write(struct target_flash *f, long unsigned int dest, const void *src, unsigned int len) {
	int32_t result = true;
	target *t = f->t;
	
	DEBUG_INFO("RP Write %08" PRIx32 " len 0x%" PRIx32 "\n", dest, (uint32_t)len);
	if ((dest & 0xff) || (len & 0xff)) {
		DEBUG_WARN("Unaligned erase\n");
		return false;
	}
	
	dest -= f->start;
	
	/* Clear any old status */
	target_mem_read32(t, ADUCM36x_FLASH_CTRL + ADUCM36x_FLASH_FEESTA);
	
	/* Enable the writing to the flash*/
	aducm36x_set_write_enable(t, 1);
	
	/* Unlock for writing */
	aducm36x_flash_unlock(t);
	
	/* Use generic CortexM write function to do the work */
	cortexm_mem_write_sized(t, dest, src, len, ALIGN_WORD);
	
	/* Check the result */
	result = aducm36x_check_flash_completion(t, 50);
	if (result != true) {
		DEBUG_WARN("flash write failed at 0x%08" PRIx32, dest);
		aducm36x_set_write_enable(t, 0);
	}
	
	return result;
}

static bool aducm36x_flash_mass_erase(target *t) {
	bool result = true;
	
	/* Clear any old status */
	target_mem_read32(t, ADUCM36x_FLASH_CTRL + ADUCM36x_FLASH_FEESTA);
	
	/* Enable the writing to the flash*/
	aducm36x_set_write_enable(t, 1);
	
	/* Unlock for writing */
	aducm36x_flash_unlock(t);
	
	/* Issue the 'MASSERASE' command */
	target_mem_write32(t, ADUCM36x_FLASH_CTRL+ADUCM36x_FLASH_FEECMD, 0x00000003);
	
	/* Check the result */
	result = aducm36x_check_flash_completion(t, 3500);
	if (result != true) {
		DEBUG_WARN("mass erase failed.");
		aducm36x_set_write_enable(t, 0);
	}
	
	return result;
}

static bool aducm36x_page_erase(target *t, uint32_t paddr) {
	bool result = true;

	/* Clear any old status */
	target_mem_read32(t, ADUCM36x_FLASH_CTRL + ADUCM36x_FLASH_FEESTA);

	/* Enable the writing to the flash*/
	aducm36x_set_write_enable(t, 1);

	/* Unlock for writing */
	aducm36x_flash_unlock(t);
	
	/* Write the sector address */
	target_mem_write32(t, ADUCM36x_FLASH_CTRL+ADUCM36x_FLASH_FEEADR0L, paddr & 0xFFFF);
	target_mem_write32(t, ADUCM36x_FLASH_CTRL+ADUCM36x_FLASH_FEEADR0H, (paddr>>16) & 0xFFFF);
	/* Issue the 'ERASEPAGE' command */
	target_mem_write32(t, ADUCM36x_FLASH_CTRL+ADUCM36x_FLASH_FEECMD, 0x00000001);

	/* Check the result */
	result = aducm36x_check_flash_completion(t, 50);
	if (result != true) {
		DEBUG_WARN("page erase failed at 0x%08" PRIx32, paddr);
		aducm36x_set_write_enable(t, 0);
	}

	return result;
}

/* ----------------------------------------------------------------------- */
/* sets FEECON0 bit 2
 * enable = 1 enables writes & erases, 0 disables them */
static bool aducm36x_set_write_enable(target *t, int enable) {
	/* don't bother to preserve int enable bit here */
	uint32_t	value;

	value = target_mem_read32(t, ADUCM36x_FLASH_CTRL + ADUCM36x_FLASH_FEECON0);
	if (enable)
		value |= 0x00000004;
	else
		value &= ~0x00000004;
	target_mem_write32(t, ADUCM36x_FLASH_CTRL + ADUCM36x_FLASH_FEECON0, value);

	return true;
}

/* ----------------------------------------------------------------------- */
/* wait up to timeout_ms for controller to not be busy,
 * then check whether the command passed or failed. */
static bool aducm36x_check_flash_completion(target *t, unsigned int timeout_ms) {
	uint32_t v = 1;
	platform_timeout timeout;

	platform_timeout_set(&timeout, timeout_ms);
	while (1) {
		v = target_mem_read32(t, ADUCM36x_FLASH_CTRL + ADUCM36x_FLASH_FEESTA);
		if ((v & 0x00000001) == 0)
			break;
		
		if (platform_timeout_is_expired(&timeout))
			break;
	}

	if (!(v & 0x00000004))	/* b2 */
		return false;

	return true;
}

static void aducm36x_flash_unlock(target *t)
{
	target_mem_write32(t, ADUCM36x_FLASH_CTRL + ADUCM36x_FLASH_FEEKEY, 0x0000F456);
	target_mem_write32(t, ADUCM36x_FLASH_CTRL + ADUCM36x_FLASH_FEEKEY, 0x0000F123);
}
