/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2008 by Maurus Cuelenaere
 *
 * 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 "config.h"
#include "audio.h"
#include "sound.h"
#include "jz4740.h"
#include "system.h"
#include "pcm_sw_volume.h"

#if 0
static unsigned short codec_volume;
static unsigned short codec_base_gain;
static unsigned short codec_mic_gain;
static int HP_register_value;
#endif
static bool HP_on_off_flag; 

static void i2s_codec_reset(void)
{
    REG_ICDC_CDCCR1 = (ICDC_CDCCR1_SW2ON | ICDC_CDCCR1_PDVR | ICDC_CDCCR1_PDVRA | ICDC_CDCCR1_VRCGL |
                       ICDC_CDCCR1_VRCGH | ICDC_CDCCR1_HPOV0 | ICDC_CDCCR1_PDHPM | ICDC_CDCCR1_PDHP |
                       ICDC_CDCCR1_SUSPD | ICDC_CDCCR1_RST);
    udelay(10);
    REG_ICDC_CDCCR1 = (ICDC_CDCCR1_SW2ON | ICDC_CDCCR1_PDVR | ICDC_CDCCR1_PDVRA | ICDC_CDCCR1_VRCGL |
                       ICDC_CDCCR1_VRCGH | ICDC_CDCCR1_HPOV0 | ICDC_CDCCR1_PDHPM | ICDC_CDCCR1_PDHP );
}

static void i2s_codec_init(void)
{
    __cpm_start_aic1();
    __cpm_start_aic2();

    __aic_enable();

    __i2s_internal_codec();
    __i2s_as_slave();
    __i2s_select_i2s();
    __aic_select_i2s(); 

    __aic_disable_byteswap();
    __aic_disable_unsignadj();
    __aic_disable_mono2stereo();

    i2s_codec_reset();

    REG_ICDC_CDCCR1 &= ~(ICDC_CDCCR1_SUSPD | ICDC_CDCCR1_RST);

    REG_ICDC_CDCCR2 = ( ICDC_CDCCR2_AINVOL(23) | ICDC_CDCCR2_SMPR(ICDC_CDCCR2_SMPR_44)
                      | ICDC_CDCCR2_HPVOL(ICDC_CDCCR2_HPVOL_0));

    mdelay(15);
    REG_ICDC_CDCCR1 &= ~(ICDC_CDCCR1_PDVR | ICDC_CDCCR1_VRCGL | ICDC_CDCCR1_VRCGH);
    REG_ICDC_CDCCR1 |= (ICDC_CDCCR1_EDAC | ICDC_CDCCR1_HPCG);

    mdelay(600);
    REG_ICDC_CDCCR1 &= ~(ICDC_CDCCR1_PDVRA | ICDC_CDCCR1_HPCG | ICDC_CDCCR1_PDHPM | ICDC_CDCCR1_PDHP);

    mdelay(2);

    /* CDCCR1.ELININ=0, CDCCR1.EMIC=0, CDCCR1.EADC=0, CDCCR1.SW1ON=0, CDCCR1.EDAC=1, CDCCR1.SW2ON=1, CDCCR1.HPMUTE=0 */
    REG_ICDC_CDCCR1 = (REG_ICDC_CDCCR1 & ~(ICDC_CDCCR1_ELININ | ICDC_CDCCR1_EMIC | ICDC_CDCCR1_EADC |
                                           ICDC_CDCCR1_SW1ON | ICDC_CDCCR1_HPMUTE)) | (ICDC_CDCCR1_EDAC
                                                                                    | ICDC_CDCCR1_SW2ON);

    HP_on_off_flag = 1; /* HP is on */
}

#if 0
static void i2s_codec_set_mic(unsigned short v) /* 0 <= v <= 100 */
{
    v &= 0xff;

    if(v > 100)
        v = 100; 
    codec_mic_gain = 31 * v/100;
    
    REG_ICDC_CDCCR2 = ((REG_ICDC_CDCCR2 & ~(0x1f << 16)) | (codec_mic_gain << 16));
}

static void i2s_codec_set_base(unsigned short v) /* 0 <= v <= 100 */
{
    v &= 0xff;

    if(v > 100)
        v = 100;

    if(v < 25)
        codec_base_gain = 0;
    if(v >= 25 && v < 50)
        codec_base_gain = 1;
    if(v >= 50 && v < 75)
        codec_base_gain = 2;
    if(v >= 75 && v <= 100)
        codec_base_gain = 3;
    
    REG_ICDC_CDCCR2 = ((REG_ICDC_CDCCR2 & ~(0x3 << 4)) | (codec_base_gain << 4));
}

static void i2s_codec_set_volume(unsigned short v) /* 0 <= v <= 100 */
{
    v &= 0xff;

    if(v > 100)
        v = 100;

    if(v < 25)
        codec_volume = 0;
    if(v >= 25 && v < 50)
        codec_volume = 1;
    if(v >= 50 && v < 75)
        codec_volume = 2;
    if(v >= 75 && v <= 100)
        codec_volume = 3;
    
    REG_ICDC_CDCCR2 = ((REG_ICDC_CDCCR2 & ~(0x3)) | codec_volume);
}

static unsigned short i2s_codec_get_bass(void)
{
    unsigned short val;
    int ret;
    
    if(codec_base_gain == 0)
        val = 0;
    if(codec_base_gain == 1)
        val = 25;
    if(codec_base_gain == 2)
        val = 50;
    if(codec_base_gain == 3)
        val = 75;
    
    ret = val << 8;
    val |= ret;
    
    return val;
}

static unsigned short i2s_codec_get_mic(void)
{
    unsigned short val;
    int ret;
    val = 100 * codec_mic_gain / 31;
    ret = val << 8;
    val |= ret;
    
    return val;
}

static unsigned short i2s_codec_get_volume(void)
{
    unsigned short val;
    int ret;

    if(codec_volume == 0)
        val = 0;
    if(codec_volume == 1)
        val = 25;
    if(codec_volume == 2)
        val = 50;
    if(codec_volume == 3)
        val = 75;
    
    ret = val << 8;
    val |= ret;
    
    return val;
}

static unsigned long HP_register_value;
static void HP_turn_on(void)
{ 
    //see 1.3.4.1
    
    REG_ICDC_CDCCR1 &= ~(ICDC_CDCCR1_SUSPD | ICDC_CDCCR1_RST); //set suspend 0

    mdelay(15);
    REG_ICDC_CDCCR1 &= ~(ICDC_CDCCR1_PDVR | ICDC_CDCCR1_VRCGL | ICDC_CDCCR1_VRCGH);
    REG_ICDC_CDCCR1 |= (ICDC_CDCCR1_EDAC | ICDC_CDCCR1_HPCG);
    
    mdelay(600);
    REG_ICDC_CDCCR1 &= ~(ICDC_CDCCR1_PDVRA | ICDC_CDCCR1_HPCG | ICDC_CDCCR1_PDHPM | ICDC_CDCCR1_PDHP);
    
    mdelay(2);
    HP_register_value = REG_ICDC_CDCCR1; 

    //see 1.3.4.2
    /*REG_ICDC_CDCCR1 &= 0xfffffffc;
    mdelay(7);
    REG_ICDC_CDCCR1 |= 0x00040400;
    mdelay(15);
    REG_ICDC_CDCCR1 &= 0xfffbfbff;
    udelay(500);
    REG_ICDC_CDCCR1 &= 0xffe5fcff;
    REG_ICDC_CDCCR1 |= 0x01000000;
    mdelay(400);
    REG_ICDC_CDCCR1 &= 0xfffeffff;
    mdelay(7);
    HP_register_value = REG_ICDC_CDCCR1;*/

    //see 1.3.4.3
}


static void HP_turn_off(void)
{
    //see 1.3.4.1
    mdelay(2);
    REG_ICDC_CDCCR1 = HP_register_value;
    REG_ICDC_CDCCR1 |= 0x001b0300;
    REG_ICDC_CDCCR1 &= 0xfeffffff;

    mdelay(15);
    REG_ICDC_CDCCR1 |= 0x00000002;//set suspend 1

    //see 1.3.4.2
    /*mdelay(4);
    REG_ICDC_CDCCR1 = HP_register_value;
    REG_ICDC_CDCCR1 |= 0x001b0300;
    REG_ICDC_CDCCR1 &= 0xfeffffff;
    mdelay(4);
    REG_ICDC_CDCCR1 |= 0x00000400;
    mdelay(15);
    REG_ICDC_CDCCR1 &= 0xfffffdff;
    mdelay(7);
    REG_ICDC_CDCCR1 |= 0x00000002;*/
    
    //see 1.3.4.3
}
#endif

static void audiohw_mute(bool mute)
{
    if(mute)
        REG_ICDC_CDCCR1 |= ICDC_CDCCR1_HPMUTE;
    else
        REG_ICDC_CDCCR1 &= ~ICDC_CDCCR1_HPMUTE;
}

void audiohw_preinit(void)
{
}

void audiohw_postinit(void)
{
    audiohw_mute(false);
    //HP_turn_on();
}

void audiohw_init(void)
{
    __aic_play_lastsample(); /* on FIFO underflow */
    i2s_codec_init();
}

void audiohw_set_volume(int vol_l, int vol_r)
{
#ifdef HAVE_SW_VOLUME_CONTROL
    /* SW volume for <= 1.0 gain, HW at unity, < -740 == MUTE */
    int sw_volume_l = vol_l <= -740 ? PCM_MUTE_LEVEL : MIN(vol_l, 0);
    int sw_volume_r = vol_r <= -740 ? PCM_MUTE_LEVEL : MIN(vol_r, 0);
    pcm_set_master_volume(sw_volume_l, sw_volume_r);
#endif /* HAVE_SW_VOLUME_CONTROL */

    /* NOTE: the channel being cut if balance is not equal will need
       adjusting downward so maintain proportion if using volume boost */

    /* HW volume for > 1.0 gain */
    int v = MAX(vol_l, vol_r);
    unsigned int hw_volume = v > 0 ? ICDC_CDCCR2_HPVOL(v / 20) : 0;

    if((REG_ICDC_CDCCR2 & ICDC_CDCCR2_HPVOL(0x3)) != hw_volume)
        REG_ICDC_CDCCR2 = (REG_ICDC_CDCCR2 & ~ICDC_CDCCR2_HPVOL(0x3)) | hw_volume;
}

void audiohw_set_frequency(int freq)
{
    unsigned int speed;

    switch(freq)
    {
        case 8000:
            speed = ICDC_CDCCR2_SMPR(ICDC_CDCCR2_SMPR_8);
            break;
        case 11025:
            speed = ICDC_CDCCR2_SMPR(ICDC_CDCCR2_SMPR_11);
            break;
        case 12000:
            speed = ICDC_CDCCR2_SMPR(ICDC_CDCCR2_SMPR_12);
            break;
        case 16000:
            speed = ICDC_CDCCR2_SMPR(ICDC_CDCCR2_SMPR_16);
            break;
        case 22050:
            speed = ICDC_CDCCR2_SMPR(ICDC_CDCCR2_SMPR_22);
            break;
        case 24000:
            speed = ICDC_CDCCR2_SMPR(ICDC_CDCCR2_SMPR_24);
            break;
        case 32000:
            speed = ICDC_CDCCR2_SMPR(ICDC_CDCCR2_SMPR_32);
            break;
        case 44100:
            speed = ICDC_CDCCR2_SMPR(ICDC_CDCCR2_SMPR_44);
            break;
        case 48000:
            speed = ICDC_CDCCR2_SMPR(ICDC_CDCCR2_SMPR_48);
            break;
        default:
            return;
    }

    if((REG_ICDC_CDCCR2 & ICDC_CDCCR2_SMPR(0xF)) != speed)
        REG_ICDC_CDCCR2 = (REG_ICDC_CDCCR2 & ~ICDC_CDCCR2_SMPR(0xF)) | speed;
}

int audio_channels = 2;
int audio_output_source = AUDIO_SRC_PLAYBACK;

void audio_set_output_source(int source)
{
    if((unsigned)source >= AUDIO_NUM_SOURCES)
        source = AUDIO_SRC_PLAYBACK;

    audio_output_source = source;
} /* audio_set_output_source */

void audio_input_mux(int source, unsigned flags)
{
    static int last_source = AUDIO_SRC_PLAYBACK;
    static bool last_recording = false;
    bool recording = flags & SRCF_RECORDING;

    switch (source)
    {
        default:                        /* playback - no recording */
            source = AUDIO_SRC_PLAYBACK;
        case AUDIO_SRC_PLAYBACK:
            audio_channels = 2;
            if(source != last_source)
                REG_ICDC_CDCCR1 = (REG_ICDC_CDCCR1 & ~(ICDC_CDCCR1_ELININ | ICDC_CDCCR1_EMIC | ICDC_CDCCR1_EADC | ICDC_CDCCR1_SW1ON | ICDC_CDCCR1_HPMUTE))
                                    | (ICDC_CDCCR1_EDAC | ICDC_CDCCR1_SW2ON);
            break;

#if INPUT_SRC_CAPS & SRC_CAP_MIC
        case AUDIO_SRC_MIC:             /* recording only */
            audio_channels = 1;
            if(source != last_source)
                REG_ICDC_CDCCR1 = (REG_ICDC_CDCCR1 & ~(ICDC_CDCCR1_ELININ | ICDC_CDCCR1_EDAC | ICDC_CDCCR1_SW2ON | ICDC_CDCCR1_HPMUTE)) 
                                    | (ICDC_CDCCR1_EADC | ICDC_CDCCR1_SW1ON | ICDC_CDCCR1_EMIC);
            break;
#endif

#if INPUT_SRC_CAPS & SRC_CAP_FMRADIO
        case AUDIO_SRC_FMRADIO:         /* recording and playback */
            audio_channels = 2;

            if(source == last_source && recording == last_recording)
                break;

            last_recording = recording;

            if(recording)
                REG_ICDC_CDCCR1 = (REG_ICDC_CDCCR1 & ~(ICDC_CDCCR1_EMIC | ICDC_CDCCR1_EDAC | ICDC_CDCCR1_SW2ON | ICDC_CDCCR1_HPMUTE))
                                    | (ICDC_CDCCR1_EADC | ICDC_CDCCR1_SW1ON | ICDC_CDCCR1_ELININ);
            else
                REG_ICDC_CDCCR1 = (REG_ICDC_CDCCR1 & ~(ICDC_CDCCR1_EMIC | ICDC_CDCCR1_EDAC | ICDC_CDCCR1_EADC |
                                   ICDC_CDCCR1_SW2ON | ICDC_CDCCR1_HPMUTE)) | (ICDC_CDCCR1_SW1ON | ICDC_CDCCR1_ELININ);
            break;
#endif
    } /* end switch */

    last_source = source;
} /* audio_input_mux */
