/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2002 by Linus Nielsen Feltzing
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include <stdbool.h>
#include "i2c.h"
#include "mas.h"
#include "sh7034.h"
#include "system.h"
#include "debug.h"
#include "kernel.h"
#include "thread.h"
#include "ata.h"
#include "disk.h"
#include "fat.h"
#include "file.h"
#include "dir.h"
#include "panic.h"

#ifndef MIN
#define MIN(a, b) (((a)<(b))?(a):(b))
#endif

#define MPEG_PLAY         1
#define MPEG_STOP         2
#define MPEG_PAUSE        3
#define MPEG_RESUME       4
#define MPEG_NEED_DATA    100

#define MP3_LOW_WATER 0x30000
#define MP3_CHUNK_SIZE 0x20000

unsigned char fliptable[] =
{
    0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 
    0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, 
    0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 
    0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 
    0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 
    0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, 
    0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 
    0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 
    0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 
    0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 
    0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 
    0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, 
    0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 
    0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, 
    0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 
    0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 
    0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 
    0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, 
    0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 
    0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 
    0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 
    0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 
    0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 
    0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, 
    0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 
    0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 
    0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 
    0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 
    0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 
    0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 
    0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 
    0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
};

extern unsigned int stack[];
/* Place the MP3 data right after the stack */

#define MP3BUF_LEN 0x100000 /* 1 Mbyte */

unsigned char *mp3buf = (unsigned char *)stack;

int mp3buf_write;
int mp3buf_read;
int last_dma_chunk_size;

bool dma_on;  /* The DMA is active */
bool playing; /* We are playing an MP3 stream */
bool filling; /* We are filling the buffer with data from disk */

struct event_queue mpeg_queue;

static void mas_poll_start(unsigned int interval_in_ms);
void mpeg_thread(void);

void reset_mp3_buffer(void)
{
    mp3buf_read = 0;
    mp3buf_write = 0;
}

void setup_sci0(void)
{
    /* PB15 is I/O, PB14 is IRQ6, PB12 is SCK0 */
    PBCR1 = (PBCR1 & 0x0cff) | 0x1200;
    
    /* Set PB12 to output */
    PBIOR |= 0x1000;

    /* Disable serial port */
    SCR0 = 0x00;

    /* Synchronous, no prescale */
    SMR0 = 0x80;

    /* Set baudrate 1Mbit/s */
    BRR0 = 0x03;

    /* use SCK as serial clock output */
    SCR0 = 0x01;

    /* Clear FER and PER */
    SSR0 &= 0xe7;

    /* Set interrupt ITU2 and SCI0 priority to 0 */
    IPRD &= 0x0ff0;

    /* set IRQ6 and IRQ7 to edge detect */
    ICR |= 0x03;

    /* set PB15 and PB14 to inputs */
    PBIOR &= 0x7fff;
    PBIOR &= 0xbfff;

    /* set IRQ6 prio 8 and IRQ7 prio 0 */
    IPRB = ( IPRB & 0xff00 ) | 0x0080;

    /* Enable End of DMA interrupt at prio 8 */
    IPRC = (IPRC & 0xf0ff) | 0x0800;
    
    /* Enable Tx (only!) */
    SCR0 |= 0x20;
}


void init_dma(void)
{
    SAR3 = (unsigned int) mp3buf + mp3buf_read;
    DAR3 = 0x5FFFEC3;
    CHCR3 &= ~0x0002; /* Clear interrupt */
    CHCR3 = 0x1504; /* Single address destination, TXI0, IE=1 */
    last_dma_chunk_size = MIN(65536, mp3buf_write - mp3buf_read);
    DTCR3 = last_dma_chunk_size & 0xffff;
    DMAOR = 0x0001; /* Enable DMA */
    CHCR3 |= 0x0001; /* Enable DMA IRQ */
}

void start_dma(void)
{
    SCR0 |= 0x80;
    dma_on = true;
}

void stop_dma(void)
{
    SCR0 &= 0x7f;
    dma_on = false;
}

void dma_tick(void)
{
    /* Start DMA if it isn't running */
    if(playing && !dma_on)
    {
        if(PBDR & 0x4000)
        {
            if(!(SCR0 & 0x80))
                start_dma();
        }
    }
}

void bitswap(unsigned char *data, int length)
{
    unsigned int i;
    for(i = 0;i < length;i++)
    {
        data[i] = fliptable[data[i]];
    }
}

int main(void)
{
    char buf[40];
    char str[32];
    int i=0;
    DIR *d;
    struct dirent *dent;
    
    /* Clear it all! */
    SSR1 &= ~(SCI_RDRF | SCI_ORER | SCI_PER | SCI_FER);

    /* This enables the serial Rx interrupt, to be able to exit into the
       debugger when you hit CTRL-C */
    SCR1 |= 0x40;
    SCR1 &= ~0x80;

    IPRE |= 0xf000; /* Highest priority */

    i2c_init();

    dma_on = true;
    
    kernel_init();

    set_irq_level(0);

    setup_sci0();

    i=mas_readmem(MAS_BANK_D1,0xff6,(unsigned long*)buf,2);
    if (i) {
        debugf("Error - mas_readmem() returned %d\n", i);
        while(1);
    }

    i = buf[0] | buf[1] << 8;
    debugf("MAS version: %x\n", i);
    i = buf[4] | buf[5] << 8;
    debugf("MAS revision: %x\n", i);

    i=mas_readmem(MAS_BANK_D1,0xff9,(unsigned long*)buf,7);
    if (i) {
        debugf("Error - mas_readmem() returned %d\n", i);
        while(1);
    }

    for(i = 0;i < 7;i++)
    {
        str[i*2+1] = buf[i*4];
        str[i*2] = buf[i*4+1];
    }
    str[i*2] = 0;
    debugf("Description: %s\n", str);

    i=mas_writereg(0x3b, 0x20);
    if (i < 0) {
        debugf("Error - mas_writereg() returned %d\n", i);
        while(1);
    }

    i = mas_run(1);
    if (i < 0) {
        debugf("Error - mas_run() returned %d\n", i);
        while(1);
    }

    i = ata_init();
    debugf("ata_init() returned %d\n", i);

    i = disk_init();
    debugf("disk_init() returned %d\n", i);

    debugf("part[0] starts at sector %d\n", part[0].start);
    
    i = fat_mount(part[0].start);
    debugf("fat_mount() returned %d\n", i);
    
    if((d = opendir("/")))
    {
        while((dent = readdir(d)))
        {
            debugf("%s\n", dent->d_name);
        }
        closedir(d);
    }

    queue_init(&mpeg_queue);

    create_thread(mpeg_thread, stack - 0x2000, 0x4000);

    mas_poll_start(2);
    
    debugf("let's play...\n");

    queue_post(&mpeg_queue, MPEG_PLAY, 0);
    
    while(1)
    {
        sleep(HZ*1000);
    }
}

#pragma interrupt
void IRQ6(void)
{
    stop_dma();
}

#pragma interrupt
void DEI3(void)
{
    int unplayed_space_left;
    int space_until_end_of_buffer;

    if(playing)
    {
        mp3buf_read += last_dma_chunk_size;
        if(mp3buf_read >= MP3BUF_LEN)
            mp3buf_read = 0;
    
        unplayed_space_left = mp3buf_write - mp3buf_read;
        if(unplayed_space_left < 0)
            unplayed_space_left = MP3BUF_LEN + unplayed_space_left;

        space_until_end_of_buffer = MP3BUF_LEN - mp3buf_read;
        
        if(!filling && unplayed_space_left < MP3_LOW_WATER)
        {
            queue_post(&mpeg_queue, MPEG_NEED_DATA, 0);
        }
        
        if(unplayed_space_left)
        {
            last_dma_chunk_size = MIN(65536, unplayed_space_left);
            last_dma_chunk_size = MIN(last_dma_chunk_size, space_until_end_of_buffer);
            DTCR3 = last_dma_chunk_size & 0xffff;
            SAR3 = (unsigned int)mp3buf + mp3buf_read;
        }
        else
        {
            debugf("No more MP3 data. Stopping.\n");
            CHCR3 = 0; /* Stop DMA interrupt */
        }
    }

    CHCR3 &= ~0x0002; /* Clear DMA interrupt */
}

static void mas_poll_start(unsigned int interval_in_ms)
{
    unsigned int count;

    count = FREQ / 1000 / 8 * interval_in_ms;

    if(count > 0xffff)
    {
        panicf("Error! The MAS poll interval is too long (%d ms)\n",
               interval_in_ms);
        return;
    }
    
    /* We are using timer 1 */
    
    TSTR &= ~0x02; /* Stop the timer */
    TSNC &= ~0x02; /* No synchronization */
    TMDR &= ~0x02; /* Operate normally */

    TCNT1 = 0;   /* Start counting at 0 */
    GRA1 = count;
    TCR1 = 0x23; /* Clear at GRA match, sysclock/8 */

    /* Enable interrupt on level 2 */
    IPRC = (IPRC & ~0x000f) | 0x0002;
    
    TSR1 &= ~0x02;
    TIER1 = 0xf9; /* Enable GRA match interrupt */

    TSTR |= 0x02; /* Start timer 2 */
}

#pragma interrupt
void IMIA1(void)
{
    dma_tick();
    TSR1 &= ~0x01;
}

char *tracks[] =
{
    "/machinae_supremacy_-_arcade.mp3"
};

char *peek_next_track(int index)
{
    return tracks[index];
}

int mpeg_file = -1;

int new_file(void)
{
    int len;
    char *trackname;

    trackname = peek_next_track(0);

    debugf("playing %s\n", trackname);
    mpeg_file = open(trackname, O_RDONLY);
    if(mpeg_file < 0)
    {
        debugf("Couldn't open file\n");
        return -1;
    }
    
    /* First read in a few seconds worth of MP3 data */
    len = read(mpeg_file, mp3buf + mp3buf_write, MP3_CHUNK_SIZE);
    
    ata_spindown(-1);
    
    bitswap(mp3buf + mp3buf_write, len);
    
    mp3buf_write = len;

    return 0;
}

void mpeg_thread(void)
{
    struct event ev;
    int len;
    int free_space_left;
    int amount_to_read;

    playing = false;

    while(1)
    {
        debugf("S\n");
        queue_wait(&mpeg_queue, &ev);
        switch(ev.id)
        {
            case MPEG_PLAY:
                /* Stop the current stream */
                playing = false;
                stop_dma();

                reset_mp3_buffer();

                new_file();
                
                /* Make it read more data */
                filling = true;
                queue_post(&mpeg_queue, MPEG_NEED_DATA, 0);

                playing = true;
                
                init_dma();
                start_dma();
                break;

            case MPEG_STOP:
                /* Stop the current stream */
                playing = false;
                stop_dma();
                break;

            case MPEG_PAUSE:
                /* Stop the current stream */
                playing = false;
                stop_dma();
                break;

            case MPEG_RESUME:
                /* Stop the current stream */
                playing = true;
                start_dma();
                break;

            case MPEG_NEED_DATA:
                free_space_left = mp3buf_read - mp3buf_write;
                if(free_space_left < 0)
                    free_space_left = MP3BUF_LEN + free_space_left;

                if(free_space_left <= MP3_CHUNK_SIZE)
                {
                    debugf("0\n");
                    ata_spindown(-1);
                    filling = false;
                    break;;
                }
            
                amount_to_read = MIN(MP3_CHUNK_SIZE, free_space_left);
                amount_to_read = MIN(MP3BUF_LEN - mp3buf_write, amount_to_read);
                
                /* Read in a few seconds worth of MP3 data. We don't want to
                   read too large chunks because the bitswapping will take
                   too much time. We must keep the DMA happy and also give
                   the other threads a chance to run. */
                debugf("R\n");
                len = read(mpeg_file, mp3buf+mp3buf_write, amount_to_read);
                if(len)
                {
                    debugf("B\n");
                    bitswap(mp3buf + mp3buf_write, len);
                    
                    mp3buf_write += len;
                    if(mp3buf_write >= MP3BUF_LEN)
                    {
                        mp3buf_write = 0;
                        debugf("W\n");
                    }

                    /* Tell ourselves that we want more data */
                    queue_post(&mpeg_queue, MPEG_NEED_DATA, 0);
                }
                else
                {
                    close(mpeg_file);
                    
                    /* Make sure that the write pointer is at a word
                       boundary */
                    mp3buf_write &= 0xfffffffe;
                        
                    if(new_file() < 0)
                    {
                        /* No more data to play */
                        debugf("Finished playing\n");
                        playing = false;
                        ata_spindown(-1);
                        filling = false;
                    }
                    else
                    {
                        /* Tell ourselves that we want more data */
                        queue_post(&mpeg_queue, MPEG_NEED_DATA, 0);
                    }
                }
                break;
        }
    }
}

/* Newlib trap honeypot */
void __trap34(void)
{
    debugf("newlib trap34\n");
    while(1);
}
