/* Copyright 2003 Tematic Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>

#include "DebugLib/DebugLib.h"
#include "callx/callx.h"
#include "swis.h"
#include "sys/callout.h"
#include "sys/time.h"
#include "Global/HALEntries.h"

#ifdef USB_DEBUG
extern int usbdebug;
int total_sleep;
#endif


extern void detach_hub (struct device*);
extern void detach_device (struct device*);

extern uint32_t hal_veneer2(uint32_t (*)(void), void*, ...);
extern uint64_t gettime (void);

uint64_t gettime (void)
{
    uint32_t cs;
    static uint32_t (*readcode) (void);
    static void* ws;
    static uint32_t ns_factor;
    static uint32_t max_count;
    if (readcode == NULL)
    {
        _swix (OS_Hardware, _INR(8,9)|_OUTR(0,1),
            1, EntryNo_HAL_CounterRead,
            &readcode, &ws);

        _swix (OS_Hardware, _INR(8,9)| _OUT(0),
            0, EntryNo_HAL_CounterPeriod,
            &max_count);

#if 0 // fixed in RISC OS 5.03
        /* bug here - force 2000000 */
        max_count = 2000000;
#endif

        /* conversion to ns, assume counter is for 1 cs */
        ns_factor = 10000000 / max_count;
    }

    _swix (OS_ReadMonotonicTime, _OUT(0), &cs);
    return (uint64_t) (max_count - hal_veneer2(readcode,ws)) * ns_factor +
        ((uint64_t) cs) * 10000000 /* 1e7 */;
}

/* cold starting */
int cold = 0;

int hz = 1000;

/* disable interrupt for input */
int spltty (void)
{
    return _kernel_irqs_disabled ();
}

void splx (int s)
{
    if (s == 0) _kernel_irqs_on ();
//    dprintf (("", "int on\n"));
}

int splbio (void)
{
    int s = _kernel_irqs_disabled ();
    _kernel_irqs_off ();
//    dprintf (("", "int off\n"));
    return s;
}


char panic_string[255];

void panic (const char* str, ...)
{
    va_list p;
    va_start (p, str);
    /* aargh! */
    dprintf (("Port", "panicking:"));
    dvprintf (("Port", str, p));
    vsnprintf(panic_string, sizeof panic_string, str, p);
}

void
delay (int d)
{
#ifdef USB_DEBUG
    if (usbdebug > 19)
    {
        time_t t;
        time (&t);
        dprintf (("Port", "wait for %dus at %s", d, ctime(&t)));
    }
#endif
    _swix (OS_Hardware, _IN(0)|_INR(8,9), d, 0, EntryNo_HAL_CounterDelay);
}

void
microtime(struct timeval *thetime)
{
    uint64_t t = gettime ();
    thetime->tv_sec = (unsigned long) (t / 1000000000);
    thetime->tv_usec = (unsigned long) ((t / 1000) % 1000000);
}

void
selrecord(struct proc *selector, struct selinfo *selinfo)
{
    dprintf (("Port", "selecting record\n"));
}

void
selwakeup(struct selinfo *selinfo)
{
    dprintf (("Port", "selwakeup called\n"));
}

typedef int devclass_t;
typedef int caddr_t;

void*
devclass_get_softc (devclass_t dc, int unit)
{
    return 0; /* TODO make this return something that won't make it crash.. */
}

int
config_deactivate (struct device* dev)
{
    dprintf (("Port", "deactivating device\n"));
    return 0;
}

struct uio;

int
uiomove(caddr_t poo, int wobble, struct uio * fred)
{
    dprintf (("Port", "uiomove\n"));
    return 0;
}

void
psignal(unsigned sig, const char *s) {
    dprintf (("Port", "wow - we've received signal %d: %s\n", sig, s));
}

/*
 * Callouts
 */
extern int private_word;
extern void callout_veneer (void);

void
callout_init (struct callout* c)
{
#ifdef USB_DEBUG
    if (usbdebug > 15) dprintf (("Port", "callout init %p\n", c));
#endif
    memset (c, 0, sizeof c);
}

_kernel_oserror*
callout_handler (_kernel_swi_regs* r, void* pw, void* _c) {
    struct callout* c = _c;
#ifdef USB_DEBUG
    if (usbdebug > 15) dprintf (("Port", "callout %p called\n", c));
#endif
    c->c_func (c->c_arg);
    return NULL;
}

void
callout_stop (struct callout *c) {
#ifdef USB_DEBUG
    if (usbdebug > 15) dprintf (("Port", "callout stop %p\n", c));
#endif
    callx_remove_callafter (callout_handler, c);
}

void
callout_reset (struct callout *c, int i, void (*f)(void *), void *v) {
    if (i <= 0) i = 1;
    c->c_arg = v;
    c->c_func = f;
    c->c_next = (void*) private_word; /* hacky thing to get handler to work */
#ifdef USB_DEBUG
    if (usbdebug > 15) dprintf (("Port", "callout %p reset %dms\n", c, i));
#endif
    callx_add_callafter ((i + 9) / 10, callout_handler, c);
}

int kthread_create (void (f) (void*), void* h) {
    (void) f;
    (void) h;
    dprintf (("Port", "creating thread\n"));

    return 0;
}

int kthread_create1 (int (f) (void*), void* a, void* b, char* c, char* d)
{
    (void) f;
    (void) a;
    (void) b;
    (void) c;
    (void) d;

    return 0;
}

void* t_handles[100];
int t_locks[100];
int nhandles = 0;

extern void triggercbs (void);

int tsleep (void* ident, int priority, const char* wmesg, int timo, int noblock)
{
    int i;
    int s = _kernel_irqs_disabled ();
    uint64_t t0, t1, t2;

    _kernel_irqs_off ();                       // lets play safe

    t1 = t0 = gettime();
    t1 += ((uint64_t) timo) * 1000000;

#ifdef USB_DEBUG
    int cs0, cs1;
    _swix (OS_ReadMonotonicTime, _OUT(0), &cs0);
    if (usbdebug > 19)
        dprintf (("Port", "%s sleeping on %p at priority %d for %dms\n"
                      "from %lu.%.09lu\n"
                      "til  %lu.%.09lu\n",
            wmesg, ident, priority, timo,
            (uint32_t) (t0 / 1000000000), (uint32_t) (t0 % 1000000000),
            (uint32_t) (t1 / 1000000000), (uint32_t) (t1 % 1000000000)));
#endif

    for (i = 0; i < nhandles && t_handles[i] && t_handles[i] != ident; ++i);

    if (i == nhandles) {
        nhandles = i + 1;
        if (nhandles >= sizeof t_handles / sizeof *t_handles)
        {
            if (s == 0) _kernel_irqs_on ();
            panic ("run out of thread handles...");
            return 1;
        }
    }

    t_handles[i] = ident;
    t_locks[i] = 0;

    /* wait until the lock is free */
    int flags = 0;

    _kernel_irqs_on ();
    if (timo) {
        do {
            triggercbs ();
            t2 = gettime ();
            _swix (OS_ReadEscapeState, _OUT(_FLAGS), &flags);
        }
        while ((t_locks[i] == 0) && (t2 < t1) && ((flags & _C) == 0));
    }
    else
    {
        while (t_locks[i] == 0 && (flags & _C) == 0)
        {
            /* allow foreground process to function while we're waiting */
/*//            if (noblock) _swix (OS_UpCall, _INR(0,1), 6, t_locks + i);*/
/*//            _swix (OS_UpCall, _INR(0,1), 6, t_locks + i);*/
            triggercbs ();
            _swix (OS_ReadEscapeState, _OUT(_FLAGS), &flags);
        }
        t2 = gettime ();
    }
    _kernel_irqs_off ();

    if (flags & _C)
    {
        _kernel_osbyte (126, 0, 0);
    }

    t_handles[i] = NULL;

#ifdef USB_DEBUG
    if (usbdebug > 19)
      dprintf (("", "now  %lu.%09lu\n",
        (uint32_t) (t2 / 100000000), (uint32_t) (t2 % 1000000000)));
    t2 -= t0;
    _swix (OS_ReadMonotonicTime, _OUT(0), &cs1);
    total_sleep += cs1 - cs0;
    if (usbdebug > 10)
        dprintf (("", "slept for %lu.%.09lu seconds (timo = %dms), %d cs\n",
            (uint32_t) (t2 / 100000000), (uint32_t) (t2 % 1000000000),
            timo, cs1 - cs0));
#endif

    if (s == 0) _kernel_irqs_on ();

    return 0;
}

int wakeup (void* ident) {
    int i;
#ifdef USB_DEBUG
    if (usbdebug > 10) dprintf (("Port", "waking up %p\n", ident));
#endif

    /* Find the index of the handle passed */
    for (i = 0; i < nhandles && t_handles[i] != ident; ++i);

    /* unlock it */
    if (t_handles[i] == ident)
    {
      t_locks[i] = 1;
      t_handles[i] =NULL;
    }  
    return 0;
}

void kthread_exit () {
    dprintf (("Port", "exit thread\n"));
}

void device_probe_and_attach () {
    dprintf (("Port", "probing device\n"));
}

/*
 * usbd_ratecheck() can limit the number of error messages that occurs.
 * When a device is unplugged it may take up to 0.25s for the hub driver
 * to notice it.  If the driver continuosly tries to do I/O operations
 * this can generate a large number of messages.
 */
int
ratecheck (void* a, void* b)
{
    return 0;
}

void* usbd_print;

void* vtophys (void** v)
{
#ifndef EMULATE
    struct {
        void*   page;
        void*   logical;
        void*   physical;
    } block;
    block.logical = v;
    _swix (OS_Memory, _INR (0, 2), (1<<9) + (1<<13) + (2<<14), &block, 1);

    return block.physical;
#else
    dprintf (("Port", "Converting physical address %p\n", *v));
    return v; // return actual address for the moment
#endif
}

void* malloc_contig(int len, int alignment)
{
    void* p;
    _kernel_oserror* e;

    e = _swix(PCI_RAMAlloc, _INR(0,2)|_OUT(0), len, alignment, 0, &p);
    if (e || !p)
    {
        dprintf (("", "failed to allocate %d bytes at %d alignment err = '%s'\n",
            len, alignment, e?e->errmess:""));

        return NULL;
    }

    memset(p, 0, len);

    return p;
}

void free_contig (void **mem)
{
    _swix(PCI_RAMFree, _IN(0), *mem);
}

char*
device_get_nameunit (void)
{
    return "OHCI USB device";
}

int min (int a, int b)
{
    if (a < b) return a;
    return b;
}

void logprintf (char* format, ...)
{
    va_list p;
    va_start (p, format);
    dvprintf (("Log", format, p));
}

#ifdef USB_USE_SOFTINTR
void* softintr_establish(int pri, void (*f) (void*), void* h)
{
    void** p = malloc (3 * sizeof *p);
    if (p == NULL) return p;

    p[0] = 0;
    p[1] = h;
    p[2] = (void*) f;

    _swix (CBAI_RegisterPollWord, _INR(0, 2), p, pri, private_word);
    return p;
}

//void softintr_schedule (void** p)
//{
//    dprintf (("", "Setting pollword to %p\n", p[2]));
//    p[0] = p[2];
//}

void softintr_disestablish (void* p)
{
    _swix (CBAI_DeregisterPollWord, _IN(0), p);
    free (p);
}
#endif