rockbox/firmware/target/arm/imx233/audioout-imx233.c
Sebastian Leonhardt 9aafbf9ca9 STMP37xx/iMX233: reduce popping noise on power on/off (Version 1)
Version 1: this patch prolongs startup time by 2 seconds, because the
sleep happens early before other threads have started.

The patch is tested on CreativeZEN and Fuze+.

The datasheet was not very helpfull, so some experimentation was needed.
I came to the following conclusions:
* setting HP to ground:
  to prevent popping noises, the headphone output can be set to ground.
  This however must be done before any part of the audioout module is
  powered up (setting HP to ground itself will lead to a pop otherwise).
  This consequently means that HP must NOT be set to ground for powerdown
  sequence!
  Further study showed that setting HP out to ground has no audible benefit,
  controversly not setting/resetting allows for noiseless RoLo-ing.
* headphone amp class A/AB mode:
  initially the HP amp is in class A mode, and should be set to
  class AB before playing audio, as the datasheet mentions.
  If the HP output is set to ground, it must be released BEFORE
  setting class AB! Releasing from ground while in AB mode leads
  to a very loud pop!
* release HP from ground:
  as said before: never release the HP from ground if the HP amp is
  set to class AB mode. Therefore the correct order is to power up the
  headphone amp, wait some time, release HP from ground, and then
  set the amp to class AB mode.
  To prevent pop, some time is needed before releasing the HP from gnd.
  On CreativeZEN 2 sec seems to be ideal; 1 sec have no audible effect,
  1.5 sec softenes the pop to some degree.
* shutting player off
  The popping noise when shutting off is much quieter that on power up,
  so depopping measures are not absolutely necessary.
  However the power off pop can be silenced by inserting a wait time
  after the audioout block is closed and before the rest of the chip
  is powered down. The longer the better, a time of 5 sec practically
  eliminates the pop.

Note that RoLo-ing can still produce noise, because the audio device
is not properly shut down.

Change-Id: Ib20e1d613b346433d2a711c442e303ededc26e78
2025-11-12 16:07:23 -05:00

372 lines
13 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"
#include "regs/audioout.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)
{
/* we forego setting HP output to ground; it seems it doesn't have an
audible benefit, but produces loud noise if done at the wrong place */
/* when RoLo-ing the headphone is still unmuted; mute to reduce noise */
BF_SET(AUDIOOUT_HPVOL, MUTE);
/* 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);
/* power up HP */
BF_CLR(AUDIOOUT_PWRDN, HEADPHONE);
/* wait until voltages have settled after powering on HP amp.
* The time delay may be target dependant; on CreativeZEN (STMP3760)
* 1.5 sec still isn't enough (only softening the pop) */
sleep(HZ*2);
/* Set HP mode to class AB. This must be done after HP is released from
* GND; releasing while AB mode is activated leads to a REALLY loud pop! */
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);
/* 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 */
BF_CLR(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);
/* Mute HP and power down */
BF_SET(AUDIOOUT_HPVOL, MUTE);
/* Power down HP */
BF_SET(AUDIOOUT_PWRDN, HEADPHONE);
/* Mute DAC */
BF_SET(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);
/* power-off-pop is reduced when waiting after shutting off
* the audioout module and before powering down the whole chip. */
sleep(HZ*2);
}
/* 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 */
BF_WR_ALL(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_OR(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 const 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_HAVE_128_([HW_FREQ_128] = { 0x4, 0x0, 0x17, 0xe00 },)
HW_HAVE_176_([HW_FREQ_176] = { 0x4, 0x0, 0x11, 0x37 },)
HW_HAVE_192_([HW_FREQ_192] = { 0x4, 0x0, 0xf, 0x13ff },)
};
BF_WR_ALL(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 = en;
#if IMX233_SUBTARGET >= 3780
if(en)
{
BF_CLR(AUDIOOUT_PWRDN, SPEAKER);
BF_CLR(AUDIOOUT_SPEAKERCTRL, MUTE);
}
else
{
BF_SET(AUDIOOUT_SPEAKERCTRL, MUTE);
/* despite what the manual says, we can perfectly set and clear this bit
* at will, no need for a reset */
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));
/* despite what the manual says, we can perfectly set and clear this bit
* at will, no need for a reset */
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, assume it is on lineout */
info.spkrvol[0] = info.spkrvol[1] = 0;
info.spkrmute[0] = info.spkrmute[1] = BF_RD(AUDIOOUT_LINEOUTCTRL, MUTE);
info.spkr = !BF_RD(AUDIOOUT_PWRDN, LINEOUT);
#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;
}