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

/***************************************************************************
 *   Copyright (C) 2013 by Andes Technology                                *
 *   Hsiangkai Wang <hkwang@andestech.com>                                 *
 ***************************************************************************/

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

/* project specific includes */
#include <jtag/interface.h>
#include <jtag/tcl.h>
#include <transport/transport.h>
#include <target/target.h>
#include <jtag/aice/aice_interface.h>
#include <jtag/aice/aice_transport.h>
#include <string.h>

/* */
static int jim_newtap_expected_id(struct jim_nvp *n, struct jim_getopt_info *goi,
		struct jtag_tap *tap)
{
	jim_wide w;
	int e = jim_getopt_wide(goi, &w);
	if (e != JIM_OK) {
		Jim_SetResultFormatted(goi->interp, "option: %s bad parameter",
				n->name);
		return e;
	}

	unsigned expected_len = sizeof(uint32_t) * tap->expected_ids_cnt;
	uint32_t *new_expected_ids = malloc(expected_len + sizeof(uint32_t));
	if (!new_expected_ids) {
		Jim_SetResultFormatted(goi->interp, "no memory");
		return JIM_ERR;
	}

	assert(tap->expected_ids);
	memcpy(new_expected_ids, tap->expected_ids, expected_len);

	new_expected_ids[tap->expected_ids_cnt] = w;

	free(tap->expected_ids);
	tap->expected_ids = new_expected_ids;
	tap->expected_ids_cnt++;

	return JIM_OK;
}

#define NTAP_OPT_EXPECTED_ID 0

/* */
static int jim_aice_newtap_cmd(struct jim_getopt_info *goi)
{
	struct jtag_tap *tap;
	int x;
	int e;
	struct jim_nvp *n;
	char *cp;
	const struct jim_nvp opts[] = {
		{.name = "-expected-id", .value = NTAP_OPT_EXPECTED_ID},
		{.name = NULL, .value = -1},
	};

	tap = calloc(1, sizeof(struct jtag_tap));
	if (!tap) {
		Jim_SetResultFormatted(goi->interp, "no memory");
		return JIM_ERR;
	}

	/*
	 * we expect CHIP + TAP + OPTIONS
	 * */
	if (goi->argc < 3) {
		Jim_SetResultFormatted(goi->interp,
				"Missing CHIP TAP OPTIONS ....");
		free(tap);
		return JIM_ERR;
	}

	const char *tmp;
	jim_getopt_string(goi, &tmp, NULL);
	tap->chip = strdup(tmp);

	jim_getopt_string(goi, &tmp, NULL);
	tap->tapname = strdup(tmp);

	/* name + dot + name + null */
	x = strlen(tap->chip) + 1 + strlen(tap->tapname) + 1;
	cp = malloc(x);
	sprintf(cp, "%s.%s", tap->chip, tap->tapname);
	tap->dotted_name = cp;

	LOG_DEBUG("Creating New Tap, Chip: %s, Tap: %s, Dotted: %s, %d params",
			tap->chip, tap->tapname, tap->dotted_name, goi->argc);

	while (goi->argc) {
		e = jim_getopt_nvp(goi, opts, &n);
		if (e != JIM_OK) {
			jim_getopt_nvp_unknown(goi, opts, 0);
			free(cp);
			free(tap);
			return e;
		}
		LOG_DEBUG("Processing option: %s", n->name);
		switch (n->value) {
			case NTAP_OPT_EXPECTED_ID:
				e = jim_newtap_expected_id(n, goi, tap);
				if (e != JIM_OK) {
					free(cp);
					free(tap);
					return e;
				}
				break;
		}		/* switch (n->value) */
	}			/* while (goi->argc) */

	/* default is enabled-after-reset */
	tap->enabled = !tap->disabled_after_reset;

	jtag_tap_init(tap);
	return JIM_OK;
}

/* */
static int jim_aice_newtap(Jim_Interp *interp, int argc, Jim_Obj * const *argv)
{
	struct jim_getopt_info goi;
	jim_getopt_setup(&goi, interp, argc - 1, argv + 1);
	return jim_aice_newtap_cmd(&goi);
}

/* */
COMMAND_HANDLER(handle_aice_init_command)
{
	if (CMD_ARGC != 0)
		return ERROR_COMMAND_SYNTAX_ERROR;

	static bool jtag_initialized;
	if (jtag_initialized) {
		LOG_INFO("'jtag init' has already been called");
		return ERROR_OK;
	}
	jtag_initialized = true;

	LOG_DEBUG("Initializing jtag devices...");
	return jtag_init(CMD_CTX);
}

COMMAND_HANDLER(handle_scan_chain_command)
{
	struct jtag_tap *tap;
	char expected_id[12];

	aice_scan_jtag_chain();
	tap = jtag_all_taps();
	command_print(CMD,
		"   TapName             Enabled  IdCode     Expected   IrLen IrCap IrMask");
	command_print(CMD,
		"-- ------------------- -------- ---------- ---------- ----- ----- ------");

	while (tap) {
		uint32_t expected, expected_mask, ii;

		snprintf(expected_id, sizeof(expected_id), "0x%08x",
			(unsigned)((tap->expected_ids_cnt > 0)
				   ? tap->expected_ids[0]
				   : 0));
		if (tap->ignore_version)
			expected_id[2] = '*';

		expected = buf_get_u32(tap->expected, 0, tap->ir_length);
		expected_mask = buf_get_u32(tap->expected_mask, 0, tap->ir_length);

		command_print(CMD,
			"%2d %-18s     %c     0x%08x %s %5d 0x%02x  0x%02x",
			tap->abs_chain_position,
			tap->dotted_name,
			tap->enabled ? 'Y' : 'n',
			(unsigned int)(tap->idcode),
			expected_id,
			(unsigned int)(tap->ir_length),
			(unsigned int)(expected),
			(unsigned int)(expected_mask));

		for (ii = 1; ii < tap->expected_ids_cnt; ii++) {
			snprintf(expected_id, sizeof(expected_id), "0x%08x",
				(unsigned) tap->expected_ids[ii]);
			if (tap->ignore_version)
				expected_id[2] = '*';

			command_print(CMD,
				"                                           %s",
				expected_id);
		}

		tap = tap->next_tap;
	}

	return ERROR_OK;
}

static int jim_aice_arp_init(Jim_Interp *interp, int argc, Jim_Obj * const *argv)
{
	LOG_DEBUG("No implement: jim_aice_arp_init");

	return JIM_OK;
}

/* */
static int aice_init_reset(struct command_context *cmd_ctx)
{
	LOG_DEBUG("Initializing with hard TRST+SRST reset");

	int retval;
	enum reset_types jtag_reset_config = jtag_get_reset_config();

	jtag_add_reset(1, 0);	/* TAP_RESET */
	if (jtag_reset_config & RESET_HAS_SRST) {
		jtag_add_reset(1, 1);
		if ((jtag_reset_config & RESET_SRST_PULLS_TRST) == 0)
			jtag_add_reset(0, 1);
	}
	jtag_add_reset(0, 0);
	retval = jtag_execute_queue();
	if (retval != ERROR_OK)
		return retval;

	return ERROR_OK;
}

/* */
static int jim_aice_arp_init_reset(Jim_Interp *interp, int argc, Jim_Obj * const *argv)
{
	int e = ERROR_OK;
	struct jim_getopt_info goi;
	jim_getopt_setup(&goi, interp, argc - 1, argv + 1);
	if (goi.argc != 0) {
		Jim_WrongNumArgs(goi.interp, 1, goi.argv - 1, "(no params)");
		return JIM_ERR;
	}
	struct command_context *context = current_command_context(interp);
	e = aice_init_reset(context);

	if (e != ERROR_OK) {
		Jim_Obj *obj = Jim_NewIntObj(goi.interp, e);
		Jim_SetResultFormatted(goi.interp, "error: %#s", obj);
		return JIM_ERR;
	}
	return JIM_OK;
}

static int jim_aice_names(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
	struct jim_getopt_info goi;
	jim_getopt_setup(&goi, interp, argc - 1, argv + 1);
	if (goi.argc != 0) {
		Jim_WrongNumArgs(goi.interp, 1, goi.argv, "Too many parameters");
		return JIM_ERR;
	}
	Jim_SetResult(goi.interp, Jim_NewListObj(goi.interp, NULL, 0));
	struct jtag_tap *tap;

	for (tap = jtag_all_taps(); tap; tap = tap->next_tap)
		Jim_ListAppendElement(goi.interp,
				Jim_GetResult(goi.interp),
				Jim_NewStringObj(goi.interp,
					tap->dotted_name, -1));

	return JIM_OK;
}

/* */
static const struct command_registration aice_transport_jtag_subcommand_handlers[] = {
	{
		.name = "init",
		.mode = COMMAND_ANY,
		.handler = handle_aice_init_command,
		.help = "initialize jtag scan chain",
		.usage = ""
	},
	{
		.name = "arp_init",
		.mode = COMMAND_ANY,
		.jim_handler = jim_aice_arp_init,
		.help = "Validates JTAG scan chain against the list of "
			"declared TAPs.",
	},
	{
		.name = "arp_init-reset",
		.mode = COMMAND_ANY,
		.jim_handler = jim_aice_arp_init_reset,
		.help = "Uses TRST and SRST to try resetting everything on "
			"the JTAG scan chain, then performs 'jtag arp_init'."
	},
	{
		.name = "newtap",
		.mode = COMMAND_CONFIG,
		.jim_handler = jim_aice_newtap,
		.help = "Create a new TAP instance named basename.tap_type, "
			"and appends it to the scan chain.",
		.usage = "basename tap_type ['-expected_id' number]"
	},
	{
		.name = "tapisenabled",
		.mode = COMMAND_EXEC,
		.jim_handler = jim_jtag_tap_enabler,
		.help = "Returns a Tcl boolean (0/1) indicating whether "
			"the TAP is enabled (1) or not (0).",
		.usage = "tap_name",
	},
	{
		.name = "tapenable",
		.mode = COMMAND_EXEC,
		.jim_handler = jim_jtag_tap_enabler,
		.help = "Try to enable the specified TAP using the "
			"'tap-enable' TAP event.",
		.usage = "tap_name",
	},
	{
		.name = "tapdisable",
		.mode = COMMAND_EXEC,
		.jim_handler = jim_jtag_tap_enabler,
		.help = "Try to disable the specified TAP using the "
			"'tap-disable' TAP event.",
		.usage = "tap_name",
	},
	{
		.name = "configure",
		.mode = COMMAND_ANY,
		.jim_handler = jim_jtag_configure,
		.help = "Provide a Tcl handler for the specified "
			"TAP event.",
		.usage = "tap_name '-event' event_name handler",
	},
	{
		.name = "cget",
		.mode = COMMAND_EXEC,
		.jim_handler = jim_jtag_configure,
		.help = "Return any Tcl handler for the specified "
			"TAP event.",
		.usage = "tap_name '-event' event_name",
	},
	{
		.name = "names",
		.mode = COMMAND_ANY,
		.jim_handler = jim_aice_names,
		.help = "Returns list of all JTAG tap names.",
	},
	{
		.name = "scan_chain",
		.handler = handle_scan_chain_command,
		.mode = COMMAND_ANY,
		.help = "print current scan chain configuration",
		.usage = ""
	},

	COMMAND_REGISTRATION_DONE
};

/* */
static const struct command_registration aice_transport_command_handlers[] = {
	{
		.name = "jtag",
		.mode = COMMAND_ANY,
		.usage = "",
		.chain = aice_transport_jtag_subcommand_handlers,
	},
	COMMAND_REGISTRATION_DONE

};

/* */
static int aice_transport_register_commands(struct command_context *cmd_ctx)
{
	return register_commands(cmd_ctx, NULL, aice_transport_command_handlers);
}

/* */
static int aice_transport_init(struct command_context *cmd_ctx)
{
	LOG_DEBUG("aice_transport_init");
	struct target *t = get_current_target(cmd_ctx);
	struct transport *transport;

	if (!t) {
		LOG_ERROR("no current target");
		return ERROR_FAIL;
	}

	transport = get_current_transport();

	if (!transport) {
		LOG_ERROR("no transport selected");
		return ERROR_FAIL;
	}

	LOG_DEBUG("current transport %s", transport->name);

	return aice_init_targets();
}

/* */
static int aice_transport_select(struct command_context *ctx)
{
	LOG_DEBUG("aice_transport_select");

	int retval;

	retval = aice_transport_register_commands(ctx);

	if (retval != ERROR_OK)
		return retval;

	return ERROR_OK;
}

static struct transport aice_jtag_transport = {
	.name = "aice_jtag",
	.select = aice_transport_select,
	.init = aice_transport_init,
};

const char *aice_transports[] = { "aice_jtag", NULL };

static void aice_constructor(void) __attribute__((constructor));
static void aice_constructor(void)
{
	transport_register(&aice_jtag_transport);
}
