/*Lark memory map :

  IOC:
  0000        : XC3030 FPGA (write)
                bit 0 - DIN
                bit 1 - CCLK
                bit 2 - INIT?  bits 2 & 3 may be reversed
                bit 3 - RESET?
  0000 - 1fff : banked ROM (read)
  2000        : ROM page register
  3000        : FPGA? 
        3000 read : IRQ status (/clear?)
                bit 0 - IRQ master
                bit 1 - AD1848 IRQ
                bit 2 - 16550 IRQ
                bit 6 - output FIFO half empty
                bit 7 - ~input FIFO half full
        3000 write :
                bit 3 - enable output FIFO
                bit 4 - enable input FIFO?
                bit 7 - reset?
  3400        : 16550 serial
  3c00 - 3c0f : AD1848 CODEC

  MEMC:
  0000 : Write to output FIFO
  0000 : Read from input FIFO
  

  Basic design :

  Lark uses an AD1848 (WSS-compatible) CODEC connected to two AM7202A 1024x9
  FIFOs - one for input, one for output. The FIFOs are 1024x9, these are used as
  512x16 with the FPGA converting one 16-bit MEMC access to 2 8-bit FIFO
  accesses (and vice versa).
  
  The Lark driver doesn't seem to use the AD1848 IRQ, just clearing the IRQ
  state. Instead the FIFO half-full flags are connected to interrupt lines and
  are used to trigger CPU transfers to and from the FIFOs.
  
  The FPGA seems to implement a single read and single write port at 0x3000.
  Read is IRQ status, writing acts as control. Control seems to include enable
  bits for AD1848 DMA transfers to/from the FIFOs, and also a reset bit? There
  are several other bits that I haven't figured out the meaning of yet.
  
  Internal Arc speaker is connected to one of the aux inputs, I am not sure
  which one. LK13 is connected to the other.
  
  MIDI is just handled by a standard 16550, same as the MIDI Max and many other
  MIDI podules.
*/

#ifdef WIN32
#include <windows.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "podule_api.h"
#include "16550.h"
#include "ad1848.h"
#include "am7202a.h"
#include "lark.h"
#include "midi.h"
#include "sound_in.h"
#include "sound_out.h"

#ifdef WIN32
extern __declspec(dllexport) const podule_header_t *podule_probe(const podule_callbacks_t *callbacks, char *path);
#else
#define BOOL int
#define APIENTRY
#endif

static const podule_callbacks_t *podule_callbacks;
char podule_path[512];

#define MIDI_UART_CLOCK 2000000 //(31250Hz * 4 * 16)

#define CTRL_OUT_FIFO_ENA (1 << 3)
#define CTRL_IN_FIFO_ENA (1 << 4)

typedef struct lark_t
{
        uint8_t rom[0x20000];
        int page;
        uint8_t irqstat;
        uint8_t ctrl;
        
        ad1848_t ad1848;
        am7202a_t out_fifo, in_fifo;
        n16550_t n16550;
        
        void *sound_in;
        void *sound_out;
        void *midi;
        
        podule_t *podule;
} lark_t;

#ifdef DEBUG_LOG
static FILE *lark_logf;
#endif
void lark_log(const char *format, ...)
{
#ifdef DEBUG_LOG
   char buf[1024];

   if (!lark_logf)
        lark_logf = fopen("lark_log.txt", "wt");
   va_list ap;
   va_start(ap, format);
   vsprintf(buf, format, ap);
   va_end(ap);
   fputs(buf, lark_logf);
   fflush(lark_logf);
#endif
}

static void lark_update_irqs(lark_t *lark)
{
        uint8_t irq_status = lark->irqstat ^ 0x80;

        if (!(lark->ctrl & 0x10))
                irq_status &= 0x7f;

        if (irq_status)
                podule_callbacks->set_irq(lark->podule, 1);
        else
                podule_callbacks->set_irq(lark->podule, 0);
}

static uint8_t lark_read_b(struct podule_t *podule, podule_io_type type, uint32_t addr)
{
        lark_t *lark = podule->p;
        uint8_t temp = 0xff;

        if (type != PODULE_IO_TYPE_IOC)
        {
                lark_log("lark_read_b: MEMC %04x\n", addr);
                return 0xff;
        }

        switch (addr&0x3c00)
        {
                case 0x0000: case 0x0400: case 0x0800: case 0x0c00:
                case 0x1000: case 0x1400: case 0x1800: case 0x1c00:
                addr = ((addr & 0x1ffc) | (lark->page << 13)) >> 2;
                //lark_log("  ROM %05X %02X\n", temp, lark_rom[temp]);
                temp = lark->rom[addr];
                break;

                case 0x3000:
                temp = lark->irqstat;
//                lark->irqstat = 0;
//                podule_callbacks->set_irq(podule, 0);
                if (temp ^ 0x80)
                        temp |= 0x01;
//                lark_log("Return IRQstat %02X\n", temp);
                break;

                case 0x3400:
                temp = n16550_read(&lark->n16550, (addr >> 2) & 7);
                break;

                case 0x3c00:
                temp = ad1848_read(&lark->ad1848, (addr >> 2) | 4);
                break;
        }
//        lark_log("lark_read_b: IOC %04x %02x\n", addr, temp);
                
        return temp;
}

static void lark_write_b(struct podule_t *podule, podule_io_type type, uint32_t addr, uint8_t val)
{        
        lark_t *lark = podule->p;

        if (type != PODULE_IO_TYPE_IOC)
        {
                lark_log("lark_write_b: MEMC %04x %02x\n", addr, val);
                return;
        }

        switch (addr & 0x3c00)
        {
                case 0x2000: 
                lark->page = val;
                break;
                
                case 0x3000:
                lark->ctrl = val;
                lark_update_irqs(lark);
//                lark_log("lark write control %02x\n", val);
                if (val & 0x80)
                        am7202a_reset(&lark->out_fifo);
                break;
                
                case 0x3400:
                n16550_write(&lark->n16550, (addr >> 2) & 7, val);
                break;

                case 0x3c00:
                ad1848_write(&lark->ad1848, (addr >> 2) | 4, val);
                break;
        }
//        lark_log("lark_write_b: IOC %04x %02x\n", addr, val);
}

static uint16_t lark_read_w(struct podule_t *podule, podule_io_type type, uint32_t addr)
{
        lark_t *lark = podule->p;
        uint16_t temp = 0xffff;
        
        if (type == PODULE_IO_TYPE_IOC)
                lark_log("lark_read_w: IOC %04x\n", addr);
        else
        {
//                lark_log("lark_read_w: MEMC %04x\n", addr);
                
                temp = am7202a_read(&lark->in_fifo);
                temp |= am7202a_read(&lark->in_fifo) << 8;
        }
                
        return temp;
}

static void lark_write_w(struct podule_t *podule, podule_io_type type, uint32_t addr, uint16_t val)
{
        lark_t *lark = podule->p;

        if (type == PODULE_IO_TYPE_IOC)
                lark_log("lark_write_w: IOC %04x %04x\n", addr, val);
        else
        {
//                lark_log("lark_write_w: MEMC %04x %04x\n", addr, val);
                am7202a_write(&lark->out_fifo, val & 0xff);
                am7202a_write(&lark->out_fifo, val >> 8);
        }
}

static int lark_run(struct podule_t *podule, int timeslice_us)
{
        lark_t *lark = podule->p;
        
        ad1848_run(&lark->ad1848, timeslice_us);
        n16550_run(&lark->n16550, timeslice_us);

        return 320; /*320us - 1 8N1 byte at 31250Hz (MIDI transfer speed)*/
}

void lark_set_irq(lark_t *lark, uint8_t irq)
{
        lark->irqstat |= irq;
        lark_update_irqs(lark);
}
void lark_clear_irq(lark_t *lark, uint8_t irq)
{
        lark->irqstat &= ~irq;
        lark_update_irqs(lark);
}

void lark_sound_out_buffer(struct lark_t *lark, void *buffer, int samples)
{
        sound_out_buffer(lark->sound_out, buffer, samples);
}

static void lark_uart_irq(void *p, int state)
{
        struct lark_t *lark = p;
        
        if (state)
                lark_set_irq(lark, IRQ_16550);
        else
                lark_clear_irq(lark, IRQ_16550);
}

static void lark_uart_send(void *p, uint8_t val)
{
        struct lark_t *lark = p;

        midi_write(lark->midi, val);
}

void lark_midi_receive(void *p, uint8_t val)
{
        struct lark_t *lark = p;
        
        n16550_receive(&lark->n16550, val);
}

void lark_sound_in_start(struct lark_t *lark)
{
        sound_in_start(lark->sound_in);
}

void lark_sound_in_stop(struct lark_t *lark)
{
        sound_in_stop(lark->sound_in);
}

static void lark_sound_in_buffer(void *p, void *buffer, int samples)
{
        struct lark_t *lark = p;
        
        ad1848_in_buffer(&lark->ad1848, buffer, samples);
}

static void lark_set_out_hf(int state, void *p)
{
        lark_t *lark = p;
        
        if (!state)
                lark_set_irq(lark, IRQ_OUT_FIFO);
        else
                lark_clear_irq(lark, IRQ_OUT_FIFO);
}

static void lark_set_in_hf(int state, void *p)
{
        lark_t *lark = p;

//        lark_log("lark_set_in_hf: state=%i\n", state);
        
        if (!state)
                lark_set_irq(lark, IRQ_IN_FIFO);
        else
                lark_clear_irq(lark, IRQ_IN_FIFO);
}

static uint8_t lark_dma_read(lark_t *lark)
{
        if (lark->ctrl & CTRL_OUT_FIFO_ENA)
                return am7202a_read(&lark->out_fifo);
        else
                return 0;
}

static void lark_dma_write(lark_t *lark, uint8_t val)
{
        if (lark->ctrl & CTRL_IN_FIFO_ENA)
                am7202a_write(&lark->in_fifo, val);
}

static int lark_init(struct podule_t *podule)
{
        FILE *f;
        char rom_fn[512];
        lark_t *lark = malloc(sizeof(lark_t));
        memset(lark, 0, sizeof(lark_t));

        sprintf(rom_fn, "%slark.rom", podule_path);
        lark_log("Lark ROM %s\n", rom_fn);
        f = fopen(rom_fn, "rb");
        if (!f)
        {
                lark_log("Failed to open LARK.ROM!\n");
                return -1;
        }
        fread(lark->rom, 0x20000, 1, f);
        fclose(f);
        
        lark->irqstat = 0x80;

        ad1848_init(&lark->ad1848, lark, lark_dma_read, lark_dma_write);
        am7202a_init(&lark->out_fifo, NULL, NULL, lark_set_out_hf, lark);
        am7202a_init(&lark->in_fifo, NULL, NULL, lark_set_in_hf, lark);
        n16550_init(&lark->n16550, MIDI_UART_CLOCK, lark_uart_irq, lark_uart_send, lark, lark_log);

        podule->p = lark;
        lark->podule = podule;

        lark->sound_out = sound_out_init(lark, 48000, 4800, lark_log, podule_callbacks, podule);
        lark->sound_in = sound_in_init(lark, lark_sound_in_buffer, lark_log, podule_callbacks, podule);
        lark->midi = midi_init(lark, lark_midi_receive, lark_log, podule_callbacks, podule);

        return 0;
}

static void lark_close(struct podule_t *podule)
{
        lark_t *lark = podule->p;

        sound_out_close(lark->sound_out);
        sound_in_close(lark->sound_in);
        midi_close(lark->midi);

        free(lark);
}

static podule_config_t lark_config =
{
        .items =
        {
                {
                        .name = "sound_in_device",
                        .description = "Sound input device",
                        .type = CONFIG_SELECTION,
                        .selection = NULL,
                        .default_int = -1
                },
                {
                        .name = "midi_out_device",
                        .description = "MIDI output device",
                        .type = CONFIG_SELECTION,
                        .selection = NULL,
                        .default_int = -1
                },
                {
                        .name = "midi_in_device",
                        .description = "MIDI input device",
                        .type = CONFIG_SELECTION,
                        .selection = NULL,
                        .default_int = -1
                },
                {
                        .type = -1
                }
        }
};

static const podule_header_t lark_podule_header =
{
        .version = PODULE_API_VERSION,
        .flags = PODULE_FLAGS_UNIQUE,
        .short_name = "lark",
        .name = "Computer Concepts Lark",
        .functions =
        {
                .init = lark_init,
                .close = lark_close,
                .read_b = lark_read_b,
                .read_w = lark_read_w,
                .write_b = lark_write_b,
                .write_w = lark_write_w,
                .run = lark_run
        },
        .config = &lark_config
};

const podule_header_t *podule_probe(const podule_callbacks_t *callbacks, char *path)
{
        char dev_name[256];

        podule_callbacks = callbacks;
        strcpy(podule_path, path);

        lark_config.items[0].selection = sound_in_devices_config();
        lark_config.items[1].selection = midi_out_devices_config();
        lark_config.items[2].selection = midi_in_devices_config();

        return &lark_podule_header;
}

#ifdef WIN32
BOOL APIENTRY DllMain (HINSTANCE hInst     /* Library instance handle. */ ,
                       DWORD reason        /* Reason this function is being called. */ ,
                       LPVOID reserved     /* Not used. */ )
{
    switch (reason)
    {
      case DLL_PROCESS_ATTACH:
        break;

      case DLL_PROCESS_DETACH:
        break;

      case DLL_THREAD_ATTACH:
        break;

      case DLL_THREAD_DETACH:
        break;
    }

    /* Returns TRUE on success, FALSE on failure */
    return TRUE;
}
#endif
