/* Copyright 2012 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.
 */

/* System module for Chrome EC */

#include "console.h"
#include "timer.h"
#include "util.h"
#include "watchdog.h"


enum format {
	FMT_WORD,
	FMT_HALF,
	FMT_BYTE,
	FMT_STRING,
};

#ifdef CONFIG_CMD_MD
static void show_val(uint32_t address, uint32_t index, enum format fmt)
{
	uint32_t val;
	uintptr_t ptr = address;

	switch (fmt) {
	case FMT_WORD:
		if (0 == (index % 4))
			ccprintf("\n%08X:", address + index * 4);
		val = *((uint32_t *)ptr + index);
		ccprintf(" %08x", val);
		break;
	case FMT_HALF:
		if (0 == (index % 8))
			ccprintf("\n%08X:", address + index * 2);
		val = *((uint16_t *)ptr + index);
		ccprintf(" %04x", val);
		break;
	case FMT_BYTE:
		if (0 == (index % 16))
			ccprintf("\n%08X:", address + index);
		val = *((uint8_t *)ptr + index);
		ccprintf(" %02x", val);
		break;
	case FMT_STRING:
		if (0 == (index % 32))
			ccprintf("\n%08X: ", address + index);
		val = *((uint8_t *)ptr + index);
		if (val >= ' ' && val <= '~')
			ccprintf("%c", val);
		else
			ccprintf("\\x%02x", val);
		break;
	}
	cflush();
}

static int command_mem_dump(int argc, char **argv)
{
	uint32_t address, i, num = 1;
	char *e;
	enum format fmt = FMT_WORD;

	if (argc > 1) {
		if ((argv[1][0] == '.') && (strlen(argv[1]) == 2)) {
			switch (argv[1][1]) {
			case 'b':
				fmt = FMT_BYTE;
				break;
			case 'h':
				fmt = FMT_HALF;
				break;
			case 's':
				fmt = FMT_STRING;
				break;
			default:
				return EC_ERROR_PARAM1;
			}
			argc--;
			argv++;
		}
	}

	if (argc < 2)
		return EC_ERROR_PARAM_COUNT;

	address = strtoi(argv[1], &e, 0);
	if (*e)
		return EC_ERROR_PARAM1;

	if (argc >= 3)
		num = strtoi(argv[2], &e, 0);

	for (i = 0; i < num; i++) {
		show_val(address, i, fmt);
		/* Lots of output could take a while.
		 * Let other things happen, too */
		if (!(i % 0x100)) {
			watchdog_reload();
			usleep(10 * MSEC);
		}
	}
	ccprintf("\n");
	cflush();
	return EC_SUCCESS;
}

DECLARE_CONSOLE_COMMAND_FLAGS
	(md, command_mem_dump,
	 "[.b|.h|.s] addr [count]",
	 "dump memory values, optionally specifying the format",
	 CMD_FLAG_RESTRICTED);
#endif /* CONFIG_CMD_MD */

#ifdef CONFIG_CMD_RW
static int command_read_word(int argc, char **argv)
{
	volatile uint32_t *address;
	uint32_t value;
	unsigned access_size = 4;
	unsigned argc_offs = 0;
	char *e;

	if (argc < 2)
		return EC_ERROR_PARAM_COUNT;

	if (argc > 2) {
		if ((argv[1][0] == '.') && (strlen(argv[1]) == 2)) {
			argc_offs = 1;
			switch (argv[1][1]) {
			case 'b':
				access_size = 1;
				break;
			case 'h':
				access_size = 2;
				break;
			default:
				return EC_ERROR_PARAM1;
			}
		}
	}

	address = (uint32_t *)(uintptr_t)strtoi(argv[1 + argc_offs], &e, 0);
	if (*e)
		return EC_ERROR_PARAM1 + argc_offs;

	/* Just reading? */
	if ((argc - argc_offs) < 3) {
		switch (access_size) {
		case 1:
			ccprintf("read 0x%pP = 0x%02x\n",
				 address, *((uint8_t *)address));
			break;
		case 2:
			ccprintf("read 0x%pP = 0x%04x\n",
				 address, *((uint16_t *)address));
			break;

		default:
			ccprintf("read 0x%pP = 0x%08x\n",  address, *address);
			break;
		}
		return EC_SUCCESS;
	}

	/* Writing! */
	value = strtoi(argv[2 + argc_offs], &e, 0);
	if (*e)
		return EC_ERROR_PARAM2 + argc_offs;

	switch (access_size) {
	case 1:
		ccprintf("write 0x%pP = 0x%02x\n", address, (uint8_t)value);
		cflush();  /* Flush before writing in case this crashes */
		*((uint8_t *)address) = (uint8_t)value;
		break;
	case 2:
		ccprintf("write 0x%pP = 0x%04x\n", address, (uint16_t)value);
		cflush();
		*((uint16_t *)address) = (uint16_t)value;
		break;
	default:
		ccprintf("write 0x%pP = 0x%02x\n", address, value);
		cflush();
		*address = value;
		break;
	}

	return EC_SUCCESS;
}

DECLARE_CONSOLE_COMMAND_FLAGS
	(rw, command_read_word,
	 "[.b|.h] addr [value]",
	 "Read or write a word in memory optionally specifying the size",
	 CMD_FLAG_RESTRICTED);
#endif	/* CONFIG_CMD_RW */
