rockbox/firmware/target/mips/ingenic_jz47xx/codec-jz4740.c
Solomon Peachy 90a4f28c27 jz47xx: Audio path tweaks:
* Increase audio buffer size to better handle IRQ latency (256->2048)
 * Ensure DMA engine is idle prior to starting transfers
 * Set AIC to repeat last sample in case of underflows

Change-Id: I9c45c20481ee072e5882b7586fb7d50bd8ef2f35
2020-09-04 15:57:00 -04:00

392 lines
11 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* 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 */