2008-04-27 10:30:54 +00:00
|
|
|
/***************************************************************************
|
|
|
|
* __________ __ ___.
|
|
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
|
|
* \/ \/ \/ \/ \/
|
|
|
|
* $Id$
|
|
|
|
*
|
|
|
|
* Copyright (C) 2008 by Michael Sevakis
|
|
|
|
*
|
|
|
|
* Driver for WM8978 audio codec
|
|
|
|
*
|
2008-06-28 18:10:04 +00:00
|
|
|
* 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.
|
2008-04-27 10:30:54 +00:00
|
|
|
*
|
|
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
|
|
* KIND, either express or implied.
|
|
|
|
*
|
|
|
|
****************************************************************************/
|
|
|
|
#include "config.h"
|
|
|
|
#include "system.h"
|
|
|
|
#include "audiohw.h"
|
|
|
|
#include "wmcodec.h"
|
|
|
|
#include "audio.h"
|
2009-11-03 16:25:03 +00:00
|
|
|
/*#define LOGF_ENABLE*/
|
2008-05-03 15:14:52 +00:00
|
|
|
#include "logf.h"
|
2011-12-21 18:02:50 +00:00
|
|
|
#include "sound.h"
|
2008-04-27 10:30:54 +00:00
|
|
|
|
2008-05-03 15:14:52 +00:00
|
|
|
/* TODO: Define/refine an API for special hardware steps outside the
|
|
|
|
* main codec driver such as special GPIO handling. */
|
2008-12-31 01:38:44 +00:00
|
|
|
/* NOTE: Much of the volume code is very interdependent and calibrated for
|
|
|
|
* the Gigabeat S. If you change anything for another device that uses this
|
|
|
|
* file it may break things. */
|
2008-05-03 15:14:52 +00:00
|
|
|
extern void audiohw_enable_headphone_jack(bool enable);
|
|
|
|
|
|
|
|
static uint16_t wmc_regs[WMC_NUM_REGISTERS] =
|
|
|
|
{
|
|
|
|
/* Initialized with post-reset default values - the 2-wire interface
|
|
|
|
* cannot be read. Or-in additional bits desired for some registers. */
|
|
|
|
[0 ... WMC_NUM_REGISTERS-1] = 0x8000, /* To ID invalids in gaps */
|
|
|
|
[WMC_SOFTWARE_RESET] = 0x000,
|
|
|
|
[WMC_POWER_MANAGEMENT1] = 0x000,
|
|
|
|
[WMC_POWER_MANAGEMENT2] = 0x000,
|
|
|
|
[WMC_POWER_MANAGEMENT3] = 0x000,
|
|
|
|
[WMC_AUDIO_INTERFACE] = 0x050,
|
|
|
|
[WMC_COMPANDING_CTRL] = 0x000,
|
|
|
|
[WMC_CLOCK_GEN_CTRL] = 0x140,
|
|
|
|
[WMC_ADDITIONAL_CTRL] = 0x000,
|
|
|
|
[WMC_GPIO] = 0x000,
|
|
|
|
[WMC_JACK_DETECT_CONTROL1] = 0x000,
|
|
|
|
[WMC_DAC_CONTROL] = 0x000,
|
2010-05-15 04:59:25 +00:00
|
|
|
[WMC_LEFT_DAC_DIGITAL_VOL] = 0x0ff, /* Latch left first */
|
2008-05-03 15:14:52 +00:00
|
|
|
[WMC_RIGHT_DAC_DIGITAL_VOL] = 0x0ff | WMC_VU,
|
|
|
|
[WMC_JACK_DETECT_CONTROL2] = 0x000,
|
|
|
|
[WMC_ADC_CONTROL] = 0x100,
|
2010-05-15 04:59:25 +00:00
|
|
|
[WMC_LEFT_ADC_DIGITAL_VOL] = 0x0ff, /* Latch left first */
|
2008-05-03 15:14:52 +00:00
|
|
|
[WMC_RIGHT_ADC_DIGITAL_VOL] = 0x0ff | WMC_VU,
|
|
|
|
[WMC_EQ1_LOW_SHELF] = 0x12c,
|
|
|
|
[WMC_EQ2_PEAK1] = 0x02c,
|
|
|
|
[WMC_EQ3_PEAK2] = 0x02c,
|
|
|
|
[WMC_EQ4_PEAK3] = 0x02c,
|
|
|
|
[WMC_EQ5_HIGH_SHELF] = 0x02c,
|
|
|
|
[WMC_DAC_LIMITER1] = 0x032,
|
|
|
|
[WMC_DAC_LIMITER2] = 0x000,
|
|
|
|
[WMC_NOTCH_FILTER1] = 0x000,
|
|
|
|
[WMC_NOTCH_FILTER2] = 0x000,
|
|
|
|
[WMC_NOTCH_FILTER3] = 0x000,
|
|
|
|
[WMC_NOTCH_FILTER4] = 0x000,
|
|
|
|
[WMC_ALC_CONTROL1] = 0x038,
|
|
|
|
[WMC_ALC_CONTROL2] = 0x00b,
|
|
|
|
[WMC_ALC_CONTROL3] = 0x032,
|
|
|
|
[WMC_NOISE_GATE] = 0x000,
|
|
|
|
[WMC_PLL_N] = 0x008,
|
|
|
|
[WMC_PLL_K1] = 0x00c,
|
|
|
|
[WMC_PLL_K2] = 0x093,
|
|
|
|
[WMC_PLL_K3] = 0x0e9,
|
|
|
|
[WMC_3D_CONTROL] = 0x000,
|
|
|
|
[WMC_BEEP_CONTROL] = 0x000,
|
|
|
|
[WMC_INPUT_CTRL] = 0x033,
|
2010-05-15 04:59:25 +00:00
|
|
|
[WMC_LEFT_INP_PGA_GAIN_CTRL] = 0x010 | WMC_ZC, /* Latch left first */
|
2008-12-31 01:38:44 +00:00
|
|
|
[WMC_RIGHT_INP_PGA_GAIN_CTRL] = 0x010 | WMC_VU | WMC_ZC,
|
2008-05-03 15:14:52 +00:00
|
|
|
[WMC_LEFT_ADC_BOOST_CTRL] = 0x100,
|
|
|
|
[WMC_RIGHT_ADC_BOOST_CTRL] = 0x100,
|
|
|
|
[WMC_OUTPUT_CTRL] = 0x002,
|
|
|
|
[WMC_LEFT_MIXER_CTRL] = 0x001,
|
|
|
|
[WMC_RIGHT_MIXER_CTRL] = 0x001,
|
2010-05-15 04:59:25 +00:00
|
|
|
[WMC_LOUT1_HP_VOLUME_CTRL] = 0x039 | WMC_ZC, /* Latch left first */
|
2008-05-03 15:14:52 +00:00
|
|
|
[WMC_ROUT1_HP_VOLUME_CTRL] = 0x039 | WMC_VU | WMC_ZC,
|
2010-05-15 04:59:25 +00:00
|
|
|
[WMC_LOUT2_SPK_VOLUME_CTRL] = 0x039 | WMC_ZC, /* Latch left first */
|
2008-05-03 15:14:52 +00:00
|
|
|
[WMC_ROUT2_SPK_VOLUME_CTRL] = 0x039 | WMC_VU | WMC_ZC,
|
|
|
|
[WMC_OUT3_MIXER_CTRL] = 0x001,
|
|
|
|
[WMC_OUT4_MONO_MIXER_CTRL] = 0x001,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct
|
|
|
|
{
|
2010-05-26 05:13:45 +00:00
|
|
|
signed char vol_l;
|
|
|
|
signed char vol_r;
|
|
|
|
unsigned char dac_l;
|
|
|
|
unsigned char dac_r;
|
|
|
|
unsigned char ahw_mute;
|
|
|
|
unsigned char prescaler; /* Attenuation */
|
|
|
|
unsigned char eq_on; /* Enabled */
|
|
|
|
signed char band_gain[5]; /* Setting */
|
|
|
|
unsigned char enh_3d_prescaler; /* Attenuation */
|
|
|
|
unsigned char enh_3d_on; /* Enabled */
|
|
|
|
unsigned char enh_3d; /* Setting */
|
2008-05-03 15:14:52 +00:00
|
|
|
} wmc_vol =
|
|
|
|
{
|
2010-05-15 13:09:45 +00:00
|
|
|
.vol_l = 0,
|
|
|
|
.vol_r = 0,
|
|
|
|
.dac_l = 0,
|
|
|
|
.dac_r = 0,
|
|
|
|
.ahw_mute = false,
|
|
|
|
.prescaler = 0,
|
2010-05-26 05:13:45 +00:00
|
|
|
.eq_on = true,
|
|
|
|
.band_gain = { 0, 0, 0, 0, 0 },
|
|
|
|
.enh_3d_prescaler = 0,
|
|
|
|
.enh_3d_on = true,
|
|
|
|
.enh_3d = 0,
|
2008-05-03 15:14:52 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static void wmc_write(unsigned int reg, unsigned int val)
|
|
|
|
{
|
|
|
|
if (reg >= WMC_NUM_REGISTERS || (wmc_regs[reg] & 0x8000))
|
|
|
|
{
|
|
|
|
logf("wm8978 invalid register: %d", reg);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wmc_regs[reg] = val & ~0x8000;
|
|
|
|
wmcodec_write(reg, val);
|
|
|
|
}
|
|
|
|
|
2008-12-09 20:48:04 +00:00
|
|
|
void wmc_set(unsigned int reg, unsigned int bits)
|
2008-05-03 15:14:52 +00:00
|
|
|
{
|
|
|
|
wmc_write(reg, wmc_regs[reg] | bits);
|
|
|
|
}
|
|
|
|
|
2008-12-09 20:48:04 +00:00
|
|
|
void wmc_clear(unsigned int reg, unsigned int bits)
|
2008-05-03 15:14:52 +00:00
|
|
|
{
|
|
|
|
wmc_write(reg, wmc_regs[reg] & ~bits);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void wmc_write_masked(unsigned int reg, unsigned int bits,
|
|
|
|
unsigned int mask)
|
|
|
|
{
|
|
|
|
wmc_write(reg, (wmc_regs[reg] & ~mask) | (bits & mask));
|
|
|
|
}
|
|
|
|
|
2013-04-13 03:35:47 +00:00
|
|
|
/* convert tenth of dB volume (-890..60) to volume register value
|
2008-05-03 15:14:52 +00:00
|
|
|
* (000000...111111) */
|
2013-04-13 03:35:47 +00:00
|
|
|
static int vol_tenthdb2hw(int db)
|
2008-05-03 15:14:52 +00:00
|
|
|
{
|
|
|
|
/* -90dB to +6dB 1dB steps (96 levels) 7bits */
|
|
|
|
/* 1100000 == +6dB (0x60,96) */
|
|
|
|
/* 1101010 == 0dB (0x5a,90) */
|
|
|
|
/* 1000001 == -57dB (0x21,33,DAC) */
|
|
|
|
/* 0000001 == -89dB (0x01,01) */
|
|
|
|
/* 0000000 == -90dB (0x00,00,Mute) */
|
2013-04-13 03:35:47 +00:00
|
|
|
if (db < VOLUME_MIN)
|
2008-05-03 15:14:52 +00:00
|
|
|
{
|
|
|
|
return 0x0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return (db - VOLUME_MIN) / 10;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-04-27 10:30:54 +00:00
|
|
|
void audiohw_preinit(void)
|
|
|
|
{
|
2008-05-03 15:14:52 +00:00
|
|
|
/* 1. Turn on external power supplies. Wait for supply voltage to settle. */
|
|
|
|
|
|
|
|
/* Step 1 should be completed already. Reset and return all registers to
|
|
|
|
* defaults */
|
|
|
|
wmcodec_write(WMC_SOFTWARE_RESET, 0xff);
|
|
|
|
sleep(HZ/10);
|
|
|
|
|
|
|
|
/* 2. Mute all analogue outputs */
|
2008-12-31 01:38:44 +00:00
|
|
|
wmc_set(WMC_LOUT1_HP_VOLUME_CTRL, WMC_MUTE);
|
|
|
|
wmc_set(WMC_ROUT1_HP_VOLUME_CTRL, WMC_MUTE);
|
2008-05-03 15:14:52 +00:00
|
|
|
wmc_set(WMC_LOUT2_SPK_VOLUME_CTRL, WMC_MUTE);
|
|
|
|
wmc_set(WMC_ROUT2_SPK_VOLUME_CTRL, WMC_MUTE);
|
|
|
|
wmc_set(WMC_OUT3_MIXER_CTRL, WMC_MUTE);
|
|
|
|
wmc_set(WMC_OUT4_MONO_MIXER_CTRL, WMC_MUTE);
|
|
|
|
|
|
|
|
/* 3. Set L/RMIXEN = 1 and DACENL/R = 1 in register R3. */
|
2010-05-15 13:09:45 +00:00
|
|
|
wmc_write(WMC_POWER_MANAGEMENT3, WMC_RMIXEN | WMC_LMIXEN);
|
|
|
|
|
|
|
|
/* EQ and 3D applied to DAC (Set before DAC enable!) */
|
|
|
|
wmc_set(WMC_EQ1_LOW_SHELF, WMC_EQ3DMODE);
|
|
|
|
|
|
|
|
wmc_set(WMC_POWER_MANAGEMENT3, WMC_DACENR | WMC_DACENL);
|
2008-05-03 15:14:52 +00:00
|
|
|
|
|
|
|
/* 4. Set BUFIOEN = 1 and VMIDSEL[1:0] to required value in register
|
|
|
|
* R1. Wait for VMID supply to settle */
|
|
|
|
wmc_write(WMC_POWER_MANAGEMENT1, WMC_BUFIOEN | WMC_VMIDSEL_300K);
|
|
|
|
sleep(HZ/10);
|
|
|
|
|
|
|
|
/* 5. Set BIASEN = 1 in register R1. */
|
|
|
|
wmc_set(WMC_POWER_MANAGEMENT1, WMC_BIASEN);
|
2010-05-26 05:13:45 +00:00
|
|
|
|
|
|
|
/* 6. Set L/ROUTEN = 1 in register R2. */
|
|
|
|
wmc_write(WMC_POWER_MANAGEMENT2, WMC_LOUT1EN | WMC_ROUT1EN);
|
2008-04-27 10:30:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void audiohw_postinit(void)
|
|
|
|
{
|
2010-05-26 05:13:45 +00:00
|
|
|
sleep(5*HZ/4);
|
2008-05-03 15:14:52 +00:00
|
|
|
|
|
|
|
/* 7. Enable other mixers as required */
|
|
|
|
|
|
|
|
/* 8. Enable other outputs as required */
|
|
|
|
|
|
|
|
/* 9. Set remaining registers */
|
2008-11-19 03:12:34 +00:00
|
|
|
wmc_write(WMC_AUDIO_INTERFACE, WMC_WL_16 | WMC_FMT_I2S);
|
2011-02-06 02:01:13 +00:00
|
|
|
wmc_write(WMC_DAC_CONTROL, WMC_DACOSR_128);
|
2008-05-03 15:14:52 +00:00
|
|
|
|
2008-12-31 01:38:44 +00:00
|
|
|
/* No ADC, no HP filter, no popping */
|
|
|
|
wmc_clear(WMC_ADC_CONTROL, WMC_HPFEN);
|
|
|
|
|
|
|
|
wmc_clear(WMC_LEFT_ADC_BOOST_CTRL, WMC_PGABOOSTL);
|
|
|
|
wmc_clear(WMC_RIGHT_ADC_BOOST_CTRL, WMC_PGABOOSTR);
|
2008-12-09 20:48:04 +00:00
|
|
|
|
2008-05-03 15:14:52 +00:00
|
|
|
/* Specific to HW clocking */
|
2008-11-22 12:17:26 +00:00
|
|
|
wmc_write_masked(WMC_CLOCK_GEN_CTRL, WMC_BCLKDIV_4 | WMC_MS,
|
|
|
|
WMC_BCLKDIV | WMC_MS | WMC_CLKSEL);
|
2008-11-22 12:23:31 +00:00
|
|
|
audiohw_set_frequency(HW_FREQ_DEFAULT);
|
2008-05-03 15:14:52 +00:00
|
|
|
|
|
|
|
audiohw_enable_headphone_jack(true);
|
|
|
|
}
|
|
|
|
|
2008-12-31 01:38:44 +00:00
|
|
|
static void get_headphone_levels(int val, int *dac_p, int *hp_p,
|
|
|
|
int *mix_p, int *boost_p)
|
2008-05-03 15:14:52 +00:00
|
|
|
{
|
2008-12-31 01:38:44 +00:00
|
|
|
int dac, hp, mix, boost;
|
2008-05-03 15:14:52 +00:00
|
|
|
|
2008-12-31 01:38:44 +00:00
|
|
|
if (val >= 33)
|
2008-05-03 15:14:52 +00:00
|
|
|
{
|
2008-12-31 01:38:44 +00:00
|
|
|
dac = 255;
|
|
|
|
hp = val - 33;
|
|
|
|
mix = 7;
|
|
|
|
boost = 5;
|
|
|
|
}
|
|
|
|
else if (val >= 21)
|
|
|
|
{
|
|
|
|
dac = 189 + val / 3 * 6;
|
|
|
|
hp = val % 3;
|
|
|
|
mix = 7;
|
|
|
|
boost = (val - 18) / 3;
|
2008-05-03 15:14:52 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2008-12-31 01:38:44 +00:00
|
|
|
dac = 189 + val / 3 * 6;
|
|
|
|
hp = val % 3;
|
|
|
|
mix = val / 3;
|
|
|
|
boost = 1;
|
2008-05-03 15:14:52 +00:00
|
|
|
}
|
|
|
|
|
2008-12-31 01:38:44 +00:00
|
|
|
*dac_p = dac;
|
|
|
|
*hp_p = hp;
|
|
|
|
*mix_p = mix;
|
|
|
|
*boost_p = boost;
|
|
|
|
}
|
2008-05-03 15:14:52 +00:00
|
|
|
|
2010-05-26 05:13:45 +00:00
|
|
|
static void sync_prescaler(void)
|
|
|
|
{
|
|
|
|
int prescaler = 0;
|
|
|
|
|
|
|
|
/* Combine all prescaling into a single DAC attenuation */
|
|
|
|
if (wmc_vol.eq_on)
|
2011-01-21 08:31:40 +00:00
|
|
|
prescaler = wmc_vol.prescaler;
|
2010-05-26 05:13:45 +00:00
|
|
|
|
|
|
|
if (wmc_vol.enh_3d_on)
|
|
|
|
prescaler += wmc_vol.enh_3d_prescaler;
|
|
|
|
|
|
|
|
wmc_write_masked(WMC_LEFT_DAC_DIGITAL_VOL, wmc_vol.dac_l - prescaler,
|
|
|
|
WMC_DVOL);
|
|
|
|
wmc_write_masked(WMC_RIGHT_DAC_DIGITAL_VOL, wmc_vol.dac_r - prescaler,
|
|
|
|
WMC_DVOL);
|
|
|
|
}
|
|
|
|
|
2013-04-13 03:35:47 +00:00
|
|
|
void audiohw_set_volume(int vol_l, int vol_r)
|
2008-12-31 01:38:44 +00:00
|
|
|
{
|
|
|
|
int prev_l = wmc_vol.vol_l;
|
|
|
|
int prev_r = wmc_vol.vol_r;
|
|
|
|
int dac_l, dac_r, hp_l, hp_r;
|
|
|
|
int mix_l, mix_r, boost_l, boost_r;
|
2008-05-03 15:14:52 +00:00
|
|
|
|
2013-04-13 03:35:47 +00:00
|
|
|
vol_l = vol_tenthdb2hw(vol_l);
|
|
|
|
vol_r = vol_tenthdb2hw(vol_r);
|
|
|
|
|
2008-12-31 01:38:44 +00:00
|
|
|
wmc_vol.vol_l = vol_l;
|
|
|
|
wmc_vol.vol_r = vol_r;
|
|
|
|
|
|
|
|
/* Mixers are synced to provide full volume range on both the analogue
|
|
|
|
* and digital pathways */
|
|
|
|
get_headphone_levels(vol_l, &dac_l, &hp_l, &mix_l, &boost_l);
|
|
|
|
get_headphone_levels(vol_r, &dac_r, &hp_r, &mix_r, &boost_r);
|
2008-05-03 15:14:52 +00:00
|
|
|
|
2010-05-15 13:09:45 +00:00
|
|
|
wmc_vol.dac_l = dac_l;
|
|
|
|
wmc_vol.dac_r = dac_r;
|
|
|
|
|
2010-05-26 05:13:45 +00:00
|
|
|
sync_prescaler();
|
2010-05-15 13:09:45 +00:00
|
|
|
|
2010-05-15 04:59:25 +00:00
|
|
|
wmc_write_masked(WMC_LEFT_MIXER_CTRL, mix_l << WMC_BYPLMIXVOL_POS,
|
2008-12-31 01:38:44 +00:00
|
|
|
WMC_BYPLMIXVOL);
|
|
|
|
wmc_write_masked(WMC_LEFT_ADC_BOOST_CTRL,
|
2010-05-15 04:59:25 +00:00
|
|
|
boost_l << WMC_L2_2BOOSTVOL_POS, WMC_L2_2BOOSTVOL);
|
2008-12-31 01:38:44 +00:00
|
|
|
wmc_write_masked(WMC_LOUT1_HP_VOLUME_CTRL, hp_l, WMC_AVOL);
|
|
|
|
|
2010-05-15 04:59:25 +00:00
|
|
|
wmc_write_masked(WMC_RIGHT_MIXER_CTRL, mix_r << WMC_BYPRMIXVOL_POS,
|
2008-12-31 01:38:44 +00:00
|
|
|
WMC_BYPRMIXVOL);
|
|
|
|
wmc_write_masked(WMC_RIGHT_ADC_BOOST_CTRL,
|
2010-05-15 04:59:25 +00:00
|
|
|
boost_r << WMC_R2_2BOOSTVOL_POS, WMC_R2_2BOOSTVOL);
|
2008-12-31 01:38:44 +00:00
|
|
|
wmc_write_masked(WMC_ROUT1_HP_VOLUME_CTRL, hp_r, WMC_AVOL);
|
2008-05-03 15:14:52 +00:00
|
|
|
|
2008-12-31 01:38:44 +00:00
|
|
|
if (vol_l > 0)
|
2008-05-03 15:14:52 +00:00
|
|
|
{
|
|
|
|
/* Not muted and going up from mute level? */
|
2008-12-31 01:38:44 +00:00
|
|
|
if (prev_l <= 0 && !wmc_vol.ahw_mute)
|
2008-05-03 15:14:52 +00:00
|
|
|
wmc_clear(WMC_LOUT1_HP_VOLUME_CTRL, WMC_MUTE);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Going to mute level? */
|
2008-12-31 01:38:44 +00:00
|
|
|
if (prev_l > 0)
|
2008-05-03 15:14:52 +00:00
|
|
|
wmc_set(WMC_LOUT1_HP_VOLUME_CTRL, WMC_MUTE);
|
|
|
|
}
|
|
|
|
|
2008-12-31 01:38:44 +00:00
|
|
|
if (vol_r > 0)
|
2008-05-03 15:14:52 +00:00
|
|
|
{
|
|
|
|
/* Not muted and going up from mute level? */
|
2008-12-31 01:38:44 +00:00
|
|
|
if (prev_r <= 0 && !wmc_vol.ahw_mute)
|
2008-05-03 15:14:52 +00:00
|
|
|
wmc_clear(WMC_ROUT1_HP_VOLUME_CTRL, WMC_MUTE);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Going to mute level? */
|
2008-12-31 01:38:44 +00:00
|
|
|
if (prev_r > 0)
|
2008-05-03 15:14:52 +00:00
|
|
|
wmc_set(WMC_ROUT1_HP_VOLUME_CTRL, WMC_MUTE);
|
|
|
|
}
|
2008-04-27 10:30:54 +00:00
|
|
|
}
|
|
|
|
|
2010-04-27 00:05:02 +00:00
|
|
|
static void audiohw_mute(bool mute)
|
2008-04-27 10:30:54 +00:00
|
|
|
{
|
2008-05-03 15:14:52 +00:00
|
|
|
wmc_vol.ahw_mute = mute;
|
|
|
|
|
|
|
|
/* No DAC mute here, please - take care of each enabled output. */
|
|
|
|
if (mute)
|
|
|
|
{
|
|
|
|
wmc_set(WMC_LOUT1_HP_VOLUME_CTRL, WMC_MUTE);
|
|
|
|
wmc_set(WMC_ROUT1_HP_VOLUME_CTRL, WMC_MUTE);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Unmute outputs not at mute level */
|
2008-12-31 01:38:44 +00:00
|
|
|
if (wmc_vol.vol_l > 0)
|
2008-05-03 15:14:52 +00:00
|
|
|
wmc_clear(WMC_LOUT1_HP_VOLUME_CTRL, WMC_MUTE);
|
|
|
|
|
2008-12-31 01:38:44 +00:00
|
|
|
if (wmc_vol.vol_r > 0)
|
2008-05-03 15:14:52 +00:00
|
|
|
wmc_clear(WMC_ROUT1_HP_VOLUME_CTRL, WMC_MUTE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-15 13:09:45 +00:00
|
|
|
/* Equalizer - set the eq band level -12 to +12 dB. */
|
|
|
|
void audiohw_set_eq_band_gain(unsigned int band, int val)
|
|
|
|
{
|
|
|
|
if (band > 4)
|
|
|
|
return;
|
|
|
|
|
2010-05-26 05:13:45 +00:00
|
|
|
wmc_vol.band_gain[band] = val;
|
|
|
|
|
|
|
|
if (!wmc_vol.eq_on)
|
|
|
|
val = 0;
|
|
|
|
|
2010-05-15 13:09:45 +00:00
|
|
|
wmc_write_masked(band + WMC_EQ1_LOW_SHELF, 12 - val, WMC_EQG);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Equalizer - set the eq band frequency index. */
|
|
|
|
void audiohw_set_eq_band_frequency(unsigned int band, int val)
|
|
|
|
{
|
|
|
|
if (band > 4)
|
|
|
|
return;
|
|
|
|
|
|
|
|
wmc_write_masked(band + WMC_EQ1_LOW_SHELF,
|
|
|
|
val << WMC_EQC_POS, WMC_EQC);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Equalizer - set bandwidth for peaking filters to wide (!= 0) or
|
|
|
|
* narrow (0); only valid for peaking filter bands 1-3. */
|
|
|
|
void audiohw_set_eq_band_width(unsigned int band, int val)
|
|
|
|
{
|
|
|
|
if (band < 1 || band > 3)
|
|
|
|
return;
|
|
|
|
|
|
|
|
wmc_write_masked(band + WMC_EQ1_LOW_SHELF,
|
|
|
|
(val == 0) ? 0 : WMC_EQBW, WMC_EQBW);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set prescaler to prevent clipping the output amp when applying positive
|
|
|
|
* gain to EQ bands. */
|
|
|
|
void audiohw_set_prescaler(int val)
|
|
|
|
{
|
2011-01-21 08:31:40 +00:00
|
|
|
wmc_vol.prescaler = val / 5; /* centibels->semi-decibels */
|
2010-05-26 05:13:45 +00:00
|
|
|
sync_prescaler();
|
2010-05-15 13:09:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Set the depth of the 3D effect */
|
|
|
|
void audiohw_set_depth_3d(int val)
|
|
|
|
{
|
2010-05-26 05:13:45 +00:00
|
|
|
wmc_vol.enh_3d_prescaler = 10*val / 15; /* -5 dB @ full setting */
|
|
|
|
wmc_vol.enh_3d = val;
|
2010-05-15 13:09:45 +00:00
|
|
|
|
2010-05-26 05:13:45 +00:00
|
|
|
if (!wmc_vol.enh_3d_on)
|
|
|
|
val = 0;
|
|
|
|
|
|
|
|
sync_prescaler();
|
2010-05-15 13:09:45 +00:00
|
|
|
wmc_write_masked(WMC_3D_CONTROL, val, WMC_DEPTH3D);
|
|
|
|
}
|
|
|
|
|
2010-04-27 00:05:02 +00:00
|
|
|
void audiohw_close(void)
|
|
|
|
{
|
|
|
|
/* 1. Mute all analogue outputs */
|
|
|
|
audiohw_mute(true);
|
|
|
|
audiohw_enable_headphone_jack(false);
|
|
|
|
|
|
|
|
/* 2. Disable power management register 1. R1 = 00 */
|
|
|
|
wmc_write(WMC_POWER_MANAGEMENT1, 0x000);
|
|
|
|
|
|
|
|
/* 3. Disable power management register 2. R2 = 00 */
|
|
|
|
wmc_write(WMC_POWER_MANAGEMENT2, 0x000);
|
|
|
|
|
|
|
|
/* 4. Disable power management register 3. R3 = 00 */
|
|
|
|
wmc_write(WMC_POWER_MANAGEMENT3, 0x000);
|
|
|
|
|
|
|
|
/* 5. Remove external power supplies. */
|
|
|
|
}
|
|
|
|
|
2008-12-12 11:01:07 +00:00
|
|
|
void audiohw_set_frequency(int fsel)
|
2008-11-22 12:17:26 +00:00
|
|
|
{
|
2011-10-17 15:37:14 +00:00
|
|
|
extern const struct wmc_srctrl_entry wmc_srctrl_table[HW_NUM_FREQ];
|
2008-11-22 12:17:26 +00:00
|
|
|
unsigned int plln;
|
|
|
|
unsigned int mclkdiv;
|
|
|
|
|
2008-12-12 11:01:07 +00:00
|
|
|
if ((unsigned)fsel >= HW_NUM_FREQ)
|
|
|
|
fsel = HW_FREQ_DEFAULT;
|
2008-11-22 12:17:26 +00:00
|
|
|
|
|
|
|
/* Setup filters. */
|
2011-10-17 15:37:14 +00:00
|
|
|
wmc_write(WMC_ADDITIONAL_CTRL, wmc_srctrl_table[fsel].filter);
|
2008-11-22 12:17:26 +00:00
|
|
|
|
2011-10-17 15:37:14 +00:00
|
|
|
plln = wmc_srctrl_table[fsel].plln;
|
|
|
|
mclkdiv = wmc_srctrl_table[fsel].mclkdiv;
|
2008-11-22 12:17:26 +00:00
|
|
|
|
|
|
|
if (plln != 0)
|
|
|
|
{
|
|
|
|
/* Using PLL to generate SYSCLK */
|
|
|
|
|
|
|
|
/* Program PLL. */
|
|
|
|
wmc_write(WMC_PLL_N, plln);
|
2011-10-17 15:37:14 +00:00
|
|
|
wmc_write(WMC_PLL_K1, wmc_srctrl_table[fsel].pllk1);
|
|
|
|
wmc_write(WMC_PLL_K2, wmc_srctrl_table[fsel].pllk2);
|
|
|
|
wmc_write(WMC_PLL_K3, wmc_srctrl_table[fsel].pllk3);
|
2008-11-22 12:17:26 +00:00
|
|
|
|
|
|
|
/* Turn on PLL. */
|
|
|
|
wmc_set(WMC_POWER_MANAGEMENT1, WMC_PLLEN);
|
|
|
|
|
|
|
|
/* Switch to PLL and set divider. */
|
|
|
|
wmc_write_masked(WMC_CLOCK_GEN_CTRL, mclkdiv | WMC_CLKSEL,
|
|
|
|
WMC_MCLKDIV | WMC_CLKSEL);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Switch away from PLL and set MCLKDIV. */
|
|
|
|
wmc_write_masked(WMC_CLOCK_GEN_CTRL, mclkdiv,
|
|
|
|
WMC_MCLKDIV | WMC_CLKSEL);
|
|
|
|
|
|
|
|
/* Turn off PLL. */
|
|
|
|
wmc_clear(WMC_POWER_MANAGEMENT1, WMC_PLLEN);
|
|
|
|
}
|
|
|
|
}
|
2008-05-03 15:14:52 +00:00
|
|
|
|
2010-05-26 05:13:45 +00:00
|
|
|
void audiohw_enable_tone_controls(bool enable)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
wmc_vol.eq_on = enable;
|
|
|
|
audiohw_set_prescaler(wmc_vol.prescaler);
|
|
|
|
|
|
|
|
for (i = 0; i < 5; i++)
|
|
|
|
audiohw_set_eq_band_gain(i, wmc_vol.band_gain[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void audiohw_enable_depth_3d(bool enable)
|
|
|
|
{
|
|
|
|
wmc_vol.enh_3d_on = enable;
|
|
|
|
audiohw_set_depth_3d(wmc_vol.enh_3d);
|
|
|
|
}
|
|
|
|
|
2008-05-03 15:14:52 +00:00
|
|
|
#ifdef HAVE_RECORDING
|
2008-12-31 01:38:44 +00:00
|
|
|
void audiohw_set_recsrc(int source, bool recording)
|
|
|
|
{
|
|
|
|
switch (source)
|
|
|
|
{
|
|
|
|
case AUDIO_SRC_PLAYBACK:
|
|
|
|
/* Disable all audio paths but DAC */
|
|
|
|
/* Disable ADCs */
|
|
|
|
wmc_clear(WMC_ADC_CONTROL, WMC_HPFEN);
|
|
|
|
wmc_clear(WMC_POWER_MANAGEMENT2, WMC_ADCENL | WMC_ADCENR);
|
|
|
|
/* Disable bypass */
|
|
|
|
wmc_clear(WMC_LEFT_MIXER_CTRL, WMC_BYPL2LMIX);
|
|
|
|
wmc_clear(WMC_RIGHT_MIXER_CTRL, WMC_BYPR2RMIX);
|
|
|
|
/* Disable IP BOOSTMIX and PGA */
|
|
|
|
wmc_clear(WMC_POWER_MANAGEMENT2, WMC_INPPGAENL | WMC_INPPGAENR |
|
|
|
|
WMC_BOOSTENL | WMC_BOOSTENR);
|
2010-05-26 05:13:45 +00:00
|
|
|
wmc_clear(WMC_INPUT_CTRL, WMC_L2_2INPPGA | WMC_R2_2INPPGA |
|
|
|
|
WMC_LIP2INPPGA | WMC_RIP2INPPGA |
|
|
|
|
WMC_LIN2INPPGA | WMC_RIN2INPPGA);
|
2008-12-31 01:38:44 +00:00
|
|
|
wmc_clear(WMC_LEFT_ADC_BOOST_CTRL, WMC_PGABOOSTL);
|
|
|
|
wmc_clear(WMC_RIGHT_ADC_BOOST_CTRL, WMC_PGABOOSTR);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AUDIO_SRC_FMRADIO:
|
|
|
|
if (recording)
|
|
|
|
{
|
|
|
|
/* Disable bypass */
|
|
|
|
wmc_clear(WMC_LEFT_MIXER_CTRL, WMC_BYPL2LMIX);
|
|
|
|
wmc_clear(WMC_RIGHT_MIXER_CTRL, WMC_BYPR2RMIX);
|
|
|
|
/* Enable ADCs, IP BOOSTMIX and PGA, route L/R2 through PGA */
|
|
|
|
wmc_set(WMC_POWER_MANAGEMENT2, WMC_ADCENL | WMC_ADCENR |
|
|
|
|
WMC_BOOSTENL | WMC_BOOSTENR | WMC_INPPGAENL |
|
|
|
|
WMC_INPPGAENR);
|
|
|
|
wmc_set(WMC_ADC_CONTROL, WMC_ADCOSR | WMC_HPFEN);
|
|
|
|
/* PGA at 0dB with +20dB boost */
|
|
|
|
wmc_write_masked(WMC_LEFT_INP_PGA_GAIN_CTRL, 0x10, WMC_AVOL);
|
|
|
|
wmc_write_masked(WMC_RIGHT_INP_PGA_GAIN_CTRL, 0x10, WMC_AVOL);
|
|
|
|
wmc_set(WMC_LEFT_ADC_BOOST_CTRL, WMC_PGABOOSTL);
|
|
|
|
wmc_set(WMC_RIGHT_ADC_BOOST_CTRL, WMC_PGABOOSTR);
|
|
|
|
/* Connect L/R2 inputs to PGA */
|
2010-05-26 05:13:45 +00:00
|
|
|
wmc_write_masked(WMC_INPUT_CTRL, WMC_L2_2INPPGA | WMC_R2_2INPPGA |
|
|
|
|
WMC_LIN2INPPGA | WMC_RIN2INPPGA,
|
|
|
|
WMC_L2_2INPPGA | WMC_R2_2INPPGA |
|
|
|
|
WMC_LIP2INPPGA | WMC_RIP2INPPGA |
|
|
|
|
WMC_LIN2INPPGA | WMC_RIN2INPPGA);
|
2008-12-31 01:38:44 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Disable PGA and ADC, enable IP BOOSTMIX, route L/R2 directly to
|
|
|
|
* IP BOOSTMIX */
|
|
|
|
wmc_clear(WMC_ADC_CONTROL, WMC_HPFEN);
|
|
|
|
wmc_write_masked(WMC_POWER_MANAGEMENT2, WMC_BOOSTENL | WMC_BOOSTENR,
|
|
|
|
WMC_BOOSTENL | WMC_BOOSTENR | WMC_INPPGAENL |
|
|
|
|
WMC_INPPGAENR | WMC_ADCENL | WMC_ADCENR);
|
2010-05-26 05:13:45 +00:00
|
|
|
wmc_clear(WMC_INPUT_CTRL, WMC_L2_2INPPGA | WMC_R2_2INPPGA |
|
|
|
|
WMC_LIP2INPPGA | WMC_RIP2INPPGA |
|
|
|
|
WMC_LIN2INPPGA | WMC_RIN2INPPGA);
|
2008-12-31 01:38:44 +00:00
|
|
|
wmc_clear(WMC_LEFT_ADC_BOOST_CTRL, WMC_PGABOOSTL);
|
|
|
|
wmc_clear(WMC_RIGHT_ADC_BOOST_CTRL, WMC_PGABOOSTR);
|
|
|
|
/* Enable bypass to L/R mixers */
|
|
|
|
wmc_set(WMC_LEFT_MIXER_CTRL, WMC_BYPL2LMIX);
|
|
|
|
wmc_set(WMC_RIGHT_MIXER_CTRL, WMC_BYPR2RMIX);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-05-03 15:14:52 +00:00
|
|
|
void audiohw_set_recvol(int left, int right, int type)
|
|
|
|
{
|
2008-12-31 01:38:44 +00:00
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case AUDIO_GAIN_LINEIN:
|
|
|
|
wmc_write_masked(WMC_LEFT_ADC_DIGITAL_VOL, left + 239, WMC_DVOL);
|
|
|
|
wmc_write_masked(WMC_RIGHT_ADC_DIGITAL_VOL, right + 239, WMC_DVOL);
|
|
|
|
return;
|
|
|
|
}
|
2008-04-27 10:30:54 +00:00
|
|
|
}
|
2008-12-31 01:38:44 +00:00
|
|
|
#endif /* HAVE_RECORDING */
|