/*	$NetBSD: switch.c,v 1.2 2014/08/10 07:40:50 isaki Exp $	*/

/*
 * Copyright (c) 2014 Tetsuya Isaki. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/param.h>
#include <lib/libsa/stand.h>
#include <lib/libkern/libkern.h>

#include "switch.h"

#define SRAM_MEMSIZE	(*((volatile uint32_t *)0x00ed0008))
#define SRAM_ROMADDR	(*((volatile uint32_t *)0x00ed000c))
#define SRAM_RAMADDR	(*((volatile uint32_t *)0x00ed0010))
#define SRAM_BOOTDEV	(*((volatile uint16_t *)0x00ed0018))

#define SYSPORT_SRAM_WP	(*((volatile uint8_t *)0x00e8e00d))

static int hextoi(const char *);
static void cmd_switch_help(void);
static void cmd_switch_show(void);
static void cmd_switch_show_boot(void);
static void cmd_switch_show_rom(void);
static void cmd_switch_show_memory(void);
static const char *romaddr_tostr(uint32_t);
static const char *get_romaddr_name(uint32_t);
static void cmd_switch_boot(const char *);
static void cmd_switch_rom(const char *);
static void cmd_switch_memory(const char *);

static inline void
sram_write_enable(void)
{
	SYSPORT_SRAM_WP = 0x31;
}

static inline void
sram_write_disable(void)
{
	SYSPORT_SRAM_WP = 0;
}

static int
hextoi(const char *in)
{
	char *c;
	int ret;

	ret = 0;
	c = (char *)in;
	for (; isxdigit(*c); c++) {
		ret = (ret * 16) +
		      (*c > '9' ? ((*c | 0x20) - 'a' + 10) : *c - '0');
	}
	return ret;
}

static void
cmd_switch_help(void)
{
	printf(
		"usage: switch <key>=<val>\n"
		"         boot=[std | inscsi<N> | exscsi<N> | fd<N> | rom ]\n"
		"         rom=[ inscsi<N> | exscsi<N> | $<addr> ]\n"
		"         memory=<1..12> (unit:MB)\n"
		"       switch show\n"
	);
}

void
cmd_switch(char *arg)
{
	char *val;

	if (strcmp(arg, "show") == 0) {
		cmd_switch_show();
		return;
	}

	val = strchr(arg, '=');
	if (val == NULL) {
		cmd_switch_help();
		return;
	}
	*val++ = '\0';

	if (strcmp(arg, "boot") == 0) {
		cmd_switch_boot(val);
	} else if (strcmp(arg, "rom") == 0) {
		cmd_switch_rom(val);
	} else if (strcmp(arg, "memory") == 0) {
		cmd_switch_memory(val);
	} else {
		cmd_switch_help();
	}
}

static void
cmd_switch_show(void)
{
	cmd_switch_show_boot();
	cmd_switch_show_rom();
	cmd_switch_show_memory();
}

static void
cmd_switch_show_boot(void)
{
	uint32_t romaddr;
	uint16_t bootdev;
	const char *name;

	bootdev = SRAM_BOOTDEV;
	romaddr = SRAM_ROMADDR;

	/*
	 * $0000: std
	 * $8n00: sasi<N>
	 * $9n70: fd<N>
	 * $a000: ROM
	 * $b000: RAM
	 */
	printf("boot=");
	switch (bootdev >> 12) {
	default:
	case 0x0:
		/*
		 * The real order is fd->sasi->rom->ram
		 * but it is a bit redundant..
		 */
		printf("std (fd -> ");
		name = get_romaddr_name(romaddr);
		if (name)
			printf("%s)", name);
		else
			printf("rom$%x)", romaddr);
		break;
	case 0x8:
		printf("sasi%d", (bootdev >> 8) & 15);
		break;
	case 0x9:
		printf("fd%d", (bootdev >> 8) & 3);
		break;
	case 0xa:
		printf("rom%s", romaddr_tostr(romaddr));
		break;
	case 0xb:
		printf("ram$%x", SRAM_RAMADDR);
		break;
	}
	printf("\n");
}

static void
cmd_switch_show_rom(void)
{
	uint32_t romaddr;

	romaddr = SRAM_ROMADDR;
	printf("rom=%s\n", romaddr_tostr(romaddr));
}

static void
cmd_switch_show_memory(void)
{
	printf("memory=%d MB\n", SRAM_MEMSIZE / (1024 * 1024));
}

/* return rom address as string with name if any */
static const char *
romaddr_tostr(uint32_t addr)
{
	static char buf[32];
	const char *name;

	name = get_romaddr_name(addr);
	if (name)
		snprintf(buf, sizeof(buf), "$%x (%s)", addr, name);
	else
		snprintf(buf, sizeof(buf), "$%x", addr);

	return buf;
}

/*
 * return "inscsiN" / "exscsiN" if addr is in range of SCSI boot.
 * Otherwise return NULL.
 */
static const char *
get_romaddr_name(uint32_t addr)
{
	static char buf[8];

	if (0xfc0000 <= addr && addr < 0xfc0020 && addr % 4 == 0) {
		snprintf(buf, sizeof(buf), "inscsi%d", (addr >> 2) & 7);
	} else if (0xea0020 <= addr && addr < 0xea0040 && addr % 4 == 0) {
		snprintf(buf, sizeof(buf), "exscsi%d", (addr >> 2) & 7);
	} else {
		return NULL;
	}
	return buf;
}

static void
cmd_switch_boot(const char *arg)
{
	int id;
	uint32_t romaddr;
	uint16_t bootdev;

	romaddr = 0xffffffff;

	if (strcmp(arg, "std") == 0) {
		bootdev = 0x0000;

	} else if (strcmp(arg, "rom") == 0) {
		bootdev = 0xa000;

	} else if (strncmp(arg, "inscsi", 6) == 0) {
		id = (arg[6] - '0') & 7;
		bootdev = 0xa000;
		romaddr = 0xfc0000 + id * 4;

	} else if (strncmp(arg, "exscsi", 6) == 0) {
		id = (arg[6] - '0') & 7;
		bootdev = 0xa000;
		romaddr = 0xea0020 + id * 4;

	} else if (strncmp(arg, "fd", 2) == 0) {
		id = (arg[2] - '0') & 3;
		bootdev = 0x9070 | (id << 8);

	} else {
		cmd_switch_help();
		return;
	}

	sram_write_enable();
	SRAM_BOOTDEV = bootdev;
	if (romaddr != 0xffffffff)
		SRAM_ROMADDR = romaddr;
	sram_write_disable();

	cmd_switch_show_boot();
}

static void
cmd_switch_rom(const char *arg)
{
	int id;
	uint32_t romaddr;

	if (strncmp(arg, "inscsi", 6) == 0) {
		id = (arg[6] - '0') & 7;
		romaddr = 0xfc0000 + id * 4;

	} else if (strncmp(arg, "exscsi", 6) == 0) {
		id = (arg[6] - '0') & 7;
		romaddr = 0xea0020 + id * 4;

	} else if (*arg == '$') {
		romaddr = hextoi(arg + 1);

	} else {
		cmd_switch_help();
		return;
	}

	sram_write_enable();
	SRAM_ROMADDR = romaddr;
	sram_write_disable();

	cmd_switch_show_rom();
}

static void
cmd_switch_memory(const char *arg)
{
	int num;

	num = atoi(arg);
	if (num < 1 || num > 12) {
		cmd_switch_help();
		return;
	}

	sram_write_enable();
	SRAM_MEMSIZE = num * (1024 * 1024);
	sram_write_disable();

	cmd_switch_show_memory();
}
