From f70575805708cabdedea7498aaa3f710fde4d920 Mon Sep 17 00:00:00 2001 From: hc <hc@nodka.com> Date: Wed, 31 Jan 2024 03:29:01 +0000 Subject: [PATCH] add lvds1024*800 --- 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