/* enumerated controls */ staticconstchar *wm8960_polarity[] = {"No Inversion", "Left Inverted", "Right Inverted", "Stereo Inversion"}; staticconstchar *wm8960_3d_upper_cutoff[] = {"High", "Low"}; staticconstchar *wm8960_3d_lower_cutoff[] = {"Low", "High"}; staticconstchar *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; staticconstchar *wm8960_alcmode[] = {"ALC", "Limiter"}; staticconstchar *wm8960_adc_data_output_sel[] = { "Left Data = Left ADC; Right Data = Right ADC", "Left Data = Left ADC; Right Data = Left ADC", "Left Data = Right ADC; Right Data = Right ADC", "Left Data = Right ADC; Right Data = Left ADC",
}; staticconstchar *wm8960_dmonomix[] = {"Stereo", "Mono"}; staticconstchar *wm8960_dacslope[] = {"Normal", "Sloping"};
staticint wm8960_set_deemph(struct snd_soc_component *component)
{ struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); int val, i, best;
/* If we're using deemphasis select the nearest available sample * rate.
*/ if (wm8960->deemph) {
best = 1; for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { if (abs(deemph_settings[i] - wm8960->lrclk) <
abs(deemph_settings[best] - wm8960->lrclk))
best = i;
}
/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */ staticconststruct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = {
SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0),
};
/* In capless mode OUT3 is used to provide VMID for the * headphone outputs, otherwise it is used as a mono mixer.
*/ if (pdata && pdata->capless) {
snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_capless,
ARRAY_SIZE(wm8960_dapm_widgets_capless));
/* We need to power up the headphone output stage out of * sequence for capless mode. To save scanning the widget * list each time to find the desired power state do so now * and save the result.
*/
list_for_each_entry(w, &component->card->widgets, list) { if (w->dapm != dapm) continue; if (strcmp(w->name, "LOUT1 PGA") == 0)
wm8960->lout1 = w; if (strcmp(w->name, "ROUT1 PGA") == 0)
wm8960->rout1 = w; if (strcmp(w->name, "OUT3 VMID") == 0)
wm8960->out3 = w;
}
/** * wm8960_configure_sysclk - checks if there is a sysclk frequency available * The sysclk must be chosen such that: * - sysclk = MCLK / sysclk_divs * - lrclk = sysclk / dac_divs * - 10 * bclk = sysclk / bclk_divs * * @wm8960: codec private data * @mclk: MCLK used to derive sysclk * @sysclk_idx: sysclk_divs index for found sysclk * @dac_idx: dac_divs index for found lrclk * @bclk_idx: bclk_divs index for found bclk * * Returns: * -1, in case no sysclk frequency available found * >=0, in case we could derive bclk and lrclk from sysclk using * (@sysclk_idx, @dac_idx, @bclk_idx) dividers
*/ static int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk, int *sysclk_idx, int *dac_idx, int *bclk_idx)
{ int sysclk, bclk, lrclk; int i, j, k; int diff;
/* marker for no match */
*bclk_idx = -1;
bclk = wm8960->bclk;
lrclk = wm8960->lrclk;
/* check if the sysclk frequency is available. */ for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { if (sysclk_divs[i] == -1) continue;
sysclk = mclk / sysclk_divs[i]; for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { if (sysclk != dac_divs[j] * lrclk) continue; for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) {
diff = sysclk - bclk * bclk_divs[k] / 10; if (diff == 0) {
*sysclk_idx = i;
*dac_idx = j;
*bclk_idx = k; break;
}
} if (k != ARRAY_SIZE(bclk_divs)) break;
} if (j != ARRAY_SIZE(dac_divs)) break;
} return *bclk_idx;
}
/** * wm8960_configure_pll - checks if there is a PLL out frequency available * The PLL out frequency must be chosen such that: * - sysclk = lrclk * dac_divs * - freq_out = sysclk * sysclk_divs * - 10 * sysclk = bclk * bclk_divs * * If we cannot find an exact match for (sysclk, lrclk, bclk) * triplet, we relax the bclk such that bclk is chosen as the * closest available frequency greater than expected bclk. * * @component: component structure * @freq_in: input frequency used to derive freq out via PLL * @sysclk_idx: sysclk_divs index for found sysclk * @dac_idx: dac_divs index for found lrclk * @bclk_idx: bclk_divs index for found bclk * * Returns: * < 0, in case no PLL frequency out available was found * >=0, in case we could derive bclk, lrclk, sysclk from PLL out using * (@sysclk_idx, @dac_idx, @bclk_idx) dividers
*/ static int wm8960_configure_pll(struct snd_soc_component *component, int freq_in, int *sysclk_idx, int *dac_idx, int *bclk_idx)
{ struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); int sysclk, bclk, lrclk, freq_out; int diff, closest, best_freq_out; int i, j, k;
/* * From Datasheet, the PLL performs best when f2 is between * 90MHz and 100MHz, the desired sysclk output is 11.2896MHz * or 12.288MHz, then sysclkdiv = 2 is the best choice. * So search sysclk_divs from 2 to 1 other than from 1 to 2.
*/ for (i = ARRAY_SIZE(sysclk_divs) - 1; i >= 0; --i) { if (sysclk_divs[i] == -1) continue; for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
sysclk = lrclk * dac_divs[j];
freq_out = sysclk * sysclk_divs[i];
for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { if (!is_pll_freq_available(freq_in, freq_out)) continue;
return best_freq_out;
} staticint wm8960_configure_clocking(struct snd_soc_component *component)
{ struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); int freq_out, freq_in;
u16 iface1 = snd_soc_component_read(component, WM8960_IFACE1); int i, j, k; int ret;
/* * For Slave mode clocking should still be configured, * so this if statement should be removed, but some platform * may not work if the sysclk is not configured, to avoid such * compatible issue, just add '!wm8960->sysclk' condition in * this if statement.
*/ if (!(iface1 & (1 << 6)) && !wm8960->sysclk) {
dev_warn(component->dev, "slave mode, but proceeding with no clock configuration\n"); return 0;
}
if (wm8960->clk_id != WM8960_SYSCLK_MCLK && !wm8960->freq_in) {
dev_err(component->dev, "No MCLK configured\n"); return -EINVAL;
}
freq_in = wm8960->freq_in; /* * If it's sysclk auto mode, check if the MCLK can provide sysclk or * not. If MCLK can provide sysclk, using MCLK to provide sysclk * directly. Otherwise, auto select a available pll out frequency * and set PLL.
*/ if (wm8960->clk_id == WM8960_SYSCLK_AUTO) { /* disable the PLL and using MCLK to provide sysclk */
wm8960_set_pll(component, 0, 0);
freq_out = freq_in;
} elseif (wm8960->sysclk) {
freq_out = wm8960->sysclk;
} else {
dev_err(component->dev, "No SYSCLK configured\n"); return -EINVAL;
}
if (wm8960->clk_id != WM8960_SYSCLK_PLL) {
ret = wm8960_configure_sysclk(wm8960, freq_out, &i, &j, &k); if (ret >= 0) { goto configure_clock;
} elseif (wm8960->clk_id != WM8960_SYSCLK_AUTO) {
dev_err(component->dev, "failed to configure clock\n"); return -EINVAL;
}
}
freq_out = wm8960_configure_pll(component, freq_in, &i, &j, &k); if (freq_out < 0) {
dev_err(component->dev, "failed to configure clock via PLL\n"); return freq_out;
}
wm8960_set_pll(component, freq_in, freq_out);
wm8960->bclk = snd_soc_params_to_bclk(params); if (params_channels(params) == 1)
wm8960->bclk *= 2;
/* bit size */ switch (params_width(params)) { case 16: break; case 20:
iface |= 0x0004; break; case 24:
iface |= 0x0008; break; case 32: /* right justify mode does not support 32 word length */ if ((iface & 0x3) != 0) {
iface |= 0x000c; break;
}
fallthrough; default:
dev_err(component->dev, "unsupported width %d\n",
params_width(params)); return -EINVAL;
}
wm8960->lrclk = params_rate(params); /* Update filters for the new rate */ if (tx) {
wm8960_set_deemph(component);
} else { for (i = 0; i < ARRAY_SIZE(alc_rates); i++) if (alc_rates[i].rate == params_rate(params))
snd_soc_component_update_bits(component,
WM8960_ADDCTL3, 0x7,
alc_rates[i].val);
}
/* set iface */
snd_soc_component_write(component, WM8960_IFACE1, iface);
wm8960->is_stream_in_use[tx] = true;
if (!wm8960->is_stream_in_use[!tx]) return wm8960_configure_clocking(component);
case SND_SOC_BIAS_PREPARE: switch (snd_soc_component_get_bias_level(component)) { case SND_SOC_BIAS_STANDBY: if (!IS_ERR(wm8960->mclk)) {
ret = clk_prepare_enable(wm8960->mclk); if (ret) {
dev_err(component->dev, "Failed to enable MCLK: %d\n",
ret); return ret;
}
}
ret = wm8960_configure_clocking(component); if (ret) return ret;
/* Set VMID to 2x50k */
snd_soc_component_update_bits(component, WM8960_POWER1, 0x180, 0x80); break;
case SND_SOC_BIAS_ON: /* * If it's sysclk auto mode, and the pll is enabled, * disable the pll
*/ if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1))
wm8960_set_pll(component, 0, 0);
if (!IS_ERR(wm8960->mclk))
clk_disable_unprepare(wm8960->mclk); break;
default: break;
}
break;
case SND_SOC_BIAS_STANDBY: if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { /* ensure discharge is complete */
tout = WM8960_DSCH_TOUT - ktime_ms_delta(ktime_get(), wm8960->dsch_start); if (tout > 0)
msleep(tout);
if (!IS_ERR(wm8960->mclk)) {
ret = clk_prepare_enable(wm8960->mclk); if (ret) {
dev_err(component->dev, "Failed to enable MCLK: %d\n",
ret); return ret;
}
}
ret = wm8960_configure_clocking(component); if (ret) return ret;
break;
case SND_SOC_BIAS_ON: /* * If it's sysclk auto mode, and the pll is enabled, * disable the pll
*/ if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1))
wm8960_set_pll(component, 0, 0);
if (!IS_ERR(wm8960->mclk))
clk_disable_unprepare(wm8960->mclk);
if (freq_in && freq_out) {
ret = pll_factors(freq_in, freq_out, &pll_div); if (ret != 0) return ret;
}
/* Disable the PLL: even if we are changing the frequency the
* PLL needs to be disabled while we do so. */
snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x1, 0);
snd_soc_component_update_bits(component, WM8960_POWER2, 0x1, 0);
wm8960->mclk = devm_clk_get(&i2c->dev, "mclk"); if (IS_ERR(wm8960->mclk)) { if (PTR_ERR(wm8960->mclk) == -EPROBE_DEFER) return -EPROBE_DEFER;
} else {
ret = clk_get_rate(wm8960->mclk); if (ret >= 0) {
wm8960->freq_in = ret;
} else {
dev_err(&i2c->dev, "Failed to read MCLK rate: %d\n",
ret);
}
}
for (i = 0; i < ARRAY_SIZE(wm8960->supplies); i++)
wm8960->supplies[i].supply = wm8960_supply_names[i];
ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8960->supplies),
wm8960->supplies); if (ret < 0) {
dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); return ret;
}
ret = regulator_bulk_enable(ARRAY_SIZE(wm8960->supplies),
wm8960->supplies); if (ret < 0) {
dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); return ret;
}
wm8960->regmap = devm_regmap_init_i2c(i2c, &wm8960_regmap); if (IS_ERR(wm8960->regmap)) {
ret = PTR_ERR(wm8960->regmap); goto bulk_disable;
}
if (pdata)
memcpy(&wm8960->pdata, pdata, sizeof(struct wm8960_data)); elseif (i2c->dev.of_node)
wm8960_set_pdata_from_of(i2c, &wm8960->pdata);
ret = i2c_master_recv(i2c, &val, sizeof(val)); if (ret >= 0) {
dev_err(&i2c->dev, "Not wm8960, wm8960 reg can not read by i2c\n");
ret = -EINVAL; goto bulk_disable;
}
ret = wm8960_reset(wm8960->regmap); if (ret != 0) {
dev_err(&i2c->dev, "Failed to issue reset\n"); goto bulk_disable;
}
if (wm8960->pdata.shared_lrclk) {
ret = regmap_update_bits(wm8960->regmap, WM8960_ADDCTL2,
0x4, 0x4); if (ret != 0) {
dev_err(&i2c->dev, "Failed to enable LRCM: %d\n",
ret); goto bulk_disable;
}
}
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.