mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-10-10 08:03:04 -04:00
- config: HAVE_RECORDING, sources microphone and FM, 8-48 kHz - wm8751.c: the YP-CP3's own audiohw_set_recsrc(). The rk27xx cannot send received samples straight back out, so what is heard of an input goes through the codec's analog bypass - the input PGA into the output mixers: the radio always, the microphone never. As in the original firmware, the radio passes the PGA at +12 dB when only listened to, and the microphone - mono, on RINPUT2 - gets +13 dB boost and is recorded by the right ADC onto both channels (the original firmware's noise gate is left out). The HD300's version assumes the microphone on INPUT3 and headphones on OUT1, and switches OUT2 - the YP-CP3's headphones - off. - wm8751.c: the playback-only FM monitor added for the YP-CP3 goes; the radio is routed by audiohw_set_recsrc() now, as on the HD300 - wm8751.h: DATSEL, which ADC feeds each channel of the output data Only partly tested on hardware: FM radio still plays, and the recording screen's peak meter follows the microphone. Not yet tested: a recorded file played back, recording FM, recording gain range. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Change-Id: I3c25f4333f765d98dddc2fd722c111f8493bbe77
762 lines
24 KiB
C
762 lines
24 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
*
|
|
* Driver for WM8751 audio codec
|
|
*
|
|
* Based on code from the ipodlinux project - http://ipodlinux.org/
|
|
* Adapted for Rockbox in December 2005
|
|
*
|
|
* Original file: linux/arch/armnommu/mach-ipod/audio.c
|
|
*
|
|
* Copyright (c) 2003-2005 Bernard Leach (leachbj@bouncycastle.org)
|
|
*
|
|
* 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 "kernel.h"
|
|
#include "wmcodec.h"
|
|
#include "audio.h"
|
|
#include "audiohw.h"
|
|
#include "system.h"
|
|
#include "sound.h"
|
|
|
|
static uint16_t wmcodec_regs[WM_NUM_REGS] =
|
|
{
|
|
[0 ... WM_NUM_REGS-1] = 0x200, /* set invalid data in gaps */
|
|
#ifdef HAVE_WM8750
|
|
[LINVOL] = 0x097,
|
|
[RINVOL] = 0x097,
|
|
#endif
|
|
[LOUT1] = 0x079,
|
|
[ROUT1] = 0x079,
|
|
[DACCTRL] = 0x008,
|
|
[AINTFCE] = 0x00a,
|
|
[CLOCKING] = 0x000,
|
|
[LEFTGAIN] = 0x0ff,
|
|
[RIGHTGAIN] = 0x0ff,
|
|
[BASSCTRL] = 0x00f,
|
|
[TREBCTRL] = 0x00f,
|
|
/* [RESET] */
|
|
#ifdef HAVE_WM8750
|
|
[ENHANCE_3D] = 0x000,
|
|
[ALC1] = 0x07b,
|
|
[ALC2] = 0x000,
|
|
[ALC3] = 0x032,
|
|
[NGAT] = 0x000,
|
|
[LADCVOL] = 0x0c3,
|
|
[RADCVOL] = 0x0c3,
|
|
#endif
|
|
[ADDITIONAL1] = 0x0c0,
|
|
[ADDITIONAL2] = 0x000,
|
|
[PWRMGMT1] = 0x000,
|
|
[PWRMGMT2] = 0x000,
|
|
[ADDITIONAL3] = 0x000,
|
|
#ifdef HAVE_WM8750
|
|
[ADCIM] = 0x000,
|
|
[ADCL] = 0x000,
|
|
[ADCR] = 0x000,
|
|
#endif
|
|
[LEFTMIX1] = 0x050,
|
|
[LEFTMIX2] = 0x050,
|
|
[RIGHTMIX1] = 0x050,
|
|
[RIGHTMIX2] = 0x050,
|
|
[MONOMIX1] = 0x050,
|
|
[MONOMIX2] = 0x050,
|
|
[LOUT2] = 0x079,
|
|
[ROUT2] = 0x079,
|
|
[MONOOUT] = 0x079
|
|
};
|
|
|
|
/* global prescaler vars */
|
|
static int prescalertone = 0;
|
|
static int prescaler3d = 0;
|
|
|
|
static void wmcodec_set_reg(unsigned int reg, unsigned int val)
|
|
{
|
|
if (reg >= WM_NUM_REGS || (wmcodec_regs[reg] & 0x200))
|
|
/* invalid register */
|
|
return;
|
|
|
|
wmcodec_regs[reg] = val & 0x1ff;
|
|
wmcodec_write(reg, wmcodec_regs[reg]);
|
|
}
|
|
|
|
static void wmcodec_set_bits(unsigned int reg, unsigned int bits)
|
|
{
|
|
wmcodec_set_reg(reg, wmcodec_regs[reg] | bits);
|
|
}
|
|
|
|
static void wmcodec_clear_bits(unsigned int reg, unsigned int bits)
|
|
{
|
|
wmcodec_set_reg(reg, wmcodec_regs[reg] & ~bits);
|
|
}
|
|
|
|
static void wmcodec_set_masked(unsigned int reg, unsigned int val,
|
|
unsigned int mask )
|
|
{
|
|
wmcodec_set_reg(reg, (wmcodec_regs[reg] & ~mask) | val);
|
|
}
|
|
|
|
/* convert tenth of dB volume (-730..60) to master volume register value */
|
|
static int vol_tenthdb2hw(int db)
|
|
{
|
|
/* +6 to -73dB 1dB steps (plus mute == 80levels) 7bits */
|
|
/* 1111111 == +6dB (0x7f) */
|
|
/* 1111001 == 0dB (0x79) */
|
|
/* 0110000 == -73dB (0x30) */
|
|
/* 0101111..0000000 == mute (<= 0x2f) */
|
|
if (db <= -740)
|
|
return 0x0;
|
|
else
|
|
return (db / 10) + 73 + 0x30;
|
|
}
|
|
|
|
static int tone_tenthdb2hw(int value)
|
|
{
|
|
/* -6.0db..+0db..+9.0db step 1.5db - translate -60..+0..+90 step 15
|
|
to 10..6..0 step -1.
|
|
*/
|
|
value = 10 - (value + 60) / 15;
|
|
|
|
if (value == 6)
|
|
value = 0xf; /* 0db -> off */
|
|
|
|
return value;
|
|
}
|
|
|
|
#ifdef AUDIOHW_HAVE_BASS_CUTOFF
|
|
void audiohw_set_bass_cutoff(int val)
|
|
{
|
|
if ( val == 130 )
|
|
wmcodec_clear_bits(BASSCTRL, BASSCTRL_BC);
|
|
else
|
|
wmcodec_set_bits(BASSCTRL, BASSCTRL_BC);
|
|
|
|
}
|
|
#endif
|
|
|
|
#ifdef AUDIOHW_HAVE_TREBLE_CUTOFF
|
|
void audiohw_set_treble_cutoff(int val)
|
|
{
|
|
if ( val == 8 )
|
|
wmcodec_clear_bits(TREBCTRL, TREBCTRL_TC);
|
|
else
|
|
wmcodec_set_bits(TREBCTRL, TREBCTRL_TC);
|
|
}
|
|
#endif
|
|
|
|
static void audiohw_mute(bool mute)
|
|
{
|
|
/* Mute: Set DACMU = 1 to soft-mute the audio DACs. */
|
|
/* Unmute: Set DACMU = 0 to soft-un-mute the audio DACs. */
|
|
if (mute)
|
|
wmcodec_set_bits(DACCTRL, DACCTRL_DACMU);
|
|
else
|
|
wmcodec_clear_bits(DACCTRL, DACCTRL_DACMU);
|
|
}
|
|
|
|
/* Reset and power up the WM8751 */
|
|
void audiohw_preinit(void)
|
|
{
|
|
#if defined(MROBE_100)
|
|
/* controls headphone ouput */
|
|
GPIOL_ENABLE |= 0x10;
|
|
GPIOL_OUTPUT_EN |= 0x10;
|
|
GPIOL_OUTPUT_VAL |= 0x10; /* disable */
|
|
#elif defined(MPIO_HD200)
|
|
/* control headphone output
|
|
* disabled on startup
|
|
*/
|
|
and_l(~(1<<25), &GPIO1_OUT);
|
|
or_l((1<<25), &GPIO1_ENABLE);
|
|
or_l((1<<25), &GPIO1_FUNCTION);
|
|
#elif defined(MPIO_HD300)
|
|
and_l(~(1<<5), &GPIO1_OUT);
|
|
or_l((1<<5), &GPIO1_ENABLE);
|
|
or_l((1<<5), &GPIO1_FUNCTION);
|
|
#elif defined(SAMSUNG_YPCP3)
|
|
/* headphone amplifier enable, GPIO F2 active high: off for now */
|
|
GPIO_PFDR &= ~(1<<2);
|
|
GPIO_PFCON |= (1<<2);
|
|
#endif
|
|
|
|
/*
|
|
* 1. Switch on power supplies.
|
|
* By default the WM8751 is in Standby Mode, the DAC is
|
|
* digitally muted and the Audio Interface, Line outputs
|
|
* and Headphone outputs are all OFF (DACMU = 1 Power
|
|
* Management registers 1 and 2 are all zeros).
|
|
*/
|
|
|
|
wmcodec_write(RESET, RESET_RESET); /*Reset*/
|
|
|
|
/* 2. Enable Vmid and VREF. */
|
|
wmcodec_set_bits(PWRMGMT1, PWRMGMT1_VREF | PWRMGMT1_VMIDSEL_5K);
|
|
|
|
#ifdef CODEC_SLAVE
|
|
wmcodec_set_bits(AINTFCE,AINTFCE_WL_16 | AINTFCE_FORMAT_I2S);
|
|
#else
|
|
/* BCLKINV=0(Dont invert BCLK) MS=1(Enable Master) LRSWAP=0 LRP=0 */
|
|
/* IWL=00(16 bit) FORMAT=10(I2S format) */
|
|
wmcodec_set_bits(AINTFCE, AINTFCE_MS | AINTFCE_WL_16 |
|
|
AINTFCE_FORMAT_I2S);
|
|
#endif
|
|
/* Set default samplerate */
|
|
|
|
audiohw_set_frequency(HW_FREQ_DEFAULT);
|
|
}
|
|
|
|
/* Enable DACs and audio output after a short delay */
|
|
void audiohw_postinit(void)
|
|
{
|
|
/* From app notes: allow Vref to stabilize to reduce clicks */
|
|
sleep(HZ);
|
|
|
|
#ifdef AUDIOHW_HAVE_DEPTH_3D
|
|
wmcodec_set_bits(ENHANCE_3D, ENHANCE_3D_MODE3D_PLAYBACK);
|
|
#endif
|
|
|
|
/* 3. Enable DACs as required. */
|
|
wmcodec_set_bits(PWRMGMT2, PWRMGMT2_DACL | PWRMGMT2_DACR);
|
|
|
|
/* 4. Enable line and / or headphone output buffers as required. */
|
|
#if defined(GIGABEAT_F)
|
|
/* headphones + line-out */
|
|
wmcodec_set_bits(PWRMGMT2, PWRMGMT2_LOUT1 | PWRMGMT2_ROUT1 |
|
|
PWRMGMT2_LOUT2 | PWRMGMT2_ROUT2);
|
|
#elif defined(SAMSUNG_YPCP3)
|
|
/* headphones, on OUT2 */
|
|
wmcodec_set_bits(PWRMGMT2, PWRMGMT2_LOUT2 | PWRMGMT2_ROUT2);
|
|
#else
|
|
/* headphones */
|
|
wmcodec_set_bits(PWRMGMT2, PWRMGMT2_LOUT1 | PWRMGMT2_ROUT1);
|
|
#endif
|
|
|
|
/* Full -0dB on the DACS */
|
|
wmcodec_set_bits(LEFTGAIN, 0xff);
|
|
wmcodec_set_bits(RIGHTGAIN, RIGHTGAIN_RDVU | 0xff);
|
|
|
|
/* Enable Thermal shutdown, Timeout when zero-crossing in use,
|
|
* set analog bias for 3.3V, monomix to DACR
|
|
*/
|
|
wmcodec_set_reg(ADDITIONAL1, ADDITIONAL1_TSDEN | ADDITIONAL1_TOEN |
|
|
ADDITIONAL1_DMONOMIX_LLRR | ADDITIONAL1_VSEL_DEFAULT);
|
|
|
|
|
|
/* Enable zero-crossing in out stage */
|
|
wmcodec_set_bits(LOUT1, LOUT1_LO1ZC);
|
|
wmcodec_set_bits(ROUT1, ROUT1_RO1ZC);
|
|
wmcodec_set_bits(LOUT2, LOUT2_LO2ZC);
|
|
wmcodec_set_bits(ROUT2, ROUT2_RO2ZC);
|
|
|
|
/* route signal from DAC to mixers */
|
|
wmcodec_set_bits(LEFTMIX1, LEFTMIX1_LD2LO);
|
|
wmcodec_set_bits(RIGHTMIX2, RIGHTMIX2_RD2RO);
|
|
|
|
#ifdef TOSHIBA_GIGABEAT_F
|
|
#ifdef HAVE_HARDWARE_BEEP
|
|
/* Single-ended mono input */
|
|
wmcodec_set_reg(MONOMIX1, 0x00);
|
|
|
|
/* Route mono input to both outputs at 0dB */
|
|
wmcodec_set_reg(LEFTMIX2, LEFTMIX2_MI2LO | LEFTMIX2_MI2LOVOL(2));
|
|
wmcodec_set_reg(RIGHTMIX1, RIGHTMIX1_MI2RO | RIGHTMIX1_MI2ROVOL(2));
|
|
#endif
|
|
#endif
|
|
|
|
/* lower power consumption */
|
|
wmcodec_set_masked(PWRMGMT1, PWRMGMT1_VMIDSEL_50K,
|
|
PWRMGMT1_VMIDSEL_MASK);
|
|
|
|
audiohw_mute(false);
|
|
|
|
#if defined(MROBE_100)
|
|
/* enable headphone output */
|
|
sleep(1);
|
|
GPIOL_OUTPUT_VAL &= ~0x10;
|
|
GPIOL_OUTPUT_EN |= 0x10;
|
|
#elif defined(MPIO_HD200)
|
|
/* enable headphone output */
|
|
or_l((1<<25), &GPIO1_OUT);
|
|
#elif defined(MPIO_HD300)
|
|
or_l((1<<5), &GPIO1_OUT);
|
|
#elif defined(SAMSUNG_YPCP3)
|
|
GPIO_PFDR |= (1<<2);
|
|
#endif
|
|
}
|
|
|
|
void audiohw_set_volume(int vol_l, int vol_r)
|
|
{
|
|
vol_l = vol_tenthdb2hw(vol_l);
|
|
vol_r = vol_tenthdb2hw(vol_r);
|
|
#if defined(SAMSUNG_YPCP3)
|
|
/* headphones on OUT2 */
|
|
wmcodec_set_masked(LOUT2, LOUT2_LOUT2VOL(vol_l),
|
|
LOUT2_LOUT2VOL_MASK);
|
|
wmcodec_set_masked(ROUT2, ROUT2_RO2VU | ROUT2_ROUT2VOL(vol_r),
|
|
ROUT2_ROUT2VOL_MASK);
|
|
#else
|
|
wmcodec_set_masked(LOUT1, LOUT1_LOUT1VOL(vol_l),
|
|
LOUT1_LOUT1VOL_MASK);
|
|
wmcodec_set_masked(ROUT1, ROUT1_RO1VU | ROUT1_ROUT1VOL(vol_r),
|
|
ROUT1_ROUT1VOL_MASK);
|
|
#endif
|
|
}
|
|
|
|
#ifdef AUDIOHW_HAVE_LINEOUT
|
|
void audiohw_set_lineout_volume(int vol_l, int vol_r)
|
|
{
|
|
vol_l = vol_tenthdb2hw(vol_l);
|
|
vol_r = vol_tenthdb2hw(vol_r);
|
|
wmcodec_set_masked(LOUT2, LOUT2_LOUT2VOL(vol_l),
|
|
LOUT2_LOUT2VOL_MASK);
|
|
wmcodec_set_masked(ROUT2, ROUT2_RO2VU | ROUT2_ROUT2VOL(vol_r),
|
|
ROUT2_ROUT2VOL_MASK);
|
|
}
|
|
#endif /* AUDIOHW_HAVE_LINEOUT */
|
|
|
|
void audiohw_set_bass(int value)
|
|
{
|
|
value = tone_tenthdb2hw(value);
|
|
wmcodec_set_masked(BASSCTRL, BASSCTRL_BASS(value),
|
|
BASSCTRL_BASS_MASK);
|
|
}
|
|
|
|
void audiohw_set_treble(int value)
|
|
{
|
|
value = tone_tenthdb2hw(value);
|
|
wmcodec_set_masked(TREBCTRL, TREBCTRL_TREB(value),
|
|
TREBCTRL_TREB_MASK);
|
|
}
|
|
|
|
static void sync_prescaler(void)
|
|
{
|
|
int prescaler;
|
|
prescaler = prescalertone + prescaler3d;
|
|
|
|
/* attenuate in 0.5dB steps (0dB - -127dB) */
|
|
wmcodec_set_reg(LEFTGAIN, 0xff - (prescaler & LEFTGAIN_LDACVOL));
|
|
wmcodec_set_reg(RIGHTGAIN, RIGHTGAIN_RDVU |
|
|
(0xff - (prescaler & RIGHTGAIN_RDACVOL)));
|
|
}
|
|
|
|
void audiohw_set_prescaler(int value)
|
|
{
|
|
prescalertone = 3 * value / 15; /* value in tdB */
|
|
sync_prescaler();
|
|
}
|
|
|
|
/* Nice shutdown of WM8751 codec */
|
|
void audiohw_close(void)
|
|
{
|
|
/* 1. Set DACMU = 1 to soft-mute the audio DACs. */
|
|
audiohw_mute(true);
|
|
|
|
#if defined(MPIO_HD200)
|
|
/* disable headphone out */
|
|
and_l(~(1<<25), &GPIO1_OUT);
|
|
#elif defined(MPIO_HD300)
|
|
and_l(~(1<<5), &GPIO1_OUT);
|
|
#elif defined(SAMSUNG_YPCP3)
|
|
GPIO_PFDR &= ~(1<<2);
|
|
#endif
|
|
|
|
/* 2. Disable all output buffers. */
|
|
wmcodec_set_reg(PWRMGMT2, 0x0);
|
|
|
|
/* 3. Switch off the power supplies. */
|
|
wmcodec_set_reg(PWRMGMT1, 0x0);
|
|
}
|
|
|
|
/* According to datasheet of WM8750
|
|
* clocking setup is needed in both slave and master mode
|
|
*/
|
|
void audiohw_set_frequency(int fsel)
|
|
{
|
|
(void)fsel;
|
|
static const unsigned char srctrl_table[HW_NUM_FREQ] =
|
|
{
|
|
HW_HAVE_11_([HW_FREQ_11] = CODEC_SRCTRL_11025HZ,)
|
|
HW_HAVE_22_([HW_FREQ_22] = CODEC_SRCTRL_22050HZ,)
|
|
HW_HAVE_44_([HW_FREQ_44] = CODEC_SRCTRL_44100HZ,)
|
|
HW_HAVE_88_([HW_FREQ_88] = CODEC_SRCTRL_88200HZ,)
|
|
};
|
|
|
|
if ((unsigned)fsel >= HW_NUM_FREQ)
|
|
fsel = HW_FREQ_DEFAULT;
|
|
|
|
wmcodec_set_reg(CLOCKING, srctrl_table[fsel]);
|
|
}
|
|
|
|
#ifdef HAVE_WM8750
|
|
#ifdef AUDIOHW_HAVE_DEPTH_3D
|
|
/* Set the depth of the 3D effect
|
|
* value in 0 .. 15, corresponds to 0% .. 100% in 6.66% steps */
|
|
void audiohw_set_depth_3d(int val)
|
|
{
|
|
if (val > 0)
|
|
{
|
|
wmcodec_set_bits(ENHANCE_3D, ENHANCE_3D_3DEN);
|
|
wmcodec_set_masked(ENHANCE_3D, ENHANCE_3D_DEPTH(val),
|
|
ENHANCE_3D_DEPTH_MASK);
|
|
}
|
|
else
|
|
{
|
|
wmcodec_clear_bits(ENHANCE_3D, ENHANCE_3D_3DEN);
|
|
}
|
|
|
|
/* -4 dB @ full setting
|
|
* this gives approximately constant volume on setting change
|
|
* and prevents clipping (at least on my HD300)
|
|
*/
|
|
prescaler3d = 8*val / 15;
|
|
sync_prescaler();
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_RECORDING
|
|
#if 0
|
|
static void audiohw_set_ngat(int ngath, int type, bool enable)
|
|
{
|
|
/* This function controls Noise gate function
|
|
* of the codec. This can only run in conjunction
|
|
* with ALC
|
|
*/
|
|
|
|
if(enable)
|
|
wmcodec_set_reg(NGAT, NGAT_NGG(type) |
|
|
NGAT_NGTH(ngath) | NGAT_NGAT);
|
|
else
|
|
wmcodec_clear_bits(NGAT, NGAT_NGAT);
|
|
}
|
|
|
|
|
|
static void audiohw_set_alc(unsigned char level, /* signal level at ADC */
|
|
bool zc, /* zero cross detection */
|
|
unsigned char hold, /* hold time */
|
|
unsigned char decay, /* decay time */
|
|
unsigned char attack, /* attack time */
|
|
bool enable) /* hw function on/off */
|
|
{
|
|
if(enable)
|
|
{
|
|
wmcodec_set_reg(ALC1, ALC1_ALCSEL_STEREO | ALC1_MAXGAIN(0x07) |
|
|
ALC1_ALCL(level));
|
|
wmcodec_set_reg(ALC2, zc?ALC2_ALCZC:0 | ALC2_HLD(hold));
|
|
wmcodec_set_reg(ALC3, ALC3_DCY(decay) | ALC3_ATK(attack));
|
|
}
|
|
else
|
|
{
|
|
wmcodec_set_masked(ALC1, ALC1_ALCSEL_DISABLED, ALC1_ALCSEL_MASK);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(SAMSUNG_YPCP3)
|
|
/* Samsung YP-CP3: the FM tuner on LINPUT1/RINPUT1, the microphone - mono -
|
|
* on RINPUT2, the headphones on OUT2. The rk27xx cannot send received
|
|
* samples straight back out, so what is heard of an input goes through the
|
|
* codec's analog bypass, the input PGA into the output mixers: the radio
|
|
* always, the microphone never. As in the original firmware, the radio
|
|
* passes the PGA at +12 dB when only listened to, and the microphone gets
|
|
* +13 dB of boost and is recorded by the right ADC onto both channels.
|
|
* While recording, the gain setting drives the PGA. */
|
|
void audiohw_set_recsrc(int source, bool recording)
|
|
{
|
|
audiohw_mute(true);
|
|
|
|
/* inputs off */
|
|
wmcodec_clear_bits(LEFTMIX1, LEFTMIX1_LI2LO);
|
|
wmcodec_clear_bits(RIGHTMIX2, RIGHTMIX2_RI2RO);
|
|
wmcodec_clear_bits(PWRMGMT1, PWRMGMT1_AINL | PWRMGMT1_AINR |
|
|
PWRMGMT1_ADCL | PWRMGMT1_ADCR);
|
|
wmcodec_set_masked(ADCR, ADCR_RMICBOOST_DISABLED, 0x3 << 4);
|
|
wmcodec_set_masked(ADDITIONAL1, ADDITIONAL1_DATSEL_LR,
|
|
ADDITIONAL1_DATSEL_MASK);
|
|
|
|
switch (source)
|
|
{
|
|
case AUDIO_SRC_FMRADIO:
|
|
wmcodec_set_masked(ADCL, ADCL_LINSEL_LINPUT1, ADCL_LINSEL_MASK);
|
|
wmcodec_set_masked(ADCR, ADCR_RINSEL_RINPUT1, ADCR_RINSEL_MASK);
|
|
wmcodec_set_bits(PWRMGMT1, PWRMGMT1_AINL | PWRMGMT1_AINR);
|
|
if (recording)
|
|
{
|
|
wmcodec_set_bits(PWRMGMT1, PWRMGMT1_ADCL | PWRMGMT1_ADCR);
|
|
}
|
|
else
|
|
{
|
|
wmcodec_set_reg(LINVOL, LINVOL_LIZC | LINVOL_LINVOL(0x27));
|
|
wmcodec_set_reg(RINVOL, RINVOL_RIVU | RINVOL_RIZC |
|
|
RINVOL_RINVOL(0x27));
|
|
}
|
|
|
|
wmcodec_set_masked(LEFTMIX1, LEFTMIX1_LMIXSEL_ADCLIN |
|
|
LEFTMIX1_LI2LOVOL(0x20),
|
|
0x7 | LEFTMIX1_LI2LOVOL_MASK);
|
|
wmcodec_set_bits(LEFTMIX1, LEFTMIX1_LI2LO);
|
|
wmcodec_set_masked(RIGHTMIX1, RIGHTMIX1_RMIXSEL_ADCRIN, 0x7);
|
|
wmcodec_set_masked(RIGHTMIX2, RIGHTMIX2_RI2ROVOL(0x20),
|
|
RIGHTMIX2_RI2ROVOL_MASK);
|
|
wmcodec_set_bits(RIGHTMIX2, RIGHTMIX2_RI2RO);
|
|
break;
|
|
|
|
case AUDIO_SRC_MIC:
|
|
wmcodec_set_masked(ADCR, ADCR_RINSEL_RINPUT2 | ADCR_RMICBOOST_13DB,
|
|
ADCR_RINSEL_MASK | (0x3 << 4));
|
|
wmcodec_set_bits(PWRMGMT1, PWRMGMT1_AINR | PWRMGMT1_ADCR);
|
|
wmcodec_set_masked(ADDITIONAL1, ADDITIONAL1_DATSEL_RR,
|
|
ADDITIONAL1_DATSEL_MASK);
|
|
break;
|
|
|
|
default: /* playback */
|
|
break;
|
|
}
|
|
|
|
audiohw_mute(false);
|
|
}
|
|
#else /* !SAMSUNG_YPCP3 */
|
|
void audiohw_set_recsrc(int source, bool recording)
|
|
{
|
|
/* INPUT1 - FM radio
|
|
* INPUT2 - Line-in
|
|
* INPUT3 - MIC
|
|
*
|
|
* if recording == false we use analog bypass from input
|
|
* turn off ADC, PGA to save power
|
|
* turn on output buffer(s)
|
|
*
|
|
* if recording == true we route input signal to PGA
|
|
* and monitoring picks up signal after PGA and ADC
|
|
* turn on ADC, PGA, DAC, output buffer(s)
|
|
*/
|
|
|
|
switch(source)
|
|
{
|
|
case AUDIO_SRC_PLAYBACK:
|
|
audiohw_mute(true);
|
|
|
|
/* mute PGA, disable all audio paths but DAC and output stage*/
|
|
wmcodec_set_bits(LINVOL, LINVOL_LINMUTE); /* Mute */
|
|
wmcodec_set_bits(RINVOL, RINVOL_RINMUTE); /* Mute */
|
|
|
|
wmcodec_clear_bits(PWRMGMT2, PWRMGMT2_OUT3 | PWRMGMT2_MOUT |
|
|
PWRMGMT2_ROUT2 | PWRMGMT2_LOUT2);
|
|
|
|
/* route DAC signal to output mixer, disable INPUT routing */
|
|
wmcodec_clear_bits(LEFTMIX1, LEFTMIX1_LI2LO);
|
|
wmcodec_set_bits(LEFTMIX1, LEFTMIX1_LD2LO);
|
|
wmcodec_clear_bits(RIGHTMIX2, RIGHTMIX2_RI2RO);
|
|
wmcodec_set_bits(RIGHTMIX2, RIGHTMIX2_RD2RO);
|
|
|
|
/* unmute DAC */
|
|
audiohw_mute(false);
|
|
break;
|
|
|
|
case AUDIO_SRC_FMRADIO:
|
|
if(recording)
|
|
{
|
|
audiohw_mute(true);
|
|
/* Turn on PGA and ADC */
|
|
wmcodec_set_bits(PWRMGMT1, PWRMGMT1_AINL | PWRMGMT1_AINR |
|
|
PWRMGMT1_ADCL | PWRMGMT1_ADCR);
|
|
|
|
/* Setup input source for PGA as INPUT1 */
|
|
wmcodec_set_masked(ADCL, ADCL_LINSEL_LINPUT1, ADCL_LINSEL_MASK);
|
|
wmcodec_set_masked(ADCR, ADCR_RINSEL_RINPUT1, ADCR_RINSEL_MASK);
|
|
|
|
/* turn off ALC and NGAT as OF do */
|
|
/*
|
|
audiohw_set_alc(0x00, false, 0x00, 0x00, 0x00, false);
|
|
audiohw_set_ngat(0x00, 0x00, false);
|
|
*/
|
|
|
|
/* setup output digital data
|
|
* default is LADC -> LDATA, RADC -> RDATA
|
|
* so we don't touch this
|
|
*/
|
|
|
|
/* skip: power up DAC and output stage as its never powered down
|
|
wmcodec_set_bits(PWRMGMT2, PWRMGMT2_DACL | PWRMGMT2_DACR |
|
|
PWRMGMT2_LOUT1 | PWRMGMT2_ROUT1);
|
|
*/
|
|
|
|
/* route DAC signal to output mixer, disable INPUT routing */
|
|
wmcodec_clear_bits(LEFTMIX1, LEFTMIX1_LI2LO);
|
|
wmcodec_set_bits(LEFTMIX1, LEFTMIX1_LD2LO);
|
|
wmcodec_clear_bits(RIGHTMIX2, RIGHTMIX2_RI2RO);
|
|
wmcodec_set_bits(RIGHTMIX2, RIGHTMIX2_RD2RO);
|
|
|
|
audiohw_mute(false);
|
|
}
|
|
else
|
|
{
|
|
audiohw_mute(true);
|
|
|
|
/* turn off ADC, PGA */
|
|
wmcodec_clear_bits(PWRMGMT1, PWRMGMT1_AINL | PWRMGMT1_AINR |
|
|
PWRMGMT1_ADCL | PWRMGMT1_ADCR);
|
|
|
|
/* setup monitor mode by routing input signal to outmix
|
|
* at 0dB volume
|
|
*/
|
|
wmcodec_set_masked(LEFTMIX1, LEFTMIX1_LI2LOVOL(0x20),
|
|
LEFTMIX1_LI2LOVOL_MASK);
|
|
wmcodec_set_bits(LEFTMIX1, LEFTMIX1_LI2LO |
|
|
LEFTMIX1_LMIXSEL_LINPUT1 |
|
|
LEFTMIX1_LD2LO);
|
|
wmcodec_set_masked(RIGHTMIX2, RIGHTMIX2_RI2ROVOL(0x20),
|
|
RIGHTMIX2_RI2ROVOL_MASK);
|
|
wmcodec_set_bits(RIGHTMIX1, RIGHTMIX1_RMIXSEL_RINPUT1);
|
|
wmcodec_set_bits(RIGHTMIX2, RIGHTMIX2_RI2RO |
|
|
RIGHTMIX2_RD2RO);
|
|
|
|
audiohw_mute(false);
|
|
}
|
|
break;
|
|
|
|
#if (INPUT_SRC_CAPS & SRC_CAP_LINEIN)
|
|
case AUDIO_SRC_LINEIN:
|
|
audiohw_mute(true);
|
|
|
|
/* Turn on PGA, ADC */
|
|
wmcodec_set_bits(PWRMGMT1, PWRMGMT1_AINL | PWRMGMT1_AINR |
|
|
PWRMGMT1_ADCL | PWRMGMT1_ADCR);
|
|
|
|
/* Setup input source for PGA as INPUT2
|
|
* MICBOOST disabled
|
|
*/
|
|
wmcodec_set_masked(ADCL, ADCL_LINSEL_LINPUT2, ADCL_LINSEL_MASK);
|
|
wmcodec_set_masked(ADCR, ADCR_RINSEL_RINPUT2, ADCR_RINSEL_MASK);
|
|
|
|
/* setup ALC and NGAT as OF do */
|
|
/* level, zc, hold, decay, attack, enable */
|
|
/* audiohw_set_alc(0x0b, true, 0x00, 0x03, 0x02, true); */
|
|
/* ngath, type, enable */
|
|
/* audiohw_set_ngat(0x08, 0x02, true); */
|
|
|
|
/* setup output digital data
|
|
* default is LADC -> LDATA, RADC -> RDATA
|
|
* so we don't touch this
|
|
*/
|
|
|
|
/* route DAC signal to output mixer, disable INPUT routing */
|
|
wmcodec_clear_bits(LEFTMIX1, LEFTMIX1_LI2LO);
|
|
wmcodec_set_bits(LEFTMIX1, LEFTMIX1_LD2LO);
|
|
wmcodec_clear_bits(RIGHTMIX2, RIGHTMIX2_RI2RO);
|
|
wmcodec_set_bits(RIGHTMIX2, RIGHTMIX2_RD2RO);
|
|
|
|
#if !defined(MPIO_HD300)
|
|
/* HD300 uses the same socket for headphones and line-in */
|
|
audiohw_mute(false);
|
|
#endif
|
|
break;
|
|
#endif
|
|
#if (INPUT_SRC_CAPS & SRC_CAP_MIC)
|
|
case AUDIO_SRC_MIC:
|
|
audiohw_mute(true);
|
|
|
|
/* Turn on PGA and ADC */
|
|
wmcodec_set_bits(PWRMGMT1, PWRMGMT1_AINL | PWRMGMT1_AINR |
|
|
PWRMGMT1_ADCL | PWRMGMT1_ADCR);
|
|
|
|
/* Setup input source for PGA as INPUT3
|
|
* MICBOOST disabled
|
|
*/
|
|
wmcodec_set_masked(ADCL, ADCL_LINSEL_LINPUT3, ADCL_LINSEL_MASK);
|
|
wmcodec_set_masked(ADCR, ADCR_RINSEL_RINPUT3, ADCR_RINSEL_MASK);
|
|
|
|
/* setup ALC and NGAT as OF do */
|
|
/* level, zc, hold, decay, attack, enable */
|
|
/* audiohw_set_alc(0x0f, false, 0x00, 0x05, 0x02, true); */
|
|
/* ngath, type, enable */
|
|
/* audiohw_set_ngat(0x1f, 0x00, true); */
|
|
|
|
/* setup output digital data
|
|
* default is LADC -> LDATA, RADC -> RDATA
|
|
* so we don't touch this
|
|
*/
|
|
|
|
/* route DAC signal to output mixer, disable INPUT routing */
|
|
wmcodec_clear_bits(LEFTMIX1, LEFTMIX1_LI2LO);
|
|
wmcodec_set_bits(LEFTMIX1, LEFTMIX1_LD2LO);
|
|
wmcodec_clear_bits(RIGHTMIX2, RIGHTMIX2_RI2RO);
|
|
wmcodec_set_bits(RIGHTMIX2, RIGHTMIX2_RD2RO);
|
|
|
|
audiohw_mute(false);
|
|
break;
|
|
#endif
|
|
} /* switch(source) */
|
|
}
|
|
#endif /* SAMSUNG_YPCP3 */
|
|
|
|
static int digital_gain2hw(int value)
|
|
{
|
|
/* -9700 to 3000 => 0 ... 195 ... 255 */
|
|
return (2 * value) / 100 + 195;
|
|
}
|
|
|
|
static int pga_gain2hw(int value)
|
|
{
|
|
/* -1725 to 3000 => 0 ... 23 ... 63 */
|
|
return ((4 * value) / 300) + 23;
|
|
}
|
|
|
|
/* volume in hundredth of dB in -1725 .. 6000 */
|
|
void audiohw_set_recvol(int vol_l, int vol_r, int type)
|
|
{
|
|
int d_vol_l = 0;
|
|
int d_vol_r = 0;
|
|
|
|
if (vol_l > 3000)
|
|
{
|
|
d_vol_l = vol_l - 3000;
|
|
vol_l = 3000;
|
|
}
|
|
|
|
if (vol_r > 3000)
|
|
{
|
|
d_vol_r = vol_r - 3000;
|
|
vol_r = 3000;
|
|
}
|
|
|
|
/* PGA left gain */
|
|
wmcodec_set_reg(LINVOL, LINVOL_LIZC | LINVOL_LINVOL(pga_gain2hw(vol_l)));
|
|
|
|
/* digital left gain set */
|
|
wmcodec_set_reg(LADCVOL, digital_gain2hw(d_vol_l));
|
|
|
|
if (type == AUDIO_GAIN_MIC)
|
|
{
|
|
/* PGA right gain = PGA left gain*/
|
|
wmcodec_set_reg(RINVOL, RINVOL_RIVU | RINVOL_RIZC |
|
|
RINVOL_RINVOL(pga_gain2hw(vol_l)));
|
|
|
|
/* digital right gain = digital left gain*/
|
|
wmcodec_set_reg(RADCVOL, RADCVOL_RAVU | digital_gain2hw(d_vol_l));
|
|
}
|
|
else
|
|
{
|
|
/* PGA right gain */
|
|
wmcodec_set_reg(RINVOL, RINVOL_RIVU | RINVOL_RIZC |
|
|
RINVOL_RINVOL(pga_gain2hw(vol_r)));
|
|
|
|
/* digital right gain */
|
|
wmcodec_set_reg(RADCVOL, RADCVOL_RAVU | digital_gain2hw(d_vol_r));
|
|
}
|
|
}
|
|
#endif /* HAVE_RECORDING */
|
|
#endif /* HAVE_WM8750 */
|