/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2021 Aidan MacDonald
 *
 * 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 "system.h"
#include <string.h>
#include "mips.h"
#include "panic.h"
#include "button.h"
#include "gpio-x1000.h"
#include "dma-x1000.h"
#include "irq-x1000.h"
#include "clk-x1000.h"
#include "boot-x1000.h"
#include "x1000/cpm.h"
#include "x1000/ost.h"
#include "x1000/tcu.h"
#include "x1000/wdt.h"
#include "x1000/intc.h"
#include "x1000/msc.h"
#include "x1000/aic.h"

#if defined(HAVE_DEVICEDATA)
#include "devicedata.h"
#endif

#if defined(EROS_QN)
#include "eros_qn_codec.h"
#endif

#ifdef X1000_CPUIDLE_STATS
int __cpu_idle_avg = 0;
int __cpu_idle_cur = 0;
uint32_t __cpu_idle_ticks = 0;
uint32_t __cpu_idle_reftick = 0;
#endif

/* Prepare the CPU to process interrupts, but don't enable them yet */
static void system_init_irq(void) INIT_ATTR;
static void system_init_irq(void)
{
    /* Mask all interrupts */
    jz_set(INTC_MSK(0), 0xffffffff);
    jz_set(INTC_MSK(1), 0xffffffff);

    /* It's safe to unmask these unconditionally */
    jz_clr(INTC_MSK(0), (1 << IRQ0_GPIO0) | (1 << IRQ0_GPIO1) |
                        (1 << IRQ0_GPIO2) | (1 << IRQ0_GPIO3) |
                        (1 << IRQ0_TCU1));

    /* Setup CP0 registers */
    write_c0_status(M_StatusCU0 | M_StatusIM2 | M_StatusIM3);
    write_c0_cause(M_CauseIV);
}

/* First function called by crt0.S */
void system_early_init(void)
{
#if defined(FIIO_M3K) && !defined(BOOTLOADER)
    /* HACK for compatibility: CPM scratchpad has undefined contents at
     * time of reset and old bootloader revisions don't initialize it.
     * Therefore we can't rely on its contents on the FiiO M3K. This does
     * kind of break the entire point of boot flags, but right now they
     * are really only used by the bootloader so it's not a huge issue.
     * This hack should keep everything working as usual. */
    if(jz_readf(CPM_MPCR, ON) == 0) {
        init_boot_flags();
        set_boot_flag(BOOT_FLAG_CLK_INIT);
    }
#endif

    /* Finish up clock init */
    clk_init();
}

#if defined (HAVE_DEVICEDATA) && defined(EROS_QN)
void fill_devicedata(struct device_data_t *data)
{
# ifdef BOOTLOADER
    memset(data->payload, 0xff, data->length);
#  if EROSQN_VER == 1
    // version 2 has newer dac, 1 has old dac.
    data->hw_rev = eros_qn_discover_dac(false) ? 2 : 1;
#  else
    // versions 3 and 4 both have new dac
    data->hw_rev = EROSQN_VER;
#  endif
    data->version = DEIVCE_DATA_VERSION;
# else
    uint8_t hw_rev = device_data.hw_rev;
    uint8_t version = device_data.version;
    memset(data->payload, 0xff, data->length);
    data->hw_rev = hw_rev;
    data->version = version;
# endif
}
#endif

/* First thing called from Rockbox main() */
void system_init(void)
{
    /* Gate all clocks except CPU/bus/memory/RTC */
    REG_CPM_CLKGR = ~jz_orm(CPM_CLKGR, CPU_BIT, DDR, AHB0, APB0, RTC);

    /* Ungate timers and turn them all off by default */
    jz_writef(CPM_CLKGR, TCU(0), OST(0));
    jz_clrf(OST_ENABLE, OST1, OST2);
    jz_write(OST_1MSK, 1);
    jz_write(OST_1FLG, 0);
    jz_clr(TCU_ENABLE, 0x80ff);
    jz_set(TCU_MASK, 0xff10ff);
    jz_clr(TCU_FLAG, 0xff10ff);
    jz_set(TCU_STOP, 0x180ff);

    /* Start OST2, needed for delay timer */
    jz_writef(OST_CTRL, PRESCALE2_V(BY_4));
    jz_writef(OST_CLEAR, OST2(1));
    jz_write(OST_2CNTH, 0);
    jz_write(OST_2CNTL, 0);
    jz_setf(OST_ENABLE, OST2);

    /* Ensure CPU sleep mode is IDLE and not SLEEP */
    jz_writef(CPM_LCR, LPM_V(IDLE));

    /* All other init */
    gpio_init();
    system_init_irq();
    dma_init();
    mmu_init();
}

#ifdef HAVE_ADJUSTABLE_CPU_FREQ
void set_cpu_frequency(long tgt_freq)
{
    /* Clamp target frequency to "sane" values */
    if(tgt_freq < 0)        tgt_freq = 0;
    if(tgt_freq > CPU_FREQ) tgt_freq = CPU_FREQ;

    /* Find out input clock */
    uint32_t in_freq;
    switch(jz_readf(CPM_CCR, SEL_CPLL)) {
    case 1: in_freq = clk_get(X1000_CLK_SCLK_A); break;
    case 2: in_freq = clk_get(X1000_CLK_MPLL); break;
    default: return;
    }

    /* Clamp to valid range */
    if(tgt_freq < 1)
        tgt_freq = 1;
    if(tgt_freq > (long)in_freq)
        tgt_freq = in_freq;

    /* Calculate CPU clock divider */
    uint32_t cdiv = clk_calc_div(in_freq, tgt_freq);
    if(cdiv > 16) cdiv = 16;
    if(cdiv < 1)  cdiv = 1;

    /* Calculate L2 cache clock. */
    uint32_t l2div = cdiv;
    if(cdiv == 1)
        l2div = 2;

    /* Change CPU/L2 frequency */
    jz_writef(CPM_CCR, CE_CPU(1), L2DIV(l2div - 1), CDIV(cdiv - 1));
    while(jz_readf(CPM_CSR, CDIV_BUSY));
    jz_writef(CPM_CCR, CE_CPU(0));

    /* Update value for Rockbox */
    cpu_frequency = in_freq / cdiv;
}
#endif

void system_reboot(void)
{
    jz_clr(TCU_STOP, 0x10000);
    jz_writef(WDT_CTRL, PRESCALE_V(BY_4), SOURCE_V(EXT));
    jz_write(WDT_COUNT, 0);
    jz_write(WDT_DATA, X1000_EXCLK_FREQ / 1000);
    jz_write(WDT_ENABLE, 1);
    while(1);
}

int system_memory_guard(int mode)
{
    /* unused */
    (void)mode;
    return 0;
}

/* Simple delay API -- slow path functions */

void __udelay(uint32_t us)
{
    while(us > MAX_UDELAY_ARG) {
        __ost_delay(MAX_UDELAY_ARG * OST_TICKS_PER_US);
        us -= MAX_UDELAY_ARG;
    }

    __ost_delay(us * OST_TICKS_PER_US);
}

void __mdelay(uint32_t ms)
{
    while(ms > MAX_MDELAY_ARG) {
        __ost_delay(MAX_MDELAY_ARG * 1000 * OST_TICKS_PER_US);
        ms -= MAX_MDELAY_ARG;
    }

    __ost_delay(ms * 1000 * OST_TICKS_PER_US);
}

uint64_t __ost_read64(void)
{
    int irq = disable_irq_save();
    uint64_t lcnt = REG_OST_2CNTL;
    uint64_t hcnt = REG_OST_2CNTHB;
    restore_irq(irq);
    return (hcnt << 32) | lcnt;
}

/* IRQ handling */
static int irq = 0;
static unsigned ipr0 = 0, ipr1 = 0;

static void UIRQ(void)
{
    panicf("Unhandled interrupt occurred: %d", irq);
}

#define intr(name) extern __attribute__((weak, alias("UIRQ"))) void name(void)
/* DWC2 USB interrupt */
#define OTG INT_USB_FUNC

/* Main interrupts */
intr(DMIC); intr(AIC);   intr(SFC);   intr(SSI0);  intr(OTG);   intr(AES);
intr(TCU2); intr(TCU1);  intr(TCU0);  intr(CIM);   intr(LCD);   intr(RTC);
intr(MSC1); intr(MSC0);  intr(SCC);   intr(PCM0);  intr(HARB2); intr(HARB0);
intr(CPM);  intr(UART2); intr(UART1); intr(UART0); intr(DDR);   intr(EFUSE);
intr(MAC);  intr(I2C2);  intr(I2C1);  intr(I2C0);  intr(JPEG);
intr(PDMA); intr(PDMAD); intr(PDMAM);
/* GPIO A - 32 pins */
intr(GPIOA00); intr(GPIOA01); intr(GPIOA02); intr(GPIOA03); intr(GPIOA04);
intr(GPIOA05); intr(GPIOA06); intr(GPIOA07); intr(GPIOA08); intr(GPIOA09);
intr(GPIOA10); intr(GPIOA11); intr(GPIOA12); intr(GPIOA13); intr(GPIOA14);
intr(GPIOA15); intr(GPIOA16); intr(GPIOA17); intr(GPIOA18); intr(GPIOA19);
intr(GPIOA20); intr(GPIOA21); intr(GPIOA22); intr(GPIOA23); intr(GPIOA24);
intr(GPIOA25); intr(GPIOA26); intr(GPIOA27); intr(GPIOA28); intr(GPIOA29);
intr(GPIOA30); intr(GPIOA31);
/* GPIO B - 32 pins */
intr(GPIOB00); intr(GPIOB01); intr(GPIOB02); intr(GPIOB03); intr(GPIOB04);
intr(GPIOB05); intr(GPIOB06); intr(GPIOB07); intr(GPIOB08); intr(GPIOB09);
intr(GPIOB10); intr(GPIOB11); intr(GPIOB12); intr(GPIOB13); intr(GPIOB14);
intr(GPIOB15); intr(GPIOB16); intr(GPIOB17); intr(GPIOB18); intr(GPIOB19);
intr(GPIOB20); intr(GPIOB21); intr(GPIOB22); intr(GPIOB23); intr(GPIOB24);
intr(GPIOB25); intr(GPIOB26); intr(GPIOB27); intr(GPIOB28); intr(GPIOB29);
intr(GPIOB30); intr(GPIOB31);
/* GPIO C - 26 pins */
intr(GPIOC00); intr(GPIOC01); intr(GPIOC02); intr(GPIOC03); intr(GPIOC04);
intr(GPIOC05); intr(GPIOC06); intr(GPIOC07); intr(GPIOC08); intr(GPIOC09);
intr(GPIOC10); intr(GPIOC11); intr(GPIOC12); intr(GPIOC13); intr(GPIOC14);
intr(GPIOC15); intr(GPIOC16); intr(GPIOC17); intr(GPIOC18); intr(GPIOC19);
intr(GPIOC20); intr(GPIOC21); intr(GPIOC22); intr(GPIOC23); intr(GPIOC24);
intr(GPIOC25);
/* GPIO D - 6 pins */
intr(GPIOD00); intr(GPIOD01); intr(GPIOD02); intr(GPIOD03); intr(GPIOD04);
intr(GPIOD05);

/* OST interrupt -- has no IRQ number since it's got special handling */
intr(OST);

#undef intr

static void(*irqvector[])(void) = {
    /* ICSR0: 0 - 31 */
    DMIC,   AIC,    UIRQ,   UIRQ,   UIRQ,   UIRQ,   UIRQ,   SFC,
    SSI0,   UIRQ,   PDMA,   PDMAD,  UIRQ,   UIRQ,   UIRQ,   UIRQ,
    UIRQ,   UIRQ,   UIRQ,   UIRQ,   UIRQ,   OTG,    UIRQ,   AES,
    UIRQ,   TCU2,   TCU1,   TCU0,   UIRQ,   UIRQ,   CIM,    LCD,
    /* ICSR1: 32 - 63 */
    RTC,    UIRQ,   UIRQ,   UIRQ,   MSC1,   MSC0,   SCC,    UIRQ,
    PCM0,   UIRQ,   UIRQ,   UIRQ,   HARB2,  UIRQ,   HARB0,  CPM,
    UIRQ,   UART2,  UART1,  UART0,  DDR,    UIRQ,   EFUSE,  MAC,
    UIRQ,   UIRQ,   I2C2,   I2C1,   I2C0,   PDMAM,  JPEG,   UIRQ,
    /* GPIO A: 64 - 95 */
    GPIOA00, GPIOA01, GPIOA02, GPIOA03, GPIOA04, GPIOA05, GPIOA06, GPIOA07,
    GPIOA08, GPIOA09, GPIOA10, GPIOA11, GPIOA12, GPIOA13, GPIOA14, GPIOA15,
    GPIOA16, GPIOA17, GPIOA18, GPIOA19, GPIOA20, GPIOA21, GPIOA22, GPIOA23,
    GPIOA24, GPIOA25, GPIOA26, GPIOA27, GPIOA28, GPIOA29, GPIOA30, GPIOA31,
    /* GPIO B: 96 - 127 */
    GPIOB00, GPIOB01, GPIOB02, GPIOB03, GPIOB04, GPIOB05, GPIOB06, GPIOB07,
    GPIOB08, GPIOB09, GPIOB10, GPIOB11, GPIOB12, GPIOB13, GPIOB14, GPIOB15,
    GPIOB16, GPIOB17, GPIOB18, GPIOB19, GPIOB20, GPIOB21, GPIOB22, GPIOB23,
    GPIOB24, GPIOB25, GPIOB26, GPIOB27, GPIOB28, GPIOB29, GPIOB30, GPIOB31,
    /* GPIO C: 128 - 159 */
    GPIOC00, GPIOC01, GPIOC02, GPIOC03, GPIOC04, GPIOC05, GPIOC06, GPIOC07,
    GPIOC08, GPIOC09, GPIOC10, GPIOC11, GPIOC12, GPIOC13, GPIOC14, GPIOC15,
    GPIOC16, GPIOC17, GPIOC18, GPIOC19, GPIOC20, GPIOC21, GPIOC22, GPIOC23,
    GPIOC24, GPIOC25, UIRQ,    UIRQ,    UIRQ,    UIRQ,    UIRQ,    UIRQ,
    /* GPIO D: 160 - 165 */
    GPIOD00, GPIOD01, GPIOD02, GPIOD03, GPIOD04, GPIOD05,
};

irq_handler_t system_set_irq_handler(int irq, irq_handler_t handler)
{
    irq_handler_t old_handler = irqvector[irq];
    irqvector[irq] = handler;
    return old_handler;
}

void system_enable_irq(int irq)
{
    if(IRQ_IS_GROUP0(irq)) {
        jz_clr(INTC_MSK(0), 1 << IRQ_TO_GROUP0(irq));
    } else if(IRQ_IS_GROUP1(irq)) {
        jz_clr(INTC_MSK(1), 1 << IRQ_TO_GROUP1(irq));
    }
}

void system_disable_irq(int irq)
{
    if(IRQ_IS_GROUP0(irq)) {
        jz_set(INTC_MSK(0), 1 << IRQ_TO_GROUP0(irq));
    } else if(IRQ_IS_GROUP1(irq)) {
        jz_set(INTC_MSK(1), 1 << IRQ_TO_GROUP1(irq));
    }
}

static int vector_gpio_irq(int port)
{
    int n = find_first_set_bit(REG_GPIO_FLAG(port));
    if(n & 32)
        return -1;

    jz_clr(GPIO_FLAG(port), 1 << n);
    return IRQ_GPIO(port, n);
}

static int vector_irq(void)
{
    int n = find_first_set_bit(ipr0);
    if(n & 32) {
        n = find_first_set_bit(ipr1);
        if(n & 32)
            return -1;
        ipr1 &= ~(1 << n);
        n += 32;
    } else {
        ipr0 &= ~(1 << n);
    }

    switch(n) {
    case IRQ0_GPIO0: n = vector_gpio_irq(GPIO_A); break;
    case IRQ0_GPIO1: n = vector_gpio_irq(GPIO_B); break;
    case IRQ0_GPIO2: n = vector_gpio_irq(GPIO_C); break;
    case IRQ0_GPIO3: n = vector_gpio_irq(GPIO_D); break;
    default: break;
    }

    return n;
}

void intr_handler(void)
{
    unsigned long cause = read_c0_cause();

    /* OST interrupt is handled separately */
    if(cause & M_CauseIP3) {
        OST();
        return;
    }

    /* Gather pending interrupts */
    ipr0 |= REG_INTC_PND(0);
    ipr1 |= REG_INTC_PND(1);

    /* Process and dispatch interrupt */
    irq = vector_irq();
    if(irq < 0)
        return;

    irqvector[irq]();
}

void system_exception_wait(void)
{
#ifdef FIIO_M3K
    while(button_read_device() != (BUTTON_POWER|BUTTON_VOL_DOWN));
#else
    while(1);
#endif
}
