/***************************************************************************
 *   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 {
	target_addr_t base;
	struct adiv5_ap *ap;
};

struct arm_cti_object {
	struct list_head lh;
	struct arm_cti cti;
	int ap_num;
	char *name;
};

static LIST_HEAD(all_cti);

const char *arm_cti_name(struct arm_cti *self)
{
	struct arm_cti_object *obj = container_of(self, struct arm_cti_object, cti);
	return obj->name;
}

struct arm_cti *cti_instance_by_jim_obj(Jim_Interp *interp, Jim_Obj *o)
{
	struct arm_cti_object *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->cti;
	return NULL;
}

static int arm_cti_mod_reg_bits(struct arm_cti *self, unsigned int reg, uint32_t mask, uint32_t value)
{
	uint32_t tmp;

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

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

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

int arm_cti_enable(struct arm_cti *self, bool enable)
{
	uint32_t val = enable ? 1 : 0;

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

int arm_cti_ack_events(struct arm_cti *self, uint32_t event)
{
	int retval;
	uint32_t tmp;

	retval = mem_ap_write_atomic_u32(self->ap, self->base + CTI_INACK, event);
	if (retval == ERROR_OK) {
		int64_t then = timeval_ms();
		for (;;) {
			retval = mem_ap_read_atomic_u32(self->ap, self->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)
{
	return mem_ap_write_atomic_u32(self->ap, self->base + reg, value);
}

int arm_cti_read_reg(struct arm_cti *self, unsigned int reg, uint32_t *p_value)
{
	if (p_value == NULL)
		return ERROR_COMMAND_ARGUMENT_INVALID;

	return mem_ap_read_atomic_u32(self->ap, self->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_object *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_object *obj = CMD_DATA;
	struct arm_cti *cti = &obj->cti;
	int retval = ERROR_OK;

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

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

	if (retval != ERROR_OK)
		return JIM_ERR;

	for (int i = 0; i < (int)ARRAY_SIZE(cti_names); i++)
		command_print(CMD_CTX, "%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_object *obj = CMD_DATA;
	Jim_Interp *interp = CMD_CTX->interp;
	struct arm_cti *cti = &obj->cti;
	bool on_off;

	if (CMD_ARGC != 1) {
		Jim_SetResultString(interp, "wrong number of args", -1);
		return ERROR_FAIL;
	}

	COMMAND_PARSE_ON_OFF(CMD_ARGV[0], on_off);

	return arm_cti_enable(cti, on_off);
}

COMMAND_HANDLER(handle_cti_testmode)
{
	struct arm_cti_object *obj = CMD_DATA;
	Jim_Interp *interp = CMD_CTX->interp;
	struct arm_cti *cti = &obj->cti;
	bool on_off;

	if (CMD_ARGC != 1) {
		Jim_SetResultString(interp, "wrong number of args", -1);
		return ERROR_FAIL;
	}

	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_object *obj = CMD_DATA;
	Jim_Interp *interp = CMD_CTX->interp;
	struct arm_cti *cti = &obj->cti;
	int offset;
	uint32_t value;

	if (CMD_ARGC != 2) {
		Jim_SetResultString(interp, "Wrong number of args", -1);
		return ERROR_FAIL;
	}

	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_object *obj = CMD_DATA;
	Jim_Interp *interp = CMD_CTX->interp;
	struct arm_cti *cti = &obj->cti;
	int offset;
	int retval;
	uint32_t value;

	if (CMD_ARGC != 1) {
		Jim_SetResultString(interp, "Wrong number of args", -1);
		return ERROR_FAIL;
	}

	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_CTX, "0x%08"PRIx32, value);

	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",
	},
	COMMAND_REGISTRATION_DONE
};

enum cti_cfg_param {
	CFG_DAP,
	CFG_AP_NUM,
	CFG_CTIBASE
};

static const Jim_Nvp nvp_config_opts[] = {
	{ .name = "-dap",     .value = CFG_DAP },
	{ .name = "-ctibase", .value = CFG_CTIBASE },
	{ .name = "-ap-num",  .value = CFG_AP_NUM },
	{ .name = NULL, .value = -1 }
};

static int cti_configure(Jim_GetOptInfo *goi, struct arm_cti_object *cti)
{
	struct adiv5_dap *dap = NULL;
	Jim_Nvp *n;
	jim_wide w;
	int e;

	/* parse config or cget options ... */
	while (goi->argc > 0) {
		Jim_SetEmptyResult(goi->interp);

		e = Jim_GetOpt_Nvp(goi, nvp_config_opts, &n);
		if (e != JIM_OK) {
			Jim_GetOpt_NvpUnknown(goi, nvp_config_opts, 0);
			return e;
		}
		switch (n->value) {
		case CFG_DAP: {
			Jim_Obj *o_t;
			e = Jim_GetOpt_Obj(goi, &o_t);
			if (e != JIM_OK)
				return e;
			dap = dap_instance_by_jim_obj(goi->interp, o_t);
			if (dap == NULL) {
				Jim_SetResultString(goi->interp, "-dap is invalid", -1);
				return JIM_ERR;
			}
			/* loop for more */
			break;
		}
		case CFG_CTIBASE:
			e = Jim_GetOpt_Wide(goi, &w);
			if (e != JIM_OK)
				return e;
			cti->cti.base = (uint32_t)w;
			/* loop for more */
			break;

		case CFG_AP_NUM:
			e = Jim_GetOpt_Wide(goi, &w);
			if (e != JIM_OK)
				return e;
			if (w < 0 || w > DP_APSEL_MAX) {
				Jim_SetResultString(goi->interp, "-ap-num is invalid", -1);
				return JIM_ERR;
			}
			cti->ap_num = (uint32_t)w;
		}
	}

	if (dap == NULL) {
		Jim_SetResultString(goi->interp, "-dap required when creating CTI", -1);
		return JIM_ERR;
	}

	cti->cti.ap = dap_ap(dap, cti->ap_num);

	return JIM_OK;
}

static int cti_create(Jim_GetOptInfo *goi)
{
	struct command_context *cmd_ctx;
	static struct arm_cti_object *cti;
	Jim_Obj *new_cmd;
	Jim_Cmd *cmd;
	const char *cp;
	int e;

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

	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_ERRMSG);
	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(struct arm_cti_object));
	if (cti == NULL)
		return JIM_ERR;

	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(cmd_ctx, NULL, cti_commands);
	if (ERROR_OK != e)
		return JIM_ERR;

	struct command *c = command_find_in_context(cmd_ctx, cp);
	assert(c);
	command_set_handler_data(c, cti);

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

	return (ERROR_OK == e) ? JIM_OK : JIM_ERR;
}

static int jim_cti_create(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
	Jim_GetOptInfo 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_object *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,
	},
	COMMAND_REGISTRATION_DONE
};

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

