From e636c8d336489bf3eed5878299e6cc045bbad077 Mon Sep 17 00:00:00 2001
From: hc <hc@nodka.com>
Date: Tue, 20 Feb 2024 01:17:29 +0000
Subject: [PATCH] debug lk
---
kernel/sound/soc/meson/axg-tdm-formatter.c | 92 +++++++++++++++++++++++++++++++++-------------
1 files changed, 66 insertions(+), 26 deletions(-)
diff --git a/kernel/sound/soc/meson/axg-tdm-formatter.c b/kernel/sound/soc/meson/axg-tdm-formatter.c
index 43e390f..4834cfd 100644
--- a/kernel/sound/soc/meson/axg-tdm-formatter.c
+++ b/kernel/sound/soc/meson/axg-tdm-formatter.c
@@ -7,6 +7,7 @@
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/regmap.h>
+#include <linux/reset.h>
#include <sound/soc.h>
#include "axg-tdm-formatter.h"
@@ -20,6 +21,7 @@
struct clk *lrclk;
struct clk *sclk_sel;
struct clk *lrclk_sel;
+ struct reset_control *reset;
bool enabled;
struct regmap *map;
};
@@ -28,27 +30,32 @@
struct axg_tdm_stream *ts,
unsigned int offset)
{
- unsigned int val, ch = ts->channels;
- unsigned long mask;
- int i, j;
+ unsigned int 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
+ * of each TDM lane. We need to go over the 32 slots ...
*/
- for (i = 0; i < AXG_TDM_NUM_LANES; i++) {
- val = 0;
- mask = ts->mask[i];
-
- for (j = find_first_bit(&mask, 32);
- (j < 32) && ch;
- j = find_next_bit(&mask, 32, j + 1)) {
- val |= 1 << j;
- ch -= 1;
+ 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;
+ }
+ }
}
-
- regmap_write(map, offset, val);
- offset += regmap_get_reg_stride(map);
}
/*
@@ -61,6 +68,11 @@
return -EINVAL;
}
+ for (i = 0; i < AXG_TDM_NUM_LANES; i++) {
+ regmap_write(map, offset, val[i]);
+ offset += regmap_get_reg_stride(map);
+ }
+
return 0;
}
EXPORT_SYMBOL_GPL(axg_tdm_formatter_set_channel_masks);
@@ -68,7 +80,7 @@
static int axg_tdm_formatter_enable(struct axg_tdm_formatter *formatter)
{
struct axg_tdm_stream *ts = formatter->stream;
- bool invert = formatter->drv->invert_sclk;
+ bool invert;
int ret;
/* Do nothing if the formatter is already enabled */
@@ -76,16 +88,37 @@
return 0;
/*
- * If sclk is inverted, invert it back and provide the inversion
- * required by the formatter
+ * 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.
*/
- invert ^= axg_tdm_sclk_invert(ts->iface->fmt);
- ret = clk_set_phase(formatter->sclk, invert ? 180 : 0);
+ 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->stream);
+ ret = formatter->drv->ops->prepare(formatter->map,
+ formatter->drv->quirks,
+ formatter->stream);
if (ret)
return ret;
@@ -231,7 +264,6 @@
struct device *dev = &pdev->dev;
const struct axg_tdm_formatter_driver *drv;
struct axg_tdm_formatter *formatter;
- struct resource *res;
void __iomem *regs;
int ret;
@@ -247,8 +279,7 @@
platform_set_drvdata(pdev, formatter);
formatter->drv = drv;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- regs = devm_ioremap_resource(dev, res);
+ regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
@@ -301,6 +332,15 @@
ret = PTR_ERR(formatter->lrclk_sel);
if (ret != -EPROBE_DEFER)
dev_err(dev, "failed to get lrclk_sel: %d\n", ret);
+ return ret;
+ }
+
+ /* Formatter dedicated reset line */
+ formatter->reset = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(formatter->reset)) {
+ ret = PTR_ERR(formatter->reset);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to get reset: %d\n", ret);
return ret;
}
@@ -368,7 +408,7 @@
/*
* 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
- * we are removing the TDM DAI. It should not be possible
+ * are removing the TDM DAI. It should not be possible
*/
WARN_ON(!list_empty(&ts->formatter_list));
mutex_destroy(&ts->lock);
--
Gitblit v1.6.2