struct wm9081_priv { struct regmap *regmap; int sysclk_source; int mclk_rate; int sysclk_rate; int fs; int bclk; int master; int fll_fref; int fll_fout; int tdm_width; struct wm9081_pdata pdata;
};
staticbool wm9081_volatile_register(struct device *dev, unsignedint reg)
{ switch (reg) { case WM9081_SOFTWARE_RESET: case WM9081_INTERRUPT_STATUS: returntrue; default: returnfalse;
}
}
staticbool wm9081_readable_register(struct device *dev, unsignedint reg)
{ switch (reg) { case WM9081_SOFTWARE_RESET: case WM9081_ANALOGUE_LINEOUT: case WM9081_ANALOGUE_SPEAKER_PGA: case WM9081_VMID_CONTROL: case WM9081_BIAS_CONTROL_1: case WM9081_ANALOGUE_MIXER: case WM9081_ANTI_POP_CONTROL: case WM9081_ANALOGUE_SPEAKER_1: case WM9081_ANALOGUE_SPEAKER_2: case WM9081_POWER_MANAGEMENT: case WM9081_CLOCK_CONTROL_1: case WM9081_CLOCK_CONTROL_2: case WM9081_CLOCK_CONTROL_3: case WM9081_FLL_CONTROL_1: case WM9081_FLL_CONTROL_2: case WM9081_FLL_CONTROL_3: case WM9081_FLL_CONTROL_4: case WM9081_FLL_CONTROL_5: case WM9081_AUDIO_INTERFACE_1: case WM9081_AUDIO_INTERFACE_2: case WM9081_AUDIO_INTERFACE_3: case WM9081_AUDIO_INTERFACE_4: case WM9081_INTERRUPT_STATUS: case WM9081_INTERRUPT_STATUS_MASK: case WM9081_INTERRUPT_POLARITY: case WM9081_INTERRUPT_CONTROL: case WM9081_DAC_DIGITAL_1: case WM9081_DAC_DIGITAL_2: case WM9081_DRC_1: case WM9081_DRC_2: case WM9081_DRC_3: case WM9081_DRC_4: case WM9081_WRITE_SEQUENCER_1: case WM9081_WRITE_SEQUENCER_2: case WM9081_MW_SLAVE_1: case WM9081_EQ_1: case WM9081_EQ_2: case WM9081_EQ_3: case WM9081_EQ_4: case WM9081_EQ_5: case WM9081_EQ_6: case WM9081_EQ_7: case WM9081_EQ_8: case WM9081_EQ_9: case WM9081_EQ_10: case WM9081_EQ_11: case WM9081_EQ_12: case WM9081_EQ_13: case WM9081_EQ_14: case WM9081_EQ_15: case WM9081_EQ_16: case WM9081_EQ_17: case WM9081_EQ_18: case WM9081_EQ_19: case WM9081_EQ_20: returntrue; default: returnfalse;
}
}
/* * Stop any attempts to change speaker mode while the speaker is enabled. * * We also have some special anti-pop controls dependent on speaker * mode which must be changed along with the mode.
*/ staticint speaker_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); unsignedint reg_pwr = snd_soc_component_read(component, WM9081_POWER_MANAGEMENT); unsignedint reg2 = snd_soc_component_read(component, WM9081_ANALOGUE_SPEAKER_2);
/* Are we changing anything? */ if (ucontrol->value.enumerated.item[0] ==
((reg2 & WM9081_SPK_MODE) != 0)) return 0;
/* Don't try to change modes while enabled */ if (reg_pwr & WM9081_SPK_ENA) return -EINVAL;
if (ucontrol->value.enumerated.item[0]) { /* Class AB */
reg2 &= ~(WM9081_SPK_INV_MUTE | WM9081_OUT_SPK_CTRL);
reg2 |= WM9081_SPK_MODE;
} else { /* Class D */
reg2 |= WM9081_SPK_INV_MUTE | WM9081_OUT_SPK_CTRL;
reg2 &= ~WM9081_SPK_MODE;
}
/* Fref must be <=13.5MHz */
div = 1; while ((Fref / div) > 13500000) {
div *= 2;
if (div > 8) {
pr_err("Can't scale %dMHz input down to <=13.5MHz\n",
Fref); return -EINVAL;
}
}
fll_div->fll_clk_ref_div = div / 2;
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 = 0;
target = Fout * 2; while (target < 90000000) {
div++;
target *= 2; if (div > 7) {
pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n",
Fout); return -EINVAL;
}
}
fll_div->fll_outdiv = div;
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;
}
/* Set gain to the recommended value */
snd_soc_component_update_bits(component, WM9081_FLL_CONTROL_4,
WM9081_FLL_GAIN_MASK, 0);
/* Enable the FLL */
snd_soc_component_write(component, WM9081_FLL_CONTROL_1, reg1 | WM9081_FLL_ENA);
/* Then bring CLK_SYS up again if it was disabled */ if (clk_sys_reg & WM9081_CLK_SYS_ENA)
snd_soc_component_write(component, WM9081_CLOCK_CONTROL_3, clk_sys_reg);
dev_dbg(component->dev, "FLL enabled at %dHz->%dHz\n", Fref, Fout);
wm9081->fll_fref = Fref;
wm9081->fll_fout = Fout;
return 0;
}
staticint configure_clock(struct snd_soc_component *component)
{ struct wm9081_priv *wm9081 = snd_soc_component_get_drvdata(component); int new_sysclk, i, target; unsignedint reg; int ret = 0; int mclkdiv = 0; int fll = 0;
case WM9081_SYSCLK_FLL_MCLK: /* If we have a sample rate calculate a CLK_SYS that * gives us a suitable DAC configuration, plus BCLK. * Ideally we would check to see if we can clock * directly from MCLK and only use the FLL if this is * not the case, though care must be taken with free * running mode.
*/ if (wm9081->master && wm9081->bclk) { /* Make sure we can generate CLK_SYS and BCLK * and that we've got 3MHz for optimal
* performance. */ for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) {
target = wm9081->fs * clk_sys_rates[i].ratio;
new_sysclk = target; if (target >= wm9081->bclk &&
target > 3000000) break;
}
if (i == ARRAY_SIZE(clk_sys_rates)) return -EINVAL;
} elseif (wm9081->fs) { for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) {
new_sysclk = clk_sys_rates[i].ratio
* wm9081->fs; if (new_sysclk > 3000000) break;
}
if (i == ARRAY_SIZE(clk_sys_rates)) return -EINVAL;
} else {
new_sysclk = 12288000;
}
ret = wm9081_set_fll(component, WM9081_SYSCLK_FLL_MCLK,
wm9081->mclk_rate, new_sysclk); if (ret == 0) {
wm9081->sysclk_rate = new_sysclk;
/* Switch SYSCLK over to FLL */
fll = 1;
} else {
wm9081->sysclk_rate = wm9081->mclk_rate;
} break;
/* This should be done on init() for bypass paths */ switch (wm9081->sysclk_source) { case WM9081_SYSCLK_MCLK:
dev_dbg(component->dev, "Using %dHz MCLK\n", wm9081->mclk_rate); break; case WM9081_SYSCLK_FLL_MCLK:
dev_dbg(component->dev, "Using %dHz MCLK with FLL\n",
wm9081->mclk_rate); break; default:
dev_err(component->dev, "System clock not configured\n"); return -EINVAL;
}
switch (event) { case SND_SOC_DAPM_PRE_PMU:
configure_clock(component); break;
case SND_SOC_DAPM_POST_PMD: /* Disable the FLL if it's running */
wm9081_set_fll(component, 0, 0, 0); break;
}
if (wm9081->tdm_width) { /* If TDM is set up then that fixes our BCLK. */ int slots = ((aif1 & WM9081_AIFDAC_TDM_MODE_MASK) >>
WM9081_AIFDAC_TDM_MODE_SHIFT) + 1;
wm9081->bclk = wm9081->fs * wm9081->tdm_width * slots;
} else { /* Otherwise work out a BCLK from the sample size */
wm9081->bclk = 2 * wm9081->fs;
dev_dbg(component->dev, "Target BCLK is %dHz\n", wm9081->bclk);
ret = configure_clock(component); if (ret != 0) return ret;
/* Select nearest CLK_SYS_RATE */
best = 0;
best_val = abs((wm9081->sysclk_rate / clk_sys_rates[0].ratio)
- wm9081->fs); for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) {
cur_val = abs((wm9081->sysclk_rate /
clk_sys_rates[i].ratio) - wm9081->fs); if (cur_val < best_val) {
best = i;
best_val = cur_val;
}
}
dev_dbg(component->dev, "Selected CLK_SYS_RATIO of %d\n",
clk_sys_rates[best].ratio);
clk_ctrl2 |= (clk_sys_rates[best].clk_sys_rate
<< WM9081_CLK_SYS_RATE_SHIFT);
/* SAMPLE_RATE */
best = 0;
best_val = abs(wm9081->fs - sample_rates[0].rate); for (i = 1; i < ARRAY_SIZE(sample_rates); i++) { /* Closest match */
cur_val = abs(wm9081->fs - sample_rates[i].rate); if (cur_val < best_val) {
best = i;
best_val = cur_val;
}
}
dev_dbg(component->dev, "Selected SAMPLE_RATE of %dHz\n",
sample_rates[best].rate);
clk_ctrl2 |= (sample_rates[best].sample_rate
<< WM9081_SAMPLE_RATE_SHIFT);
/* BCLK_DIV */
best = 0;
best_val = INT_MAX; for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
cur_val = ((wm9081->sysclk_rate * 10) / bclk_divs[i].div)
- wm9081->bclk; if (cur_val < 0) /* Table is sorted */ break; if (cur_val < best_val) {
best = i;
best_val = cur_val;
}
}
wm9081->bclk = (wm9081->sysclk_rate * 10) / bclk_divs[best].div;
dev_dbg(component->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n",
bclk_divs[best].div, wm9081->bclk);
aif3 |= bclk_divs[best].bclk_div;
/* LRCLK is a simple fraction of BCLK */
dev_dbg(component->dev, "LRCLK_RATE is %d\n", wm9081->bclk / wm9081->fs);
aif4 |= wm9081->bclk / wm9081->fs;
/* Apply a ReTune Mobile configuration if it's in use */ if (wm9081->pdata.num_retune_configs) { struct wm9081_pdata *pdata = &wm9081->pdata; struct wm9081_retune_mobile_setting *s; int eq1;
best = 0;
best_val = abs(pdata->retune_configs[0].rate - wm9081->fs); for (i = 0; i < pdata->num_retune_configs; i++) {
cur_val = abs(pdata->retune_configs[i].rate -
wm9081->fs); if (cur_val < best_val) {
best_val = cur_val;
best = i;
}
}
s = &pdata->retune_configs[best];
dev_dbg(component->dev, "ReTune Mobile %s tuned for %dHz\n",
s->name, s->rate);
/* If the EQ is enabled then disable it while we write out */
eq1 = snd_soc_component_read(component, WM9081_EQ_1) & WM9081_EQ_ENA; if (eq1 & WM9081_EQ_ENA)
snd_soc_component_write(component, WM9081_EQ_1, 0);
/* Write out the other values */ for (i = 1; i < ARRAY_SIZE(s->config); i++)
snd_soc_component_write(component, WM9081_EQ_1 + i, s->config[i]);
/* We report two channels because the CODEC processes a stereo signal, even * though it is only capable of handling a mono output.
*/ staticstruct snd_soc_dai_driver wm9081_dai = {
.name = "wm9081-hifi",
.playback = {
.stream_name = "AIF",
.channels_min = 1,
.channels_max = 2,
.rates = WM9081_RATES,
.formats = WM9081_FORMATS,
},
.ops = &wm9081_dai_ops,
};
/* Enable zero cross by default */
snd_soc_component_update_bits(component, WM9081_ANALOGUE_LINEOUT,
WM9081_LINEOUTZC, WM9081_LINEOUTZC);
snd_soc_component_update_bits(component, WM9081_ANALOGUE_SPEAKER_PGA,
WM9081_SPKPGAZC, WM9081_SPKPGAZC);
if (!wm9081->pdata.num_retune_configs) {
dev_dbg(component->dev, "No ReTune Mobile data, using normal EQ\n");
snd_soc_add_component_controls(component, wm9081_eq_controls,
ARRAY_SIZE(wm9081_eq_controls));
}
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.