/***************************************************************************
 *   Copyright (C) 2018 by Texas Instruments, Inc.                         *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/

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

#include "imp.h"
#include "msp432.h"
#include <helper/binarybuffer.h>
#include <helper/time_support.h>
#include <target/algorithm.h>
#include <target/armv7m.h>
#include <target/image.h>

/* MSP432P4 hardware registers */
#define P4_FLASH_MAIN_SIZE_REG 0xE0043020
#define P4_FLASH_INFO_SIZE_REG 0xE0043024
#define P4_DEVICE_ID_REG       0x0020100C
#define P4_HARDWARE_REV_REG    0x00201010

/* MSP432E4 hardware registers */
#define E4_DID0_REG 0x400FE000
#define E4_DID1_REG 0x400FE004

#define FLASH_TIMEOUT 8000

#define SUPPORT_MESSAGE \
	"Your pre-production MSP432P401x silicon is not fully supported\n" \
	"You can find more information at www.ti.com/product/MSP432P401R"

struct msp432_bank {
	uint32_t device_id;
	uint32_t hardware_rev;
	int family_type;
	int device_type;
	uint32_t sector_length;
	bool probed_main;
	bool probed_info;
	bool unlock_bsl;
	struct working_area *working_area;
	struct armv7m_algorithm armv7m_info;
};

static int msp432_auto_probe(struct flash_bank *bank);

static int msp432_device_type(uint32_t family_type, uint32_t device_id,
	uint32_t hardware_rev)
{
	int device_type = MSP432_NO_TYPE;

	if (MSP432E4 == family_type) {
		/* MSP432E4 device family */

		if (device_id == 0x180C0002) {
			if (hardware_rev == 0x102DC06E) {
				/* The 01Y variant */
				device_type = MSP432E401Y;
			} else if (hardware_rev == 0x1032E076) {
				/* The 11Y variant */
				device_type = MSP432E411Y;
			} else {
				/* Reasonable guess that this is a new variant */
				device_type = MSP432E4X_GUESS;
			}
		} else {
			/* Wild guess that this is an MSP432E4 */
			device_type = MSP432E4X_GUESS;
		}
	} else {
		/* MSP432P4 device family */

		/* Examine the device ID and hardware revision to get the device type */
		switch (device_id) {
			case 0xA000:
			case 0xA001:
			case 0xA002:
			case 0xA003:
			case 0xA004:
			case 0xA005:
				/* Device is definitely MSP432P401x, check hardware revision */
				if (hardware_rev == 0x41 || hardware_rev == 0x42) {
					/* Rev A or B of the silicon has been deprecated */
					device_type = MSP432P401X_DEPR;
				} else if (hardware_rev >= 0x43 && hardware_rev <= 0x49) {
					/* Current and future revisions of the MSP432P401x device */
					device_type = MSP432P401X;
				} else {
					/* Unknown or unanticipated hardware revision */
					device_type = MSP432P401X_GUESS;
				}
				break;
			case 0xA010:
			case 0xA012:
			case 0xA016:
			case 0xA019:
			case 0xA01F:
			case 0xA020:
			case 0xA022:
			case 0xA026:
			case 0xA029:
			case 0xA02F:
				/* Device is definitely MSP432P411x, check hardware revision */
				if (hardware_rev >= 0x41 && hardware_rev <= 0x49) {
					/* Current and future revisions of the MSP432P411x device */
					device_type = MSP432P411X;
				} else {
					/* Unknown or unanticipated hardware revision */
					device_type = MSP432P411X_GUESS;
				}
				break;
			case 0xFFFF:
				/* Device is very early silicon that has been deprecated */
				device_type = MSP432P401X_DEPR;
				break;
			default:
				if (device_id < 0xA010) {
					/* Wild guess that this is an MSP432P401x */
					device_type = MSP432P401X_GUESS;
				} else {
					/* Reasonable guess that this is a new variant */
					device_type = MSP432P411X_GUESS;
				}
				break;
		}
	}

	return device_type;
}

static const char *msp432_return_text(uint32_t return_code)
{
	switch (return_code) {
		case FLASH_BUSY:
			return "FLASH_BUSY";
		case FLASH_SUCCESS:
			return "FLASH_SUCCESS";
		case FLASH_ERROR:
			return "FLASH_ERROR";
		case FLASH_TIMEOUT_ERROR:
			return "FLASH_TIMEOUT_ERROR";
		case FLASH_VERIFY_ERROR:
			return "FLASH_VERIFY_WRONG";
		case FLASH_WRONG_COMMAND:
			return "FLASH_WRONG_COMMAND";
		case FLASH_POWER_ERROR:
			return "FLASH_POWER_ERROR";
		default:
			return "UNDEFINED_RETURN_CODE";
	}
}

static void msp432_init_params(struct msp432_algo_params *algo_params)
{
	buf_set_u32(algo_params->flash_command, 0, 32, FLASH_NO_COMMAND);
	buf_set_u32(algo_params->return_code, 0, 32, 0);
	buf_set_u32(algo_params->_reserved0, 0, 32, 0);
	buf_set_u32(algo_params->address, 0, 32, 0);
	buf_set_u32(algo_params->length, 0, 32, 0);
	buf_set_u32(algo_params->buffer1_status, 0, 32, BUFFER_INACTIVE);
	buf_set_u32(algo_params->buffer2_status, 0, 32, BUFFER_INACTIVE);
	buf_set_u32(algo_params->erase_param, 0, 32, FLASH_ERASE_MAIN);
	buf_set_u32(algo_params->unlock_bsl, 0, 32, FLASH_LOCK_BSL);
}

static int msp432_exec_cmd(struct target *target, struct msp432_algo_params
			*algo_params, uint32_t command)
{
	int retval;

	/* Make sure the given params do not include the command */
	buf_set_u32(algo_params->flash_command, 0, 32, FLASH_NO_COMMAND);
	buf_set_u32(algo_params->return_code, 0, 32, 0);
	buf_set_u32(algo_params->buffer1_status, 0, 32, BUFFER_INACTIVE);
	buf_set_u32(algo_params->buffer2_status, 0, 32, BUFFER_INACTIVE);

	/* Write out parameters to target memory */
	retval = target_write_buffer(target, ALGO_PARAMS_BASE_ADDR,
				sizeof(struct msp432_algo_params), (uint8_t *)algo_params);
	if (ERROR_OK != retval)
		return retval;

	/* Write out command to target memory */
	retval = target_write_u32(target, ALGO_FLASH_COMMAND_ADDR, command);

	return retval;
}

static int msp432_wait_return_code(struct target *target)
{
	uint32_t return_code = 0;
	long long start_ms;
	long long elapsed_ms;

	int retval = ERROR_OK;

	start_ms = timeval_ms();
	while ((0 == return_code) || (FLASH_BUSY == return_code)) {
		retval = target_read_u32(target, ALGO_RETURN_CODE_ADDR, &return_code);
		if (ERROR_OK != retval)
			return retval;

		elapsed_ms = timeval_ms() - start_ms;
		if (elapsed_ms > 500)
			keep_alive();
		if (elapsed_ms > FLASH_TIMEOUT)
			break;
	};

	if (FLASH_SUCCESS != return_code) {
		LOG_ERROR("msp432: Flash operation failed: %s",
			msp432_return_text(return_code));
		return ERROR_FAIL;
	}

	return ERROR_OK;
}

static int msp432_wait_inactive(struct target *target, uint32_t buffer)
{
	uint32_t status_code = BUFFER_ACTIVE;
	uint32_t status_addr;
	long long start_ms;
	long long elapsed_ms;

	int retval;

	switch (buffer) {
		case 1: /* Buffer 1 */
			status_addr = ALGO_BUFFER1_STATUS_ADDR;
			break;
		case 2: /* Buffer 2 */
			status_addr = ALGO_BUFFER2_STATUS_ADDR;
			break;
		default:
			return ERROR_FAIL;
	}

	start_ms = timeval_ms();
	while (BUFFER_INACTIVE != status_code) {
		retval = target_read_u32(target, status_addr, &status_code);
		if (ERROR_OK != retval)
			return retval;

		elapsed_ms = timeval_ms() - start_ms;
		if (elapsed_ms > 500)
			keep_alive();
		if (elapsed_ms > FLASH_TIMEOUT)
			break;
	};

	if (BUFFER_INACTIVE != status_code) {
		LOG_ERROR(
			"msp432: Flash operation failed: buffer not written to flash");
		return ERROR_FAIL;
	}

	return ERROR_OK;
}

static int msp432_init(struct flash_bank *bank)
{
	struct target *target = bank->target;
	struct msp432_bank *msp432_bank = bank->driver_priv;
	struct msp432_algo_params algo_params;
	struct reg_param reg_params[1];

	const uint8_t *loader_code;
	uint32_t loader_size;
	uint32_t algo_entry_addr;
	int retval;

	/* Make sure we've probed the flash to get the device and size */
	retval = msp432_auto_probe(bank);
	if (ERROR_OK != retval)
		return retval;

	/* Choose appropriate flash helper algorithm */
	switch (msp432_bank->device_type) {
		case MSP432P401X:
		case MSP432P401X_DEPR:
		case MSP432P401X_GUESS:
		default:
			loader_code = msp432p401x_algo;
			loader_size = sizeof(msp432p401x_algo);
			algo_entry_addr = P4_ALGO_ENTRY_ADDR;
			break;
		case MSP432P411X:
		case MSP432P411X_GUESS:
			loader_code = msp432p411x_algo;
			loader_size = sizeof(msp432p411x_algo);
			algo_entry_addr = P4_ALGO_ENTRY_ADDR;
			break;
		case MSP432E401Y:
		case MSP432E411Y:
		case MSP432E4X_GUESS:
			loader_code = msp432e4x_algo;
			loader_size = sizeof(msp432e4x_algo);
			algo_entry_addr = E4_ALGO_ENTRY_ADDR;
			break;
	}

	/* Issue warnings if this is a device we may not be able to flash */
	if (MSP432P401X_GUESS == msp432_bank->device_type ||
		MSP432P411X_GUESS == msp432_bank->device_type) {
		/* Explicit device type check failed. Report this. */
		LOG_WARNING(
			"msp432: Unrecognized MSP432P4 Device ID and Hardware "
			"Rev (%04" PRIX32 ", %02" PRIX32 ")", msp432_bank->device_id,
			msp432_bank->hardware_rev);
	} else if (MSP432P401X_DEPR == msp432_bank->device_type) {
		LOG_WARNING(
			"msp432: MSP432P401x pre-production device (deprecated "
			"silicon)\n" SUPPORT_MESSAGE);
	} else if (MSP432E4X_GUESS == msp432_bank->device_type) {
		/* Explicit device type check failed. Report this. */
		LOG_WARNING(
			"msp432: Unrecognized MSP432E4 DID0 and DID1 values "
			"(%08" PRIX32 ", %08" PRIX32 ")", msp432_bank->device_id,
			msp432_bank->hardware_rev);
	}

	/* Check for working area to use for flash helper algorithm */
	if (NULL != msp432_bank->working_area)
		target_free_working_area(target, msp432_bank->working_area);
	retval = target_alloc_working_area(target, ALGO_WORKING_SIZE,
				&msp432_bank->working_area);
	if (ERROR_OK != retval)
		return retval;

	/* Confirm the defined working address is the area we need to use */
	if (ALGO_BASE_ADDR != msp432_bank->working_area->address)
		return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;

	/* Write flash helper algorithm into target memory */
	retval = target_write_buffer(target, ALGO_BASE_ADDR, loader_size,
				loader_code);
	if (ERROR_OK != retval)
		return retval;

	/* Initialize the ARMv7 specific info to run the algorithm */
	msp432_bank->armv7m_info.common_magic = ARMV7M_COMMON_MAGIC;
	msp432_bank->armv7m_info.core_mode = ARM_MODE_THREAD;

	/* Initialize algorithm parameters to default values */
	msp432_init_params(&algo_params);

	/* Write out parameters to target memory */
	retval = target_write_buffer(target, ALGO_PARAMS_BASE_ADDR,
				sizeof(algo_params), (uint8_t *)&algo_params);
	if (ERROR_OK != retval)
		return retval;

	/* Initialize stack pointer for flash helper algorithm */
	init_reg_param(&reg_params[0], "sp", 32, PARAM_OUT);
	buf_set_u32(reg_params[0].value, 0, 32, ALGO_STACK_POINTER_ADDR);

	/* Begin executing the flash helper algorithm */
	retval = target_start_algorithm(target, 0, 0, 1, reg_params,
				algo_entry_addr, 0, &msp432_bank->armv7m_info);
	destroy_reg_param(&reg_params[0]);
	if (ERROR_OK != retval) {
		LOG_ERROR("msp432: Failed to start flash helper algorithm");
		return retval;
	}

	/*
	 * At this point, the algorithm is running on the target and
	 * ready to receive commands and data to flash the target
	 */

	/* Issue the init command to the flash helper algorithm */
	retval = msp432_exec_cmd(target, &algo_params, FLASH_INIT);
	if (ERROR_OK != retval)
		return retval;

	retval = msp432_wait_return_code(target);

	return retval;
}

static int msp432_quit(struct flash_bank *bank)
{
	struct target *target = bank->target;
	struct msp432_bank *msp432_bank = bank->driver_priv;
	struct msp432_algo_params algo_params;

	int retval;

	/* Initialize algorithm parameters to default values */
	msp432_init_params(&algo_params);

	/* Issue the exit command to the flash helper algorithm */
	retval = msp432_exec_cmd(target, &algo_params, FLASH_EXIT);
	if (ERROR_OK != retval)
		return retval;

	(void)msp432_wait_return_code(target);

	/* Regardless of the return code, attempt to halt the target */
	(void)target_halt(target);

	/* Now confirm target halted and clean up from flash helper algorithm */
	retval = target_wait_algorithm(target, 0, NULL, 0, NULL, 0, FLASH_TIMEOUT,
				&msp432_bank->armv7m_info);

	target_free_working_area(target, msp432_bank->working_area);
	msp432_bank->working_area = NULL;

	return retval;
}

static int msp432_mass_erase(struct flash_bank *bank, bool all)
{
	struct target *target = bank->target;
	struct msp432_bank *msp432_bank = bank->driver_priv;
	struct msp432_algo_params algo_params;

	int retval;

	if (TARGET_HALTED != target->state) {
		LOG_ERROR("Target not halted");
		return ERROR_TARGET_NOT_HALTED;
	}

	retval = msp432_init(bank);
	if (ERROR_OK != retval)
		return retval;

	/* Initialize algorithm parameters to default values */
	msp432_init_params(&algo_params);
	if (all) {
		buf_set_u32(algo_params.erase_param, 0, 32,
			FLASH_ERASE_MAIN | FLASH_ERASE_INFO);
		if (msp432_bank->unlock_bsl)
			buf_set_u32(algo_params.unlock_bsl, 0, 32, FLASH_UNLOCK_BSL);
	}

	/* Issue the mass erase command to the flash helper algorithm */
	retval = msp432_exec_cmd(target, &algo_params, FLASH_MASS_ERASE);
	if (ERROR_OK != retval) {
		(void)msp432_quit(bank);
		return retval;
	}

	retval = msp432_wait_return_code(target);
	if (ERROR_OK != retval) {
		(void)msp432_quit(bank);
		return retval;
	}

	retval = msp432_quit(bank);
	if (ERROR_OK != retval)
		return retval;

	return retval;
}

COMMAND_HANDLER(msp432_mass_erase_command)
{
	struct flash_bank *bank;
	struct msp432_bank *msp432_bank;
	bool all;

	int retval;

	if (1 > CMD_ARGC)
		return ERROR_COMMAND_SYNTAX_ERROR;

	retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank);
	if (retval != ERROR_OK)
		return retval;

	if (1 == CMD_ARGC) {
		all = false;
	} else if (2 == CMD_ARGC) {
		/* Check argument for how much to erase */
		if (0 == strcmp(CMD_ARGV[1], "main"))
			all = false;
		else if (0 == strcmp(CMD_ARGV[1], "all"))
			all = true;
		else
			return ERROR_COMMAND_SYNTAX_ERROR;
	} else {
		return ERROR_COMMAND_SYNTAX_ERROR;
	}

	msp432_bank = bank->driver_priv;

	if (MSP432E4 == msp432_bank->family_type) {
		/* MSP432E4 does not have main vs info regions, ignore "all" */
		all = false;
	}

	retval = msp432_mass_erase(bank, all);
	if (ERROR_OK != retval)
		return retval;

	if (MSP432E4 == msp432_bank->family_type) {
		/* MSP432E4 does not have main vs info regions */
		LOG_INFO("msp432: Mass erase of flash is complete");
	} else {
		LOG_INFO("msp432: Mass erase of %s is complete",
			all ? "main + information flash" : "main flash");
	}

	return ERROR_OK;
}

COMMAND_HANDLER(msp432_bsl_command)
{
	struct flash_bank *bank;
	struct msp432_bank *msp432_bank;

	int retval;

	if (1 > CMD_ARGC)
		return ERROR_COMMAND_SYNTAX_ERROR;

	retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank);
	if (retval != ERROR_OK)
		return retval;

	msp432_bank = bank->driver_priv;

	if (MSP432E4 == msp432_bank->family_type) {
		LOG_WARNING("msp432: MSP432E4 does not have a BSL region");
		return ERROR_OK;
	}

	if (2 == CMD_ARGC) {
		if (0 == strcmp(CMD_ARGV[1], "lock"))
			msp432_bank->unlock_bsl = false;
		else if (0 == strcmp(CMD_ARGV[1], "unlock"))
			msp432_bank->unlock_bsl = true;
		else
			return ERROR_COMMAND_SYNTAX_ERROR;
	} else if (1 != CMD_ARGC) {
		/* Extra, unknown argument passed in */
		return ERROR_COMMAND_SYNTAX_ERROR;
	}

	LOG_INFO("msp432: BSL flash region is currently %slocked",
		msp432_bank->unlock_bsl ? "un" : "");

	return ERROR_OK;
}

FLASH_BANK_COMMAND_HANDLER(msp432_flash_bank_command)
{
	struct msp432_bank *msp432_bank;

	if (CMD_ARGC < 6)
		return ERROR_COMMAND_SYNTAX_ERROR;

	/* Create shared private struct for flash banks */
	msp432_bank = malloc(sizeof(struct msp432_bank));
	if (NULL == msp432_bank)
		return ERROR_FAIL;

	/* Initialize private flash information */
	msp432_bank->device_id = 0;
	msp432_bank->hardware_rev = 0;
	msp432_bank->family_type = MSP432_NO_FAMILY;
	msp432_bank->device_type = MSP432_NO_TYPE;
	msp432_bank->sector_length = 0x1000;
	msp432_bank->probed_main = false;
	msp432_bank->probed_info = false;
	msp432_bank->unlock_bsl = false;
	msp432_bank->working_area = NULL;

	/* Finish up initial settings here */
	bank->driver_priv = msp432_bank;
	bank->base = FLASH_BASE;

	return ERROR_OK;
}

static int msp432_erase(struct flash_bank *bank, unsigned int first,
		unsigned int last)
{
	struct target *target = bank->target;
	struct msp432_bank *msp432_bank = bank->driver_priv;
	struct msp432_algo_params algo_params;

	bool is_main = FLASH_BASE == bank->base;
	bool is_info = P4_FLASH_INFO_BASE == bank->base;

	int retval;

	if (TARGET_HALTED != target->state) {
		LOG_ERROR("Target not halted");
		return ERROR_TARGET_NOT_HALTED;
	}

	/* Do a mass erase if user requested all sectors of main flash */
	if (is_main && (first == 0) && (last == (bank->num_sectors - 1))) {
		/* Request mass erase of main flash */
		return msp432_mass_erase(bank, false);
	}

	retval = msp432_init(bank);
	if (ERROR_OK != retval)
		return retval;

	/* Initialize algorithm parameters to default values */
	msp432_init_params(&algo_params);

	/* Adjust params if this is the info bank */
	if (is_info) {
		buf_set_u32(algo_params.erase_param, 0, 32, FLASH_ERASE_INFO);
		/* And flag if BSL is unlocked */
		if (msp432_bank->unlock_bsl)
			buf_set_u32(algo_params.unlock_bsl, 0, 32, FLASH_UNLOCK_BSL);
	}

	/* Erase requested sectors one by one */
	for (unsigned int i = first; i <= last; i++) {

		/* Skip TVL (read-only) sector of the info bank */
		if (is_info && 1 == i)
			continue;

		/* Skip BSL sectors of info bank if locked */
		if (is_info && (2 == i || 3 == i) &&
			!msp432_bank->unlock_bsl)
			continue;

		/* Convert sector number to starting address of sector */
		buf_set_u32(algo_params.address, 0, 32, bank->base +
			(i * msp432_bank->sector_length));

		/* Issue the sector erase command to the flash helper algorithm */
		retval = msp432_exec_cmd(target, &algo_params, FLASH_SECTOR_ERASE);
		if (ERROR_OK != retval) {
			(void)msp432_quit(bank);
			return retval;
		}

		retval = msp432_wait_return_code(target);
		if (ERROR_OK != retval) {
			(void)msp432_quit(bank);
			return retval;
		}
	}

	retval = msp432_quit(bank);
	if (ERROR_OK != retval)
		return retval;

	return retval;
}

static int msp432_write(struct flash_bank *bank, const uint8_t *buffer,
	uint32_t offset, uint32_t count)
{
	struct target *target = bank->target;
	struct msp432_bank *msp432_bank = bank->driver_priv;
	struct msp432_algo_params algo_params;
	uint32_t size;
	uint32_t data_ready = BUFFER_DATA_READY;
	long long start_ms;
	long long elapsed_ms;

	bool is_info = P4_FLASH_INFO_BASE == bank->base;

	int retval;

	if (TARGET_HALTED != target->state) {
		LOG_ERROR("Target not halted");
		return ERROR_TARGET_NOT_HALTED;
	}

	/*
	 * Block attempts to write to read-only sectors of flash
	 * The TVL region in sector 1 of the info flash is always read-only
	 * The BSL region in sectors 2 and 3 of the info flash may be unlocked
	 * The helper algorithm will hang on attempts to write to TVL
	 */
	if (is_info) {
		/* Set read-only start to TVL sector */
		uint32_t start = 0x1000;
		/* Set read-only end after BSL region if locked */
		uint32_t end = (msp432_bank->unlock_bsl) ? 0x2000 : 0x4000;
		/* Check if request includes anything in read-only sectors */
		if ((offset + count - 1) < start || offset >= end) {
			/* The request includes no bytes in read-only sectors */
			/* Fall out and process the request normally */
		} else {
			/* Send a request for anything before read-only sectors */
			if (offset < start) {
				uint32_t start_count = MIN(start - offset, count);
				retval = msp432_write(bank, buffer, offset, start_count);
				if (ERROR_OK != retval)
					return retval;
			}
			/* Send a request for anything after read-only sectors */
			if ((offset + count - 1) >= end) {
				uint32_t skip = end - offset;
				count -= skip;
				offset += skip;
				buffer += skip;
				return msp432_write(bank, buffer, offset, count);
			} else {
				/* Request is entirely in read-only sectors */
				return ERROR_OK;
			}
		}
	}

	retval = msp432_init(bank);
	if (ERROR_OK != retval)
		return retval;

	/* Initialize algorithm parameters to default values */
	msp432_init_params(&algo_params);

	/* Set up parameters for requested flash write operation */
	buf_set_u32(algo_params.address, 0, 32, bank->base + offset);
	buf_set_u32(algo_params.length, 0, 32, count);

	/* Check if this is the info bank */
	if (is_info) {
		/* And flag if BSL is unlocked */
		if (msp432_bank->unlock_bsl)
			buf_set_u32(algo_params.unlock_bsl, 0, 32, FLASH_UNLOCK_BSL);
	}

	/* Set up flash helper algorithm to continuous flash mode */
	retval = msp432_exec_cmd(target, &algo_params, FLASH_CONTINUOUS);
	if (ERROR_OK != retval) {
		(void)msp432_quit(bank);
		return retval;
	}

	/* Write requested data, one buffer at a time */
	start_ms = timeval_ms();
	while (count > 0) {

		if (count > ALGO_BUFFER_SIZE)
			size = ALGO_BUFFER_SIZE;
		else
			size = count;

		/* Put next block of data to flash into buffer */
		retval = target_write_buffer(target, ALGO_BUFFER1_ADDR, size, buffer);
		if (ERROR_OK != retval) {
			LOG_ERROR("Unable to write data to target memory");
			(void)msp432_quit(bank);
			return ERROR_FLASH_OPERATION_FAILED;
		}

		/* Signal the flash helper algorithm that data is ready to flash */
		retval = target_write_u32(target, ALGO_BUFFER1_STATUS_ADDR,
					data_ready);
		if (ERROR_OK != retval) {
			(void)msp432_quit(bank);
			return ERROR_FLASH_OPERATION_FAILED;
		}

		retval = msp432_wait_inactive(target, 1);
		if (ERROR_OK != retval) {
			(void)msp432_quit(bank);
			return retval;
		}

		count -= size;
		buffer += size;

		elapsed_ms = timeval_ms() - start_ms;
		if (elapsed_ms > 500)
			keep_alive();
	}

	/* Confirm that the flash helper algorithm is finished */
	retval = msp432_wait_return_code(target);
	if (ERROR_OK != retval) {
		(void)msp432_quit(bank);
		return retval;
	}

	retval = msp432_quit(bank);
	if (ERROR_OK != retval)
		return retval;

	return retval;
}

static int msp432_probe(struct flash_bank *bank)
{
	struct target *target = bank->target;
	struct msp432_bank *msp432_bank = bank->driver_priv;

	uint32_t device_id;
	uint32_t hardware_rev;

	uint32_t sector_length;
	uint32_t size;
	unsigned int num_sectors;

	bool is_main = FLASH_BASE == bank->base;
	bool is_info = P4_FLASH_INFO_BASE == bank->base;

	int retval;

	/* Check if this bank has already been successfully probed */
	if (is_main && msp432_bank->probed_main)
		return ERROR_OK;
	if (is_info && msp432_bank->probed_info)
		return ERROR_OK;

	/* Read the flash size register to determine this is a P4 or not */
	/* MSP432P4s will return the size of flash.  MSP432E4s will return zero */
	retval = target_read_u32(target, P4_FLASH_MAIN_SIZE_REG, &size);
	if (ERROR_OK != retval)
		return retval;

	if (0 == size) {
		/* This is likely an MSP432E4 */
		msp432_bank->family_type = MSP432E4;

		retval = target_read_u32(target, E4_DID0_REG, &device_id);
		if (ERROR_OK != retval)
			return retval;

		msp432_bank->device_id = device_id;

		retval = target_read_u32(target, E4_DID1_REG, &hardware_rev);
		if (ERROR_OK != retval)
			return retval;

		msp432_bank->hardware_rev = hardware_rev;
	} else {
		/* This is likely an MSP432P4 */
		msp432_bank->family_type = MSP432P4;

		retval = target_read_u32(target, P4_DEVICE_ID_REG, &device_id);
		if (ERROR_OK != retval)
			return retval;

		msp432_bank->device_id = device_id & 0xFFFF;

		retval = target_read_u32(target, P4_HARDWARE_REV_REG, &hardware_rev);
		if (ERROR_OK != retval)
			return retval;

		msp432_bank->hardware_rev = hardware_rev & 0xFF;
	}

	msp432_bank->device_type = msp432_device_type(msp432_bank->family_type,
		msp432_bank->device_id, msp432_bank->hardware_rev);

	if (MSP432P4 == msp432_bank->family_type) {
		/* Set up MSP432P4 specific flash parameters */
		if (is_main) {
			retval = target_read_u32(target, P4_FLASH_MAIN_SIZE_REG, &size);
			if (ERROR_OK != retval)
				return retval;

			sector_length = P4_SECTOR_LENGTH;
			num_sectors = size / sector_length;
		} else if (is_info) {
			if (msp432_bank->device_type == MSP432P411X ||
				msp432_bank->device_type == MSP432P411X_GUESS) {
				/* MSP432P411x has an info size register, use that for size */
				retval = target_read_u32(target, P4_FLASH_INFO_SIZE_REG, &size);
				if (ERROR_OK != retval)
					return retval;
			} else {
				/* All other MSP432P401x devices have fixed info region size */
				size = 0x4000; /* 16 KB info region */
			}
			sector_length = P4_SECTOR_LENGTH;
			num_sectors = size / sector_length;
		} else {
			/* Invalid bank somehow */
			return ERROR_FAIL;
		}
	} else {
		/* Set up MSP432E4 specific flash parameters */
		if (is_main) {
			size = E4_FLASH_SIZE;
			sector_length = E4_SECTOR_LENGTH;
			num_sectors = size / sector_length;
		} else {
			/* Invalid bank somehow */
			return ERROR_FAIL;
		}
	}

	free(bank->sectors);
	bank->sectors = NULL;

	if (num_sectors > 0) {
		bank->sectors = malloc(sizeof(struct flash_sector) * num_sectors);
		if (NULL == bank->sectors)
			return ERROR_FAIL;
	}

	bank->size = size;
	bank->write_start_alignment = 0;
	bank->write_end_alignment = 0;
	bank->num_sectors = num_sectors;
	msp432_bank->sector_length = sector_length;

	for (unsigned int i = 0; i < num_sectors; i++) {
		bank->sectors[i].offset = i * sector_length;
		bank->sectors[i].size = sector_length;
		bank->sectors[i].is_erased = -1;
		bank->sectors[i].is_protected = 0;
	}

	/* We've successfully determined the stats on this flash bank */
	if (is_main)
		msp432_bank->probed_main = true;
	if (is_info)
		msp432_bank->probed_info = true;

	if (is_main && MSP432P4 == msp432_bank->family_type) {
		/* Create the info flash bank needed by MSP432P4 variants */
		struct flash_bank *info = calloc(sizeof(struct flash_bank), 1);
		if (NULL == info)
			return ERROR_FAIL;

		/* Create a name for the info bank, append "_1" to main name */
		char *name = malloc(strlen(bank->name) + 3);
		strcpy(name, bank->name);
		strcat(name, "_1");

		/* Initialize info bank */
		info->name = name;
		info->target = bank->target;
		info->driver = bank->driver;
		info->driver_priv = msp432_bank;
		info->base = P4_FLASH_INFO_BASE;

		flash_bank_add(info);
	}

	/* If we fall through to here, then all went well */

	return ERROR_OK;
}

static int msp432_auto_probe(struct flash_bank *bank)
{
	struct msp432_bank *msp432_bank = bank->driver_priv;

	bool is_main = FLASH_BASE == bank->base;
	bool is_info = P4_FLASH_INFO_BASE == bank->base;

	int retval = ERROR_OK;

	if (is_main)
		if (!msp432_bank->probed_main)
			retval = msp432_probe(bank);
	if (is_info)
		if (!msp432_bank->probed_info)
			retval = msp432_probe(bank);

	return retval;
}

static int msp432_info(struct flash_bank *bank, char *buf, int buf_size)
{
	struct msp432_bank *msp432_bank = bank->driver_priv;
	int printed = 0;

	switch (msp432_bank->device_type) {
		case MSP432P401X_DEPR:
			if (0xFFFF == msp432_bank->device_id) {
				/* Very early pre-production silicon currently deprecated */
				printed = snprintf(buf, buf_size,
					"MSP432P401x pre-production device (deprecated silicon)\n"
					SUPPORT_MESSAGE);
			} else {
				/* Revision A or B silicon, also deprecated */
				printed = snprintf(buf, buf_size,
					"MSP432P401x Device Rev %c (deprecated silicon)\n"
					SUPPORT_MESSAGE, (char)msp432_bank->hardware_rev);
			}
			break;
		case MSP432P401X:
			printed = snprintf(buf, buf_size,
				"MSP432P401x Device Rev %c\n",
				(char)msp432_bank->hardware_rev);
			break;
		case MSP432P411X:
			printed = snprintf(buf, buf_size,
				"MSP432P411x Device Rev %c\n",
				(char)msp432_bank->hardware_rev);
			break;
		case MSP432E401Y:
			printed = snprintf(buf, buf_size, "MSP432E401Y Device\n");
			break;
		case MSP432E411Y:
			printed = snprintf(buf, buf_size, "MSP432E411Y Device\n");
			break;
		case MSP432E4X_GUESS:
			printed = snprintf(buf, buf_size,
				"Unrecognized MSP432E4 DID0 and DID1 IDs (%08" PRIX32 ", %08" PRIX32 ")",
				msp432_bank->device_id, msp432_bank->hardware_rev);
			break;
		case MSP432P401X_GUESS:
		case MSP432P411X_GUESS:
		default:
			printed = snprintf(buf, buf_size,
				"Unrecognized MSP432P4 Device ID and Hardware Rev (%04" PRIX32 ", %02" PRIX32 ")",
				msp432_bank->device_id, msp432_bank->hardware_rev);
			break;
	}

	buf_size -= printed;

	if (0 > buf_size)
		return ERROR_BUF_TOO_SMALL;

	return ERROR_OK;
}

static int msp432_protect_check(struct flash_bank *bank)
{
	/* Added to suppress warning, not needed for MSP432 flash */
	return ERROR_OK;
}

static void msp432_flash_free_driver_priv(struct flash_bank *bank)
{
	bool is_main = FLASH_BASE == bank->base;

	/* A single private struct is shared between main and info banks */
	/* Only free it on the call for main bank */
	if (is_main)
		free(bank->driver_priv);

	/* Forget about the private struct on both main and info banks */
	bank->driver_priv = NULL;
}

static const struct command_registration msp432_exec_command_handlers[] = {
	{
		.name = "mass_erase",
		.handler = msp432_mass_erase_command,
		.mode = COMMAND_EXEC,
		.help = "Erase entire flash memory on device.",
		.usage = "bank_id ['main' | 'all']",
	},
	{
		.name = "bsl",
		.handler = msp432_bsl_command,
		.mode = COMMAND_EXEC,
		.help = "Allow BSL to be erased or written by flash commands.",
		.usage = "bank_id ['unlock' | 'lock']",
	},
	COMMAND_REGISTRATION_DONE
};

static const struct command_registration msp432_command_handlers[] = {
	{
		.name = "msp432",
		.mode = COMMAND_EXEC,
		.help = "MSP432 flash command group",
		.usage = "",
		.chain = msp432_exec_command_handlers,
	},
	COMMAND_REGISTRATION_DONE
};

const struct flash_driver msp432_flash = {
	.name = "msp432",
	.commands = msp432_command_handlers,
	.flash_bank_command = msp432_flash_bank_command,
	.erase = msp432_erase,
	.write = msp432_write,
	.read = default_flash_read,
	.probe = msp432_probe,
	.auto_probe = msp432_auto_probe,
	.erase_check = default_flash_blank_check,
	.protect_check = msp432_protect_check,
	.info = msp432_info,
	.free_driver_priv = msp432_flash_free_driver_priv,
};
