/***************************************************************************
 *             __________               __   ___.
 *   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.
 *
 ****************************************************************************/

#ifndef __SYSTEM_TARGET_H__
#define __SYSTEM_TARGET_H__

#ifdef DEBUG
/* Define this to get CPU idle stats, visible in the debug menu. */
# define X1000_CPUIDLE_STATS
#endif

#include "cpu.h"
#include "mmu-mips.h"
#include "mipsregs.h"
#include "mipsr2-endian.h"
#include "system-mips.h"
#include <stdint.h>

/* Rockbox API */
#define enable_irq()        set_c0_status(ST0_IE)
#define disable_irq()       clear_c0_status(ST0_IE)
#define disable_irq_save()  set_irq_level(0)
#define restore_irq(arg)    write_c0_status(arg)
#define HIGHEST_IRQ_LEVEL   0

static inline int set_irq_level(int lev)
{
    unsigned reg, oldreg;
    reg = oldreg = read_c0_status();
    if(lev)
        reg |= ST0_IE;
    else
        reg &= ~ST0_IE;

    write_c0_status(reg);
    return oldreg;
}

#ifdef X1000_CPUIDLE_STATS
/* CPU idle stats, updated each kernel tick in kernel-x1000.c */
extern int __cpu_idle_avg;
extern int __cpu_idle_cur;
extern uint32_t __cpu_idle_ticks;
extern uint32_t __cpu_idle_reftick;
#endif

static inline uint32_t __ost_read32(void);
static inline void core_sleep(void)
{
#ifdef X1000_CPUIDLE_STATS
    uint32_t t1 = __ost_read32();
#endif

    __asm__ __volatile__(
        ".set push\n\t"
        ".set mips32r2\n\t"
        "mfc0 $8, $12\n\t"
        "move $9, $8\n\t"
        "la   $10, 0x8000000\n\t"
        "or   $8, $10\n\t"
        "mtc0 $8, $12\n\t"
        "wait\n\t"
        "mtc0 $9, $12\n\t"
        ".set pop\n\t"
        ::: "t0", "t1", "t2");

#ifdef X1000_CPUIDLE_STATS
    uint32_t t2 = __ost_read32();
    __cpu_idle_ticks += t2 - t1;
#endif

    enable_irq();
}

/* IRQ control */
typedef void(*irq_handler_t)(void);

extern irq_handler_t system_set_irq_handler(int irq, irq_handler_t handler);
extern void system_enable_irq(int irq);
extern void system_disable_irq(int irq);

extern void system_early_init(void) INIT_ATTR;

/* Simple delay API */
#define OST_FREQUENCY       (X1000_EXCLK_FREQ / 4)
#define OST_TICKS_PER_US    (OST_FREQUENCY / 1000000)
#define MAX_OST_DELAY_ARG   0x7fffffff
#define MAX_UDELAY_ARG      (MAX_OST_DELAY_ARG / OST_TICKS_PER_US)
#define MAX_MDELAY_ARG      (MAX_UDELAY_ARG / 1000)

/* Macros adapted from include/linux/delay.h,
 *   Copyright (C) 1993 Linus Torvalds
 *
 * These optimize away all calculations to compile time for the common case
 * of small constant arguments, reducing to a single __ost_delay() call.
 */

#define udelay(n) \
    ((__builtin_constant_p(n) && (n) <= MAX_UDELAY_ARG) ? \
      __ost_delay((n) * OST_TICKS_PER_US) : __udelay((n)))

#define mdelay(n) \
    ((__builtin_constant_p(n) && (n) <= MAX_MDELAY_ARG) ? \
      __ost_delay((n) * 1000 * OST_TICKS_PER_US) : __mdelay((n)))

/* Slow path implementations which handle their full argument range by
 * looping and calling __ost_delay() repeatedly.
 */
extern void __udelay(uint32_t us);
extern void __mdelay(uint32_t ms);

/* Read full 64-bit OST counter value; this requires disabling IRQs
 * to safely read the counter.
 */
extern uint64_t __ost_read64(void);

static inline uint32_t __ost_read32(void)
{
    /* Read OST_2CNTL using raw address to avoid exposing internal headers.
     * The 64-bit counter is read with IRQs disabled and since threads are
     * not pre-emptive in Rockbox we won't trash anybody's 64-bit read by
     * reading the low count without locking.
     */
    return *(const volatile uint32_t*)0xb2000020;
}

/* NOTE: it is required that count < MAX_OST_DELAY_ARG, this is to provide
 * some slack in the 32-bit counter so we can reliably detect the timeout.
 */
static inline void __ost_delay(uint32_t count)
{
    /* Add one to ensure we delay for at least the time given */
    count += 1;
    uint32_t start = __ost_read32();
    while(__ost_read32() - start < count);
}

#endif /* __SYSTEM_TARGET_H__ */
