1450f2658a
Change-Id: Ic4c8b536fde7a840d3f3f295531cc1253de6320d
357 lines
12 KiB
C
357 lines
12 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2011 by Amaury Pouly
|
|
*
|
|
* 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 <string.h>
|
|
|
|
#include "config.h"
|
|
#include "kernel.h"
|
|
#include "audioout-imx233.h"
|
|
#include "clkctrl-imx233.h"
|
|
#include "rtc-imx233.h"
|
|
#include "pcm_sampr.h"
|
|
#include "audio-target.h"
|
|
#include "power-imx233.h"
|
|
|
|
#ifndef IMX233_AUDIO_COUPLING_MODE
|
|
#error You must define IMX233_AUDIO_COUPLING_MODE
|
|
#endif
|
|
|
|
#if IMX233_AUDIO_COUPLING_MODE != ACM_CAP && IMX233_AUDIO_COUPLING_MODE != ACM_CAPLESS
|
|
#error Invalid value for IMX233_AUDIO_COUPLING_MODE
|
|
#endif
|
|
|
|
static int hp_vol_l, hp_vol_r;
|
|
static bool input_line1;
|
|
static struct timeout hp_unmute_oneshort;
|
|
|
|
static int hp_unmute_cb(struct timeout *tmo)
|
|
{
|
|
(void) tmo;
|
|
/* unmute HP */
|
|
BF_CLR(AUDIOOUT_HPVOL, MUTE);
|
|
return 0;
|
|
}
|
|
|
|
void imx233_audioout_preinit(void)
|
|
{
|
|
|
|
/* Enable AUDIOOUT block */
|
|
imx233_reset_block(&HW_AUDIOOUT_CTRL);
|
|
/* Enable digital filter clock */
|
|
imx233_clkctrl_enable(CLK_FILT, true);
|
|
/* Enable DAC */
|
|
BF_CLR(AUDIOOUT_ANACLKCTRL, CLKGATE);
|
|
/* Set capless mode */
|
|
#if IMX233_AUDIO_COUPLING_MODE == ACM_CAP
|
|
BF_SET(AUDIOOUT_PWRDN, CAPLESS);
|
|
#else
|
|
BF_CLR(AUDIOOUT_PWRDN, CAPLESS);
|
|
#endif
|
|
/* Set word-length to 16-bit */
|
|
BF_SET(AUDIOOUT_CTRL, WORD_LENGTH);
|
|
/* Power up DAC */
|
|
BF_CLR(AUDIOOUT_PWRDN, DAC);
|
|
/* Hold HP to ground to avoid pop, then release and power up HP */
|
|
BF_SET(AUDIOOUT_ANACTRL, HP_HOLD_GND);
|
|
BF_CLR(AUDIOOUT_PWRDN, HEADPHONE);
|
|
/* Set HP mode to AB */
|
|
BF_SET(AUDIOOUT_ANACTRL, HP_CLASSAB);
|
|
/* change bias to -50% */
|
|
BF_WR(AUDIOOUT_TEST, HP_I1_ADJ, 1);
|
|
BF_WR(AUDIOOUT_REFCTRL, BIAS_CTRL, 1);
|
|
#if IMX233_SUBTARGET >= 3700
|
|
BF_SET(AUDIOOUT_REFCTRL, RAISE_REF);
|
|
#endif
|
|
BF_SET(AUDIOOUT_REFCTRL, XTAL_BGR_BIAS);
|
|
/* Stop holding to ground */
|
|
BF_CLR(AUDIOOUT_ANACTRL, HP_HOLD_GND);
|
|
/* Set dmawait count to 31 (see errata, workaround random stop) */
|
|
BF_WR(AUDIOOUT_CTRL, DMAWAIT_COUNT, 31);
|
|
/* start converting audio */
|
|
BF_SET(AUDIOOUT_CTRL, RUN);
|
|
/* unmute DAC */
|
|
HW_AUDIOOUT_DACVOLUME_CLR = BM_OR2(AUDIOOUT_DACVOLUME, MUTE_LEFT, MUTE_RIGHT);
|
|
/* send a few samples to avoid pop */
|
|
HW_AUDIOOUT_DATA = 0;
|
|
HW_AUDIOOUT_DATA = 0;
|
|
HW_AUDIOOUT_DATA = 0;
|
|
HW_AUDIOOUT_DATA = 0;
|
|
}
|
|
|
|
void imx233_audioout_postinit(void)
|
|
{
|
|
/* wait for everything to stabilize before unmuting */
|
|
timeout_register(&hp_unmute_oneshort, hp_unmute_cb, HZ / 2, 0);
|
|
}
|
|
|
|
void imx233_audioout_close(void)
|
|
{
|
|
/* Switch to class A */
|
|
BF_CLR(AUDIOOUT_ANACTRL, HP_CLASSAB);
|
|
/* Hold HP to ground */
|
|
BF_SET(AUDIOOUT_ANACTRL, HP_HOLD_GND);
|
|
/* Mute HP and power down */
|
|
BF_SET(AUDIOOUT_HPVOL, MUTE);
|
|
/* Power down HP */
|
|
BF_SET(AUDIOOUT_PWRDN, HEADPHONE);
|
|
/* Mute DAC */
|
|
HW_AUDIOOUT_DACVOLUME_SET = BM_OR2(AUDIOOUT_DACVOLUME, MUTE_LEFT, MUTE_RIGHT);
|
|
/* Power down DAC */
|
|
BF_SET(AUDIOOUT_PWRDN, DAC);
|
|
/* Gate off DAC */
|
|
BF_SET(AUDIOOUT_ANACLKCTRL, CLKGATE);
|
|
/* Disable digital filter clock */
|
|
imx233_clkctrl_enable(CLK_FILT, false);
|
|
/* will also gate off the module */
|
|
BF_CLR(AUDIOOUT_CTRL, RUN);
|
|
}
|
|
|
|
/* volume in half dB
|
|
* don't check input values */
|
|
static void set_dac_vol(int vol_l, int vol_r)
|
|
{
|
|
/* minimum is -100dB and max is 0dB */
|
|
vol_l = MAX(-200, MIN(vol_l, 0));
|
|
vol_r = MAX(-200, MIN(vol_r, 0));
|
|
/* unmute, enable zero cross and set volume.
|
|
* 0xff is 0dB */
|
|
HW_AUDIOOUT_DACVOLUME = BF_OR3(AUDIOOUT_DACVOLUME,
|
|
VOLUME_LEFT(0xff + vol_l), VOLUME_RIGHT(0xff + vol_r), EN_ZCD(1));
|
|
}
|
|
|
|
/* volume in half dB
|
|
* don't check input values */
|
|
static void set_hp_vol(int vol_l, int vol_r)
|
|
{
|
|
/* minimum is -57.5dB and max is 6dB in DAC mode
|
|
* and -51.5dB / 12dB in Line1 mode */
|
|
int min = input_line1 ? -103 : -115;
|
|
int max = input_line1 ? 24 : 12;
|
|
|
|
vol_l = MAX(min, MIN(vol_l, max));
|
|
vol_r = MAX(min, MIN(vol_r, max));
|
|
/* unmute, enable zero cross and set volume.*/
|
|
#if IMX233_SUBTARGET >= 3700
|
|
unsigned mstr_zcd = BM_AUDIOOUT_HPVOL_EN_MSTR_ZCD;
|
|
#else
|
|
unsigned mstr_zcd = 0;
|
|
#endif
|
|
HW_AUDIOOUT_HPVOL = mstr_zcd | BF_OR3(AUDIOOUT_HPVOL, SELECT(input_line1),
|
|
VOL_LEFT(max - vol_l), VOL_RIGHT(max - vol_r));
|
|
}
|
|
|
|
static void apply_volume(void)
|
|
{
|
|
/* Two cases: line1 and dac */
|
|
if(input_line1)
|
|
{
|
|
/* In line1 mode, the HP is the only way to adjust the volume */
|
|
set_hp_vol(hp_vol_l, hp_vol_r);
|
|
}
|
|
else
|
|
{
|
|
/* In DAC mode we can use both the HP and the DAC volume.
|
|
* Use the DAC for volume <0 and HP for volume >0 */
|
|
set_dac_vol(MIN(0, hp_vol_l), MIN(0, hp_vol_r));
|
|
set_hp_vol(MAX(0, hp_vol_l), MAX(0, hp_vol_r));
|
|
}
|
|
}
|
|
|
|
void imx233_audioout_set_hp_vol(int vol_l, int vol_r)
|
|
{
|
|
hp_vol_l = vol_l;
|
|
hp_vol_r = vol_r;
|
|
apply_volume();
|
|
}
|
|
|
|
void imx233_audioout_set_freq(int fsel)
|
|
{
|
|
static struct
|
|
{
|
|
int base_mult;
|
|
int src_hold;
|
|
int src_int;
|
|
int src_frac;
|
|
}dacssr[HW_NUM_FREQ] =
|
|
{
|
|
HW_HAVE_8_([HW_FREQ_8] = { 0x1, 0x3, 0x17, 0xe00 } ,)
|
|
HW_HAVE_11_([HW_FREQ_11] = { 0x1, 0x3, 0x11, 0x37 } ,)
|
|
HW_HAVE_12_([HW_FREQ_12] = { 0x1, 0x3, 0xf, 0x13ff },)
|
|
HW_HAVE_16_([HW_FREQ_16] = { 0x1, 0x1, 0x17, 0xe00},)
|
|
HW_HAVE_22_([HW_FREQ_22] = { 0x1, 0x1, 0x11, 0x37 },)
|
|
HW_HAVE_24_([HW_FREQ_24] = { 0x1, 0x1, 0xf, 0x13ff },)
|
|
HW_HAVE_32_([HW_FREQ_32] = { 0x1, 0x0, 0x17, 0xe00},)
|
|
HW_HAVE_44_([HW_FREQ_44] = { 0x1, 0x0, 0x11, 0x37 },)
|
|
HW_HAVE_48_([HW_FREQ_48] = { 0x1, 0x0, 0xf, 0x13ff },)
|
|
HW_HAVE_64_([HW_FREQ_64] = { 0x2, 0x0, 0x17, 0xe00},)
|
|
HW_HAVE_88_([HW_FREQ_88] = { 0x2, 0x0, 0x11, 0x37 },)
|
|
HW_HAVE_96_([HW_FREQ_96] = { 0x2, 0x0, 0xf, 0x13ff },)
|
|
};
|
|
|
|
HW_AUDIOOUT_DACSRR = BF_OR4(AUDIOOUT_DACSRR,
|
|
SRC_FRAC(dacssr[fsel].src_frac), SRC_INT(dacssr[fsel].src_int),
|
|
SRC_HOLD(dacssr[fsel].src_hold), BASEMULT(dacssr[fsel].base_mult));
|
|
|
|
#if 0
|
|
/* Select base_mult and src_hold depending on the audio range:
|
|
* 0 < f <= 12000 --> base_mult = 1, src_hold = 3 (div by 4)
|
|
* 12000 < f <= 24000 --> base_mult = 1, src_hold = 1 (div by 2)
|
|
* 24000 < f <= 48000 --> base_mult = 1, src_hold = 0 (div by 1)
|
|
* 48000 < f <= 96000 --> base_mult = 2, src_hold = 0 (mul by 2)
|
|
* 96000 < f <= ..... --> base_mult = 4, src_hold = 0 (mul by 4) */
|
|
int base_mult = 1;
|
|
int src_hold = 0;
|
|
if(f > 96000)
|
|
base_mult = 4;
|
|
else if(f > 48000)
|
|
base_mult = 2;
|
|
else if(f <= 12000)
|
|
src_hold = 3;
|
|
else if(f <= 24000)
|
|
src_hold = 1;
|
|
/* compute the divisor (a tricky to keep to do it with 32-bit words only) */
|
|
int src_int = (750000 * base_mult) / (f * (src_hold + 1));
|
|
int src_frac = ((750000 * base_mult - src_int * f * (src_hold + 1)) << 13) / (f * (src_hold + 1));
|
|
|
|
HW_AUDIOOUT_DACSRR = BF_OR4(AUDIOOUT_DACSRR,
|
|
SRC_FRAC(src_frac), SRC_INT(src_int),
|
|
SRC_HOLD(src_hold), BASEMULT(base_mult));
|
|
#endif
|
|
}
|
|
|
|
/* select between DAC and Line1 */
|
|
void imx233_audioout_select_hp_input(bool line1)
|
|
{
|
|
input_line1 = line1;
|
|
/* reapply volume setting */
|
|
apply_volume();
|
|
}
|
|
|
|
void imx233_audioout_set_3d_effect(int val)
|
|
{
|
|
switch(val)
|
|
{
|
|
/* 0 and 1.5dB: off */
|
|
case 0: case 1: BF_WR(AUDIOOUT_CTRL, SS3D_EFFECT, 0); break;
|
|
/* 3dB: low */
|
|
case 2: BF_WR(AUDIOOUT_CTRL, SS3D_EFFECT, 1); break;
|
|
/* 4.5dB: low */
|
|
case 3: BF_WR(AUDIOOUT_CTRL, SS3D_EFFECT, 2); break;
|
|
/* 6dB: low */
|
|
case 4: BF_WR(AUDIOOUT_CTRL, SS3D_EFFECT, 3); break;
|
|
/* others: off */
|
|
default: BF_WR(AUDIOOUT_CTRL, SS3D_EFFECT, 0); break;
|
|
}
|
|
}
|
|
|
|
void imx233_audioout_enable_spkr(bool en)
|
|
{
|
|
/* avoid power sequence if not needed */
|
|
static bool spkr_en = false;
|
|
if(en == spkr_en)
|
|
return;
|
|
spkr_en = true;
|
|
#if IMX233_SUBTARGET >= 3780
|
|
if(en)
|
|
{
|
|
BF_CLR(AUDIOOUT_PWRDN, SPEAKER);
|
|
BF_CLR(AUDIOOUT_SPEAKERCTRL, MUTE);
|
|
}
|
|
else
|
|
{
|
|
BF_SET(AUDIOOUT_SPEAKERCTRL, MUTE);
|
|
BF_SET(AUDIOOUT_PWRDN, SPEAKER);
|
|
}
|
|
#elif IMX233_SUBTARGET >= 3700
|
|
/* assume speaker is wired to lineout */
|
|
if(en)
|
|
{
|
|
/** 1) make sure charge capacitors are discharged */
|
|
BF_WR(AUDIOOUT_LINEOUTCTRL, CHARGE_CAP, 2);
|
|
/** 2) set min gain, nominal vag levels and zerocross desires */
|
|
/* volume is decreasing with the value in the register */
|
|
BF_SET(AUDIOOUT_LINEOUTCTRL, VOLUME_LEFT);
|
|
BF_SET(AUDIOOUT_LINEOUTCTRL, VOLUME_RIGHT);
|
|
BF_SET(AUDIOOUT_LINEOUTCTRL, EN_LINEOUT_ZCD);
|
|
/* vag should be set to VDDIO/2, 0 is 1.725V, 15 is 1.350V, 25mV steps */
|
|
int vddio;
|
|
imx233_power_get_regulator(REGULATOR_VDDIO, &vddio, NULL);
|
|
BF_WR(AUDIOOUT_LINEOUTCTRL, VAG_CTRL, 15 - (vddio / 2 - 1350) / 25);
|
|
/** 3) Power up lineout */
|
|
BF_CLR(AUDIOOUT_PWRDN, LINEOUT);
|
|
/** 4) Ramp the vag */
|
|
BF_WR(AUDIOOUT_LINEOUTCTRL, CHARGE_CAP, 1);
|
|
/** 5) Unmute */
|
|
BF_CLR(AUDIOOUT_LINEOUTCTRL, MUTE);
|
|
/** 6) Ramp volume */
|
|
BF_WR(AUDIOOUT_LINEOUTCTRL, VOLUME_LEFT, 0);
|
|
BF_WR(AUDIOOUT_LINEOUTCTRL, VOLUME_RIGHT, 0);
|
|
}
|
|
else
|
|
{
|
|
/** Reverse procedure */
|
|
BF_SET(AUDIOOUT_LINEOUTCTRL, MUTE);
|
|
BF_WR(AUDIOOUT_LINEOUTCTRL, CHARGE_CAP, 2);
|
|
BF_SET(AUDIOOUT_PWRDN, LINEOUT);
|
|
}
|
|
#else
|
|
(void) en;
|
|
#endif
|
|
}
|
|
|
|
struct imx233_audioout_info_t imx233_audioout_get_info(void)
|
|
{
|
|
struct imx233_audioout_info_t info;
|
|
memset(&info, 0, sizeof(info));
|
|
/* 6*10^6*basemult/(src_frac*8*(src_hold+1)) in Hz */
|
|
info.freq = 60000000 * BF_RD(AUDIOOUT_DACSRR, BASEMULT) / 8 /
|
|
BF_RD(AUDIOOUT_DACSRR, SRC_FRAC) / (1 + BF_RD(AUDIOOUT_DACSRR, SRC_HOLD));
|
|
info.hpselect = BF_RD(AUDIOOUT_HPVOL, SELECT);
|
|
/* convert half-dB to tenth-dB */
|
|
info.dacvol[0] = MAX((int)BF_RD(AUDIOOUT_DACVOLUME, VOLUME_LEFT) - 0xff, -100) * 5;
|
|
info.dacvol[1] = MAX((int)BF_RD(AUDIOOUT_DACVOLUME, VOLUME_RIGHT) - 0xff, -100) * 5;
|
|
info.dacmute[0] = BF_RD(AUDIOOUT_DACVOLUME, MUTE_LEFT);
|
|
info.dacmute[1] = BF_RD(AUDIOOUT_DACVOLUME, MUTE_RIGHT);
|
|
info.hpvol[0] = (info.hpselect ? 120 : 60) - 5 * BF_RD(AUDIOOUT_HPVOL, VOL_LEFT);
|
|
info.hpvol[1] = (info.hpselect ? 120 : 60) - 5 * BF_RD(AUDIOOUT_HPVOL, VOL_RIGHT);
|
|
info.hpmute[0] = info.hpmute[1] = BF_RD(AUDIOOUT_HPVOL, MUTE);
|
|
#if IMX233_SUBTARGET >= 3780
|
|
info.spkrvol[0] = info.spkrvol[1] = 155; // volume is fixed to 15.5 dB gain
|
|
info.spkrmute[0] = info.spkrmute[1] = BF_RD(AUDIOOUT_SPEAKERCTRL, MUTE);
|
|
info.spkr = !BF_RD(AUDIOOUT_PWRDN, SPEAKER);
|
|
#elif IMX233_SUBTARGET < 3700
|
|
/* convert 2-dB to tenth-dB */
|
|
info.spkrvol[0] = MIN(info.spkrvol[1] = BF_RD(AUDIOOUT_SPKRVOL, VOL), 6) * 20;
|
|
info.spkrmute[0] = info.spkrmute[1] = BF_RD(AUDIOOUT_SPKRVOL, MUTE);
|
|
info.spkr = !BF_RD(AUDIOOUT_PWRDN, SPEAKER);
|
|
#else
|
|
/* STMP3700/3770 has not speaker amplifier */
|
|
info.spkrvol[0] = info.spkrvol[1] = 0;
|
|
info.spkrmute[0] = info.spkrmute[1] = true;
|
|
info.spkr = false;
|
|
#endif
|
|
info.ss3d = BF_RD(AUDIOOUT_CTRL, SS3D_EFFECT);
|
|
info.ss3d = info.ss3d == 0 ? 0 : 15 * (1 + info.ss3d);
|
|
info.hp = !BF_RD(AUDIOOUT_PWRDN, HEADPHONE);
|
|
info.dac = !BF_RD(AUDIOOUT_PWRDN, DAC);
|
|
info.capless = BF_RD(AUDIOOUT_PWRDN, CAPLESS);
|
|
return info;
|
|
}
|