switch (reg) { case TWL4030_REG_EAR_CTL: case TWL4030_REG_PREDL_CTL: case TWL4030_REG_PREDR_CTL: case TWL4030_REG_PRECKL_CTL: case TWL4030_REG_PRECKR_CTL: case TWL4030_REG_HS_GAIN_SET:
value = twl4030->ctl_cache[reg - TWL4030_REG_EAR_CTL]; break; default:
twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &value, reg); break;
}
/* Decide if the given register can be written */ switch (reg) { case TWL4030_REG_EAR_CTL: if (twl4030->earpiece_enabled)
write_to_reg = true; break; case TWL4030_REG_PREDL_CTL: if (twl4030->predrivel_enabled)
write_to_reg = true; break; case TWL4030_REG_PREDR_CTL: if (twl4030->predriver_enabled)
write_to_reg = true; break; case TWL4030_REG_PRECKL_CTL: if (twl4030->carkitl_enabled)
write_to_reg = true; break; case TWL4030_REG_PRECKR_CTL: if (twl4030->carkitr_enabled)
write_to_reg = true; break; case TWL4030_REG_HS_GAIN_SET: if (twl4030->hsl_enabled || twl4030->hsr_enabled)
write_to_reg = true; break; default: /* All other register can be written */
write_to_reg = true; break;
}
/* Update the ctl cache */ switch (reg) { case TWL4030_REG_EAR_CTL: case TWL4030_REG_PREDL_CTL: case TWL4030_REG_PREDR_CTL: case TWL4030_REG_PRECKL_CTL: case TWL4030_REG_PRECKR_CTL: case TWL4030_REG_HS_GAIN_SET:
twl4030->ctl_cache[reg - TWL4030_REG_EAR_CTL] = value; break; default: break;
}
if (twl4030_can_write_to_chip(twl4030, reg)) return twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, reg);
return 0;
}
staticinlinevoid twl4030_wait_ms(int time)
{ if (time < 60) {
time *= 1000;
usleep_range(time, time + 500);
} else {
msleep(time);
}
}
staticvoid twl4030_codec_enable(struct snd_soc_component *component, int enable)
{ struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); int mode;
if (enable == twl4030->codec_powered) return;
if (enable)
mode = twl4030_audio_enable_resource(TWL4030_AUDIO_RES_POWER); else
mode = twl4030_audio_disable_resource(TWL4030_AUDIO_RES_POWER);
if (mode >= 0)
twl4030->codec_powered = enable;
/* REVISIT: this delay is present in TI sample drivers */ /* but there seems to be no TRM requirement for it */
udelay(10);
}
staticvoid
twl4030_get_board_param_values(struct twl4030_board_params *board_params, struct device_node *node)
{ int value;
if (board_params && board_params->hs_extmute) {
board_params->hs_extmute_gpio = devm_gpiod_get_optional(component->dev, "ti,hs_extmute",
GPIOD_OUT_LOW); if (IS_ERR(board_params->hs_extmute_gpio)) return dev_err_probe(component->dev, PTR_ERR(board_params->hs_extmute_gpio), "Failed to get hs_extmute GPIO\n");
if (board_params->hs_extmute_gpio) {
gpiod_set_consumer_name(board_params->hs_extmute_gpio, "hs_extmute");
} else {
u8 pin_mux;
dev_info(component->dev, "use TWL4030 GPIO6\n");
/* Set TWL4030 GPIO6 as EXTMUTE signal */
twl_i2c_read_u8(TWL4030_MODULE_INTBR, &pin_mux,
TWL4030_PMBR1_REG);
pin_mux &= ~TWL4030_GPIO6_PWM0_MUTE(0x03);
pin_mux |= TWL4030_GPIO6_PWM0_MUTE(0x02);
twl_i2c_write_u8(TWL4030_MODULE_INTBR, pin_mux,
TWL4030_PMBR1_REG);
}
}
/* Initialize the local ctl register cache */
tw4030_init_ctl_cache(twl4030);
/* anti-pop when changing analog gain */
reg = twl4030_read(component, TWL4030_REG_MISC_SET_1);
twl4030_write(component, TWL4030_REG_MISC_SET_1,
reg | TWL4030_SMOOTH_ANAVOL_EN);
/* * Wait for offset cancellation to complete. * Since this takes a while, do not slam the i2c. * Start polling the status after ~20ms.
*/
msleep(20); do {
usleep_range(1000, 2000);
twl_set_regcache_bypass(TWL4030_MODULE_AUDIO_VOICE, true);
twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte,
TWL4030_REG_ANAMICL);
twl_set_regcache_bypass(TWL4030_MODULE_AUDIO_VOICE, false);
} while ((i++ < 100) &&
((byte & TWL4030_CNCL_OFFSET_START) ==
TWL4030_CNCL_OFFSET_START));
/* Analog bypass for AudioR1 */ staticconststruct snd_kcontrol_new twl4030_dapm_abypassr1_control =
SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXR1_APGA_CTL, 2, 1, 0);
/* Analog bypass for AudioL1 */ staticconststruct snd_kcontrol_new twl4030_dapm_abypassl1_control =
SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL1_APGA_CTL, 2, 1, 0);
/* Analog bypass for AudioR2 */ staticconststruct snd_kcontrol_new twl4030_dapm_abypassr2_control =
SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXR2_APGA_CTL, 2, 1, 0);
/* Analog bypass for AudioL2 */ staticconststruct snd_kcontrol_new twl4030_dapm_abypassl2_control =
SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL2_APGA_CTL, 2, 1, 0);
/* Analog bypass for Voice */ staticconststruct snd_kcontrol_new twl4030_dapm_abypassv_control =
SOC_DAPM_SINGLE("Switch", TWL4030_REG_VDL_APGA_CTL, 2, 1, 0);
/* Digital bypass gain, mute instead of -30dB */ staticconst DECLARE_TLV_DB_RANGE(twl4030_dapm_dbypass_tlv,
0, 1, TLV_DB_SCALE_ITEM(-3000, 600, 1),
2, 3, TLV_DB_SCALE_ITEM(-2400, 0, 0),
4, 7, TLV_DB_SCALE_ITEM(-1800, 600, 0)
);
/* Digital bypass left (TX1L -> RX2L) */ staticconststruct snd_kcontrol_new twl4030_dapm_dbypassl_control =
SOC_DAPM_SINGLE_TLV("Volume",
TWL4030_REG_ATX2ARXPGA, 3, 7, 0,
twl4030_dapm_dbypass_tlv);
/* Digital bypass right (TX1R -> RX2R) */ staticconststruct snd_kcontrol_new twl4030_dapm_dbypassr_control =
SOC_DAPM_SINGLE_TLV("Volume",
TWL4030_REG_ATX2ARXPGA, 0, 7, 0,
twl4030_dapm_dbypass_tlv);
/* * Voice Sidetone GAIN volume control: * from -51 to -10 dB in 1 dB steps (mute instead of -51 dB)
*/ static DECLARE_TLV_DB_SCALE(twl4030_dapm_dbypassv_tlv, -5100, 100, 1);
audio_if = twl4030_read(component, TWL4030_REG_AUDIO_IF); switch (event) { case SND_SOC_DAPM_PRE_PMU: /* Enable AIF */ /* enable the PLL before we use it to clock the DAI */
twl4030_apll_enable(component, 1);
twl4030_write(component, TWL4030_REG_AUDIO_IF,
audio_if | TWL4030_AIF_EN); break; case SND_SOC_DAPM_POST_PMD: /* disable the DAI before we stop it's source PLL */
twl4030_write(component, TWL4030_REG_AUDIO_IF,
audio_if & ~TWL4030_AIF_EN);
twl4030_apll_enable(component, 0); break;
} return 0;
}
/* Enable external mute control, this dramatically reduces
* the pop-noise */ if (board_params && board_params->hs_extmute) { if (board_params->hs_extmute_gpio) {
gpiod_set_value(board_params->hs_extmute_gpio, 1);
} else {
hs_pop |= TWL4030_EXTMUTE;
twl4030_write(component, TWL4030_REG_HS_POPN_SET, hs_pop);
}
}
if (ramp) { /* Headset ramp-up according to the TRM */
hs_pop |= TWL4030_VMID_EN;
twl4030_write(component, TWL4030_REG_HS_POPN_SET, hs_pop); /* Actually write to the register */
twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, hs_gain,
TWL4030_REG_HS_GAIN_SET);
hs_pop |= TWL4030_RAMP_EN;
twl4030_write(component, TWL4030_REG_HS_POPN_SET, hs_pop); /* Wait ramp delay time + 1, so the VMID can settle */
twl4030_wait_ms(delay);
} else { /* Headset ramp-down _not_ according to
* the TRM, but in a way that it is working */
hs_pop &= ~TWL4030_RAMP_EN;
twl4030_write(component, TWL4030_REG_HS_POPN_SET, hs_pop); /* Wait ramp delay time + 1, so the VMID can settle */
twl4030_wait_ms(delay); /* Bypass the reg_cache to mute the headset */
twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, hs_gain & (~0x0f),
TWL4030_REG_HS_GAIN_SET);
switch (event) { case SND_SOC_DAPM_POST_PMU: /* Do the ramp-up only once */ if (!twl4030->hsr_enabled)
headset_ramp(component, 1);
twl4030->hsl_enabled = 1; break; case SND_SOC_DAPM_POST_PMD: /* Do the ramp-down only if both headsetL/R is disabled */ if (!twl4030->hsr_enabled)
headset_ramp(component, 0);
switch (event) { case SND_SOC_DAPM_POST_PMU: /* Do the ramp-up only once */ if (!twl4030->hsl_enabled)
headset_ramp(component, 1);
twl4030->hsr_enabled = 1; break; case SND_SOC_DAPM_POST_PMD: /* Do the ramp-down only if both headsetL/R is disabled */ if (!twl4030->hsl_enabled)
headset_ramp(component, 0);
if (board_params && board_params->digimic_delay)
twl4030_wait_ms(board_params->digimic_delay); return 0;
}
/* * Some of the gain controls in TWL (mostly those which are associated with * the outputs) are implemented in an interesting way: * 0x0 : Power down (mute) * 0x1 : 6dB * 0x2 : 0 dB * 0x3 : -6 dB * Inverting not going to help with these. * Custom volsw and volsw_2r get/put functions to handle these gain bits.
*/ staticint snd_soc_get_volsw_twl4030(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{ struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); unsignedint reg = mc->reg; unsignedint shift = mc->shift; unsignedint rshift = mc->rshift; int max = mc->max; int mask = (1 << fls(max)) - 1;
ucontrol->value.integer.value[0] =
(twl4030_read(component, reg) >> shift) & mask; if (ucontrol->value.integer.value[0])
ucontrol->value.integer.value[0] =
max + 1 - ucontrol->value.integer.value[0];
if (shift != rshift) {
ucontrol->value.integer.value[1] =
(twl4030_read(component, reg) >> rshift) & mask; if (ucontrol->value.integer.value[1])
ucontrol->value.integer.value[1] =
max + 1 - ucontrol->value.integer.value[1];
}
if (ucontrol->value.integer.value[0])
ucontrol->value.integer.value[0] =
max + 1 - ucontrol->value.integer.value[0]; if (ucontrol->value.integer.value[1])
ucontrol->value.integer.value[1] =
max + 1 - ucontrol->value.integer.value[1];
/* * FGAIN volume control: * from -62 to 0 dB in 1 dB steps (mute instead of -63 dB)
*/ static DECLARE_TLV_DB_SCALE(digital_fine_tlv, -6300, 100, 1);
/* * CGAIN volume control: * 0 dB to 12 dB in 6 dB steps * value 2 and 3 means 12 dB
*/ static DECLARE_TLV_DB_SCALE(digital_coarse_tlv, 0, 600, 0);
/* * Voice Downlink GAIN volume control: * from -37 to 12 dB in 1 dB steps (mute instead of -37 dB)
*/ static DECLARE_TLV_DB_SCALE(digital_voice_downlink_tlv, -3700, 100, 1);
/* * Analog playback gain * -24 dB to 12 dB in 2 dB steps
*/ static DECLARE_TLV_DB_SCALE(analog_tlv, -2400, 200, 0);
/* * Gain controls tied to outputs * -6 dB to 6 dB in 6 dB steps (mute instead of -12)
*/ static DECLARE_TLV_DB_SCALE(output_tvl, -1200, 600, 1);
/* * Gain control for earpiece amplifier * 0 dB to 12 dB in 6 dB steps (mute instead of -6)
*/ static DECLARE_TLV_DB_SCALE(output_ear_tvl, -600, 600, 1);
/* * Capture gain after the ADCs * from 0 dB to 31 dB in 1 dB steps
*/ static DECLARE_TLV_DB_SCALE(digital_capture_tlv, 0, 100, 0);
/* * Gain control for input amplifiers * 0 dB to 30 dB in 6 dB steps
*/ static DECLARE_TLV_DB_SCALE(input_gain_tlv, 0, 600, 0);
/* AVADC clock priority */ staticconstchar *twl4030_avadc_clk_priority_texts[] = { "Voice high priority", "HiFi high priority"
};
/* Common playback gain controls */
SOC_DOUBLE_R_TLV("DAC1 Digital Fine Playback Volume",
TWL4030_REG_ARXL1PGA, TWL4030_REG_ARXR1PGA,
0, 0x3f, 0, digital_fine_tlv),
SOC_DOUBLE_R_TLV("DAC2 Digital Fine Playback Volume",
TWL4030_REG_ARXL2PGA, TWL4030_REG_ARXR2PGA,
0, 0x3f, 0, digital_fine_tlv),
/* Analog/Digital mic path selection. TX1 Left/Right: either analog Left/Right or Digimic0
TX2 Left/Right: either analog Left/Right or Digimic1 */
SND_SOC_DAPM_MUX("TX1 Capture Route", SND_SOC_NOPM, 0, 0,
&twl4030_dapm_micpathtx1_control),
SND_SOC_DAPM_MUX("TX2 Capture Route", SND_SOC_NOPM, 0, 0,
&twl4030_dapm_micpathtx2_control),
/* Analog input mixers for the capture amplifiers */
SND_SOC_DAPM_MIXER("Analog Left",
TWL4030_REG_ANAMICL, 4, 0,
&twl4030_dapm_analoglmic_controls[0],
ARRAY_SIZE(twl4030_dapm_analoglmic_controls)),
SND_SOC_DAPM_MIXER("Analog Right",
TWL4030_REG_ANAMICR, 4, 0,
&twl4030_dapm_analogrmic_controls[0],
ARRAY_SIZE(twl4030_dapm_analogrmic_controls)),
/* Pick the stream, which need to be constrained */ if (mst_substream == twl4030->master_substream)
slv_substream = twl4030->slave_substream; elseif (mst_substream == twl4030->slave_substream)
slv_substream = twl4030->master_substream; else/* This should not happen.. */ return;
/* Set the constraints according to the already configured stream */
snd_pcm_hw_constraint_single(slv_substream->runtime,
SNDRV_PCM_HW_PARAM_RATE,
twl4030->rate);
/* In case of 4 channel mode, the RX1 L/R for playback and the TX2 L/R for
* capture has to be enabled/disabled. */ staticvoid twl4030_tdm_enable(struct snd_soc_component *component, int direction, int enable)
{
u8 reg, mask;
if (twl4030->master_substream) {
twl4030->slave_substream = substream; /* The DAI has one configuration for playback and capture, so * if the DAI has been already configured then constrain this
* substream to match it. */ if (twl4030->configured)
twl4030_constraints(twl4030, twl4030->master_substream);
} else { if (!(twl4030_read(component, TWL4030_REG_CODEC_MODE) &
TWL4030_OPTION_1)) { /* In option2 4 channel is not supported, set the * constraint for the first stream for channels, the
* second stream will 'inherit' this cosntraint */
snd_pcm_hw_constraint_single(substream->runtime,
SNDRV_PCM_HW_PARAM_CHANNELS,
2);
}
twl4030->master_substream = substream;
}
if (twl4030->master_substream == substream)
twl4030->master_substream = twl4030->slave_substream;
twl4030->slave_substream = NULL;
/* If all streams are closed, or the remaining stream has not yet
* been configured than set the DAI as not configured. */ if (!twl4030->master_substream)
twl4030->configured = 0; elseif (!twl4030->master_substream->runtime->channels)
twl4030->configured = 0;
/* If the closing substream had 4 channel, do the necessary cleanup */ if (substream->runtime->channels == 4)
twl4030_tdm_enable(component, substream->stream, 0);
}
/* If the substream has 4 channel, do the necessary setup */ if (params_channels(params) == 4) {
format = twl4030_read(component, TWL4030_REG_AUDIO_IF);
mode = twl4030_read(component, TWL4030_REG_CODEC_MODE);
/* Safety check: are we in the correct operating mode and
* the interface is in TDM mode? */ if ((mode & TWL4030_OPTION_1) &&
((format & TWL4030_AIF_FORMAT) == TWL4030_AIF_FORMAT_TDM))
twl4030_tdm_enable(component, substream->stream, 1); else return -EINVAL;
}
if (twl4030->configured) /* Ignoring hw_params for already configured DAI */ return 0;
switch (params_rate(params)) { case 8000:
mode |= TWL4030_APLL_RATE_8000; break; case 11025:
mode |= TWL4030_APLL_RATE_11025; break; case 12000:
mode |= TWL4030_APLL_RATE_12000; break; case 16000:
mode |= TWL4030_APLL_RATE_16000; break; case 22050:
mode |= TWL4030_APLL_RATE_22050; break; case 24000:
mode |= TWL4030_APLL_RATE_24000; break; case 32000:
mode |= TWL4030_APLL_RATE_32000; break; case 44100:
mode |= TWL4030_APLL_RATE_44100; break; case 48000:
mode |= TWL4030_APLL_RATE_48000; break; case 96000:
mode |= TWL4030_APLL_RATE_96000; break; default:
dev_err(component->dev, "%s: unknown rate %d\n", __func__,
params_rate(params)); return -EINVAL;
}
/* sample size */
old_format = twl4030_read(component, TWL4030_REG_AUDIO_IF);
format = old_format;
format &= ~TWL4030_DATA_WIDTH; switch (params_width(params)) { case 16:
format |= TWL4030_DATA_WIDTH_16S_16W; break; case 32:
format |= TWL4030_DATA_WIDTH_32S_24W; break; default:
dev_err(component->dev, "%s: unsupported bits/sample %d\n",
__func__, params_width(params)); return -EINVAL;
}
if (format != old_format || mode != old_mode) { if (twl4030->codec_powered) { /* * If the codec is powered, than we need to toggle the * codec power.
*/
twl4030_codec_enable(component, 0);
twl4030_write(component, TWL4030_REG_CODEC_MODE, mode);
twl4030_write(component, TWL4030_REG_AUDIO_IF, format);
twl4030_codec_enable(component, 1);
} else {
twl4030_write(component, TWL4030_REG_CODEC_MODE, mode);
twl4030_write(component, TWL4030_REG_AUDIO_IF, format);
}
}
/* Store the important parameters for the DAI configuration and set
* the DAI as configured */
twl4030->configured = 1;
twl4030->rate = params_rate(params);
twl4030->sample_bits = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
twl4030->channels = params_channels(params);
/* If both playback and capture streams are open, and one of them * is setting the hw parameters right now (since we are here), set
* constraints to the other stream to match the current one. */ if (twl4030->slave_substream)
twl4030_constraints(twl4030, substream);
/* get format */
old_format = twl4030_read(component, TWL4030_REG_AUDIO_IF);
format = old_format;
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { case SND_SOC_DAIFMT_CBP_CFP:
format &= ~(TWL4030_AIF_SLAVE_EN);
format &= ~(TWL4030_CLK256FS_EN); break; case SND_SOC_DAIFMT_CBC_CFC:
format |= TWL4030_AIF_SLAVE_EN;
format |= TWL4030_CLK256FS_EN; break; default: return -EINVAL;
}
/* interface format */
format &= ~TWL4030_AIF_FORMAT; switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S:
format |= TWL4030_AIF_FORMAT_CODEC; break; case SND_SOC_DAIFMT_DSP_A:
format |= TWL4030_AIF_FORMAT_TDM; break; default: return -EINVAL;
}
if (format != old_format) { if (twl4030->codec_powered) { /* * If the codec is powered, than we need to toggle the * codec power.
*/
twl4030_codec_enable(component, 0);
twl4030_write(component, TWL4030_REG_AUDIO_IF, format);
twl4030_codec_enable(component, 1);
} else {
twl4030_write(component, TWL4030_REG_AUDIO_IF, format);
}
}
/* In case of voice mode, the RX1 L(VRX) for downlink and the TX2 L/R
* (VTXL, VTXR) for uplink has to be enabled/disabled. */ staticvoid twl4030_voice_enable(struct snd_soc_component *component, int direction, int enable)
{
u8 reg, mask;
/* If the system master clock is not 26MHz, the voice PCM interface is * not available.
*/ if (twl4030->sysclk != 26000) {
dev_err(component->dev, "%s: HFCLKIN is %u KHz, voice interface needs 26MHz\n",
__func__, twl4030->sysclk); return -EINVAL;
}
/* If the codec mode is not option2, the voice PCM interface is not * available.
*/
mode = twl4030_read(component, TWL4030_REG_CODEC_MODE)
& TWL4030_OPT_MODE;
if (mode != TWL4030_OPTION_2) {
dev_err(component->dev, "%s: the codec mode is not option2\n",
__func__); return -EINVAL;
}
if (mode != old_mode) { if (twl4030->codec_powered) { /* * If the codec is powered, than we need to toggle the * codec power.
*/
twl4030_codec_enable(component, 0);
twl4030_write(component, TWL4030_REG_CODEC_MODE, mode);
twl4030_codec_enable(component, 1);
} else {
twl4030_write(component, TWL4030_REG_CODEC_MODE, mode);
}
}
/* get format */
old_format = twl4030_read(component, TWL4030_REG_VOICE_IF);
format = old_format;
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { case SND_SOC_DAIFMT_CBP_CFP:
format &= ~(TWL4030_VIF_SLAVE_EN); break; case SND_SOC_DAIFMT_CBC_CFC:
format |= TWL4030_VIF_SLAVE_EN; break; default: return -EINVAL;
}
/* clock inversion */ switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_IB_NF:
format &= ~(TWL4030_VIF_FORMAT); break; case SND_SOC_DAIFMT_NB_IF:
format |= TWL4030_VIF_FORMAT; break; default: return -EINVAL;
}
if (format != old_format) { if (twl4030->codec_powered) { /* * If the codec is powered, than we need to toggle the * codec power.
*/
twl4030_codec_enable(component, 0);
twl4030_write(component, TWL4030_REG_VOICE_IF, format);
twl4030_codec_enable(component, 1);
} else {
twl4030_write(component, TWL4030_REG_VOICE_IF, format);
}
}
return 0;
}
staticint twl4030_voice_set_tristate(struct snd_soc_dai *dai, int tristate)
{
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5
¤ Dauer der Verarbeitung: 0.25 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.