int axg_tdm_formatter_set_channel_masks(struct regmap *map, struct axg_tdm_stream *ts, unsignedint offset)
{ unsignedint ch = ts->channels;
u32 val[AXG_TDM_NUM_LANES]; int i, j, k;
/* * We need to mimick the slot distribution used by the HW to keep the * channel placement consistent regardless of the number of channel * in the stream. This is why the odd algorithm below is used.
*/
memset(val, 0, sizeof(*val) * AXG_TDM_NUM_LANES);
/* * Distribute the channels of the stream over the available slots * of each TDM lane. We need to go over the 32 slots ...
*/ for (i = 0; (i < 32) && ch; i += 2) { /* ... of all the lanes ... */ for (j = 0; j < AXG_TDM_NUM_LANES; j++) { /* ... then distribute the channels in pairs */ for (k = 0; k < 2; k++) { if ((BIT(i + k) & ts->mask[j]) && ch) {
val[j] |= BIT(i + k);
ch -= 1;
}
}
}
}
/* * If we still have channel left at the end of the process, it means * the stream has more channels than we can accommodate and we should * have caught this earlier.
*/ if (WARN_ON(ch != 0)) {
pr_err("channel mask error\n"); return -EINVAL;
}
for (i = 0; i < AXG_TDM_NUM_LANES; i++) {
regmap_write(map, offset, val[i]);
offset += regmap_get_reg_stride(map);
}
/* Do nothing if the formatter is already enabled */ if (formatter->enabled) return 0;
/* * On the g12a (and possibly other SoCs), when a stream using * multiple lanes is restarted, it will sometimes not start * from the first lane, but randomly from another used one. * The result is an unexpected and random channel shift. * * The hypothesis is that an HW counter is not properly reset * and the formatter simply starts on the lane it stopped * before. Unfortunately, there does not seems to be a way to * reset this through the registers of the block. * * However, the g12a has indenpendent reset lines for each audio * devices. Using this reset before each start solves the issue.
*/
ret = reset_control_reset(formatter->reset); if (ret) return ret;
/* * If sclk is inverted, it means the bit should latched on the * rising edge which is what our HW expects. If not, we need to * invert it before the formatter.
*/
invert = axg_tdm_sclk_invert(ts->iface->fmt);
ret = clk_set_phase(formatter->sclk, invert ? 0 : 180); if (ret) return ret;
/* Setup the stream parameter in the formatter */
ret = formatter->drv->ops->prepare(formatter->map,
formatter->drv->quirks,
formatter->stream); if (ret) return ret;
/* Enable the signal clocks feeding the formatter */
ret = clk_prepare_enable(formatter->sclk); if (ret) return ret;
ret = clk_prepare_enable(formatter->lrclk); if (ret) {
clk_disable_unprepare(formatter->sclk); return ret;
}
staticvoid axg_tdm_formatter_disable(struct axg_tdm_formatter *formatter)
{ /* Do nothing if the formatter is already disabled */ if (!formatter->enabled) return;
staticint axg_tdm_formatter_attach(struct axg_tdm_formatter *formatter)
{ struct axg_tdm_stream *ts = formatter->stream; int ret = 0;
mutex_lock(&ts->lock);
/* Catch up if the stream is already running when we attach */ if (ts->ready) {
ret = axg_tdm_formatter_enable(formatter); if (ret) {
pr_err("failed to enable formatter\n"); goto out;
}
}
/* * If we don't get a stream at this stage, it would mean that the * widget is powering up but is not attached to any backend DAI. * It should not happen, ever !
*/ if (WARN_ON(!ts)) return -ENODEV;
/* Clock our device */
ret = clk_prepare_enable(formatter->pclk); if (ret) return ret;
/* Reparent the bit clock to the TDM interface */
ret = clk_set_parent(formatter->sclk_sel, ts->iface->sclk); if (ret) goto disable_pclk;
/* Reparent the sample clock to the TDM interface */
ret = clk_set_parent(formatter->lrclk_sel, ts->iface->lrclk); if (ret) goto disable_pclk;
formatter->stream = ts;
ret = axg_tdm_formatter_attach(formatter); if (ret) goto disable_pclk;
regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(regs)) return PTR_ERR(regs);
formatter->map = devm_regmap_init_mmio(dev, regs, drv->regmap_cfg); if (IS_ERR(formatter->map)) {
dev_err(dev, "failed to init regmap: %ld\n",
PTR_ERR(formatter->map)); return PTR_ERR(formatter->map);
}
/* Peripharal clock */
formatter->pclk = devm_clk_get(dev, "pclk"); if (IS_ERR(formatter->pclk)) return dev_err_probe(dev, PTR_ERR(formatter->pclk), "failed to get pclk\n");
/* Formatter bit clock */
formatter->sclk = devm_clk_get(dev, "sclk"); if (IS_ERR(formatter->sclk)) return dev_err_probe(dev, PTR_ERR(formatter->sclk), "failed to get sclk\n");
/* Formatter sample clock */
formatter->lrclk = devm_clk_get(dev, "lrclk"); if (IS_ERR(formatter->lrclk)) return dev_err_probe(dev, PTR_ERR(formatter->lrclk), "failed to get lrclk\n");
/* Formatter bit clock input multiplexer */
formatter->sclk_sel = devm_clk_get(dev, "sclk_sel"); if (IS_ERR(formatter->sclk_sel)) return dev_err_probe(dev, PTR_ERR(formatter->sclk_sel), "failed to get sclk_sel\n");
/* Formatter sample clock input multiplexer */
formatter->lrclk_sel = devm_clk_get(dev, "lrclk_sel"); if (IS_ERR(formatter->lrclk_sel)) return dev_err_probe(dev, PTR_ERR(formatter->lrclk_sel), "failed to get lrclk_sel\n");
/* Formatter dedicated reset line */
formatter->reset = devm_reset_control_get_optional_exclusive(dev, NULL); if (IS_ERR(formatter->reset)) return dev_err_probe(dev, PTR_ERR(formatter->reset), "failed to get reset\n");
int axg_tdm_stream_start(struct axg_tdm_stream *ts)
{ struct axg_tdm_formatter *formatter; int ret = 0;
mutex_lock(&ts->lock);
ts->ready = true;
/* Start all the formatters attached to the stream */
list_for_each_entry(formatter, &ts->formatter_list, list) {
ret = axg_tdm_formatter_enable(formatter); if (ret) {
pr_err("failed to start tdm stream\n"); goto out;
}
}
/* Stop all the formatters attached to the stream */
list_for_each_entry(formatter, &ts->formatter_list, list) {
axg_tdm_formatter_disable(formatter);
}
void axg_tdm_stream_free(struct axg_tdm_stream *ts)
{ /* * If the list is not empty, it would mean that one of the formatter * widget is still powered and attached to the interface while we * are removing the TDM DAI. It should not be possible
*/
WARN_ON(!list_empty(&ts->formatter_list));
mutex_destroy(&ts->lock);
kfree(ts);
}
EXPORT_SYMBOL_GPL(axg_tdm_stream_free);
int axg_tdm_stream_set_cont_clocks(struct axg_tdm_stream *ts, unsignedint fmt)
{ int ret = 0;
if (fmt & SND_SOC_DAIFMT_CONT) { /* Clock are already enabled - skipping */ if (ts->clk_enabled) return 0;
ret = clk_prepare_enable(ts->iface->mclk); if (ret) return ret;
ret = clk_prepare_enable(ts->iface->sclk); if (ret) goto err_sclk;
ret = clk_prepare_enable(ts->iface->lrclk); if (ret) goto err_lrclk;
ts->clk_enabled = true; return 0;
}
/* Clocks are already disabled - skipping */ if (!ts->clk_enabled) return 0;
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.