staticbool wm8904_volatile_register(struct device *dev, unsignedint reg)
{ switch (reg) { case WM8904_SW_RESET_AND_ID: case WM8904_REVISION: case WM8904_DC_SERVO_1: case WM8904_DC_SERVO_6: case WM8904_DC_SERVO_7: case WM8904_DC_SERVO_8: case WM8904_DC_SERVO_9: case WM8904_DC_SERVO_READBACK_0: case WM8904_INTERRUPT_STATUS: returntrue; default: returnfalse;
}
}
staticbool wm8904_readable_register(struct device *dev, unsignedint reg)
{ switch (reg) { case WM8904_SW_RESET_AND_ID: case WM8904_REVISION: case WM8904_BIAS_CONTROL_0: case WM8904_VMID_CONTROL_0: case WM8904_MIC_BIAS_CONTROL_0: case WM8904_MIC_BIAS_CONTROL_1: case WM8904_ANALOGUE_DAC_0: case WM8904_MIC_FILTER_CONTROL: case WM8904_ANALOGUE_ADC_0: case WM8904_POWER_MANAGEMENT_0: case WM8904_POWER_MANAGEMENT_2: case WM8904_POWER_MANAGEMENT_3: case WM8904_POWER_MANAGEMENT_6: case WM8904_CLOCK_RATES_0: case WM8904_CLOCK_RATES_1: case WM8904_CLOCK_RATES_2: case WM8904_AUDIO_INTERFACE_0: case WM8904_AUDIO_INTERFACE_1: case WM8904_AUDIO_INTERFACE_2: case WM8904_AUDIO_INTERFACE_3: case WM8904_DAC_DIGITAL_VOLUME_LEFT: case WM8904_DAC_DIGITAL_VOLUME_RIGHT: case WM8904_DAC_DIGITAL_0: case WM8904_DAC_DIGITAL_1: case WM8904_ADC_DIGITAL_VOLUME_LEFT: case WM8904_ADC_DIGITAL_VOLUME_RIGHT: case WM8904_ADC_DIGITAL_0: case WM8904_DIGITAL_MICROPHONE_0: case WM8904_DRC_0: case WM8904_DRC_1: case WM8904_DRC_2: case WM8904_DRC_3: case WM8904_ANALOGUE_LEFT_INPUT_0: case WM8904_ANALOGUE_RIGHT_INPUT_0: case WM8904_ANALOGUE_LEFT_INPUT_1: case WM8904_ANALOGUE_RIGHT_INPUT_1: case WM8904_ANALOGUE_OUT1_LEFT: case WM8904_ANALOGUE_OUT1_RIGHT: case WM8904_ANALOGUE_OUT2_LEFT: case WM8904_ANALOGUE_OUT2_RIGHT: case WM8904_ANALOGUE_OUT12_ZC: case WM8904_DC_SERVO_0: case WM8904_DC_SERVO_1: case WM8904_DC_SERVO_2: case WM8904_DC_SERVO_4: case WM8904_DC_SERVO_5: case WM8904_DC_SERVO_6: case WM8904_DC_SERVO_7: case WM8904_DC_SERVO_8: case WM8904_DC_SERVO_9: case WM8904_DC_SERVO_READBACK_0: case WM8904_ANALOGUE_HP_0: case WM8904_ANALOGUE_LINEOUT_0: case WM8904_CHARGE_PUMP_0: case WM8904_CLASS_W_0: case WM8904_WRITE_SEQUENCER_0: case WM8904_WRITE_SEQUENCER_1: case WM8904_WRITE_SEQUENCER_2: case WM8904_WRITE_SEQUENCER_3: case WM8904_WRITE_SEQUENCER_4: case WM8904_FLL_CONTROL_1: case WM8904_FLL_CONTROL_2: case WM8904_FLL_CONTROL_3: case WM8904_FLL_CONTROL_4: case WM8904_FLL_CONTROL_5: case WM8904_GPIO_CONTROL_1: case WM8904_GPIO_CONTROL_2: case WM8904_GPIO_CONTROL_3: case WM8904_GPIO_CONTROL_4: case WM8904_DIGITAL_PULLS: case WM8904_INTERRUPT_STATUS: case WM8904_INTERRUPT_STATUS_MASK: case WM8904_INTERRUPT_POLARITY: case WM8904_INTERRUPT_DEBOUNCE: case WM8904_EQ1: case WM8904_EQ2: case WM8904_EQ3: case WM8904_EQ4: case WM8904_EQ5: case WM8904_EQ6: case WM8904_EQ7: case WM8904_EQ8: case WM8904_EQ9: case WM8904_EQ10: case WM8904_EQ11: case WM8904_EQ12: case WM8904_EQ13: case WM8904_EQ14: case WM8904_EQ15: case WM8904_EQ16: case WM8904_EQ17: case WM8904_EQ18: case WM8904_EQ19: case WM8904_EQ20: case WM8904_EQ21: case WM8904_EQ22: case WM8904_EQ23: case WM8904_EQ24: case WM8904_CONTROL_INTERFACE_TEST_1: case WM8904_ADC_TEST_0: case WM8904_ANALOGUE_OUTPUT_BIAS_0: case WM8904_FLL_NCO_TEST_0: case WM8904_FLL_NCO_TEST_1: returntrue; default: returnfalse;
}
}
/* Gate the clock while we're updating to avoid misclocking */
clock2 = snd_soc_component_read(component, WM8904_CLOCK_RATES_2);
snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2,
WM8904_SYSCLK_SRC, 0);
/* This should be done on init() for bypass paths */ switch (wm8904->sysclk_src) { case WM8904_CLK_MCLK:
dev_dbg(component->dev, "Using %dHz MCLK\n", wm8904->mclk_rate);
if (!pdata || !wm8904->num_retune_mobile_texts) return;
/* Find the version of the currently selected configuration
* with the nearest sample rate. */
cfg = wm8904->retune_mobile_cfg;
best = 0;
best_val = INT_MAX; for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) { if (strcmp(pdata->retune_mobile_cfgs[i].name,
wm8904->retune_mobile_texts[cfg]) == 0 &&
abs(pdata->retune_mobile_cfgs[i].rate
- wm8904->fs) < best_val) {
best = i;
best_val = abs(pdata->retune_mobile_cfgs[i].rate
- wm8904->fs);
}
}
dev_dbg(component->dev, "ReTune Mobile %s/%dHz for %dHz sample rate\n",
pdata->retune_mobile_cfgs[best].name,
pdata->retune_mobile_cfgs[best].rate,
wm8904->fs);
/* The EQ will be disabled while reconfiguring it, remember the * current configuration.
*/
save = snd_soc_component_read(component, WM8904_EQ1);
for (i = 0; i < WM8904_EQ_REGS; i++)
snd_soc_component_update_bits(component, WM8904_EQ1 + i, 0xffff,
pdata->retune_mobile_cfgs[best].regs[i]);
staticint wm8904_set_deemph(struct snd_soc_component *component)
{ struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); int val, i, best;
/* If we're using deemphasis select the nearest available sample * rate.
*/ if (wm8904->deemph) {
best = 1; for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { if (abs(deemph_settings[i] - wm8904->fs) <
abs(deemph_settings[best] - wm8904->fs))
best = i;
}
val = best << WM8904_DEEMPH_SHIFT;
} else {
val = 0;
}
switch (event) { case SND_SOC_DAPM_PRE_PMU: /* If we're using the FLL then we only start it when * required; we assume that the configuration has been * done previously and all we need to do is kick it * off.
*/ switch (wm8904->sysclk_src) { case WM8904_CLK_FLL:
snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1,
WM8904_FLL_OSC_ENA,
WM8904_FLL_OSC_ENA);
case SND_SOC_DAPM_POST_PMD:
snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1,
WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0); break;
}
return 0;
}
staticint out_pga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event)
{ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); int reg, val; int dcs_mask; int dcs_l, dcs_r; int dcs_l_reg, dcs_r_reg; int an_out_reg; int timeout; int pwr_reg;
/* This code is shared between HP and LINEOUT; we do all our * power management in stereo pairs to avoid latency issues so * we reuse shift to identify which rather than strcmp() the
* name. */
reg = w->shift;
switch (event) { case SND_SOC_DAPM_PRE_PMU: /* Power on the PGAs */
snd_soc_component_update_bits(component, pwr_reg,
WM8904_HPL_PGA_ENA | WM8904_HPR_PGA_ENA,
WM8904_HPL_PGA_ENA | WM8904_HPR_PGA_ENA);
/* Power on the amplifier */
snd_soc_component_update_bits(component, reg,
WM8904_HPL_ENA | WM8904_HPR_ENA,
WM8904_HPL_ENA | WM8904_HPR_ENA);
/* Enable the first stage */
snd_soc_component_update_bits(component, reg,
WM8904_HPL_ENA_DLY | WM8904_HPR_ENA_DLY,
WM8904_HPL_ENA_DLY | WM8904_HPR_ENA_DLY);
/* Power up the DC servo */
snd_soc_component_update_bits(component, WM8904_DC_SERVO_0,
dcs_mask, dcs_mask);
/* Either calibrate the DC servo or restore cached state * if we have that.
*/ if (wm8904->dcs_state[dcs_l] || wm8904->dcs_state[dcs_r]) {
dev_dbg(component->dev, "Restoring DC servo state\n");
/* Wait for DC servo to complete */
dcs_mask <<= WM8904_DCS_CAL_COMPLETE_SHIFT; do {
val = snd_soc_component_read(component, WM8904_DC_SERVO_READBACK_0); if ((val & dcs_mask) == dcs_mask) break;
/* Update volume, requires PGA to be powered */
val = snd_soc_component_read(component, an_out_reg);
snd_soc_component_write(component, an_out_reg, val); break;
case SND_SOC_DAPM_POST_PMU: /* Unshort the output itself */
snd_soc_component_update_bits(component, reg,
WM8904_HPL_RMV_SHORT |
WM8904_HPR_RMV_SHORT,
WM8904_HPL_RMV_SHORT |
WM8904_HPR_RMV_SHORT);
break;
case SND_SOC_DAPM_PRE_PMD: /* Short the output */
snd_soc_component_update_bits(component, reg,
WM8904_HPL_RMV_SHORT |
WM8904_HPR_RMV_SHORT, 0); break;
case SND_SOC_DAPM_POST_PMD: /* Cache the DC servo configuration; this will be
* invalidated if we change the configuration. */
wm8904->dcs_state[dcs_l] = snd_soc_component_read(component, dcs_l_reg);
wm8904->dcs_state[dcs_r] = snd_soc_component_read(component, dcs_r_reg);
staticint wm8904_set_tdm_slot(struct snd_soc_dai *dai, unsignedint tx_mask, unsignedint rx_mask, int slots, int slot_width)
{ struct snd_soc_component *component = dai->component; struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); int aif1 = 0;
/* Don't need to validate anything if we're turning off TDM */ if (slots == 0) goto out;
/* Note that we allow configurations we can't handle ourselves - * for example, we can generate clocks for slots 2 and up even if * we can't use those slots ourselves.
*/
aif1 |= WM8904_AIFADC_TDM | WM8904_AIFDAC_TDM;
switch (rx_mask) { case 3: break; case 0xc:
aif1 |= WM8904_AIFADC_TDM_CHAN; break; default: return -EINVAL;
}
switch (tx_mask) { case 3: break; case 0xc:
aif1 |= WM8904_AIFDAC_TDM_CHAN; break; default: return -EINVAL;
}
/* Fref must be <=13.5MHz */
div = 1;
fll_div->fll_clk_ref_div = 0; while ((Fref / div) > 13500000) {
div *= 2;
fll_div->fll_clk_ref_div++;
if (div > 8) {
pr_err("Can't scale %dMHz input down to <=13.5MHz\n",
Fref); return -EINVAL;
}
}
pr_debug("Fref=%u Fout=%u\n", Fref, Fout);
/* Apply the division for our remaining calculations */
Fref /= div;
/* Fvco should be 90-100MHz; don't check the upper bound */
div = 4; while (Fout * div < 90000000) {
div++; if (div > 64) {
pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n",
Fout); return -EINVAL;
}
}
target = Fout * div;
fll_div->fll_outdiv = div - 1;
pr_debug("Fvco=%dHz\n", target);
/* Find an appropriate FLL_FRATIO and factor it out of the target */ for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) { if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) {
fll_div->fll_fratio = fll_fratios[i].fll_fratio;
target /= fll_fratios[i].ratio; break;
}
} if (i == ARRAY_SIZE(fll_fratios)) {
pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref); return -EINVAL;
}
/* Validate the FLL ID */ switch (source) { case WM8904_FLL_MCLK: case WM8904_FLL_LRCLK: case WM8904_FLL_BCLK:
ret = fll_factors(&fll_div, Fref, Fout); if (ret != 0) return ret; break;
case WM8904_FLL_FREE_RUNNING:
dev_dbg(component->dev, "Using free running FLL\n"); /* Force 12MHz and output/4 for now */
Fout = 12000000;
Fref = 12000000;
default:
dev_err(component->dev, "Unknown FLL ID %d\n", fll_id); return -EINVAL;
}
/* Save current state then disable the FLL and SYSCLK to avoid
* misclocking */
fll1 = snd_soc_component_read(component, WM8904_FLL_CONTROL_1);
snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2,
WM8904_CLK_SYS_ENA, 0);
snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1,
WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
/* Unlock forced oscilator control to switch it on/off */
snd_soc_component_update_bits(component, WM8904_CONTROL_INTERFACE_TEST_1,
WM8904_USER_KEY, WM8904_USER_KEY);
if (fll_id == WM8904_FLL_FREE_RUNNING) {
val = WM8904_FLL_FRC_NCO;
} else {
val = 0;
}
/* Enable the FLL if it was previously active */
snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1,
WM8904_FLL_OSC_ENA, fll1);
snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1,
WM8904_FLL_ENA, fll1);
out: /* Reenable SYSCLK if it was previously active */
snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2,
WM8904_CLK_SYS_ENA, clock2);
return 0;
}
staticint wm8904_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsignedint freq, int dir)
{ struct snd_soc_component *component = dai->component; struct wm8904_priv *priv = snd_soc_component_get_drvdata(component); unsignedlong mclk_freq; int ret;
switch (clk_id) { case WM8904_CLK_AUTO: /* We don't have any rate constraints, so just ignore the * request to disable constraining.
*/ if (!freq) return 0;
mclk_freq = clk_get_rate(priv->mclk); /* enable FLL if a different sysclk is desired */ if (mclk_freq != freq) {
priv->sysclk_src = WM8904_CLK_FLL;
ret = wm8904_set_fll(dai, WM8904_FLL_MCLK,
WM8904_FLL_MCLK,
mclk_freq, freq); if (ret) return ret; break;
}
clk_id = WM8904_CLK_MCLK;
fallthrough;
case WM8904_CLK_MCLK:
priv->sysclk_src = clk_id;
priv->mclk_rate = freq; break;
case WM8904_CLK_FLL:
priv->sysclk_src = clk_id; break;
default: return -EINVAL;
}
dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
wm8904_configure_clocking(component);
return 0;
}
staticint wm8904_mute(struct snd_soc_dai *codec_dai, int mute, int direction)
{ struct snd_soc_component *component = codec_dai->component; int val;
staticvoid wm8904_handle_retune_mobile_pdata(struct snd_soc_component *component)
{ struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); struct wm8904_pdata *pdata = wm8904->pdata; struct snd_kcontrol_new control =
SOC_ENUM_EXT("EQ Mode",
wm8904->retune_mobile_enum,
wm8904_get_retune_mobile_enum,
wm8904_put_retune_mobile_enum); int ret, i, j; constchar **t;
/* We need an array of texts for the enum API but the number * of texts is likely to be less than the number of * configurations due to the sample rate dependency of the
* configurations. */
wm8904->num_retune_mobile_texts = 0;
wm8904->retune_mobile_texts = NULL; for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) { for (j = 0; j < wm8904->num_retune_mobile_texts; j++) { if (strcmp(pdata->retune_mobile_cfgs[i].name,
wm8904->retune_mobile_texts[j]) == 0) break;
}
if (j != wm8904->num_retune_mobile_texts) continue;
/* Expand the array... */
t = krealloc(wm8904->retune_mobile_texts, sizeof(char *) *
(wm8904->num_retune_mobile_texts + 1),
GFP_KERNEL); if (t == NULL) continue;
/* ...store the new entry... */
t[wm8904->num_retune_mobile_texts] =
pdata->retune_mobile_cfgs[i].name;
/* ...and remember the new version. */
wm8904->num_retune_mobile_texts++;
wm8904->retune_mobile_texts = t;
}
dev_dbg(component->dev, "Allocated %d unique ReTune Mobile names\n",
wm8904->num_retune_mobile_texts);
ret = snd_soc_add_component_controls(component, &control, 1); if (ret != 0)
dev_err(component->dev, "Failed to add ReTune Mobile control: %d\n", ret);
}
/* Need a control and routing to switch between DMIC and ADC */
snd_soc_dapm_new_controls(dapm, wm8904_cin_dapm_widgets,
ARRAY_SIZE(wm8904_cin_dapm_widgets));
snd_soc_dapm_add_routes(dapm, cin_adc_dmic_con,
ARRAY_SIZE(cin_adc_dmic_con));
if (pdata->in1l_as_dmicdat1 && pdata->in1r_as_dmicdat2) { /* Need a control and routing to mux between DMICDAT1 and 2 */
dev_dbg(component->dev, "DMICDAT1 and DMICDAT2 in use\n");
snd_soc_dapm_new_controls(dapm, wm8904_dmic_dapm_widgets,
ARRAY_SIZE(wm8904_dmic_dapm_widgets));
snd_soc_dapm_add_routes(dapm, cin_2dmics_con,
ARRAY_SIZE(cin_2dmics_con)); return;
}
/* Either DMICDAT1 or DMICDAT2 is in use, not both */ if (pdata->in1l_as_dmicdat1) {
dmic_src = 0;
snd_soc_dapm_add_routes(dapm, cin_dmic1_con,
ARRAY_SIZE(cin_dmic1_con));
} else {
dmic_src = 1;
snd_soc_dapm_add_routes(dapm, cin_dmic2_con,
ARRAY_SIZE(cin_dmic2_con));
}
dev_dbg(component->dev, "DMIC_SRC (0 or 1): %d\n", dmic_src);
snd_soc_component_update_bits(component, WM8904_DIGITAL_MICROPHONE_0,
WM8904_DMIC_SRC_MASK,
dmic_src << WM8904_DMIC_SRC_SHIFT);
}
if (pdata->num_drc_cfgs) { struct snd_kcontrol_new control =
SOC_ENUM_EXT("DRC Mode", wm8904->drc_enum,
wm8904_get_drc_enum, wm8904_put_drc_enum);
/* We need an array of texts for the enum API */
wm8904->drc_texts = kmalloc_array(pdata->num_drc_cfgs, sizeof(char *),
GFP_KERNEL); if (!wm8904->drc_texts) return;
for (i = 0; i < pdata->num_drc_cfgs; i++)
wm8904->drc_texts[i] = pdata->drc_cfgs[i].name;
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.