/*
 * TI TLV320AIC23B Stereo codec support code
 *
 * TODO: Use interrupt instead of polling for I2C communication.
 */
#include <t_services.h>
#include <lpc23xx.h>
#include <string.h>
#include "nxpmp3.h"
#include "kernel_id.h"
#include "gpdma.h"
#include "player.h"

/* TLV320AIC23B definitions */
#define	CODEC_ADDR	0x1a

#define	REG_LEFT_INVOL	0
#define	REG_RIGHT_INVOL	1
#define	REG_LEFT_HPVOL	2
#define	REG_RIGHT_HPVOL	3
#define	REG_ANALOG_PATH	4
#define	REG_DIGITAL_PATH 5
#define	REG_POWER_DOWN	6
#define	REG_AUDIO_FORMAT 7
#define	REG_SAMPLE_RATE	8
#define	REG_IF_ACTIVATE	9
#define	REG_RESET	15

#define	DEF_VOLUME	100		/* Default volume level */
#define	MAX_VOLUME	128

#define	CONT_AA		(1<<2)
#define	CONT_SI		(1<<3)
#define	CONT_STO	(1<<4)
#define	CONT_STA	(1<<5)
#define	CONT_I2EN	(1<<6)

#define	CODEC_TXLEVEL	4
#define	CODEC_RXLEVEL	4

static int cvol;

static void codec_volset(int val);
static void codec_set_rate(int rate);
static void codec_pcm_output(pcm_data *pcm);
static void codec_init_output(void);
static void codec_stop_output(void);
static void codec_restart_output(void);

static devfunc codec_dev_funcs = {
    codec_volset, codec_set_rate, codec_pcm_output,
    codec_init_output, codec_stop_output, codec_restart_output
};

enum { CODEC_SEND, CODEC_RECV };
static int codec_mode;
static int in_dma, itc_handler, codec_halt;

t_codec_buffer codec_buffer __attribute__ ((section(".usbram"))); /* DMA buffer */
extern UB *UserBuffer;	/* Buffer space reserved for USB */

#define TEST_PARAMS
#ifdef TEST_PARAMS
static int TP1 = 2;
static int TP2 = 2;
static int TP3 = 1;
static int TP4 = 1;
static int TP5 = CODEC_TXLEVEL;
static int TP6 = CODEC_RXLEVEL;
#endif

devfunc *codec_func_init()
{
    return &codec_dev_funcs;
}

/* Initialize I2S for transmit */
void codec_ring_init()
{
    int i;
    UW dma_ctrl;
    t_codec_buffer *pcb = &codec_buffer;

    codec_mode = CODEC_SEND;
    pcb->buff_index = pcb->put_index = 0;
    in_dma = 0;

    /* ------ Setting up GPDMA Ch-1 ------ */
#define	IntTCClear1	(1<<1)

    /*
       1111111111111_0_0_0_0_0_000_1_0000_1_0000_0
                     H A L ITC     - DestPeripheral
                             IE           - SrcPeripheral
                               FlowCntrl         E
    */
    GPDMA_CH1_CFG &= 0xFFF80420;	/* Disable ch-1 */
    GPDMA_INT_TCCLR = IntTCClear1;	/* Clear interrupt flag */
    dma_ctrl = (1<<31) |	/* Trigger interrupt */
               (0 << 28) |	/* Protection */
               (0 << 27) |	/* Destination increment */
               (1 << 26) |	/* Source increment */
#ifdef TEST_PARAMS
               (TP1 << 21) |	/* DWidth */
               (TP2 << 18) |	/* SWidth */
               (TP3 << 15) |	/* DBsize */
               (TP4 << 12) |	/* SBsize */
#else
               (2 << 21) |	/* DWidth */
               (2 << 18) |	/* SWidth */
               (1 << 15) |	/* DBsize */
               (1 << 12) |	/* SBsize */
#endif
               (192 / 2);

    for (i = 0; i < DMA_NBUF; i++) {
        pcb->buffer[i] = (UH *)UserBuffer + i * BCODEC_SSIZE;
        pcb->codec_LLI[i].DMACCxSrcAddr = (UB *)pcb->buffer[i];
        pcb->codec_LLI[i].DMACCxDestAddr = (UB *)&I2S_TX_FIFO;
	if (i == (DMA_NBUF-1)) {
            pcb->codec_LLI[i].pDMACCxLLI = &pcb->codec_LLI[0];
        } else {
            pcb->codec_LLI[i].pDMACCxLLI = &pcb->codec_LLI[i+1];
        }
        pcb->codec_LLI[i].DMACCxControl = dma_ctrl;
        sig_sem(SEM_I2SOUT);
    }
}

static void codec_i2c_send(int regno, int val)
{
    UB stat;

    I22CONSET = CONT_I2EN | CONT_STA;   /* Send START */
    while (I22STAT != 0x08)
        ;
    I22DAT = (CODEC_ADDR << 1) | 0;     /* Add write bit */
    I22CONSET = CONT_I2EN | CONT_AA;
    I22CONCLR = CONT_STA | CONT_SI;     /* Clear SI bit */
    while ((I22CONSET & CONT_SI) == 0)
        ;
    stat = I22STAT & 0xff;
    if (stat != 0x18 ) {
        I22CONSET = CONT_I2EN | CONT_AA | CONT_STO;
        I22CONCLR = CONT_SI;        /* Clear SI bit */
        syslog(LOG_NOTICE, "codec_i2c_send: expect 0x18, but got 0x%x", stat);
        return;
    }
    I22DAT = (regno << 1) | ((val >> 8) & 1);
    I22CONSET = CONT_I2EN | CONT_AA;
    I22CONCLR = CONT_SI;        /* Clear SI bit */
    while ((I22CONSET & CONT_SI) == 0)
        ;
    stat = I22STAT & 0xff;
    if (stat != 0x28 ) {
        syslog(LOG_NOTICE, "codec_i2c_send2: expect 0x28, but got 0x%x (%x)", stat, I22CONSET);
        I22CONSET = CONT_I2EN | CONT_AA | CONT_STO;
        I22CONCLR = CONT_SI;        /* Clear SI bit */
        return;
    }
    I22DAT = val & 0xff;
    I22CONCLR = CONT_SI;        /* Clear SI bit */
    while ((I22CONSET & CONT_SI) == 0)
        ;
    stat = I22STAT & 0xff;
    if (stat != 0x28 ) {
        syslog(LOG_NOTICE, "codec_i2c_send3: expect 0x28, but got 0x%x", stat);
    }
    I22CONSET = CONT_I2EN | CONT_AA | CONT_STO;
    I22CONCLR = CONT_SI;        /* Clear SI bit */
}

#define	DA_STOP		0x08
#define	DA_RESET	0x10

static void codec_reset()
{
    I2S_DMA1 = I2S_DMA2 = 0;	/* disable DMA for I2S */
    I2S_DAO |= (DA_RESET | DA_STOP);
    I2S_DAO &= ~DA_RESET;
    dly_tsk(2);
    I2S_DAO &= ~(DA_RESET | DA_STOP);
    codec_ring_init();
}

static void codec_init_output()
{
}

static void codec_stop_output()
{
}

static void codec_restart_output()
{
    I2S_DAO |= (15 << 6) | (1 << 5) | 0x01; /* Slave, 16 bit data */
#ifdef TEST_PARAMS
    I2S_DMA1 = (TP5 << 16) | (1<<1);
#else
    I2S_DMA1 = (CODEC_TXLEVEL << 16) | (1<<1);
#endif
}

void codec_input_reset()
{
    codec_i2c_send(REG_LEFT_INVOL, 0x1f);
    codec_i2c_send(REG_RIGHT_INVOL, 0x1f);
}

/* Initialize I2S for receive */
void i2s_flush()
{
    int level, dummy;
    int i;
    UW dma_ctrl;
    t_codec_buffer *pcb = &codec_buffer;
    UH *fm_buffer;

    codec_mode = CODEC_RECV;
    pcb->buff_index = pcb->put_index = 0;

    GPDMA_CH1_CFG &= 0xFFF80420;	/* Disable ch-1 */
    GPDMA_INT_TCCLR = IntTCClear1;	/* Clear interrupt flag */
    dma_ctrl = (1<<31) |	/* Trigger interrupt */
               (0 << 28) |	/* Protection */
               (1 << 27) |	/* Destination increment */
               (0 << 26) |	/* Source increment */
#ifdef TEST_PARAMS
               (TP1 << 21) |	/* DWidth */
               (TP2 << 18) |	/* SWidth */
               (TP3 << 15) |	/* DBsize */
               (TP4 << 12) |	/* SBsize */
#else
               (2 << 21) |	/* DWidth */
               (2 << 18) |	/* SWidth */
               (1 << 15) |	/* DBsize */
               (1 << 12) |	/* SBsize */
#endif
               (CODEC_SSIZE / 2);

    fm_buffer = (UH *)(UserBuffer + 192 * 32);
    for (i = 0; i < DMA_NBUF; i++) {
        pcb->buffer[i] = fm_buffer + i * CODEC_SSIZE;
        pcb->codec_LLI[i].DMACCxSrcAddr = (UB *)&I2S_RX_FIFO;
        pcb->codec_LLI[i].DMACCxDestAddr = (UB *)pcb->buffer[i];
	if (i == (DMA_NBUF-1)) {
            pcb->codec_LLI[i].pDMACCxLLI = &pcb->codec_LLI[0];
        } else {
            pcb->codec_LLI[i].pDMACCxLLI = &pcb->codec_LLI[i+1];
        }
        pcb->codec_LLI[i].DMACCxControl = dma_ctrl;
#ifdef DEBUG
        syslog(LOG_NOTICE, "%d: SrcAddr = %x, DestAddr = %x", i, pcb->codec_LLI[i].DMACCxSrcAddr, pcb->codec_LLI[i].DMACCxDestAddr);
        dly_tsk(10);
#endif
    }

    I2S_DAI |= (DA_RESET | DA_STOP);
    I2S_DAI &= ~DA_RESET;
    /* Discard FIFO contents */
    do {
        dummy = I2S_RX_FIFO;
        level = (I2S_STATE >> 8) & 0xff;		/* RX level */
    } while (level > 0);
    I2S_DAI &= ~(DA_RESET | DA_STOP);
}

void i2s_stop()
{
    GPDMA_CH1_CFG |= (1<<18);	/* Halt */
    I2S_TX_FIFO = 0;
}

void codec_bypass(int flag)
{
    codec_input_reset();
    if (flag) {
        codec_i2c_send(REG_ANALOG_PATH, 0x0a);	/* Set BYP and MICM bits */
    } else {
        codec_i2c_send(REG_ANALOG_PATH, 0x12);	/* Set DAC and MICM bits */
    }
}

#ifdef DEBUG_COMMAND
void codec_filter(int rate)
{
    int val = 0;

    switch (rate) {
    case 32000:
        val = 2;
        break;
    case 44100:
        val = 4;
        break;
    case 48000:
        val = 6;
        break;
    }
    codec_i2c_send(REG_DIGITAL_PATH, val);
}
#endif

void codec_init(int flag)
{
    /* Initialize I2C as master transmitter mode */
    I22CONSET = CONT_I2EN;
    I22SCLL = I22SCLH = 200;	/* about 150K Hz */

    I2S_DAO = (DA_RESET | DA_STOP);	/* stop and reset */
    I2S_DAO = (15 << 6) | (1 << 5) | 0x01; /* Slave, 16 bit data */
    I2S_DAI = (DA_RESET | DA_STOP);	/* stop and reset */
    I2S_DAI = (15 << 6) | (1 << 5) | 0x01;

    codec_i2c_send(REG_RESET, 0);
    codec_i2c_send(REG_POWER_DOWN, 1);
    codec_i2c_send(REG_POWER_DOWN, 0);
    if (flag) {
        codec_i2c_send(REG_AUDIO_FORMAT, 0x42);	/* Master, I2S */
        codec_i2c_send(REG_SAMPLE_RATE, 0xa3);	/* 44.1k, USB mode */
        codec_i2c_send(REG_ANALOG_PATH, 0x10);
        codec_i2c_send(REG_DIGITAL_PATH, 0);
        codec_i2c_send(REG_IF_ACTIVATE, 1);	/* Activate */
        codec_i2c_send(REG_LEFT_HPVOL, 0x100 | DEF_VOLUME);
        codec_i2c_send(REG_RIGHT_HPVOL, 0x100 | DEF_VOLUME);
        cvol = 100;
    }
    codec_ring_init();
#ifdef DEBUG
    syslog(LOG_NOTICE, "codec_init done.");
#endif
}

static void codec_set_rate(int rate)
{
    if (rate == 48000)
    switch (rate) {
    case 48000:
        codec_i2c_send(REG_SAMPLE_RATE, 0x81);	/* 48k */
        break;
    case 32000:
        codec_i2c_send(REG_SAMPLE_RATE, 0x99);	/* 32k */
        break;
    default:        /* Treat as 44100 */
        codec_i2c_send(REG_SAMPLE_RATE, 0xa3);	/* 44.1k */
        break;
    }
}

void codec_dump()
{
    syslog(LOG_NOTICE, "I2SDAO: %x, I2SDAI: %x, I2SSTATE: %x, I2SIRQ: %x",
                I2S_DAO, I2S_DAI, I2S_STATE, I2S_IRQ);
}

#ifdef DEBUG
void codec_output_enable(int flag)
{
    if (flag) {
        I2S_DAO &= ~(DA_RESET | DA_STOP);	/* clear stop and reset */
    } else {
        I2S_DAO |= (DA_RESET | DA_STOP);	/* stop and reset */
        I2S_DAO &= ~DA_RESET;	/* clear reset bit */
    }
}
#endif

static UH *last_buffer;   /* Last buffer pointer */
static int ns_remain;	  /* samples remaining in the buffer */

void codec_ring_read(UH *bp, int cnt)
{
    int cbuff, ns;
    UW dma_ctrl;
    t_codec_buffer *pcb = &codec_buffer;

    if (!(GPDMA_CH1_CFG & 0x01)) { /* DMA is not enabled, yet */
        /* Load first LLI */
        GPDMA_CH1_SRC = (UW) pcb->codec_LLI[0].DMACCxSrcAddr;
        GPDMA_CH1_DEST = (UW) pcb->codec_LLI[0].DMACCxDestAddr;
        GPDMA_CH1_LLI = (UW) pcb->codec_LLI[0].pDMACCxLLI;
        GPDMA_CH1_CTRL = pcb->codec_LLI[0].DMACCxControl;
        /* Enable DMA on ch-1 */
        /* *************_0_0_1_1_0_010_*_0000_*_0101_1
                         H A L ITC       DestPeripheral
                                 IE             SrcPeripheral
                                   FlowCntrl         E
        */
        GPDMA_CH1_CFG |= 0x1900b;
        /* Enable DMA on I2S receive side */
#ifdef TEST_PARAMS
        I2S_DMA1 = (TP6 << 8) | (1<<0);
#else
        I2S_DMA1 = (CODEC_RXLEVEL << 8) | (1<<0);
#endif
    }

    if (ns_remain) {
        memcpy(bp, last_buffer, ns_remain << 1);
        bp += ns_remain;
        cnt -= ns_remain;
        ns_remain = 0;
        last_buffer = NULL;
    }
    while (cnt > 0) {
        wai_sem(SEM_I2SIN);    /* Wait until we have availabe buffer */
        cbuff = pcb->buff_index;
        if (cnt > CODEC_SSIZE) {
            ns = CODEC_SSIZE;
        } else {
            ns = cnt;
        }
        last_buffer = pcb->buffer[cbuff];
        memcpy(bp, last_buffer, ns << 1);
        bp += ns;
        cnt -= ns;
        pcb->buff_index++;
        pcb->buff_index &= (DMA_NBUF-1);
    }
    if (ns != CODEC_SSIZE) {
        last_buffer += ns;
        ns_remain = CODEC_SSIZE - ns;
    }
}

void codec_dma_itc_handler()
{
itc_handler++;
    GPDMA_INT_TCCLR = (1<<1);	/* Clear GPDMA interrupt flag */
    if (codec_mode == CODEC_SEND) {
        in_dma--;
        if (in_dma == 0) { /* All queued DMA data has been drained */
            GPDMA_CH1_CFG |= (1<<18);	/* Halt */
codec_halt++;
        }
        isig_sem(SEM_I2SOUT);
    } else {
        isig_sem(SEM_I2SIN);
    }
}

static void codec_pcm_output(pcm_data *pcm)
{
    int samples = pcm->samples;
    UH *bp = pcm->raw_buffer;
    int cbuff, ns;
    UW dma_ctrl;
    t_codec_buffer *pcb = &codec_buffer;

    while (samples > 0) {
        if (samples > BCODEC_SSIZE)
            ns = BCODEC_SSIZE;
        else
            ns = samples;
        samples -= ns;

        wai_sem(SEM_I2SOUT);    /* Wait until we have availabe buffer */
        cbuff = pcb->buff_index;
        /* Copy to DMA memory space */
        memcpy(pcb->buffer[cbuff], bp, ns << 1);
        dma_ctrl = pcb->codec_LLI[cbuff].DMACCxControl;
        dma_ctrl &= ~((1<<12)-1);
        pcb->codec_LLI[cbuff].DMACCxControl = dma_ctrl | (ns / 2);
        pcb->buff_index++;
        pcb->buff_index &= (DMA_NBUF-1);
        bp += ns;
	in_dma++;
        if (!(GPDMA_CH1_CFG & 0x01)) { /* DMA is not enabled, yet */
	    /* Load first LLI */
	    GPDMA_CH1_SRC = (UW) pcb->codec_LLI[0].DMACCxSrcAddr;
	    GPDMA_CH1_DEST = (UW) pcb->codec_LLI[0].DMACCxDestAddr;
	    GPDMA_CH1_LLI = (UW) pcb->codec_LLI[0].pDMACCxLLI;
	    GPDMA_CH1_CTRL = pcb->codec_LLI[0].DMACCxControl;
	    /* Enable DMA on ch-1 */
            /* *************_0_0_1_1_0_001_*_0101_*_0000_1
                             H A L ITC       DestPeripheral
                                     IE             SrcPeripheral
                                       FlowCntrl         E
             */
	    GPDMA_CH1_CFG |= 0x18941;
            /* Enable DMA on I2S transmit side */
#ifdef TEST_PARAMS
            I2S_DMA1 = (TP5 << 16) | (1<<1);
#else
            I2S_DMA1 = (CODEC_TXLEVEL << 16) | (1<<1);
#endif
        }
        if (GPDMA_CH1_CFG & (1<<18)) { /* Halted */
            GPDMA_CH1_CFG &= ~(1<<18);
        }
    }
}

#ifdef DEBUG_COMMAND
void codec_command(int argc, char **argv)
{
    if (argc > 1) {
        char *cp = argv[1];
        int dummy;

        if (strcmp(cp, "enable") == 0) {
            codec_output_enable(1);
        } else if (strcmp(cp, "disable") == 0) {
            codec_output_enable(0);
        } else if (strcmp(cp, "dump") == 0) {
            codec_dump();
        } else if (strcmp(cp, "init") == 0) {
            codec_init(1);
        } else if (strncmp(cp, "bypass", 6) == 0) {
            codec_bypass(1);
        } else if (strncmp(cp, "32000", 5) == 0) {
            codec_filter(32000);
        } else if (strncmp(cp, "44100", 5) == 0) {
            codec_filter(44100);
        } else if (strncmp(cp, "48000", 5) == 0) {
            codec_filter(48000);
        } else if (strncmp(cp, "0", 5) == 0) {
            codec_filter(0);
        } else if (strncmp(cp, "get", 3) == 0) {
            dummy = I2S_RX_FIFO;
        } else if (strncmp(cp, "put", 3) == 0) {
            I2S_TX_FIFO = 0x11;
        }
        return;
    }
    codec_init(0);
}
#else
void codec_command(int argc, char **argv)
{
    syslog(LOG_NOTICE, "DMACIntStatus: %x", GPDMA_INT_STAT);
    syslog(LOG_NOTICE, "DMACIntTCStatus: %x", GPDMA_INT_TCSTAT);
    syslog(LOG_NOTICE, "DMACRawIntTCStatus: %x", GPDMA_RAW_INT_TCSTAT);
    syslog(LOG_NOTICE, "DMACEnblkChns: %x", GPDMA_ENABLED_CHNS);
dly_tsk(1);
    syslog(LOG_NOTICE, "DMACRawIntErrorStatus: %x", GPDMA_RAW_INT_ERR_STAT);
    syslog(LOG_NOTICE, "DMACC1Configuration: %x", GPDMA_CH1_CFG);
dly_tsk(1);
    syslog(LOG_NOTICE, "itc_handler: %d, halt: %d, in_dma: %d", itc_handler, codec_halt, in_dma);
    syslog(LOG_NOTICE, "I2SDAO: %x, I2SSTATE: %x", I2S_DAO, I2S_STATE);
    syslog(LOG_NOTICE, "I2SDMA1: %x, I2SDMA2: %x, I2SIRQ: %x", I2S_DMA1, I2S_DMA2, I2S_IRQ);
dly_tsk(1);
    syslog(LOG_NOTICE, "CH1_SRC: %x, CH1_DEST: %x", GPDMA_CH1_SRC, GPDMA_CH1_DEST);
    syslog(LOG_NOTICE, "CH1_LLI: %x, CH1_CTRL: %x", GPDMA_CH1_LLI, GPDMA_CH1_CTRL);
}
#endif

static UH volmap[] = { 50, 70, 80, 90, 95, 100, 105, 110, 115, 120, 125};

static void codec_volset(int val)
{
    cvol = volmap[val];
    codec_i2c_send(REG_RIGHT_HPVOL, 0x180 | cvol);
}

#ifdef DEBUG_COMMAND
void volume_command(int argc, char **argv)
{
    int val;

    if (argc > 1) {
        val = atoi(argv[1]);
        if (val >= 0 && val < 128) {
            codec_i2c_send(REG_RIGHT_HPVOL, 0x180 | val);
            cvol = val;
        }
    }
    syslog(LOG_NOTICE, "Voulume: %d", cvol);
}
#else
void volume_command(int argc, char **argv)
{
}
#endif

#ifdef DEBUG_COMMAND
void path_command(int argc, char **argv)
{
    int val;

    if (argc > 1) {
        val = atoi(argv[1]) & 0x0f;
        codec_i2c_send(REG_DIGITAL_PATH, val);
        syslog(LOG_NOTICE, "%d (%x)", val, val);
    }
}
#else
void path_command(int argc, char **argv)
{
}
#endif

void dma_command(int argc, char **argv)
{
    if (argc == 7) {
        TP1 = atoi(argv[1]);
        TP2 = atoi(argv[2]);
        TP3 = atoi(argv[3]);
        TP4 = atoi(argv[4]);
        TP5 = atoi(argv[5]);
        TP6 = atoi(argv[6]);
    }
    syslog(LOG_NOTICE, "TP1: %d, TP2: %d, TP3: %d, TP4: %d, TP5: %d, TP6: %d",
	TP1, TP2, TP3, TP4, TP5, TP6);
    codec_init(1);
}
