/***************************************************************************
*             __________               __   ___.
*   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
*   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
*   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
*   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
*                     \/            \/     \/    \/            \/
*
* Copyright (C) 2007 by Michael Sevakis
*
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "kernel.h"
#include "system.h"
#include "panic.h"
#include "cpu.h"
#include "gcc_extensions.h"

#define default_interrupt(name) \
  extern __attribute__((weak,alias("UIRQ"))) void name (void)

default_interrupt(EINT0);
default_interrupt(EINT1);
default_interrupt(EINT2);
default_interrupt(EINT3);
default_interrupt(EINT4_7);
default_interrupt(EINT8_23);
default_interrupt(CAM);
default_interrupt(nBATT_FLT);
default_interrupt(TICK);
default_interrupt(WDT_AC97);
default_interrupt(TIMER0);
default_interrupt(TIMER1);
default_interrupt(TIMER2);
default_interrupt(TIMER3);
default_interrupt(TIMER4);
default_interrupt(UART2);
default_interrupt(LCD);
default_interrupt(DMA0);
default_interrupt(DMA1);
default_interrupt(DMA2);
default_interrupt(DMA3);
default_interrupt(SDI);
default_interrupt(SPI0);
default_interrupt(UART1);
default_interrupt(NFCON);
default_interrupt(USBD);
default_interrupt(USBH);
default_interrupt(IIC);
default_interrupt(UART0);
default_interrupt(SPI1);
default_interrupt(RTC);
default_interrupt(ADC);

static void (* const irqvector[32])(void) USED_ATTR =
{
    EINT0, EINT1, EINT2, EINT3,
    EINT4_7, EINT8_23, CAM, nBATT_FLT, TICK, WDT_AC97,
    TIMER0, TIMER1, TIMER2, TIMER3, TIMER4, UART2,
    LCD, DMA0, DMA1, DMA2, DMA3, SDI,
    SPI0, UART1, NFCON, USBD, USBH, IIC,
    UART0, SPI1, RTC, ADC,
};

static const char * const irqname[32] =
{
    "EINT0", "EINT1", "EINT2", "EINT3",
    "EINT4_7", "EINT8_23", "CAM", "nBATT_FLT", "TICK", "WDT_AC97",
    "TIMER0", "TIMER1", "TIMER2", "TIMER3", "TIMER4", "UART2",
    "LCD", "DMA0", "DMA1", "DMA2", "DMA3", "SDI",
    "SPI0", "UART1", "NFCON", "USBD", "USBH", "IIC",
    "UART0", "SPI1", "RTC", "ADC"
};

static void UIRQ(void)
{
    unsigned int offset = INTOFFSET;
    panicf("Unhandled IRQ %02X: %s", offset, irqname[offset]);
}

void irq_handler(void) __attribute__((interrupt ("IRQ"), naked));
void irq_handler(void)
{
    asm volatile (
        "sub    lr, lr, #4            \r\n"
        "stmfd  sp!, {r0-r3, ip, lr}  \r\n"
        "mov    r0, #0x4a000000       \r\n" /* INTOFFSET = 0x4a000014 */
        "ldr    r0, [r0, #0x14]       \r\n"
        "ldr    r1, =irqvector        \r\n"
        "ldr    r1, [r1, r0, lsl #2]  \r\n"
        "mov    lr, pc                \r\n"
        "bx     r1                    \r\n"
        "ldmfd  sp!, {r0-r3, ip, pc}^ \r\n"
    );
}

void system_reboot(void)
{
    WTCON = 0;
    WTCNT = WTDAT = 1 ;
    WTCON = 0x21;
    for(;;)
        ;
}

void system_exception_wait(void)
{
    INTMSK = 0xFFFFFFFF;
    while ((GPGDAT & (1 << 0)) == 0); /* Wait for power button */
}

static void set_page_tables(void)
{
    /* map every memory region to itself */
    map_section(0, 0, 0x1000, CACHE_NONE); 
    
    /* map RAM to 0 and enable caching for it */
    map_section(0x30000000, 0, MEMORYSIZE, CACHE_ALL); 
    
    /* enable buffered writing for the framebuffer */
    map_section((int)FRAME, (int)FRAME, 1, BUFFERED); 
}

void memory_init(void)
{
    ttb_init();
    set_page_tables();
    enable_mmu();
}

#ifdef BOOTLOADER
void system_prepare_fw_start(void)
{
    tick_stop();
    disable_interrupt(IRQ_FIQ_STATUS);
    INTMSK = 0xFFFFFFFF;
}
#endif

void system_init(void)
{
#ifdef GIGABEAT_F
    INTMSK = 0xFFFFFFFF;
    INTMOD =  0;
    SRCPND = 0xFFFFFFFF;
    INTPND = 0xFFFFFFFF;
    INTSUBMSK = 0xFFFFFFFF;
    SUBSRCPND = 0xFFFFFFFF;

    GPBCON |= 0x85;
    GPBDAT |= 0x07;
    GPBUP  |= 0x20F;
 
    /* Take care of flash related pins */
    GPCCON |= 0x1000;
    GPCDAT &= ~0x40;
    GPCUP  |= 0x51;

    GPDCON |= 0x05;
    GPDUP  |= 0x03;
    GPDDAT &= ~0x03;

    GPFCON |= 0x00000AAA;
    GPFUP  |= 0xFF;

    GPGCON |= 0x01001000;
    GPGUP  |= 0x70;

    GPHCON |= 0x4005;
    GPHDAT |= 0x03;

    /* TODO: do something with PRIORITY */
 
    /* Turn off currently-not or never-needed devices.
     *  Be careful here, it is possible to freeze the device by disabling
     *  clocks at the wrong time.
     *
     * Turn off AC97, Camera, SPI, IIS, I2C, UARTS, MMC/SD/SDIO Controller 
     * USB device, USB host, NAND flash controller.
     *
     * IDLE, Sleep, LCDC, PWM timer, GPIO, RTC, and ADC are untouched (on)
     */
    CLKCON &= ~0xFF1ED0;
 
    CLKSLOW |= 0x80;
#else
#error Unknown target    
#endif
}

int system_memory_guard(int newmode)
{
    (void)newmode;
    return 0;
}

#ifdef HAVE_ADJUSTABLE_CPU_FREQ

void set_cpu_frequency(long frequency)
{
    if (frequency == CPUFREQ_MAX)
    {
        asm volatile("mov r0, #0\n"
            "mrc p15, 0, r0, c1, c0, 0\n"
            "orr r0, r0, #3<<30\n" /* set to Asynchronous mode*/
            "mcr p15, 0, r0, c1, c0, 0" : : : "r0");

        FREQ = CPUFREQ_MAX;
    }
    else
    {
        asm volatile("mov r0, #0\n"
            "mrc p15, 0, r0, c1, c0, 0\n"
            "bic r0, r0, #3<<30\n" /* set to FastBus mode*/
            "mcr p15, 0, r0, c1, c0, 0" : : : "r0");

        FREQ = CPUFREQ_NORMAL;
    }
}

#endif
