/* Copyright 2019 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/* MAX32660 Flash Memory Module for Chrome EC */

#include "flash.h"
#include "switch.h"
#include "system.h"
#include "timer.h"
#include "util.h"
#include "watchdog.h"
#include "registers.h"
#include "common.h"
#include "icc_regs.h"
#include "flc_regs.h"

#define CPUTS(outstr) cputs(CC_SYSTEM, outstr)
#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ##args)

/***** Definitions *****/

/// Bit mask that can be used to find the starting address of a page in flash
#define MXC_FLASH_PAGE_MASK ~(MXC_FLASH_PAGE_SIZE - 1)

/// Calculate the address of a page in flash from the page number
#define MXC_FLASH_PAGE_ADDR(page)                                              \
	(MXC_FLASH_MEM_BASE + ((unsigned long)page * MXC_FLASH_PAGE_SIZE))

void flash_operation(void)
{
	volatile uint32_t *line_addr;
	volatile uint32_t __attribute__((unused)) line;

	// Clear the cache
	MXC_ICC->cache_ctrl ^= MXC_F_ICC_CACHE_CTRL_CACHE_EN;
	MXC_ICC->cache_ctrl ^= MXC_F_ICC_CACHE_CTRL_CACHE_EN;

	// Clear the line fill buffer
	line_addr = (uint32_t *)(MXC_FLASH_MEM_BASE);
	line = *line_addr;

	line_addr = (uint32_t *)(MXC_FLASH_MEM_BASE + MXC_FLASH_PAGE_SIZE);
	line = *line_addr;
}

static int flash_busy(void)
{
	return (MXC_FLC->cn &
		(MXC_F_FLC_CN_WR | MXC_F_FLC_CN_ME | MXC_F_FLC_CN_PGE));
}

static int flash_init_controller(void)
{
	// Set flash clock divider to generate a 1MHz clock from the APB clock
	MXC_FLC->clkdiv = SystemCoreClock / 1000000;

	/* Check if the flash controller is busy */
	if (flash_busy()) {
		return EC_ERROR_BUSY;
	}

	/* Clear stale errors */
	if (MXC_FLC->intr & MXC_F_FLC_INTR_AF) {
		MXC_FLC->intr &= ~MXC_F_FLC_INTR_AF;
	}

	/* Unlock flash */
	MXC_FLC->cn = (MXC_FLC->cn & ~MXC_F_FLC_CN_UNLOCK) |
		      MXC_S_FLC_CN_UNLOCK_UNLOCKED;

	return EC_SUCCESS;
}

static int flash_device_page_erase(uint32_t address)
{
	int err;

	if ((err = flash_init_controller()) != EC_SUCCESS)
		return err;

	// Align address on page boundary
	address = address - (address % MXC_FLASH_PAGE_SIZE);

	/* Write paflash_init_controllerde */
	MXC_FLC->cn = (MXC_FLC->cn & ~MXC_F_FLC_CN_ERASE_CODE) |
		      MXC_S_FLC_CN_ERASE_CODE_ERASEPAGE;
	/* Issue page erase command */
	MXC_FLC->addr = address;
	MXC_FLC->cn |= MXC_F_FLC_CN_PGE;

	/* Wait until flash operation is complete */
	while (flash_busy())
		;

	/* Lock flash */
	MXC_FLC->cn &= ~MXC_F_FLC_CN_UNLOCK;

	/* Check access violations */
	if (MXC_FLC->intr & MXC_F_FLC_INTR_AF) {
		MXC_FLC->intr &= ~MXC_F_FLC_INTR_AF;
		return EC_ERROR_UNKNOWN;
	}

	flash_operation();

	return EC_SUCCESS;
}

int flash_physical_write(int offset, int size, const char *data)
{
	int err;
	uint32_t bytes_written;
	uint8_t current_data[4];

	if ((err = flash_init_controller()) != EC_SUCCESS)
		return err;

	// write in 32-bit units until we are 128-bit aligned
	MXC_FLC->cn &= ~MXC_F_FLC_CN_BRST;
	MXC_FLC->cn |= MXC_F_FLC_CN_WDTH;

	// Align the address and read/write if we have to
	if (offset & 0x3) {

		// Figure out how many bytes we have to write to round up the
		// address
		bytes_written = 4 - (offset & 0x3);

		// Save the data currently in the flash
		memcpy(current_data, (void *)(offset & (~0x3)), 4);

		// Modify current_data to insert the data from buffer
		memcpy(&current_data[4 - bytes_written], data, bytes_written);

		// Write the modified data
		MXC_FLC->addr = offset - (offset % 4);
		memcpy((void *)&MXC_FLC->data[0], &current_data, 4);
		MXC_FLC->cn |= MXC_F_FLC_CN_WR;

		/* Wait until flash operation is complete */
		while (flash_busy())
			;

		offset += bytes_written;
		size -= bytes_written;
		data += bytes_written;
	}

	while ((size >= 4) && ((offset & 0x1F) != 0)) {
		MXC_FLC->addr = offset;
		memcpy((void *)&MXC_FLC->data[0], data, 4);
		MXC_FLC->cn |= MXC_F_FLC_CN_WR;

		/* Wait until flash operation is complete */
		while (flash_busy())
			;

		offset += 4;
		size -= 4;
		data += 4;
	}

	if (size >= 16) {

		// write in 128-bit bursts while we can
		MXC_FLC->cn &= ~MXC_F_FLC_CN_WDTH;

		while (size >= 16) {
			MXC_FLC->addr = offset;
			memcpy((void *)&MXC_FLC->data[0], data, 16);
			MXC_FLC->cn |= MXC_F_FLC_CN_WR;

			/* Wait until flash operation is complete */
			while (flash_busy())
				;

			offset += 16;
			size -= 16;
			data += 16;
		}

		// Return to 32-bit writes.
		MXC_FLC->cn |= MXC_F_FLC_CN_WDTH;
	}

	while (size >= 4) {
		MXC_FLC->addr = offset;
		memcpy((void *)&MXC_FLC->data[0], data, 4);
		MXC_FLC->cn |= MXC_F_FLC_CN_WR;

		/* Wait until flash operation is complete */
		while (flash_busy())
			;

		offset += 4;
		size -= 4;
		data += 4;
	}

	if (size > 0) {
		// Save the data currently in the flash
		memcpy(current_data, (void *)(offset), 4);

		// Modify current_data to insert the data from data
		memcpy(current_data, data, size);

		MXC_FLC->addr = offset;
		memcpy((void *)&MXC_FLC->data[0], current_data, 4);
		MXC_FLC->cn |= MXC_F_FLC_CN_WR;

		/* Wait until flash operation is complete */
		while (flash_busy())
			;
	}

	/* Lock flash */
	MXC_FLC->cn &= ~MXC_F_FLC_CN_UNLOCK;

	/* Check access violations */
	if (MXC_FLC->intr & MXC_F_FLC_INTR_AF) {
		MXC_FLC->intr &= ~MXC_F_FLC_INTR_AF;
		return EC_ERROR_UNKNOWN;
	}

	flash_operation();

	return EC_SUCCESS;
}

/*****************************************************************************/
/* Physical layer APIs */

int flash_physical_erase(int offset, int size)
{
	int i;
	int pages;
	int error_status;

	/*
	 * erase 'size' number of bytes starting at address 'offset'
	 */
	/* calculate the number of pages */
	pages = size / CONFIG_FLASH_ERASE_SIZE;
	/* iterate over the number of pages */
	for (i = 0; i < pages; i++) {
		/* erase the page after calculating the start address */
		error_status = flash_device_page_erase(
			offset + (i * CONFIG_FLASH_ERASE_SIZE));
		if (error_status != EC_SUCCESS) {
			return error_status;
		}
	}
	return EC_SUCCESS;
}

int flash_physical_get_protect(int bank)
{
	/* Not protected */
	return 0;
}

uint32_t flash_physical_get_protect_flags(void)
{
	/* no flags set */
	return 0;
}

uint32_t flash_physical_get_valid_flags(void)
{
	/* These are the flags we're going to pay attention to */
	return EC_FLASH_PROTECT_RO_AT_BOOT | EC_FLASH_PROTECT_RO_NOW |
	       EC_FLASH_PROTECT_ALL_NOW;
}

uint32_t flash_physical_get_writable_flags(uint32_t cur_flags)
{
	/* no flags writable */
	return 0;
}

int flash_physical_protect_at_boot(uint32_t new_flags)
{
	/* nothing to do here */
	return EC_SUCCESS;
}

int flash_physical_protect_now(int all)
{
	/* nothing to do here */
	return EC_SUCCESS;
}

/*****************************************************************************/
/* High-level APIs */

int flash_pre_init(void)
{
	return EC_SUCCESS;
}

/*****************************************************************************/
/* Test Commands */

/*
 * Read, Write, and Erase a page of flash memory using chip routines
 * NOTE: This is a DESTRUCTIVE test for the range of flash pages tested
 *       make sure that PAGE_START is beyond your flash code.
 */
static int command_flash_test1(int argc, char **argv)
{
	int i;
	uint8_t *ptr;
	const uint32_t PAGE_START = 9;
	const uint32_t PAGE_END = 32;
	uint32_t page;
	int error_status;
	uint32_t flash_address;
	const int BUFFER_SIZE = 32;
	uint8_t buffer[BUFFER_SIZE];

	/*
	 * As a test, write unique data to each page in this for loop, later
	 * verify data in pages
	 */
	for (page = PAGE_START; page < PAGE_END; page++) {
		flash_address = page * CONFIG_FLASH_ERASE_SIZE;

		/*
		 * erase page
		 */
		error_status = flash_physical_erase(flash_address,
						    CONFIG_FLASH_ERASE_SIZE);
		if (error_status != EC_SUCCESS) {
			CPRINTS("Error with flash_physical_erase\n");
			return EC_ERROR_UNKNOWN;
		}

		/*
		 * verify page was erased
		 */
		// CPRINTS("read flash page %d, address %x, ", page,
		// flash_address);
		ptr = (uint8_t *)flash_address;
		for (i = 0; i < CONFIG_FLASH_ERASE_SIZE; i++) {
			if (*ptr++ != 0xff) {
				CPRINTS("Error with verifying page erase\n");
				return EC_ERROR_UNKNOWN;
			}
		}

		/*
		 * write pattern to page, just write BUFFER_SIZE worth of data
		 */
		for (i = 0; i < BUFFER_SIZE; i++) {
			buffer[i] = i + page;
		}
		error_status = flash_physical_write(flash_address, BUFFER_SIZE,
						    buffer);
		if (error_status != EC_SUCCESS) {
			CPRINTS("Error with flash_physical_write\n");
			return EC_ERROR_UNKNOWN;
		}
	}

	/*
	 * Verify data in pages
	 */
	for (page = PAGE_START; page < PAGE_END; page++) {
		flash_address = page * CONFIG_FLASH_ERASE_SIZE;

		/*
		 * read a portion of flash memory
		 */
		ptr = (uint8_t *)flash_address;
		for (i = 0; i < BUFFER_SIZE; i++) {
			if (*ptr++ != (i + page)) {
				CPRINTS("Error with verifing written test "
					"data\n");
				return EC_ERROR_UNKNOWN;
			}
		}
		CPRINTS("Verified Erase, Write, Read page %d", page);
	}

	/*
	 * Clean up after tests
	 */
	for (page = PAGE_START; page <= PAGE_END; page++) {
		flash_address = page * CONFIG_FLASH_ERASE_SIZE;
		error_status = flash_physical_erase(flash_address,
						    CONFIG_FLASH_ERASE_SIZE);
		if (error_status != EC_SUCCESS) {
			CPRINTS("Error with flash_physical_erase\n");
			return EC_ERROR_UNKNOWN;
		}
	}

	CPRINTS("done command_flash_test1.");
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(flashtest1, command_flash_test1, "flashtest1",
			"Flash chip routine tests");
