/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2007 and 2009 by Karl Kurbjun
 *
 * 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 <stdlib.h>
#include "system.h"
#include "kernel.h"
#include "logf.h"
#include "audio.h"
#include "sound.h"
#include "file.h"
#include "dsp-target.h"
#include "dsp/ipc.h"
#include "pcm-internal.h"
#include "pcm_sink.h"

/* This is global to save some latency when pcm_play_dma_get_peak_buffer is 
 *  called.
 */
static const void *start;

static void sink_dma_postinit(void)
{
    /* Configure clock divider */
    tsc2100_writereg(CONTROL_PAGE2, TSPP1_ADDRESS, 0x1120);
    tsc2100_writereg(CONTROL_PAGE2, TSAC3_ADDRESS, 0x0800);
    tsc2100_writereg(CONTROL_PAGE2, TSCPC_ADDRESS, 0x3B00);
    tsc2100_writereg(CONTROL_PAGE2, TSAC1_ADDRESS, 0x0300);
    tsc2100_writereg(CONTROL_PAGE2, TSCSC_ADDRESS, 0xC580);
    audiohw_postinit();
}

static void sink_dma_init(void)
{
    IO_INTC_IRQ0 = INTR_IRQ0_IMGBUF;
    bitset16(&IO_INTC_EINT0, INTR_EINT0_IMGBUF);
    
    /* Set this as a FIQ */
    bitset16(&IO_INTC_FISEL0, INTR_EINT0_IMGBUF);
    
    /* Enable the HPIB clock */
    bitset16(&IO_CLK_MOD0, (CLK_MOD0_HPIB | CLK_MOD0_DSP));

    IO_SDRAM_SDDMASEL = 0x24;

    IO_DSPC_HPIB_CONTROL = 1 << 10 | 1 << 9 | 1 << 8 | 1 << 7 | 1 << 3 | 1 << 0;
    
    dsp_reset();
    dsp_load(dsp_image);

    DSP_(_dma0_stopped)=1;
    dsp_wake();
}

static void sink_set_freq(uint16_t freq)
{
    audiohw_set_frequency(freq);
}

/* Note that size is actually limited to the size of a short right now due to
 *  the implementation on the DSP side (and the way that we access it)
 */
static void sink_dma_start(const void *addr, size_t size)
{
    unsigned long sdem_addr=(unsigned long)addr - CONFIG_SDRAM_START;
    /* Initialize codec. */
    DSP_(_sdem_addrl) = sdem_addr & 0xffff;
    DSP_(_sdem_addrh) = sdem_addr >> 16;
    DSP_(_sdem_dsp_size) = size;
    DSP_(_dma0_stopped)=0;
    
    dsp_wake();
}

static void sink_dma_stop(void)
{
    DSP_(_dma0_stopped)=1;
    dsp_wake();
}

static void sink_lock(void)
{

}

static void sink_unlock(void)
{

}

/* Only used when debugging */
static char buffer[80];

void DSPHINT(void) __attribute__ ((section(".icode")));
void DSPHINT(void)
{
    unsigned int i;
    size_t size;

    IO_INTC_FIQ0 = INTR_IRQ0_IMGBUF;
    
    switch (dsp_message.msg) 
    {
    case MSG_DEBUGF:
        /* DSP stores one character per word. */
        for (i = 0; i < sizeof(buffer); i++) 
        {
            buffer[i] = dsp_message.payload.debugf.buffer[i];
        }
        
        DEBUGF("DSP: %s", buffer);
        break;
        
    case MSG_REFILL:
        /* Buffer empty.  Try to get more. */
        if (pcm_play_dma_complete_callback(PCM_DMAST_OK, &start, &size))
        {
            unsigned long sdem_addr=(unsigned long)start - CONFIG_SDRAM_START;
            /* Flush any pending cache writes */
            commit_dcache_range(start, size);

            /* set the new DMA values */
            DSP_(_sdem_addrl) = sdem_addr & 0xffff;
            DSP_(_sdem_addrh) = sdem_addr >> 16;
            DSP_(_sdem_dsp_size) = size;
            
            DEBUGF("pcm_sdram at 0x%08lx, sdem_addr 0x%08lx",
                (unsigned long)start, (unsigned long)sdem_addr);

            pcm_play_dma_status_callback(PCM_DMAST_STARTED);
        }
        
        break;
    default:
        DEBUGF("DSP: unknown msg 0x%04x", dsp_message.msg);
        break;
    }
    
    /* Re-Activate the channel */
    dsp_wake();
    
    DEBUGF("DSP: %s", buffer);
}

struct pcm_sink builtin_pcm_sink = {
    .caps = {
        .samprs       = hw_freq_sampr,
        .num_samprs   = HW_NUM_FREQ,
        .default_freq = HW_FREQ_DEFAULT,
    },
    .ops = {
        .init     = sink_dma_init,
        .postinit = sink_dma_postinit,
        .set_freq = sink_set_freq,
        .lock     = sink_lock,
        .unlock   = sink_unlock,
        .play     = sink_dma_start,
        .stop     = sink_dma_stop,
    },
};
