/*      $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();
}