/***************************************************************************
 *   Copyright (C) 2016 by Matthias Welwarsky                              *
 *                                                                         *
 *   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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *                                                                         *
 ***************************************************************************/

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

#include <stdlib.h>
#include <stdint.h>
#include "target/arm_adi_v5.h"
#include "target/arm_cti.h"
#include "target/target.h"
#include "helper/time_support.h"
#include "helper/list.h"
#include "helper/command.h"

struct arm_cti {
	struct list_head lh;
	char *name;
	struct adiv5_mem_ap_spot spot;
};

static LIST_HEAD(all_cti);

const char *arm_cti_name(struct arm_cti *self)
{
	return self->name;
}

struct arm_cti *cti_instance_by_jim_obj(Jim_Interp *interp, Jim_Obj *o)
{
	struct arm_cti *obj = NULL;
	const char *name;
	bool found = false;

	name = Jim_GetString(o, NULL);

	list_for_each_entry(obj, &all_cti, lh) {
		if (!strcmp(name, obj->name)) {
			found = true;
			break;
		}
	}

	if (found)
		return obj;
	return NULL;
}

static int arm_cti_mod_reg_bits(struct arm_cti *self, unsigned int reg, uint32_t mask, uint32_t value)
{
	struct adiv5_ap *ap = dap_ap(self->spot.dap, self->spot.ap_num);
	uint32_t tmp;

	/* Read register */
	int retval = mem_ap_read_atomic_u32(ap, self->spot.base + reg, &tmp);
	if (retval != ERROR_OK)
		return retval;

	/* clear bitfield */
	tmp &= ~mask;
	/* put new value */
	tmp |= value & mask;

	/* write new value */
	return mem_ap_write_atomic_u32(ap, self->spot.base + reg, tmp);
}

int arm_cti_enable(struct arm_cti *self, bool enable)
{
	struct adiv5_ap *ap = dap_ap(self->spot.dap, self->spot.ap_num);
	uint32_t val = enable ? 1 : 0;

	return mem_ap_write_atomic_u32(ap, self->spot.base + CTI_CTR, val);
}

int arm_cti_ack_events(struct arm_cti *self, uint32_t event)
{
	struct adiv5_ap *ap = dap_ap(self->spot.dap, self->spot.ap_num);
	int retval;
	uint32_t tmp;

	retval = mem_ap_write_atomic_u32(ap, self->spot.base + CTI_INACK, event);
	if (retval == ERROR_OK) {
		int64_t then = timeval_ms();
		for (;;) {
			retval = mem_ap_read_atomic_u32(ap, self->spot.base + CTI_TROUT_STATUS, &tmp);
			if (retval != ERROR_OK)
				break;
			if ((tmp & event) == 0)
				break;
			if (timeval_ms() > then + 1000) {
				LOG_ERROR("timeout waiting for target");
				retval = ERROR_TARGET_TIMEOUT;
				break;
			}
		}
	}

	return retval;
}

int arm_cti_gate_channel(struct arm_cti *self, uint32_t channel)
{
	if (channel > 31)
		return ERROR_COMMAND_ARGUMENT_INVALID;

	return arm_cti_mod_reg_bits(self, CTI_GATE, CTI_CHNL(channel), 0);
}

int arm_cti_ungate_channel(struct arm_cti *self, uint32_t channel)
{
	if (channel > 31)
		return ERROR_COMMAND_ARGUMENT_INVALID;

	return arm_cti_mod_reg_bits(self, CTI_GATE, CTI_CHNL(channel), 0xFFFFFFFF);
}

int arm_cti_write_reg(struct arm_cti *self, unsigned int reg, uint32_t value)
{
	struct adiv5_ap *ap = dap_ap(self->spot.dap, self->spot.ap_num);

	return mem_ap_write_atomic_u32(ap, self->spot.base + reg, value);
}

int arm_cti_read_reg(struct arm_cti *self, unsigned int reg, uint32_t *p_value)
{
	struct adiv5_ap *ap = dap_ap(self->spot.dap, self->spot.ap_num);

	if (!p_value)
		return ERROR_COMMAND_ARGUMENT_INVALID;

	return mem_ap_read_atomic_u32(ap, self->spot.base + reg, p_value);
}

int arm_cti_pulse_channel(struct arm_cti *self, uint32_t channel)
{
	if (channel > 31)
		return ERROR_COMMAND_ARGUMENT_INVALID;

	return arm_cti_write_reg(self, CTI_APPPULSE, CTI_CHNL(channel));
}

int arm_cti_set_channel(struct arm_cti *self, uint32_t channel)
{
	if (channel > 31)
		return ERROR_COMMAND_ARGUMENT_INVALID;

	return arm_cti_write_reg(self, CTI_APPSET, CTI_CHNL(channel));
}

int arm_cti_clear_channel(struct arm_cti *self, uint32_t channel)
{
	if (channel > 31)
		return ERROR_COMMAND_ARGUMENT_INVALID;

	return arm_cti_write_reg(self, CTI_APPCLEAR, CTI_CHNL(channel));
}

static uint32_t cti_regs[28];

static const struct {
	uint32_t offset;
	const char *label;
	uint32_t *p_val;
} cti_names[] = {
	{ CTI_CTR,		"CTR",		&cti_regs[0] },
	{ CTI_GATE,		"GATE",		&cti_regs[1] },
	{ CTI_INEN0,	"INEN0",	&cti_regs[2] },
	{ CTI_INEN1,	"INEN1",	&cti_regs[3] },
	{ CTI_INEN2,	"INEN2",	&cti_regs[4] },
	{ CTI_INEN3,	"INEN3",	&cti_regs[5] },
	{ CTI_INEN4,	"INEN4",	&cti_regs[6] },
	{ CTI_INEN5,	"INEN5",	&cti_regs[7] },
	{ CTI_INEN6,	"INEN6",	&cti_regs[8] },
	{ CTI_INEN7,	"INEN7",	&cti_regs[9] },
	{ CTI_INEN8,	"INEN8",	&cti_regs[10] },
	{ CTI_OUTEN0,	"OUTEN0",	&cti_regs[11] },
	{ CTI_OUTEN1,	"OUTEN1",	&cti_regs[12] },
	{ CTI_OUTEN2,	"OUTEN2",	&cti_regs[13] },
	{ CTI_OUTEN3,	"OUTEN3",	&cti_regs[14] },
	{ CTI_OUTEN4,	"OUTEN4",	&cti_regs[15] },
	{ CTI_OUTEN5,	"OUTEN5",	&cti_regs[16] },
	{ CTI_OUTEN6,	"OUTEN6",	&cti_regs[17] },
	{ CTI_OUTEN7,	"OUTEN7",	&cti_regs[18] },
	{ CTI_OUTEN8,	"OUTEN8",	&cti_regs[19] },
	{ CTI_TRIN_STATUS,	"TRIN",	&cti_regs[20] },
	{ CTI_TROUT_STATUS,	"TROUT", &cti_regs[21] },
	{ CTI_CHIN_STATUS,	"CHIN",	&cti_regs[22] },
	{ CTI_CHOU_STATUS,	"CHOUT", &cti_regs[23] },
	{ CTI_APPSET,	"APPSET",	&cti_regs[24] },
	{ CTI_APPCLEAR,	"APPCLR",	&cti_regs[25] },
	{ CTI_APPPULSE,	"APPPULSE",	&cti_regs[26] },
	{ CTI_INACK,	"INACK",	&cti_regs[27] },
};

static int cti_find_reg_offset(const char *name)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(cti_names); i++) {
		if (!strcmp(name, cti_names[i].label))
			return cti_names[i].offset;
	}

	LOG_ERROR("unknown CTI register %s", name);
	return -1;
}

int arm_cti_cleanup_all(void)
{
	struct arm_cti *obj, *tmp;

	list_for_each_entry_safe(obj, tmp, &all_cti, lh) {
		free(obj->name);
		free(obj);
	}

	return ERROR_OK;
}

COMMAND_HANDLER(handle_cti_dump)
{
	struct arm_cti *cti = CMD_DATA;
	struct adiv5_ap *ap = dap_ap(cti->spot.dap, cti->spot.ap_num);
	int retval = ERROR_OK;

	for (int i = 0; (retval == ERROR_OK) && (i < (int)ARRAY_SIZE(cti_names)); i++)
		retval = mem_ap_read_u32(ap,
				cti->spot.base + cti_names[i].offset, cti_names[i].p_val);

	if (retval == ERROR_OK)
		retval = dap_run(ap->dap);

	if (retval != ERROR_OK)
		return JIM_ERR;

	for (int i = 0; i < (int)ARRAY_SIZE(cti_names); i++)
		command_print(CMD, "%8.8s (0x%04"PRIx32") 0x%08"PRIx32,
				cti_names[i].label, cti_names[i].offset, *cti_names[i].p_val);

	return JIM_OK;
}

COMMAND_HANDLER(handle_cti_enable)
{
	struct arm_cti *cti = CMD_DATA;
	bool on_off;

	if (CMD_ARGC != 1)
		return ERROR_COMMAND_SYNTAX_ERROR;

	COMMAND_PARSE_ON_OFF(CMD_ARGV[0], on_off);

	return arm_cti_enable(cti, on_off);
}

COMMAND_HANDLER(handle_cti_testmode)
{
	struct arm_cti *cti = CMD_DATA;
	bool on_off;

	if (CMD_ARGC != 1)
		return ERROR_COMMAND_SYNTAX_ERROR;

	COMMAND_PARSE_ON_OFF(CMD_ARGV[0], on_off);

	return arm_cti_write_reg(cti, 0xf00, on_off ? 0x1 : 0x0);
}

COMMAND_HANDLER(handle_cti_write)
{
	struct arm_cti *cti = CMD_DATA;
	int offset;
	uint32_t value;

	if (CMD_ARGC != 2)
		return ERROR_COMMAND_SYNTAX_ERROR;

	offset = cti_find_reg_offset(CMD_ARGV[0]);
	if (offset < 0)
		return ERROR_FAIL;

	COMMAND_PARSE_NUMBER(u32, CMD_ARGV[1], value);

	return arm_cti_write_reg(cti, offset, value);
}

COMMAND_HANDLER(handle_cti_read)
{
	struct arm_cti *cti = CMD_DATA;
	int offset;
	int retval;
	uint32_t value;

	if (CMD_ARGC != 1)
		return ERROR_COMMAND_SYNTAX_ERROR;

	offset = cti_find_reg_offset(CMD_ARGV[0]);
	if (offset < 0)
		return ERROR_FAIL;

	retval = arm_cti_read_reg(cti, offset, &value);
	if (retval != ERROR_OK)
		return retval;

	command_print(CMD, "0x%08"PRIx32, value);

	return ERROR_OK;
}

COMMAND_HANDLER(handle_cti_ack)
{
	struct arm_cti *cti = CMD_DATA;
	uint32_t event;

	if (CMD_ARGC != 1)
		return ERROR_COMMAND_SYNTAX_ERROR;

	COMMAND_PARSE_NUMBER(u32, CMD_ARGV[0], event);

	int retval = arm_cti_ack_events(cti, 1 << event);


	if (retval != ERROR_OK)
		return retval;

	return ERROR_OK;
}

COMMAND_HANDLER(handle_cti_channel)
{
	struct arm_cti *cti = CMD_DATA;
	int retval = ERROR_OK;
	uint32_t ch_num;

	if (CMD_ARGC != 2)
		return ERROR_COMMAND_SYNTAX_ERROR;

	COMMAND_PARSE_NUMBER(u32, CMD_ARGV[0], ch_num);

	if (!strcmp(CMD_ARGV[1], "gate"))
		retval = arm_cti_gate_channel(cti, ch_num);
	else if (!strcmp(CMD_ARGV[1], "ungate"))
		retval = arm_cti_ungate_channel(cti, ch_num);
	else if (!strcmp(CMD_ARGV[1], "pulse"))
		retval = arm_cti_pulse_channel(cti, ch_num);
	else if (!strcmp(CMD_ARGV[1], "set"))
		retval = arm_cti_set_channel(cti, ch_num);
	else if (!strcmp(CMD_ARGV[1], "clear"))
		retval = arm_cti_clear_channel(cti, ch_num);
	else {
		command_print(CMD, "Possible channel operations: gate|ungate|set|clear|pulse");
		return ERROR_COMMAND_ARGUMENT_INVALID;
	}

	if (retval != ERROR_OK)
		return retval;

	return ERROR_OK;
}

static const struct command_registration cti_instance_command_handlers[] = {
	{
		.name  = "dump",
		.mode  = COMMAND_EXEC,
		.handler = handle_cti_dump,
		.help  = "dump CTI registers",
		.usage = "",
	},
	{
		.name = "enable",
		.mode = COMMAND_EXEC,
		.handler = handle_cti_enable,
		.help = "enable or disable the CTI",
		.usage = "'on'|'off'",
	},
	{
		.name = "testmode",
		.mode = COMMAND_EXEC,
		.handler = handle_cti_testmode,
		.help = "enable or disable integration test mode",
		.usage = "'on'|'off'",
	},
	{
		.name = "write",
		.mode = COMMAND_EXEC,
		.handler = handle_cti_write,
		.help = "write to a CTI register",
		.usage = "register_name value",
	},
	{
		.name = "read",
		.mode = COMMAND_EXEC,
		.handler = handle_cti_read,
		.help = "read a CTI register",
		.usage = "register_name",
	},
	{
		.name = "ack",
		.mode = COMMAND_EXEC,
		.handler = handle_cti_ack,
		.help = "acknowledge a CTI event",
		.usage = "event",
	},
	{
		.name = "channel",
		.mode = COMMAND_EXEC,
		.handler = handle_cti_channel,
		.help = "do an operation on one CTI channel, possible operations: "
				"gate, ungate, set, clear and pulse",
		.usage = "channel_number operation",
	},
	COMMAND_REGISTRATION_DONE
};

static int cti_configure(struct jim_getopt_info *goi, struct arm_cti *cti)
{
	/* parse config or cget options ... */
	while (goi->argc > 0) {
		int e = adiv5_jim_mem_ap_spot_configure(&cti->spot, goi);
		if (e != JIM_OK)
			return e;
	}

	if (!cti->spot.dap) {
		Jim_SetResultString(goi->interp, "-dap required when creating CTI", -1);
		return JIM_ERR;
	}

	return JIM_OK;
}
static int cti_create(struct jim_getopt_info *goi)
{
	struct command_context *cmd_ctx;
	static struct arm_cti *cti;
	Jim_Obj *new_cmd;
	Jim_Cmd *cmd;
	const char *cp;
	int e;

	cmd_ctx = current_command_context(goi->interp);
	assert(cmd_ctx);

	if (goi->argc < 3) {
		Jim_WrongNumArgs(goi->interp, 1, goi->argv, "?name? ..options...");
		return JIM_ERR;
	}
	/* COMMAND */
	jim_getopt_obj(goi, &new_cmd);
	/* does this command exist? */
	cmd = Jim_GetCommand(goi->interp, new_cmd, JIM_NONE);
	if (cmd) {
		cp = Jim_GetString(new_cmd, NULL);
		Jim_SetResultFormatted(goi->interp, "Command: %s Exists", cp);
		return JIM_ERR;
	}

	/* Create it */
	cti = calloc(1, sizeof(*cti));
	if (!cti)
		return JIM_ERR;

	adiv5_mem_ap_spot_init(&cti->spot);

	/* Do the rest as "configure" options */
	goi->isconfigure = 1;
	e = cti_configure(goi, cti);
	if (e != JIM_OK) {
		free(cti);
		return e;
	}

	cp = Jim_GetString(new_cmd, NULL);
	cti->name = strdup(cp);

	/* now - create the new cti name command */
	const struct command_registration cti_subcommands[] = {
		{
			.chain = cti_instance_command_handlers,
		},
		COMMAND_REGISTRATION_DONE
	};
	const struct command_registration cti_commands[] = {
		{
			.name = cp,
			.mode = COMMAND_ANY,
			.help = "cti instance command group",
			.usage = "",
			.chain = cti_subcommands,
		},
		COMMAND_REGISTRATION_DONE
	};
	e = register_commands_with_data(cmd_ctx, NULL, cti_commands, cti);
	if (e != ERROR_OK)
		return JIM_ERR;

	list_add_tail(&cti->lh, &all_cti);

	return JIM_OK;
}

static int jim_cti_create(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 < 2) {
		Jim_WrongNumArgs(goi.interp, goi.argc, goi.argv,
			"<name> [<cti_options> ...]");
		return JIM_ERR;
	}
	return cti_create(&goi);
}

static int jim_cti_names(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
	struct arm_cti *obj;

	if (argc != 1) {
		Jim_WrongNumArgs(interp, 1, argv, "Too many parameters");
		return JIM_ERR;
	}
	Jim_SetResult(interp, Jim_NewListObj(interp, NULL, 0));
	list_for_each_entry(obj, &all_cti, lh) {
		Jim_ListAppendElement(interp, Jim_GetResult(interp),
			Jim_NewStringObj(interp, obj->name, -1));
	}
	return JIM_OK;
}


static const struct command_registration cti_subcommand_handlers[] = {
	{
		.name = "create",
		.mode = COMMAND_ANY,
		.jim_handler = jim_cti_create,
		.usage = "name '-chain-position' name [options ...]",
		.help = "Creates a new CTI object",
	},
	{
		.name = "names",
		.mode = COMMAND_ANY,
		.jim_handler = jim_cti_names,
		.usage = "",
		.help = "Lists all registered CTI objects by name",
	},
	COMMAND_REGISTRATION_DONE
};

static const struct command_registration cti_command_handlers[] = {
	{
		.name = "cti",
		.mode = COMMAND_CONFIG,
		.help = "CTI commands",
		.chain = cti_subcommand_handlers,
		.usage = "",
	},
	COMMAND_REGISTRATION_DONE
};

int cti_register_commands(struct command_context *cmd_ctx)
{
	return register_commands(cmd_ctx, NULL, cti_command_handlers);
}
