#include <stdio.h>
#include <inttypes.h>
#include "config.h"
#include "system.h"
#include "../kernel-internal.h"
#include "gcc_extensions.h"
#include "lcd.h"
#include "font.h"
#include "backlight.h"
#include "adc.h"
#include "button-target.h"
#include "button.h"
#include "common.h"
#include "storage.h"
#include "file_internal.h"
#include "disk.h"
#include "panic.h"
#include "power.h"
#include "string.h"
#include "file.h"
#include "crc32-rkw.h"
#include "rkw-loader.h"
#include "version.h"
#include "i2c-rk27xx.h"
#include "loader_strerror.h"

/* beginning of DRAM */
#define DRAM_ORIG 0x60000000

/* bootloader code runs from 0x60700000
 * so we cannot load more code to not overwrite ourself
 */
#define LOAD_SIZE 0x700000

/* This function setup bare minimum
 * and jumps to rom in order to activate
 * hardcoded rkusb mode
 */
static void enter_rkusb(void)
{
    asm volatile (
        /* Turn off cache */
        "ldr     r0, =0xefff0000 \n"
        "ldrh    r1, [r0] \n"
        "strh    r1, [r0] \n"

        /* Turn off interrupts */
        "mrs     r0, cpsr \n"
        "bic     r0, r0, #0x1f \n"
        "orr     r0, r0, #0xd3 \n"
        "msr     cpsr, r0 \n"

        /* Disable iram remap */
        "mov     r0, #0x18000000 \n"
        "add     r0, r0, #0x1c000 \n"
        "mov     r1, #0 \n"
        "str     r1, [r0, #4] \n"

        /* Ungate all clocks */
        "str     r1, [r0, #0x18] \n"

        /* Read SCU_ID to determine
         * which version of bootrom we have
         * 2706A has ID 0xa1000604
         * 2706B and 2705 have ID 0xa100027b
         */
        "ldr     r1, [r0] \n"
        "ldr     r2, =0xa1000604 \n"
        "cmp     r1, r2 \n"
        "bne     2f \n"

        /* Setup stacks in unmapped
         * iram just as rom will do.
         *
         * We know about two versions
         * of bootrom which are very similar
         * but memory addresses are slightly
         * different.
         */
        "1: \n"  /* rk27xx_old */
        "ldr     r1, =0x18200258 \n"
        "ldr     r0, =0xaf0 \n"
        "b       3f \n"

        "2: \n"  /* rk27xx_new */
        "ldr     r1, =0x18200274 \n"
        "ldr     r0, =0xec0 \n"

        "3: \n"  /* jump to ROM */
        "msr     cpsr, #0xd2 \n"
        "add     r1, r1, #0x200 \n"
        "mov     sp, r1 \n"
        "msr     cpsr, #0xd3 \n"
        "add     r1, r1, #0x400 \n"
        "mov     sp, r1 \n"

        /* Finaly jump to rkusb handler
         * in bootrom.
         */
        "bx      r0 \n"
    );
}


void main(void) NORETURN_ATTR;
void main(void)
{
    char filename[MAX_PATH];
    unsigned char* loadbuffer;
    void(*kernel_entry)(void);
    int ret;
    enum {rb, of} boot = rb;

    power_init();
    system_init();
    kernel_init();
    i2c_init();
    enable_irq();

    adc_init();
    lcd_init();
    backlight_init();
    button_init_device();

    font_init();
    lcd_setfont(FONT_SYSFIXED);

    show_logo();

    int btn = button_read_device();

    /* if there is some other button pressed
     * besides POWER/PLAY we boot into OF
     */
    if ((btn & ~POWEROFF_BUTTON))
        boot = of;

    /* if we are woken up by USB insert boot into OF */
    if (DEV_INFO & (1<<20))
        boot = of;

    lcd_clear_display();

    ret = storage_init();
    if(ret < 0)
        error(EATA, ret, true);

    filesystem_init();

    while((ret = disk_mount_all()) <= 0)
        error(EDISK, ret, true);

    loadbuffer = (unsigned char*)DRAM_ORIG; /* DRAM */

    if (boot == rb)
        snprintf(filename,sizeof(filename), BOOTDIR "/%s", BOOTFILE);
    else if (boot == of)
        snprintf(filename,sizeof(filename), BOOTDIR "/%s", "BASE.RKW");

    printf("Bootloader version: %s", rbversion);
    printf("Loading: %s", filename);

    ret = load_rkw(loadbuffer, filename, LOAD_SIZE);

    if (ret <= EFILE_EMPTY)
    {
        error(EBOOTFILE, ret, false);

        /* if we boot rockbox we shutdown on error
         * if we boot OF we fall back to rkusb mode on error
         */
        if (boot == rb)
        {
            power_off();
        }
        else
        {
            /* give visual feedback what we are doing */
            printf("Entering rockchip USB mode...");
            lcd_update();

            enter_rkusb();
        }
    }
    else
    {
        /* print 'Loading OK' */
        printf("Loading OK");
        sleep(HZ);
    }

    /* jump to entrypoint */
    kernel_entry = (void*) loadbuffer;
    commit_discard_idcache();

    printf("Executing");
    kernel_entry();

    /* this should never be reached actually */
    printf("ERR: Failed to boot");
    sleep(5*HZ);

    if (boot == rb)
    {
        power_off();
    }
    else
    {
        /* give visual feedback what we are doing */
        printf("Entering rockchip USB mode...");
        lcd_update();

        enter_rkusb();
    }

    /* hang */
    while(1);
}
