/* Copyright (c) 1999 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * Copyright (c) 2008 Microsoft.  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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``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 FOUNDATION OR CONTRIBUTORS
 * 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)
 * 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 <lib/libsa/stand.h>
#include <lib/libsa/loadfile.h>
#include <lib/libkern/libkern.h>

#include <sys/param.h>
#include <sys/exec.h>
#include <sys/exec_elf.h>
#include <sys/reboot.h>

#include <machine/emipsreg.h>

#include "common.h"
#include "bootinfo.h"
#include "start.h"
#include "prom_iface.h"

#if _DEBUG
#define TRACE(x) printf x
#else
#define TRACE(x)
#endif

void epmc_halt(void);
void save_locore(void);
void restore_locore(void);

static void *nope(void) {return NULL;}
int getchar(void){return GetChar();}

static void
real_halt(void *arg)
{
    int howto = (int)arg;
    u_int ps = GetPsr();

    /* Turn off interrupts and the TLB */
#define EMIPS_SR_RP  0x08000000  /* reduced power */
#define EMIPS_SR_TS  0x00200000  /* TLB shutdown  */
#define EMIPS_SR_RST 0x00000080  /* Soft-reset    */
#define EMIPS_SR_INT 0x0000ff00  /* Interrupt enable mask */
#define EMIPS_SR_IEc 0x00000001  /* Interrupt enable current */

    ps &= ~(EMIPS_SR_INT | EMIPS_SR_IEc);
    ps |= EMIPS_SR_TS;
    SetPsr(ps);

    /* Reset entry must be restored for reboot 
     */
    restore_locore();

    /* Tell the power manager to halt? */
    for (;howto & RB_HALT;) {
        epmc_halt();

        /* We should not be here!! */
        ps |= EMIPS_SR_RP | EMIPS_SR_INT; /* but not current */
        SetPsr(ps);
    }

    /* For a reboot, all we can really do is a reset actually */
    for (;;) {
        ps |= EMIPS_SR_RST;
        SetPsr(ps);
    }
}

static void
halt(int *unused, int howto)
{
    /* We must switch to a safe stack! TLB will go down 
     */
    switch_stack_and_call((void *)howto,real_halt);
    /* no return, stack lost */
}

struct callback cb = {
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    getchar,
    nope,
    nope,
    printf,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    getsysid,
    nope,
    nope,
    nope,
    nope,
    nope,
    nope,
    halt
};

typedef char *string_t;

void epmc_halt(void)
{
    struct _Pmc *pm = (struct _Pmc *)0xfff00000;

    pm->SystemPowerDisable = PMCSC_CPU;
}


int init_usart(void)
{
    struct _Usart *us = (struct _Usart *)0xfff90000;

    us->Baud = 0x29;
    us->Control = (USC_RXEN|USC_TXEN|USC_BPC_8|USC_NONE|USC_1STOP|USC_CLKDIV_4);
    return 1;
}

/* Need to scan the PMT for all memory controllers
 * Actually.. just enough to make the kernel fit but we dont know how big it is
 */

/* Common format for SRAM, DDRAM, and FLASH controllers. 
 * Use SRAM decl. and careful about DDRAM that is twice as big.
 */
typedef struct _Sram *ram_controller_t;
# define                RAMBT_TAG   SRAMBT_TAG
# define                RAMBT_BASE  SRAMBT_BASE
# define                RAMST_SIZE  SRAMST_SIZE

int init_memory(void)
{
    struct _Pmt *Pmt;
    ram_controller_t Ram, Ours, First;
    uint32_t base, addr, moi = (uint32_t)(&init_memory) & 0x3ffff000;
    size_t size;
    uint16_t tag;
    int nsr, ndr, nfl;

    /* Make three passes.
     * First find which controller we are running under, cuz we cant touch it.
     * Then remap every RAM segment around it.
     * Then make sure FLASH segments do not overlap RAM.
     */

    nsr = ndr = nfl = 0;
    First = Ours = NULL;
    base = ~0;
    for (Pmt = ThePmt;;Pmt--) {
        tag = Pmt->Tag;
        //printf("PMT @%x tag=%x\n",Pmt,tag);
        switch (tag) {
        case PMTTAG_END_OF_TABLE:
            goto DoneFirst;
        case PMTTAG_SRAM:
        case PMTTAG_DDRAM:
        case PMTTAG_FLASH:
            Ram = (ram_controller_t)(Pmt->TopOfPhysicalAddress << 16);
            /* Scan the whole segment */
            for (;;) {
                //printf("RAM @%x tag=%x ctl=%x\n", Ram, Ram->BaseAddressAndTag,Ram->Control);
                if (tag != (Ram->BaseAddressAndTag & RAMBT_TAG))
                    break;
                addr = Ram->BaseAddressAndTag & RAMBT_BASE;
                if ((tag != PMTTAG_FLASH) && (addr < base)) {
                    base = addr;
                    First = Ram;
                }
                size = Ram->Control & RAMST_SIZE;
                if ((moi >= addr) && (moi < (addr + size))) {
                    Ours = Ram;
                }
                /* Next one.. and count them */
                Ram++;
                switch (tag) {
                case PMTTAG_SRAM:
                    nsr++;
                    break;
                case PMTTAG_FLASH:
                    nfl++;
                    break;
                case PMTTAG_DDRAM:
                    Ram++; /* yeach */
                    ndr++;
                    break;
                }
            }
            break;
        default:
            break;
        }
    }

    /* Make sure we know */
 DoneFirst:
    if ((First == NULL) || (Ours == NULL)) {
        printf("Bad memory layout (%p, %p), wont work.\n", First, Ours);
        return 0;
    }

    /* Second pass now */
    base += First->BaseAddressAndTag & RAMBT_BASE;
    for (Pmt = ThePmt;;Pmt--) {
        tag = Pmt->Tag;
        //printf("PMT @%x tag=%x\n",Pmt,tag);
        switch (tag) {
        case PMTTAG_END_OF_TABLE:
            goto DoneSecond;
        case PMTTAG_SRAM:
        case PMTTAG_DDRAM:
        case PMTTAG_FLASH:
            Ram = (ram_controller_t)(Pmt->TopOfPhysicalAddress << 16);
            /* Scan the whole segment */
            for (;;) {
                //printf("RAM @%x tag=%x ctl=%x\n", Ram, Ram->BaseAddressAndTag,Ram->Control);
                if (tag != (Ram->BaseAddressAndTag & RAMBT_TAG))
                    break;
                /* Leave us alone */
                if (Ram == Ours)
                    goto Next;
                /* Leave the first alone too */
                if (Ram == First)
                    goto Next;
                /* We do FLASH next round */
                if (tag == PMTTAG_FLASH)
                    goto Next;

                addr = Ram->BaseAddressAndTag & RAMBT_BASE;
                size = Ram->Control & RAMST_SIZE;

                /* Dont make it overlap with us */
                if ((moi >= base) && (moi < (base + size)))
                    base += Ours->Control & RAMST_SIZE;

                if (addr != base) {
                    printf("remapping %x+%zx to %x\n", addr, size, base);
                    Ram->BaseAddressAndTag = base;
                }
                base += size;

            Next:
                Ram++;
                if (tag == PMTTAG_DDRAM) Ram++; /* yeach */
            }
            break;
        default:
            break;
        }
    }
 DoneSecond:

    /* Third pass now: FLASH */
    for (Pmt = ThePmt;;Pmt--) {
        tag = Pmt->Tag;
        //printf("PMT @%x tag=%x\n",Pmt,tag);
        switch (tag) {
        case PMTTAG_END_OF_TABLE:
            goto DoneThird;
        case PMTTAG_FLASH:
            Ram = (ram_controller_t)(Pmt->TopOfPhysicalAddress << 16);
            /* Scan the whole segment */
            for (;;Ram++) {
                //printf("RAM @%x tag=%x ctl=%x\n", Ram, Ram->BaseAddressAndTag,Ram->Control);
                if (tag != (Ram->BaseAddressAndTag & RAMBT_TAG))
                    break;
                /* Leave us alone */
                if (Ram == Ours)
                    continue;

                addr = Ram->BaseAddressAndTag & RAMBT_BASE;
                size = Ram->Control & RAMST_SIZE;

                /* No need to move if it does not overlap RAM */
                if (addr >= base)
                    continue;

                /* Ahi */
                printf("remapping FLASH %x+%x to %x\n", addr, size, base);
                Ram->BaseAddressAndTag = base;
                base += size;
            }
            break;
        default:
            break;
        }
    }
DoneThird:
    return (nfl<<16) | (nsr << 8) | (ndr << 0);
}

u_int startjump[2];
u_int exceptioncode[(0x200-0x080)/4]; /* Change if ExceptionHandlerEnd changes */

void save_locore(void)
{
    memcpy(startjump,start,sizeof startjump);
    memcpy(exceptioncode,ExceptionHandler,sizeof exceptioncode);
}

void restore_locore(void)
{
    memcpy(start,startjump,sizeof startjump);
    memcpy(ExceptionHandler,exceptioncode,sizeof exceptioncode);
    /* BUGBUG flush icache */
}

void call_kernel(uint32_t addr, char *kname, char *kargs, u_int bim, char *bip)
{
    int argc = 0;
    string_t argv[3];
    int code = PROM_MAGIC;
    struct callback * cv = &cb;

    /* Safeguard ourselves */
    save_locore();

    if (kargs == NULL) kargs = "";
    argv[0] = kname;
    argv[1] = kargs;
    argv[2] = NULL;
    argc = 2;

    TRACE(("Geronimo(%x,%s %s)!\n",addr,kname,kargs));
    ((void(*)(int,char**,int,struct callback *,u_int,char*))addr)
           (argc,argv,code,cv,bim,bip);
}

