linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 00/15] ASoC: meson: add axg audio subsystem support
@ 2018-07-17 15:36 Jerome Brunet
  2018-07-17 15:36 ` [PATCH 01/15] ASoC: meson: add axg fifos DT binding documentation Jerome Brunet
                   ` (19 more replies)
  0 siblings, 20 replies; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

This patchset adds support for the audio subsystem found on Amlogic's
axg SoC family. The first SoC of this family is the A113D/X targeted
at smart speaker application. The G12a SoC family, Amlogic next-gen
STB chipset, will reuse this audio subsystem with a few changes.

This patchset implements the DPCM machine driver and the different
components it made of. Several components are still missing, such
spdif input and pdm, and will submitted later on.

The fancier part of this patchset is probably around the TDM. Properly
modeling the SoC architecture and its capatilities made things a little
more complex than usual (details in the related commit descriptions)

If necessary, this patchset could broken into smaller series. For the
intial submission, I wanted to give a global view of the subsystem.
Hopefully, it will make it a bit easier to understand.

Jerome Brunet (15):
  ASoC: meson: add axg fifos DT binding documentation
  ASoC: meson: add axg fifo base driver
  ASoC: meson: add axg frddr driver
  ASoC: meson: add axg toddr driver
  ASoC: meson: add axg spdif output DT bindings documentation
  ASoC: meson: add axg spdif output
  ASoC: meson: add axg tdm formatters DT bindings documentation
  ASoC: meson: add axg tdm interface DT bindings documentation
  ASoC: meson: add tdm formatter base driver
  ASoC: meson: add tdm interface driver
  ASoC: meson: add tdm output driver
  ASoC: meson: add tdm input driver
  ASoC: export snd_soc_of_get_slot_mask
  ASoC: meson: add axg sound card DT bindings documentation
  ASoC: meson: add axg sound card support

 .../devicetree/bindings/sound/amlogic,axg-fifo.txt |  23 +
 .../bindings/sound/amlogic,axg-sound-card.txt      | 124 ++++
 .../bindings/sound/amlogic,axg-spdifout.txt        |  20 +
 .../bindings/sound/amlogic,axg-tdm-formatters.txt  |  28 +
 .../bindings/sound/amlogic,axg-tdm-iface.txt       |  22 +
 include/sound/soc.h                                |   3 +
 sound/soc/Kconfig                                  |   1 +
 sound/soc/Makefile                                 |   1 +
 sound/soc/meson/Kconfig                            |  64 ++
 sound/soc/meson/Makefile                           |  21 +
 sound/soc/meson/axg-card.c                         | 671 +++++++++++++++++++++
 sound/soc/meson/axg-fifo.c                         | 341 +++++++++++
 sound/soc/meson/axg-fifo.h                         |  80 +++
 sound/soc/meson/axg-frddr.c                        | 141 +++++
 sound/soc/meson/axg-spdifout.c                     | 456 ++++++++++++++
 sound/soc/meson/axg-tdm-formatter.c                | 381 ++++++++++++
 sound/soc/meson/axg-tdm-formatter.h                |  39 ++
 sound/soc/meson/axg-tdm-interface.c                | 542 +++++++++++++++++
 sound/soc/meson/axg-tdm.h                          |  78 +++
 sound/soc/meson/axg-tdmin.c                        | 229 +++++++
 sound/soc/meson/axg-tdmout.c                       | 259 ++++++++
 sound/soc/meson/axg-toddr.c                        | 199 ++++++
 sound/soc/soc-core.c                               |   7 +-
 23 files changed, 3727 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-fifo.txt
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-spdifout.txt
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.txt
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt
 create mode 100644 sound/soc/meson/Kconfig
 create mode 100644 sound/soc/meson/Makefile
 create mode 100644 sound/soc/meson/axg-card.c
 create mode 100644 sound/soc/meson/axg-fifo.c
 create mode 100644 sound/soc/meson/axg-fifo.h
 create mode 100644 sound/soc/meson/axg-frddr.c
 create mode 100644 sound/soc/meson/axg-spdifout.c
 create mode 100644 sound/soc/meson/axg-tdm-formatter.c
 create mode 100644 sound/soc/meson/axg-tdm-formatter.h
 create mode 100644 sound/soc/meson/axg-tdm-interface.c
 create mode 100644 sound/soc/meson/axg-tdm.h
 create mode 100644 sound/soc/meson/axg-tdmin.c
 create mode 100644 sound/soc/meson/axg-tdmout.c
 create mode 100644 sound/soc/meson/axg-toddr.c

-- 
2.14.4


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH 01/15] ASoC: meson: add axg fifos DT binding documentation
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-18 12:27   ` Applied "ASoC: meson: add axg fifos DT binding documentation" to the asoc tree Mark Brown
  2018-07-17 15:36 ` [PATCH 02/15] ASoC: meson: add axg fifo base driver Jerome Brunet
                   ` (18 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add the DT bindings documentation for axg's FIFOs: TODDR and FRDDR.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 .../devicetree/bindings/sound/amlogic,axg-fifo.txt | 23 ++++++++++++++++++++++
 1 file changed, 23 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-fifo.txt

diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-fifo.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-fifo.txt
new file mode 100644
index 000000000000..3dfc2515e5c6
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-fifo.txt
@@ -0,0 +1,23 @@
+* Amlogic Audio FIFO controllers
+
+Required properties:
+- compatible: 'amlogic,axg-toddr' or
+	      'amlogic,axg-frddr'
+- reg: physical base address of the controller and length of memory
+       mapped region.
+- interrupts: interrupt specifier for the fifo.
+- clocks: phandle to the fifo peripheral clock provided by the audio
+	  clock controller.
+- resets: phandle to memory ARB line provided by the arb reset controller.
+- #sound-dai-cells: must be 0.
+
+Example of FRDDR A on the A113 SoC:
+
+frddr_a: audio-controller@1c0 {
+	compatible = "amlogic,axg-frddr";
+	reg = <0x0 0x1c0 0x0 0x1c>;
+	#sound-dai-cells = <0>;
+	interrupts = <GIC_SPI 88 IRQ_TYPE_EDGE_RISING>;
+	clocks = <&clkc_audio AUD_CLKID_FRDDR_A>;
+	resets = <&arb AXG_ARB_FRDDR_A>;
+};
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 02/15] ASoC: meson: add axg fifo base driver
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
  2018-07-17 15:36 ` [PATCH 01/15] ASoC: meson: add axg fifos DT binding documentation Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-18 12:26   ` Applied "ASoC: meson: add axg fifo base driver" to the asoc tree Mark Brown
  2018-07-17 15:36 ` [PATCH 03/15] ASoC: meson: add axg frddr driver Jerome Brunet
                   ` (17 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Amlogic's axg SoCs have two types of fifos which are the memory
interfaces of the audio subsystem. FRDDR provides the playback
interface while TODDR provides the capture interface.

The way these fifos operate is very similar. Only a few settings
are specific to each.

They implement the same pcm driver here and the specifics of each
will be dealt with the related DAI driver.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 sound/soc/Kconfig          |   1 +
 sound/soc/Makefile         |   1 +
 sound/soc/meson/Kconfig    |   8 ++
 sound/soc/meson/Makefile   |   5 +
 sound/soc/meson/axg-fifo.c | 341 +++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/meson/axg-fifo.h |  80 +++++++++++
 6 files changed, 436 insertions(+)
 create mode 100644 sound/soc/meson/Kconfig
 create mode 100644 sound/soc/meson/Makefile
 create mode 100644 sound/soc/meson/axg-fifo.c
 create mode 100644 sound/soc/meson/axg-fifo.h

diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 41af6b9cc350..1cf11cf51e1d 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -57,6 +57,7 @@ source "sound/soc/kirkwood/Kconfig"
 source "sound/soc/img/Kconfig"
 source "sound/soc/intel/Kconfig"
 source "sound/soc/mediatek/Kconfig"
+source "sound/soc/meson/Kconfig"
 source "sound/soc/mxs/Kconfig"
 source "sound/soc/pxa/Kconfig"
 source "sound/soc/qcom/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 06389a5385d7..62a5f87c3cfc 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -38,6 +38,7 @@ obj-$(CONFIG_SND_SOC)	+= jz4740/
 obj-$(CONFIG_SND_SOC)	+= img/
 obj-$(CONFIG_SND_SOC)	+= intel/
 obj-$(CONFIG_SND_SOC)	+= mediatek/
+obj-$(CONFIG_SND_SOC)	+= meson/
 obj-$(CONFIG_SND_SOC)	+= mxs/
 obj-$(CONFIG_SND_SOC)	+= nuc900/
 obj-$(CONFIG_SND_SOC)	+= omap/
diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
new file mode 100644
index 000000000000..c3eb5e050308
--- /dev/null
+++ b/sound/soc/meson/Kconfig
@@ -0,0 +1,8 @@
+menu "ASoC support for Amlogic platforms"
+	depends on ARCH_MESON || COMPILE_TEST
+
+config SND_MESON_AXG_FIFO
+	tristate
+	select REGMAP_MMIO
+
+endmenu
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
new file mode 100644
index 000000000000..75289b6b3ade
--- /dev/null
+++ b/sound/soc/meson/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: (GPL-2.0 OR MIT)
+
+snd-soc-meson-axg-fifo-objs := axg-fifo.o
+
+obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
diff --git a/sound/soc/meson/axg-fifo.c b/sound/soc/meson/axg-fifo.c
new file mode 100644
index 000000000000..db367d85290f
--- /dev/null
+++ b/sound/soc/meson/axg-fifo.c
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/clk.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "axg-fifo.h"
+
+/*
+ * This file implements the platform operations common to the playback and
+ * capture frontend DAI. The logic behind this two types of fifo is very
+ * similar but some difference exist.
+ * These differences the respective DAI drivers
+ */
+
+static struct snd_pcm_hardware axg_fifo_hw = {
+	.info = (SNDRV_PCM_INFO_INTERLEAVED |
+		 SNDRV_PCM_INFO_MMAP |
+		 SNDRV_PCM_INFO_MMAP_VALID |
+		 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		 SNDRV_PCM_INFO_PAUSE),
+
+	.formats = AXG_FIFO_FORMATS,
+	.rate_min = 5512,
+	.rate_max = 192000,
+	.channels_min = 1,
+	.channels_max = AXG_FIFO_CH_MAX,
+	.period_bytes_min = AXG_FIFO_MIN_DEPTH,
+	.period_bytes_max = UINT_MAX,
+	.periods_min = 2,
+	.periods_max = UINT_MAX,
+
+	/* No real justification for this */
+	.buffer_bytes_max = 1 * 1024 * 1024,
+};
+
+static struct snd_soc_dai *axg_fifo_dai(struct snd_pcm_substream *ss)
+{
+	struct snd_soc_pcm_runtime *rtd = ss->private_data;
+
+	return rtd->cpu_dai;
+}
+
+static struct axg_fifo *axg_fifo_data(struct snd_pcm_substream *ss)
+{
+	struct snd_soc_dai *dai = axg_fifo_dai(ss);
+
+	return snd_soc_dai_get_drvdata(dai);
+}
+
+static struct device *axg_fifo_dev(struct snd_pcm_substream *ss)
+{
+	struct snd_soc_dai *dai = axg_fifo_dai(ss);
+
+	return dai->dev;
+}
+
+static void __dma_enable(struct axg_fifo *fifo,  bool enable)
+{
+	regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_DMA_EN,
+			   enable ? CTRL0_DMA_EN : 0);
+}
+
+static int axg_fifo_pcm_trigger(struct snd_pcm_substream *ss, int cmd)
+{
+	struct axg_fifo *fifo = axg_fifo_data(ss);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		__dma_enable(fifo, true);
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_STOP:
+		__dma_enable(fifo, false);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t axg_fifo_pcm_pointer(struct snd_pcm_substream *ss)
+{
+	struct axg_fifo *fifo = axg_fifo_data(ss);
+	struct snd_pcm_runtime *runtime = ss->runtime;
+	unsigned int addr;
+
+	regmap_read(fifo->map, FIFO_STATUS2, &addr);
+
+	return bytes_to_frames(runtime, addr - (unsigned int)runtime->dma_addr);
+}
+
+static int axg_fifo_pcm_hw_params(struct snd_pcm_substream *ss,
+				  struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = ss->runtime;
+	struct axg_fifo *fifo = axg_fifo_data(ss);
+	dma_addr_t end_ptr;
+	unsigned int burst_num;
+	int ret;
+
+	ret = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(params));
+	if (ret < 0)
+		return ret;
+
+	/* Setup dma memory pointers */
+	end_ptr = runtime->dma_addr + runtime->dma_bytes - AXG_FIFO_BURST;
+	regmap_write(fifo->map, FIFO_START_ADDR, runtime->dma_addr);
+	regmap_write(fifo->map, FIFO_FINISH_ADDR, end_ptr);
+
+	/* Setup interrupt periodicity */
+	burst_num = params_period_bytes(params) / AXG_FIFO_BURST;
+	regmap_write(fifo->map, FIFO_INT_ADDR, burst_num);
+
+	/* Enable block count irq */
+	regmap_update_bits(fifo->map, FIFO_CTRL0,
+			   CTRL0_INT_EN(FIFO_INT_COUNT_REPEAT),
+			   CTRL0_INT_EN(FIFO_INT_COUNT_REPEAT));
+
+	return 0;
+}
+
+static int axg_fifo_pcm_hw_free(struct snd_pcm_substream *ss)
+{
+	struct axg_fifo *fifo = axg_fifo_data(ss);
+
+	/* Disable the block count irq */
+	regmap_update_bits(fifo->map, FIFO_CTRL0,
+			   CTRL0_INT_EN(FIFO_INT_COUNT_REPEAT), 0);
+
+	return snd_pcm_lib_free_pages(ss);
+}
+
+static void axg_fifo_ack_irq(struct axg_fifo *fifo, u8 mask)
+{
+	regmap_update_bits(fifo->map, FIFO_CTRL1,
+			   CTRL1_INT_CLR(FIFO_INT_MASK),
+			   CTRL1_INT_CLR(mask));
+
+	/* Clear must also be cleared */
+	regmap_update_bits(fifo->map, FIFO_CTRL1,
+			   CTRL1_INT_CLR(FIFO_INT_MASK),
+			   0);
+}
+
+static irqreturn_t axg_fifo_pcm_irq_block(int irq, void *dev_id)
+{
+	struct snd_pcm_substream *ss = dev_id;
+	struct axg_fifo *fifo = axg_fifo_data(ss);
+	unsigned int status;
+
+	regmap_read(fifo->map, FIFO_STATUS1, &status);
+
+	status = STATUS1_INT_STS(status) & FIFO_INT_MASK;
+	if (status & FIFO_INT_COUNT_REPEAT)
+		snd_pcm_period_elapsed(ss);
+	else
+		dev_dbg(axg_fifo_dev(ss), "unexpected irq - STS 0x%02x\n",
+			status);
+
+	/* Ack irqs */
+	axg_fifo_ack_irq(fifo, status);
+
+	return !status ? IRQ_NONE : IRQ_HANDLED;
+}
+
+static int axg_fifo_pcm_open(struct snd_pcm_substream *ss)
+{
+	struct axg_fifo *fifo = axg_fifo_data(ss);
+	struct device *dev = axg_fifo_dev(ss);
+	int ret;
+
+	snd_soc_set_runtime_hwparams(ss, &axg_fifo_hw);
+
+	/*
+	 * Make sure the buffer and period size are multiple of the FIFO
+	 * minimum depth size
+	 */
+	ret = snd_pcm_hw_constraint_step(ss->runtime, 0,
+					 SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+					 AXG_FIFO_MIN_DEPTH);
+	if (ret)
+		return ret;
+
+	ret = snd_pcm_hw_constraint_step(ss->runtime, 0,
+					 SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+					 AXG_FIFO_MIN_DEPTH);
+	if (ret)
+		return ret;
+
+	ret = request_irq(fifo->irq, axg_fifo_pcm_irq_block, 0,
+			  dev_name(dev), ss);
+
+	/* Enable pclk to access registers and clock the fifo ip */
+	ret = clk_prepare_enable(fifo->pclk);
+	if (ret)
+		return ret;
+
+	/* Setup status2 so it reports the memory pointer */
+	regmap_update_bits(fifo->map, FIFO_CTRL1,
+			   CTRL1_STATUS2_SEL_MASK,
+			   CTRL1_STATUS2_SEL(STATUS2_SEL_DDR_READ));
+
+	/* Make sure the dma is initially disabled */
+	__dma_enable(fifo, false);
+
+	/* Disable irqs until params are ready */
+	regmap_update_bits(fifo->map, FIFO_CTRL0,
+			   CTRL0_INT_EN(FIFO_INT_MASK), 0);
+
+	/* Clear any pending interrupt */
+	axg_fifo_ack_irq(fifo, FIFO_INT_MASK);
+
+	/* Take memory arbitror out of reset */
+	ret = reset_control_deassert(fifo->arb);
+	if (ret)
+		clk_disable_unprepare(fifo->pclk);
+
+	return ret;
+}
+
+static int axg_fifo_pcm_close(struct snd_pcm_substream *ss)
+{
+	struct axg_fifo *fifo = axg_fifo_data(ss);
+	int ret;
+
+	/* Put the memory arbitror back in reset */
+	ret = reset_control_assert(fifo->arb);
+
+	/* Disable fifo ip and register access */
+	clk_disable_unprepare(fifo->pclk);
+
+	/* remove IRQ */
+	free_irq(fifo->irq, ss);
+
+	return ret;
+}
+
+const struct snd_pcm_ops axg_fifo_pcm_ops = {
+	.open =		axg_fifo_pcm_open,
+	.close =        axg_fifo_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	axg_fifo_pcm_hw_params,
+	.hw_free =      axg_fifo_pcm_hw_free,
+	.pointer =	axg_fifo_pcm_pointer,
+	.trigger =	axg_fifo_pcm_trigger,
+};
+EXPORT_SYMBOL_GPL(axg_fifo_pcm_ops);
+
+int axg_fifo_pcm_new(struct snd_soc_pcm_runtime *rtd, unsigned int type)
+{
+	struct snd_card *card = rtd->card->snd_card;
+	size_t size = axg_fifo_hw.buffer_bytes_max;
+
+	return snd_pcm_lib_preallocate_pages(rtd->pcm->streams[type].substream,
+					     SNDRV_DMA_TYPE_DEV, card->dev,
+					     size, size);
+}
+EXPORT_SYMBOL_GPL(axg_fifo_pcm_new);
+
+static const struct regmap_config axg_fifo_regmap_cfg = {
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_stride	= 4,
+	.max_register	= FIFO_STATUS2,
+};
+
+int axg_fifo_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const struct axg_fifo_match_data *data;
+	struct axg_fifo *fifo;
+	struct resource *res;
+	void __iomem *regs;
+
+	data = of_device_get_match_data(dev);
+	if (!data) {
+		dev_err(dev, "failed to match device\n");
+		return -ENODEV;
+	}
+
+	fifo = devm_kzalloc(dev, sizeof(*fifo), GFP_KERNEL);
+	if (!fifo)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, fifo);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	fifo->map = devm_regmap_init_mmio(dev, regs, &axg_fifo_regmap_cfg);
+	if (IS_ERR(fifo->map)) {
+		dev_err(dev, "failed to init regmap: %ld\n",
+			PTR_ERR(fifo->map));
+		return PTR_ERR(fifo->map);
+	}
+
+	fifo->pclk = devm_clk_get(dev, NULL);
+	if (IS_ERR(fifo->pclk)) {
+		if (PTR_ERR(fifo->pclk) != -EPROBE_DEFER)
+			dev_err(dev, "failed to get pclk: %ld\n",
+				PTR_ERR(fifo->pclk));
+		return PTR_ERR(fifo->pclk);
+	}
+
+	fifo->arb = devm_reset_control_get_exclusive(dev, NULL);
+	if (IS_ERR(fifo->arb)) {
+		if (PTR_ERR(fifo->arb) != -EPROBE_DEFER)
+			dev_err(dev, "failed to get arb reset: %ld\n",
+				PTR_ERR(fifo->arb));
+		return PTR_ERR(fifo->arb);
+	}
+
+	fifo->irq = of_irq_get(dev->of_node, 0);
+	if (fifo->irq <= 0) {
+		dev_err(dev, "failed to get irq: %d\n", fifo->irq);
+		return fifo->irq;
+	}
+
+	return devm_snd_soc_register_component(dev, data->component_drv,
+					       data->dai_drv, 1);
+}
+EXPORT_SYMBOL_GPL(axg_fifo_probe);
+
+MODULE_DESCRIPTION("Amlogic AXG fifo driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/meson/axg-fifo.h b/sound/soc/meson/axg-fifo.h
new file mode 100644
index 000000000000..cb6c4013ca33
--- /dev/null
+++ b/sound/soc/meson/axg-fifo.h
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Copyright (c) 2018 BayLibre, SAS.
+ * Author: Jerome Brunet <jbrunet@baylibre.com>
+ */
+
+#ifndef _MESON_AXG_FIFO_H
+#define _MESON_AXG_FIFO_H
+
+struct clk;
+struct platform_device;
+struct regmap;
+struct reset_control;
+
+struct snd_soc_component_driver;
+struct snd_soc_dai;
+struct snd_soc_dai_driver;
+struct snd_pcm_ops;
+struct snd_soc_pcm_runtime;
+
+#define AXG_FIFO_CH_MAX			128
+#define AXG_FIFO_RATES			(SNDRV_PCM_RATE_5512 |		\
+					 SNDRV_PCM_RATE_8000_192000)
+#define AXG_FIFO_FORMATS		(SNDRV_PCM_FMTBIT_S8 |		\
+					 SNDRV_PCM_FMTBIT_S16_LE |	\
+					 SNDRV_PCM_FMTBIT_S20_LE |	\
+					 SNDRV_PCM_FMTBIT_S24_LE |	\
+					 SNDRV_PCM_FMTBIT_S32_LE)
+
+#define AXG_FIFO_BURST			8
+#define AXG_FIFO_MIN_CNT		64
+#define AXG_FIFO_MIN_DEPTH		(AXG_FIFO_BURST * AXG_FIFO_MIN_CNT)
+
+#define FIFO_INT_ADDR_FINISH		BIT(0)
+#define FIFO_INT_ADDR_INT		BIT(1)
+#define FIFO_INT_COUNT_REPEAT		BIT(2)
+#define FIFO_INT_COUNT_ONCE		BIT(3)
+#define FIFO_INT_FIFO_ZERO		BIT(4)
+#define FIFO_INT_FIFO_DEPTH		BIT(5)
+#define FIFO_INT_MASK			GENMASK(7, 0)
+
+#define FIFO_CTRL0			0x00
+#define  CTRL0_DMA_EN			BIT(31)
+#define  CTRL0_INT_EN(x)		((x) << 16)
+#define  CTRL0_SEL_MASK			GENMASK(2, 0)
+#define  CTRL0_SEL_SHIFT		0
+#define FIFO_CTRL1			0x04
+#define  CTRL1_INT_CLR(x)		((x) << 0)
+#define  CTRL1_STATUS2_SEL_MASK		GENMASK(11, 8)
+#define  CTRL1_STATUS2_SEL(x)		((x) << 8)
+#define   STATUS2_SEL_DDR_READ		0
+#define  CTRL1_THRESHOLD_MASK		GENMASK(23, 16)
+#define  CTRL1_THRESHOLD(x)		((x) << 16)
+#define  CTRL1_FRDDR_DEPTH_MASK		GENMASK(31, 24)
+#define  CTRL1_FRDDR_DEPTH(x)		((x) << 24)
+#define FIFO_START_ADDR			0x08
+#define FIFO_FINISH_ADDR		0x0c
+#define FIFO_INT_ADDR			0x10
+#define FIFO_STATUS1			0x14
+#define  STATUS1_INT_STS(x)		((x) << 0)
+#define FIFO_STATUS2			0x18
+
+struct axg_fifo {
+	struct regmap *map;
+	struct clk *pclk;
+	struct reset_control *arb;
+	int irq;
+};
+
+struct axg_fifo_match_data {
+	const struct snd_soc_component_driver *component_drv;
+	struct snd_soc_dai_driver *dai_drv;
+};
+
+extern const struct snd_pcm_ops axg_fifo_pcm_ops;
+
+int axg_fifo_pcm_new(struct snd_soc_pcm_runtime *rtd, unsigned int type);
+int axg_fifo_probe(struct platform_device *pdev);
+
+#endif /* _MESON_AXG_FIFO_H */
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 03/15] ASoC: meson: add axg frddr driver
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
  2018-07-17 15:36 ` [PATCH 01/15] ASoC: meson: add axg fifos DT binding documentation Jerome Brunet
  2018-07-17 15:36 ` [PATCH 02/15] ASoC: meson: add axg fifo base driver Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-18 12:26   ` Applied "ASoC: meson: add axg frddr driver" to the asoc tree Mark Brown
  2018-07-17 15:36 ` [PATCH 04/15] ASoC: meson: add axg toddr driver Jerome Brunet
                   ` (16 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add the playback memory interface of Amlogic's axg SoCs.
This device pulls data from DDR to an internal FIFO.
This FIFO is then used to feed TDM and SPDIF Output devices.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 sound/soc/meson/Kconfig     |   7 +++
 sound/soc/meson/Makefile    |   2 +
 sound/soc/meson/axg-frddr.c | 141 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 150 insertions(+)
 create mode 100644 sound/soc/meson/axg-frddr.c

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index c3eb5e050308..cdd78f62e8d7 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -5,4 +5,11 @@ config SND_MESON_AXG_FIFO
 	tristate
 	select REGMAP_MMIO
 
+config SND_MESON_AXG_FRDDR
+	tristate "Amlogic AXG Playback FIFO support"
+	select SND_MESON_AXG_FIFO
+	help
+	  Select Y or M to add support for the frontend playback interfaces
+	  embedded in the Amlogic AXG SoC family
+
 endmenu
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index 75289b6b3ade..9c5d7d4a8e33 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -1,5 +1,7 @@
 # SPDX-License-Identifier: (GPL-2.0 OR MIT)
 
 snd-soc-meson-axg-fifo-objs := axg-fifo.o
+snd-soc-meson-axg-frddr-objs := axg-frddr.o
 
 obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
+obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
diff --git a/sound/soc/meson/axg-frddr.c b/sound/soc/meson/axg-frddr.c
new file mode 100644
index 000000000000..a6f6f6a2eca8
--- /dev/null
+++ b/sound/soc/meson/axg-frddr.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+/* This driver implements the frontend playback DAI of AXG based SoCs */
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "axg-fifo.h"
+
+#define CTRL0_FRDDR_PP_MODE	BIT(30)
+
+static int axg_frddr_dai_startup(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
+	unsigned int fifo_depth, fifo_threshold;
+	int ret;
+
+	/* Enable pclk to access registers and clock the fifo ip */
+	ret = clk_prepare_enable(fifo->pclk);
+	if (ret)
+		return ret;
+
+	/* Apply single buffer mode to the interface */
+	regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_FRDDR_PP_MODE, 0);
+
+	/*
+	 * TODO: We could adapt the fifo depth and the fifo threshold
+	 * depending on the expected memory throughput and lantencies
+	 * For now, we'll just use the same values as the vendor kernel
+	 * Depth and threshold are zero based.
+	 */
+	fifo_depth = AXG_FIFO_MIN_CNT - 1;
+	fifo_threshold = (AXG_FIFO_MIN_CNT / 2) - 1;
+	regmap_update_bits(fifo->map, FIFO_CTRL1,
+			   CTRL1_FRDDR_DEPTH_MASK | CTRL1_THRESHOLD_MASK,
+			   CTRL1_FRDDR_DEPTH(fifo_depth) |
+			   CTRL1_THRESHOLD(fifo_threshold));
+
+	return 0;
+}
+
+static void axg_frddr_dai_shutdown(struct snd_pcm_substream *substream,
+				   struct snd_soc_dai *dai)
+{
+	struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
+
+	clk_disable_unprepare(fifo->pclk);
+}
+
+static int axg_frddr_pcm_new(struct snd_soc_pcm_runtime *rtd,
+			     struct snd_soc_dai *dai)
+{
+	return axg_fifo_pcm_new(rtd, SNDRV_PCM_STREAM_PLAYBACK);
+}
+
+static const struct snd_soc_dai_ops axg_frddr_ops = {
+	.startup	= axg_frddr_dai_startup,
+	.shutdown	= axg_frddr_dai_shutdown,
+};
+
+static struct snd_soc_dai_driver axg_frddr_dai_drv = {
+	.name = "FRDDR",
+	.playback = {
+		.stream_name	= "Playback",
+		.channels_min	= 1,
+		.channels_max	= AXG_FIFO_CH_MAX,
+		.rates		= AXG_FIFO_RATES,
+		.formats	= AXG_FIFO_FORMATS,
+	},
+	.ops		= &axg_frddr_ops,
+	.pcm_new	= axg_frddr_pcm_new,
+};
+
+static const char * const axg_frddr_sel_texts[] = {
+	"OUT 0", "OUT 1", "OUT 2", "OUT 3"
+};
+
+static SOC_ENUM_SINGLE_DECL(axg_frddr_sel_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT,
+			    axg_frddr_sel_texts);
+
+static const struct snd_kcontrol_new axg_frddr_out_demux =
+	SOC_DAPM_ENUM("Output Sink", axg_frddr_sel_enum);
+
+static const struct snd_soc_dapm_widget axg_frddr_dapm_widgets[] = {
+	SND_SOC_DAPM_DEMUX("SINK SEL", SND_SOC_NOPM, 0, 0,
+			   &axg_frddr_out_demux),
+	SND_SOC_DAPM_AIF_OUT("OUT 0", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("OUT 1", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("OUT 2", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("OUT 3", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route axg_frddr_dapm_routes[] = {
+	{ "SINK SEL", NULL, "Playback" },
+	{ "OUT 0", "OUT 0",  "SINK SEL" },
+	{ "OUT 1", "OUT 1",  "SINK SEL" },
+	{ "OUT 2", "OUT 2",  "SINK SEL" },
+	{ "OUT 3", "OUT 3",  "SINK SEL" },
+};
+
+static const struct snd_soc_component_driver axg_frddr_component_drv = {
+	.dapm_widgets		= axg_frddr_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(axg_frddr_dapm_widgets),
+	.dapm_routes		= axg_frddr_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(axg_frddr_dapm_routes),
+	.ops			= &axg_fifo_pcm_ops
+};
+
+static const struct axg_fifo_match_data axg_frddr_match_data = {
+	.component_drv	= &axg_frddr_component_drv,
+	.dai_drv	= &axg_frddr_dai_drv
+};
+
+static const struct of_device_id axg_frddr_of_match[] = {
+	{
+		.compatible = "amlogic,axg-frddr",
+		.data = &axg_frddr_match_data,
+	}, {}
+};
+MODULE_DEVICE_TABLE(of, axg_frddr_of_match);
+
+static struct platform_driver axg_frddr_pdrv = {
+	.probe = axg_fifo_probe,
+	.driver = {
+		.name = "axg-frddr",
+		.of_match_table = axg_frddr_of_match,
+	},
+};
+module_platform_driver(axg_frddr_pdrv);
+
+MODULE_DESCRIPTION("Amlogic AXG playback fifo driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 04/15] ASoC: meson: add axg toddr driver
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (2 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 03/15] ASoC: meson: add axg frddr driver Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-18 12:25   ` Applied "ASoC: meson: add axg toddr driver" to the asoc tree Mark Brown
  2018-07-17 15:36 ` [PATCH 05/15] ASoC: meson: add axg spdif output DT binding documentation Jerome Brunet
                   ` (15 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add the capture memory interface of Amlogic's axg SoCs.
TDM, SPDIF or PDM input devices place audio samples inside this FIFO.
The FIFO content is then pushed to DDR

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 sound/soc/meson/Kconfig     |   7 ++
 sound/soc/meson/Makefile    |   2 +
 sound/soc/meson/axg-toddr.c | 199 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 208 insertions(+)
 create mode 100644 sound/soc/meson/axg-toddr.c

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index cdd78f62e8d7..3916060edcae 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -12,4 +12,11 @@ config SND_MESON_AXG_FRDDR
 	  Select Y or M to add support for the frontend playback interfaces
 	  embedded in the Amlogic AXG SoC family
 
+config SND_MESON_AXG_TODDR
+	tristate "Amlogic AXG Capture FIFO support"
+	select SND_MESON_AXG_FIFO
+	help
+	  Select Y or M to add support for the frontend capture interfaces
+	  embedded in the Amlogic AXG SoC family
+
 endmenu
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index 9c5d7d4a8e33..12edf2db2a24 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -2,6 +2,8 @@
 
 snd-soc-meson-axg-fifo-objs := axg-fifo.o
 snd-soc-meson-axg-frddr-objs := axg-frddr.o
+snd-soc-meson-axg-toddr-objs := axg-toddr.o
 
 obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
 obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
+obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o
diff --git a/sound/soc/meson/axg-toddr.c b/sound/soc/meson/axg-toddr.c
new file mode 100644
index 000000000000..c2c9bb312586
--- /dev/null
+++ b/sound/soc/meson/axg-toddr.c
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+/* This driver implements the frontend capture DAI of AXG based SoCs */
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "axg-fifo.h"
+
+#define CTRL0_TODDR_SEL_RESAMPLE	BIT(30)
+#define CTRL0_TODDR_EXT_SIGNED		BIT(29)
+#define CTRL0_TODDR_PP_MODE		BIT(28)
+#define CTRL0_TODDR_TYPE_MASK		GENMASK(15, 13)
+#define CTRL0_TODDR_TYPE(x)		((x) << 13)
+#define CTRL0_TODDR_MSB_POS_MASK	GENMASK(12, 8)
+#define CTRL0_TODDR_MSB_POS(x)		((x) << 8)
+#define CTRL0_TODDR_LSB_POS_MASK	GENMASK(7, 3)
+#define CTRL0_TODDR_LSB_POS(x)		((x) << 3)
+
+static int axg_toddr_pcm_new(struct snd_soc_pcm_runtime *rtd,
+			     struct snd_soc_dai *dai)
+{
+	return axg_fifo_pcm_new(rtd, SNDRV_PCM_STREAM_CAPTURE);
+}
+
+static int axg_toddr_dai_hw_params(struct snd_pcm_substream *substream,
+				   struct snd_pcm_hw_params *params,
+				   struct snd_soc_dai *dai)
+{
+	struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
+	unsigned int type, width, msb = 31;
+
+	/*
+	 * NOTE:
+	 * Almost all backend will place the MSB at bit 31, except SPDIF Input
+	 * which will put it at index 28. When adding support for the SPDIF
+	 * Input, we'll need to find which type of backend we are connected to.
+	 */
+
+	switch (params_physical_width(params)) {
+	case 8:
+		type = 0; /* 8 samples of 8 bits */
+		break;
+	case 16:
+		type = 2; /* 4 samples of 16 bits - right justified */
+		break;
+	case 32:
+		type = 4; /* 2 samples of 32 bits - right justified */
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	width = params_width(params);
+
+	regmap_update_bits(fifo->map, FIFO_CTRL0,
+			   CTRL0_TODDR_TYPE_MASK |
+			   CTRL0_TODDR_MSB_POS_MASK |
+			   CTRL0_TODDR_LSB_POS_MASK,
+			   CTRL0_TODDR_TYPE(type) |
+			   CTRL0_TODDR_MSB_POS(msb) |
+			   CTRL0_TODDR_LSB_POS(msb - (width - 1)));
+
+	return 0;
+}
+
+static int axg_toddr_dai_startup(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
+	unsigned int fifo_threshold;
+	int ret;
+
+	/* Enable pclk to access registers and clock the fifo ip */
+	ret = clk_prepare_enable(fifo->pclk);
+	if (ret)
+		return ret;
+
+	/* Select orginal data - resampling not supported ATM */
+	regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_SEL_RESAMPLE, 0);
+
+	/* Only signed format are supported ATM */
+	regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_EXT_SIGNED,
+			   CTRL0_TODDR_EXT_SIGNED);
+
+	/* Apply single buffer mode to the interface */
+	regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_PP_MODE, 0);
+
+	/* TODDR does not have a configurable fifo depth */
+	fifo_threshold = AXG_FIFO_MIN_CNT - 1;
+	regmap_update_bits(fifo->map, FIFO_CTRL1, CTRL1_THRESHOLD_MASK,
+			   CTRL1_THRESHOLD(fifo_threshold));
+
+	return 0;
+}
+
+static void axg_toddr_dai_shutdown(struct snd_pcm_substream *substream,
+				   struct snd_soc_dai *dai)
+{
+	struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
+
+	clk_disable_unprepare(fifo->pclk);
+}
+
+static const struct snd_soc_dai_ops axg_toddr_ops = {
+	.hw_params	= axg_toddr_dai_hw_params,
+	.startup	= axg_toddr_dai_startup,
+	.shutdown	= axg_toddr_dai_shutdown,
+};
+
+static struct snd_soc_dai_driver axg_toddr_dai_drv = {
+	.name = "TODDR",
+	.capture = {
+		.stream_name	= "Capture",
+		.channels_min	= 1,
+		.channels_max	= AXG_FIFO_CH_MAX,
+		.rates		= AXG_FIFO_RATES,
+		.formats	= AXG_FIFO_FORMATS,
+	},
+	.ops		= &axg_toddr_ops,
+	.pcm_new	= axg_toddr_pcm_new,
+};
+
+static const char * const axg_toddr_sel_texts[] = {
+	"IN 0", "IN 1", "IN 2", "IN 3", "IN 4", "IN 6"
+};
+
+static const unsigned int axg_toddr_sel_values[] = {
+	0, 1, 2, 3, 4, 6
+};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(axg_toddr_sel_enum, FIFO_CTRL0,
+				  CTRL0_SEL_SHIFT, CTRL0_SEL_MASK,
+				  axg_toddr_sel_texts, axg_toddr_sel_values);
+
+static const struct snd_kcontrol_new axg_toddr_in_mux =
+	SOC_DAPM_ENUM("Input Source", axg_toddr_sel_enum);
+
+static const struct snd_soc_dapm_widget axg_toddr_dapm_widgets[] = {
+	SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_toddr_in_mux),
+	SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 6", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route axg_toddr_dapm_routes[] = {
+	{ "Capture", NULL, "SRC SEL" },
+	{ "SRC SEL", "IN 0", "IN 0" },
+	{ "SRC SEL", "IN 1", "IN 1" },
+	{ "SRC SEL", "IN 2", "IN 2" },
+	{ "SRC SEL", "IN 3", "IN 3" },
+	{ "SRC SEL", "IN 4", "IN 4" },
+	{ "SRC SEL", "IN 6", "IN 6" },
+};
+
+static const struct snd_soc_component_driver axg_toddr_component_drv = {
+	.dapm_widgets		= axg_toddr_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(axg_toddr_dapm_widgets),
+	.dapm_routes		= axg_toddr_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(axg_toddr_dapm_routes),
+	.ops			= &axg_fifo_pcm_ops
+};
+
+static const struct axg_fifo_match_data axg_toddr_match_data = {
+	.component_drv	= &axg_toddr_component_drv,
+	.dai_drv	= &axg_toddr_dai_drv
+};
+
+static const struct of_device_id axg_toddr_of_match[] = {
+	{
+		.compatible = "amlogic,axg-toddr",
+		.data = &axg_toddr_match_data,
+	}, {}
+};
+MODULE_DEVICE_TABLE(of, axg_toddr_of_match);
+
+static struct platform_driver axg_toddr_pdrv = {
+	.probe = axg_fifo_probe,
+	.driver = {
+		.name = "axg-toddr",
+		.of_match_table = axg_toddr_of_match,
+	},
+};
+module_platform_driver(axg_toddr_pdrv);
+
+MODULE_DESCRIPTION("Amlogic AXG capture fifo driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 05/15] ASoC: meson: add axg spdif output DT binding documentation
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (3 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 04/15] ASoC: meson: add axg toddr driver Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-18 12:25   ` Applied "ASoC: meson: add axg spdif output DT bindings documentation" to the asoc tree Mark Brown
  2018-07-17 15:36 ` [PATCH 05/15] ASoC: meson: add axg spdif output DT bindings documentation Jerome Brunet
                   ` (14 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add the DT binding documentation for axg's SPDIF output.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 .../bindings/sound/amlogic,axg-spdifout.txt          | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-spdifout.txt

diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-spdifout.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-spdifout.txt
new file mode 100644
index 000000000000..521c38ad89e7
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-spdifout.txt
@@ -0,0 +1,20 @@
+* Amlogic Audio SPDIF Output
+
+Required properties:
+- compatible: 'amlogic,axg-spdifout'
+- clocks: list of clock phandle, one for each entry clock-names.
+- clock-names: should contain the following:
+  * "pclk" : peripheral clock.
+  * "mclk" : master clock
+- #sound-dai-cells: must be 0.
+
+Example on the A113 SoC:
+
+spdifout: audio-controller@480 {
+	compatible = "amlogic,axg-spdifout";
+	reg = <0x0 0x480 0x0 0x50>;
+	#sound-dai-cells = <0>;
+	clocks = <&clkc_audio AUD_CLKID_SPDIFOUT>,
+		 <&clkc_audio AUD_CLKID_SPDIFOUT_CLK>;
+	clock-names = "pclk", "mclk";
+};
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 05/15] ASoC: meson: add axg spdif output DT bindings documentation
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (4 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 05/15] ASoC: meson: add axg spdif output DT binding documentation Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-17 15:36 ` [PATCH 06/15] ASoC: meson: add axg spdif output Jerome Brunet
                   ` (13 subsequent siblings)
  19 siblings, 0 replies; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add the DT bindings documentation for axg's SPDIF output.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 .../bindings/sound/amlogic,axg-spdifout.txt          | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-spdifout.txt

diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-spdifout.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-spdifout.txt
new file mode 100644
index 000000000000..521c38ad89e7
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-spdifout.txt
@@ -0,0 +1,20 @@
+* Amlogic Audio SPDIF Output
+
+Required properties:
+- compatible: 'amlogic,axg-spdifout'
+- clocks: list of clock phandle, one for each entry clock-names.
+- clock-names: should contain the following:
+  * "pclk" : peripheral clock.
+  * "mclk" : master clock
+- #sound-dai-cells: must be 0.
+
+Example on the A113 SoC:
+
+spdifout: audio-controller@480 {
+	compatible = "amlogic,axg-spdifout";
+	reg = <0x0 0x480 0x0 0x50>;
+	#sound-dai-cells = <0>;
+	clocks = <&clkc_audio AUD_CLKID_SPDIFOUT>,
+		 <&clkc_audio AUD_CLKID_SPDIFOUT_CLK>;
+	clock-names = "pclk", "mclk";
+};
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 06/15] ASoC: meson: add axg spdif output
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (5 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 05/15] ASoC: meson: add axg spdif output DT bindings documentation Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-18 12:25   ` Applied "ASoC: meson: add axg spdif output" to the asoc tree Mark Brown
  2018-07-17 15:36 ` [PATCH 07/15] ASoC: meson: add axg tdm formatters DT binding documentation Jerome Brunet
                   ` (12 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add support for the spdif output serializer of the axg SoC family

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 sound/soc/meson/Kconfig        |   7 +
 sound/soc/meson/Makefile       |   2 +
 sound/soc/meson/axg-spdifout.c | 456 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 465 insertions(+)
 create mode 100644 sound/soc/meson/axg-spdifout.c

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index 3916060edcae..9408214b5854 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -19,4 +19,11 @@ config SND_MESON_AXG_TODDR
 	  Select Y or M to add support for the frontend capture interfaces
 	  embedded in the Amlogic AXG SoC family
 
+config SND_MESON_AXG_SPDIFOUT
+	tristate "Amlogic AXG SPDIF Output Support"
+	imply SND_SOC_SPDIF
+	help
+	  Select Y or M to add support for SPDIF output serializer embedded
+	  in the Amlogic AXG SoC family
+
 endmenu
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index 12edf2db2a24..d51ae045ef08 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -3,7 +3,9 @@
 snd-soc-meson-axg-fifo-objs := axg-fifo.o
 snd-soc-meson-axg-frddr-objs := axg-frddr.o
 snd-soc-meson-axg-toddr-objs := axg-toddr.o
+snd-soc-meson-axg-spdifout-objs := axg-spdifout.o
 
 obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
 obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
 obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o
+obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o
diff --git a/sound/soc/meson/axg-spdifout.c b/sound/soc/meson/axg-spdifout.c
new file mode 100644
index 000000000000..9dea528053ad
--- /dev/null
+++ b/sound/soc/meson/axg-spdifout.c
@@ -0,0 +1,456 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <sound/pcm_params.h>
+#include <sound/pcm_iec958.h>
+
+/*
+ * NOTE:
+ * The meaning of bits SPDIFOUT_CTRL0_XXX_SEL is actually the opposite
+ * of what the documentation says. Manual control on V, U and C bits is
+ * applied when the related sel bits are cleared
+ */
+
+#define SPDIFOUT_STAT			0x00
+#define SPDIFOUT_GAIN0			0x04
+#define SPDIFOUT_GAIN1			0x08
+#define SPDIFOUT_CTRL0			0x0c
+#define  SPDIFOUT_CTRL0_EN		BIT(31)
+#define  SPDIFOUT_CTRL0_RST_OUT		BIT(29)
+#define  SPDIFOUT_CTRL0_RST_IN		BIT(28)
+#define  SPDIFOUT_CTRL0_USEL		BIT(26)
+#define  SPDIFOUT_CTRL0_USET		BIT(25)
+#define  SPDIFOUT_CTRL0_CHSTS_SEL	BIT(24)
+#define  SPDIFOUT_CTRL0_DATA_SEL	BIT(20)
+#define  SPDIFOUT_CTRL0_MSB_FIRST	BIT(19)
+#define  SPDIFOUT_CTRL0_VSEL		BIT(18)
+#define  SPDIFOUT_CTRL0_VSET		BIT(17)
+#define  SPDIFOUT_CTRL0_MASK_MASK	GENMASK(11, 4)
+#define  SPDIFOUT_CTRL0_MASK(x)		((x) << 4)
+#define SPDIFOUT_CTRL1			0x10
+#define  SPDIFOUT_CTRL1_MSB_POS_MASK	GENMASK(12, 8)
+#define  SPDIFOUT_CTRL1_MSB_POS(x)	((x) << 8)
+#define  SPDIFOUT_CTRL1_TYPE_MASK	GENMASK(6, 4)
+#define  SPDIFOUT_CTRL1_TYPE(x)		((x) << 4)
+#define SPDIFOUT_PREAMB			0x14
+#define SPDIFOUT_SWAP			0x18
+#define SPDIFOUT_CHSTS0			0x1c
+#define SPDIFOUT_CHSTS1			0x20
+#define SPDIFOUT_CHSTS2			0x24
+#define SPDIFOUT_CHSTS3			0x28
+#define SPDIFOUT_CHSTS4			0x2c
+#define SPDIFOUT_CHSTS5			0x30
+#define SPDIFOUT_CHSTS6			0x34
+#define SPDIFOUT_CHSTS7			0x38
+#define SPDIFOUT_CHSTS8			0x3c
+#define SPDIFOUT_CHSTS9			0x40
+#define SPDIFOUT_CHSTSA			0x44
+#define SPDIFOUT_CHSTSB			0x48
+#define SPDIFOUT_MUTE_VAL		0x4c
+
+struct axg_spdifout {
+	struct regmap *map;
+	struct clk *mclk;
+	struct clk *pclk;
+};
+
+static void axg_spdifout_enable(struct regmap *map)
+{
+	/* Apply both reset */
+	regmap_update_bits(map, SPDIFOUT_CTRL0,
+			   SPDIFOUT_CTRL0_RST_OUT | SPDIFOUT_CTRL0_RST_IN,
+			   0);
+
+	/* Clear out reset before in reset */
+	regmap_update_bits(map, SPDIFOUT_CTRL0,
+			   SPDIFOUT_CTRL0_RST_OUT, SPDIFOUT_CTRL0_RST_OUT);
+	regmap_update_bits(map, SPDIFOUT_CTRL0,
+			   SPDIFOUT_CTRL0_RST_IN,  SPDIFOUT_CTRL0_RST_IN);
+
+	/* Enable spdifout */
+	regmap_update_bits(map, SPDIFOUT_CTRL0, SPDIFOUT_CTRL0_EN,
+			   SPDIFOUT_CTRL0_EN);
+}
+
+static void axg_spdifout_disable(struct regmap *map)
+{
+	regmap_update_bits(map, SPDIFOUT_CTRL0, SPDIFOUT_CTRL0_EN, 0);
+}
+
+static int axg_spdifout_trigger(struct snd_pcm_substream *substream, int cmd,
+				struct snd_soc_dai *dai)
+{
+	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		axg_spdifout_enable(priv->map);
+		return 0;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		axg_spdifout_disable(priv->map);
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int axg_spdifout_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
+
+	/* Use spdif valid bit to perform digital mute */
+	regmap_update_bits(priv->map, SPDIFOUT_CTRL0, SPDIFOUT_CTRL0_VSET,
+			   mute ? SPDIFOUT_CTRL0_VSET : 0);
+
+	return 0;
+}
+
+static int axg_spdifout_sample_fmt(struct snd_pcm_hw_params *params,
+				   struct snd_soc_dai *dai)
+{
+	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
+	unsigned int val;
+
+	/* Set the samples spdifout will pull from the FIFO */
+	switch (params_channels(params)) {
+	case 1:
+		val = SPDIFOUT_CTRL0_MASK(0x1);
+		break;
+	case 2:
+		val = SPDIFOUT_CTRL0_MASK(0x3);
+		break;
+	default:
+		dev_err(dai->dev, "too many channels for spdif dai: %u\n",
+			params_channels(params));
+		return -EINVAL;
+	}
+
+	regmap_update_bits(priv->map, SPDIFOUT_CTRL0,
+			   SPDIFOUT_CTRL0_MASK_MASK, val);
+
+	/* FIFO data are arranged in chunks of 64bits */
+	switch (params_physical_width(params)) {
+	case 8:
+		/* 8 samples of 8 bits */
+		val = SPDIFOUT_CTRL1_TYPE(0);
+		break;
+	case 16:
+		/* 4 samples of 16 bits - right justified */
+		val = SPDIFOUT_CTRL1_TYPE(2);
+		break;
+	case 32:
+		/* 2 samples of 32 bits - right justified */
+		val = SPDIFOUT_CTRL1_TYPE(4);
+		break;
+	default:
+		dev_err(dai->dev, "Unsupported physical width: %u\n",
+			params_physical_width(params));
+		return -EINVAL;
+	}
+
+	/* Position of the MSB in FIFO samples */
+	val |= SPDIFOUT_CTRL1_MSB_POS(params_width(params) - 1);
+
+	regmap_update_bits(priv->map, SPDIFOUT_CTRL1,
+			   SPDIFOUT_CTRL1_MSB_POS_MASK |
+			   SPDIFOUT_CTRL1_TYPE_MASK, val);
+
+	regmap_update_bits(priv->map, SPDIFOUT_CTRL0,
+			   SPDIFOUT_CTRL0_MSB_FIRST | SPDIFOUT_CTRL0_DATA_SEL,
+			   0);
+
+	return 0;
+}
+
+static int axg_spdifout_set_chsts(struct snd_pcm_hw_params *params,
+				  struct snd_soc_dai *dai)
+{
+	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
+	unsigned int offset;
+	int ret;
+	u8 cs[4];
+	u32 val;
+
+	ret = snd_pcm_create_iec958_consumer_hw_params(params, cs, 4);
+	if (ret < 0) {
+		dev_err(dai->dev, "Creating IEC958 channel status failed %d\n",
+			ret);
+		return ret;
+	}
+	val = cs[0] | cs[1] << 8 | cs[2] << 16 | cs[3] << 24;
+
+	/* Setup channel status A bits [31 - 0]*/
+	regmap_write(priv->map, SPDIFOUT_CHSTS0, val);
+
+	/* Clear channel status A bits [191 - 32] */
+	for (offset = SPDIFOUT_CHSTS1; offset <= SPDIFOUT_CHSTS5;
+	     offset += regmap_get_reg_stride(priv->map))
+		regmap_write(priv->map, offset, 0);
+
+	/* Setup channel status B bits [31 - 0]*/
+	regmap_write(priv->map, SPDIFOUT_CHSTS6, val);
+
+	/* Clear channel status B bits [191 - 32] */
+	for (offset = SPDIFOUT_CHSTS7; offset <= SPDIFOUT_CHSTSB;
+	     offset += regmap_get_reg_stride(priv->map))
+		regmap_write(priv->map, offset, 0);
+
+	return 0;
+}
+
+static int axg_spdifout_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params,
+				  struct snd_soc_dai *dai)
+{
+	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
+	unsigned int rate = params_rate(params);
+	int ret;
+
+	/* 2 * 32bits per subframe * 2 channels = 128 */
+	ret = clk_set_rate(priv->mclk, rate * 128);
+	if (ret) {
+		dev_err(dai->dev, "failed to set spdif clock\n");
+		return ret;
+	}
+
+	ret = axg_spdifout_sample_fmt(params, dai);
+	if (ret) {
+		dev_err(dai->dev, "failed to setup sample format\n");
+		return ret;
+	}
+
+	ret = axg_spdifout_set_chsts(params, dai);
+	if (ret) {
+		dev_err(dai->dev, "failed to setup channel status words\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int axg_spdifout_startup(struct snd_pcm_substream *substream,
+				struct snd_soc_dai *dai)
+{
+	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
+	int ret;
+
+	/* Clock the spdif output block */
+	ret = clk_prepare_enable(priv->pclk);
+	if (ret) {
+		dev_err(dai->dev, "failed to enable pclk\n");
+		return ret;
+	}
+
+	/* Make sure the block is initially stopped */
+	axg_spdifout_disable(priv->map);
+
+	/* Insert data from bit 27 lsb first */
+	regmap_update_bits(priv->map, SPDIFOUT_CTRL0,
+			   SPDIFOUT_CTRL0_MSB_FIRST | SPDIFOUT_CTRL0_DATA_SEL,
+			   0);
+
+	/* Manual control of V, C and U, U = 0 */
+	regmap_update_bits(priv->map, SPDIFOUT_CTRL0,
+			   SPDIFOUT_CTRL0_CHSTS_SEL | SPDIFOUT_CTRL0_VSEL |
+			   SPDIFOUT_CTRL0_USEL | SPDIFOUT_CTRL0_USET,
+			   0);
+
+	/* Static SWAP configuration ATM */
+	regmap_write(priv->map, SPDIFOUT_SWAP, 0x10);
+
+	return 0;
+}
+
+static void axg_spdifout_shutdown(struct snd_pcm_substream *substream,
+				  struct snd_soc_dai *dai)
+{
+	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
+
+	clk_disable_unprepare(priv->pclk);
+}
+
+static const struct snd_soc_dai_ops axg_spdifout_ops = {
+	.trigger	= axg_spdifout_trigger,
+	.digital_mute	= axg_spdifout_digital_mute,
+	.hw_params	= axg_spdifout_hw_params,
+	.startup	= axg_spdifout_startup,
+	.shutdown	= axg_spdifout_shutdown,
+};
+
+static struct snd_soc_dai_driver axg_spdifout_dai_drv[] = {
+	{
+		.name = "SPDIF Output",
+		.playback = {
+			.stream_name	= "Playback",
+			.channels_min	= 1,
+			.channels_max	= 2,
+			.rates		= (SNDRV_PCM_RATE_32000  |
+					   SNDRV_PCM_RATE_44100  |
+					   SNDRV_PCM_RATE_48000  |
+					   SNDRV_PCM_RATE_88200  |
+					   SNDRV_PCM_RATE_96000  |
+					   SNDRV_PCM_RATE_176400 |
+					   SNDRV_PCM_RATE_192000),
+			.formats	= (SNDRV_PCM_FMTBIT_S8     |
+					   SNDRV_PCM_FMTBIT_S16_LE |
+					   SNDRV_PCM_FMTBIT_S20_LE |
+					   SNDRV_PCM_FMTBIT_S24_LE),
+		},
+		.ops = &axg_spdifout_ops,
+	},
+};
+
+static const char * const spdifout_sel_texts[] = {
+	"IN 0", "IN 1", "IN 2",
+};
+
+static SOC_ENUM_SINGLE_DECL(axg_spdifout_sel_enum, SPDIFOUT_CTRL1, 24,
+			    spdifout_sel_texts);
+
+static const struct snd_kcontrol_new axg_spdifout_in_mux =
+	SOC_DAPM_ENUM("Input Source", axg_spdifout_sel_enum);
+
+static const struct snd_soc_dapm_widget axg_spdifout_dapm_widgets[] = {
+	SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_spdifout_in_mux),
+};
+
+static const struct snd_soc_dapm_route axg_spdifout_dapm_routes[] = {
+	{ "SRC SEL", "IN 0", "IN 0" },
+	{ "SRC SEL", "IN 1", "IN 1" },
+	{ "SRC SEL", "IN 2", "IN 2" },
+	{ "Playback", NULL, "SRC SEL" },
+};
+
+static const struct snd_kcontrol_new axg_spdifout_controls[] = {
+	SOC_DOUBLE("Playback Volume", SPDIFOUT_GAIN0,  0,  8, 255, 0),
+	SOC_DOUBLE("Playback Switch", SPDIFOUT_CTRL0, 22, 21, 1, 1),
+	SOC_SINGLE("Playback Gain Enable Switch",
+		   SPDIFOUT_CTRL1, 26, 1, 0),
+	SOC_SINGLE("Playback Channels Mix Switch",
+		   SPDIFOUT_CTRL0, 23, 1, 0),
+};
+
+static int axg_spdifout_set_bias_level(struct snd_soc_component *component,
+				       enum snd_soc_bias_level level)
+{
+	struct axg_spdifout *priv = snd_soc_component_get_drvdata(component);
+	enum snd_soc_bias_level now =
+		snd_soc_component_get_bias_level(component);
+	int ret = 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (now == SND_SOC_BIAS_STANDBY)
+			ret = clk_prepare_enable(priv->mclk);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (now == SND_SOC_BIAS_PREPARE)
+			clk_disable_unprepare(priv->mclk);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+	case SND_SOC_BIAS_ON:
+		break;
+	}
+
+	return ret;
+}
+
+static const struct snd_soc_component_driver axg_spdifout_component_drv = {
+	.controls		= axg_spdifout_controls,
+	.num_controls		= ARRAY_SIZE(axg_spdifout_controls),
+	.dapm_widgets		= axg_spdifout_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(axg_spdifout_dapm_widgets),
+	.dapm_routes		= axg_spdifout_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(axg_spdifout_dapm_routes),
+	.set_bias_level		= axg_spdifout_set_bias_level,
+};
+
+static const struct regmap_config axg_spdifout_regmap_cfg = {
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_stride	= 4,
+	.max_register	= SPDIFOUT_MUTE_VAL,
+};
+
+static const struct of_device_id axg_spdifout_of_match[] = {
+	{ .compatible = "amlogic,axg-spdifout", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, axg_spdifout_of_match);
+
+static int axg_spdifout_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct axg_spdifout *priv;
+	struct resource *res;
+	void __iomem *regs;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, priv);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	priv->map = devm_regmap_init_mmio(dev, regs, &axg_spdifout_regmap_cfg);
+	if (IS_ERR(priv->map)) {
+		dev_err(dev, "failed to init regmap: %ld\n",
+			PTR_ERR(priv->map));
+		return PTR_ERR(priv->map);
+	}
+
+	priv->pclk = devm_clk_get(dev, "pclk");
+	if (IS_ERR(priv->pclk)) {
+		ret = PTR_ERR(priv->pclk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get pclk: %d\n", ret);
+		return ret;
+	}
+
+	priv->mclk = devm_clk_get(dev, "mclk");
+	if (IS_ERR(priv->mclk)) {
+		ret = PTR_ERR(priv->mclk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get mclk: %d\n", ret);
+		return ret;
+	}
+
+	return devm_snd_soc_register_component(dev, &axg_spdifout_component_drv,
+			axg_spdifout_dai_drv, ARRAY_SIZE(axg_spdifout_dai_drv));
+}
+
+static struct platform_driver axg_spdifout_pdrv = {
+	.probe = axg_spdifout_probe,
+	.driver = {
+		.name = "axg-spdifout",
+		.of_match_table = axg_spdifout_of_match,
+	},
+};
+module_platform_driver(axg_spdifout_pdrv);
+
+MODULE_DESCRIPTION("Amlogic AXG SPDIF Output driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 07/15] ASoC: meson: add axg tdm formatters DT binding documentation
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (6 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 06/15] ASoC: meson: add axg spdif output Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-18 12:25   ` Applied "ASoC: meson: add axg tdm formatters DT bindings documentation" to the asoc tree Mark Brown
  2018-07-17 15:36 ` [PATCH 07/15] ASoC: meson: add axg tdm formatters DT bindings documentation Jerome Brunet
                   ` (11 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add the DT binding documentation for axg's TDM formatters: TDMIN
and TDMOUT.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 .../bindings/sound/amlogic,axg-tdm-formatters.txt  | 28 ++++++++++++++++++++++
 1 file changed, 28 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.txt

diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.txt
new file mode 100644
index 000000000000..1c1b7490554e
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.txt
@@ -0,0 +1,28 @@
+* Amlogic Audio TDM formatters
+
+Required properties:
+- compatible: 'amlogic,axg-tdmin' or
+	      'amlogic,axg-tdmout'
+- reg: physical base address of the controller and length of memory
+       mapped region.
+- clocks: list of clock phandle, one for each entry clock-names.
+- clock-names: should contain the following:
+  * "pclk"     : peripheral clock.
+  * "sclk"     : bit clock.
+  * "sclk_sel" : bit clock input multiplexer.
+  * "lrclk"    : sample clock
+  * "lrclk_sel": sample clock input multiplexer
+
+Example of TDMOUT_A on the A113 SoC:
+
+tdmout_a: audio-controller@500 {
+	compatible = "amlogic,axg-tdmout";
+	reg = <0x0 0x500 0x0 0x40>;
+	clocks = <&clkc_audio AUD_CLKID_TDMOUT_A>,
+		 <&clkc_audio AUD_CLKID_TDMOUT_A_SCLK>,
+		 <&clkc_audio AUD_CLKID_TDMOUT_A_SCLK_SEL>,
+		 <&clkc_audio AUD_CLKID_TDMOUT_A_LRCLK>,
+		 <&clkc_audio AUD_CLKID_TDMOUT_A_LRCLK>;
+	clock-names = "pclk", "sclk", "sclk_sel",
+		      "lrclk", "lrclk_sel";
+};
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 07/15] ASoC: meson: add axg tdm formatters DT bindings documentation
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (7 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 07/15] ASoC: meson: add axg tdm formatters DT binding documentation Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-17 15:36 ` [PATCH 08/15] ASoC: meson: add axg tdm interface DT binding documentation Jerome Brunet
                   ` (10 subsequent siblings)
  19 siblings, 0 replies; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add the DT bindings documentation for axg's TDM formatters: TDMIN
and TDMOUT.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 .../bindings/sound/amlogic,axg-tdm-formatters.txt  | 28 ++++++++++++++++++++++
 1 file changed, 28 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.txt

diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.txt
new file mode 100644
index 000000000000..1c1b7490554e
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.txt
@@ -0,0 +1,28 @@
+* Amlogic Audio TDM formatters
+
+Required properties:
+- compatible: 'amlogic,axg-tdmin' or
+	      'amlogic,axg-tdmout'
+- reg: physical base address of the controller and length of memory
+       mapped region.
+- clocks: list of clock phandle, one for each entry clock-names.
+- clock-names: should contain the following:
+  * "pclk"     : peripheral clock.
+  * "sclk"     : bit clock.
+  * "sclk_sel" : bit clock input multiplexer.
+  * "lrclk"    : sample clock
+  * "lrclk_sel": sample clock input multiplexer
+
+Example of TDMOUT_A on the A113 SoC:
+
+tdmout_a: audio-controller@500 {
+	compatible = "amlogic,axg-tdmout";
+	reg = <0x0 0x500 0x0 0x40>;
+	clocks = <&clkc_audio AUD_CLKID_TDMOUT_A>,
+		 <&clkc_audio AUD_CLKID_TDMOUT_A_SCLK>,
+		 <&clkc_audio AUD_CLKID_TDMOUT_A_SCLK_SEL>,
+		 <&clkc_audio AUD_CLKID_TDMOUT_A_LRCLK>,
+		 <&clkc_audio AUD_CLKID_TDMOUT_A_LRCLK>;
+	clock-names = "pclk", "sclk", "sclk_sel",
+		      "lrclk", "lrclk_sel";
+};
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 08/15] ASoC: meson: add axg tdm interface DT binding documentation
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (8 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 07/15] ASoC: meson: add axg tdm formatters DT bindings documentation Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-20 16:45   ` Applied "ASoC: meson: add axg tdm interface DT bindings documentation" to the asoc tree Mark Brown
  2018-07-17 15:36 ` [PATCH 08/15] ASoC: meson: add axg tdm interface DT bindings documentation Jerome Brunet
                   ` (9 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add the DT binding documentation for axg's TDM interfaces

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 .../bindings/sound/amlogic,axg-tdm-iface.txt       | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt

diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt
new file mode 100644
index 000000000000..cabfb26a5f22
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt
@@ -0,0 +1,22 @@
+* Amlogic Audio TDM Interfaces
+
+Required properties:
+- compatible: 'amlogic,axg-tdm-iface'
+- clocks: list of clock phandle, one for each entry clock-names.
+- clock-names: should contain the following:
+  * "sclk" : bit clock.
+  * "lrclk": sample clock
+  * "mclk" : master clock
+	     -> optional if the interface is in clock slave mode.
+- #sound-dai-cells: must be 0.
+
+Example of TDM_A on the A113 SoC:
+
+tdmif_a: audio-controller@0 {
+	compatible = "amlogic,axg-tdm-iface";
+	#sound-dai-cells = <0>;
+	clocks = <&clkc_audio AUD_CLKID_MST_A_MCLK>,
+		 <&clkc_audio AUD_CLKID_MST_A_SCLK>,
+		 <&clkc_audio AUD_CLKID_MST_A_LRCLK>;
+	clock-names = "mclk", "sclk", "lrclk";
+};
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 08/15] ASoC: meson: add axg tdm interface DT bindings documentation
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (9 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 08/15] ASoC: meson: add axg tdm interface DT binding documentation Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-17 15:36 ` [PATCH 09/15] ASoC: meson: add tdm formatter base driver Jerome Brunet
                   ` (8 subsequent siblings)
  19 siblings, 0 replies; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add the DT bindings documentation for axg's TDM interfaces

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 .../bindings/sound/amlogic,axg-tdm-iface.txt       | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt

diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt
new file mode 100644
index 000000000000..cabfb26a5f22
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt
@@ -0,0 +1,22 @@
+* Amlogic Audio TDM Interfaces
+
+Required properties:
+- compatible: 'amlogic,axg-tdm-iface'
+- clocks: list of clock phandle, one for each entry clock-names.
+- clock-names: should contain the following:
+  * "sclk" : bit clock.
+  * "lrclk": sample clock
+  * "mclk" : master clock
+	     -> optional if the interface is in clock slave mode.
+- #sound-dai-cells: must be 0.
+
+Example of TDM_A on the A113 SoC:
+
+tdmif_a: audio-controller@0 {
+	compatible = "amlogic,axg-tdm-iface";
+	#sound-dai-cells = <0>;
+	clocks = <&clkc_audio AUD_CLKID_MST_A_MCLK>,
+		 <&clkc_audio AUD_CLKID_MST_A_SCLK>,
+		 <&clkc_audio AUD_CLKID_MST_A_LRCLK>;
+	clock-names = "mclk", "sclk", "lrclk";
+};
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 09/15] ASoC: meson: add tdm formatter base driver
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (10 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 08/15] ASoC: meson: add axg tdm interface DT bindings documentation Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-20 16:44   ` Applied "ASoC: meson: add tdm formatter base driver" to the asoc tree Mark Brown
  2018-07-17 15:36 ` [PATCH 10/15] ASoC: meson: add tdm interface driver Jerome Brunet
                   ` (7 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add Amlogic's axg TDM core driver. On this SoC, tdm is bit more
complex than usual, mainly because the different TDM input decoders can
be attached to any of TDM pad interface, including the output pads.

For the this, TDM on this SoC is modeled like this:
- TDM interface provides the DAIs the codecs will be attached to.
  The main responsibility of this driver is to manage the pad format
  and the TDM clock rates.
- TDM Formatters: These are the entities which are actually dealing with
  the TDM signal. TDMOUT produce a TDM signal from the audio sample
  provided by FRDDR using the clocks provided the TDM interface. TDMIN
  feeds TODDR with audio sample using the clocks and TDM signal provided
  by the TDM Interface.
- TDM Streams: This provides the link between 1 DAI stream of the TDM
  interface and one (or more) TDM formatters.

This driver provides the TDM formatter and TDM stream operations.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 sound/soc/meson/Kconfig             |   4 +
 sound/soc/meson/Makefile            |   2 +
 sound/soc/meson/axg-tdm-formatter.c | 381 ++++++++++++++++++++++++++++++++++++
 sound/soc/meson/axg-tdm-formatter.h |  39 ++++
 sound/soc/meson/axg-tdm.h           |  74 +++++++
 5 files changed, 500 insertions(+)
 create mode 100644 sound/soc/meson/axg-tdm-formatter.c
 create mode 100644 sound/soc/meson/axg-tdm-formatter.h
 create mode 100644 sound/soc/meson/axg-tdm.h

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index 9408214b5854..80a88689491f 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -19,6 +19,10 @@ config SND_MESON_AXG_TODDR
 	  Select Y or M to add support for the frontend capture interfaces
 	  embedded in the Amlogic AXG SoC family
 
+config SND_MESON_AXG_TDM_FORMATTER
+	tristate
+	select REGMAP_MMIO
+
 config SND_MESON_AXG_SPDIFOUT
 	tristate "Amlogic AXG SPDIF Output Support"
 	imply SND_SOC_SPDIF
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index d51ae045ef08..a06b56a1c995 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -3,9 +3,11 @@
 snd-soc-meson-axg-fifo-objs := axg-fifo.o
 snd-soc-meson-axg-frddr-objs := axg-frddr.o
 snd-soc-meson-axg-toddr-objs := axg-toddr.o
+snd-soc-meson-axg-tdm-formatter-objs := axg-tdm-formatter.o
 snd-soc-meson-axg-spdifout-objs := axg-spdifout.o
 
 obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
 obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
 obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o
+obj-$(CONFIG_SND_MESON_AXG_TDM_FORMATTER) += snd-soc-meson-axg-tdm-formatter.o
 obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o
diff --git a/sound/soc/meson/axg-tdm-formatter.c b/sound/soc/meson/axg-tdm-formatter.c
new file mode 100644
index 000000000000..43e390f9358a
--- /dev/null
+++ b/sound/soc/meson/axg-tdm-formatter.c
@@ -0,0 +1,381 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+
+#include "axg-tdm-formatter.h"
+
+struct axg_tdm_formatter {
+	struct list_head list;
+	struct axg_tdm_stream *stream;
+	const struct axg_tdm_formatter_driver *drv;
+	struct clk *pclk;
+	struct clk *sclk;
+	struct clk *lrclk;
+	struct clk *sclk_sel;
+	struct clk *lrclk_sel;
+	bool enabled;
+	struct regmap *map;
+};
+
+int axg_tdm_formatter_set_channel_masks(struct regmap *map,
+					struct axg_tdm_stream *ts,
+					unsigned int offset)
+{
+	unsigned int val, ch = ts->channels;
+	unsigned long mask;
+	int i, j;
+
+	/*
+	 * Distribute the channels of the stream over the available slots
+	 * of each TDM lane
+	 */
+	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;
+		}
+
+		regmap_write(map, offset, val);
+		offset += regmap_get_reg_stride(map);
+	}
+
+	/*
+	 * 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;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(axg_tdm_formatter_set_channel_masks);
+
+static int axg_tdm_formatter_enable(struct axg_tdm_formatter *formatter)
+{
+	struct axg_tdm_stream *ts = formatter->stream;
+	bool invert = formatter->drv->invert_sclk;
+	int ret;
+
+	/* Do nothing if the formatter is already enabled */
+	if (formatter->enabled)
+		return 0;
+
+	/*
+	 * If sclk is inverted, invert it back and provide the inversion
+	 * required by the formatter
+	 */
+	invert ^= axg_tdm_sclk_invert(ts->iface->fmt);
+	ret = clk_set_phase(formatter->sclk, invert ? 180 : 0);
+	if (ret)
+		return ret;
+
+	/* Setup the stream parameter in the formatter */
+	ret = formatter->drv->ops->prepare(formatter->map, 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;
+	}
+
+	/* Finally, actually enable the formatter */
+	formatter->drv->ops->enable(formatter->map);
+	formatter->enabled = true;
+
+	return 0;
+}
+
+static void axg_tdm_formatter_disable(struct axg_tdm_formatter *formatter)
+{
+	/* Do nothing if the formatter is already disabled */
+	if (!formatter->enabled)
+		return;
+
+	formatter->drv->ops->disable(formatter->map);
+	clk_disable_unprepare(formatter->lrclk);
+	clk_disable_unprepare(formatter->sclk);
+	formatter->enabled = false;
+}
+
+static int 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;
+		}
+	}
+
+	list_add_tail(&formatter->list, &ts->formatter_list);
+out:
+	mutex_unlock(&ts->lock);
+	return ret;
+}
+
+static void axg_tdm_formatter_dettach(struct axg_tdm_formatter *formatter)
+{
+	struct axg_tdm_stream *ts = formatter->stream;
+
+	mutex_lock(&ts->lock);
+	list_del(&formatter->list);
+	mutex_unlock(&ts->lock);
+
+	axg_tdm_formatter_disable(formatter);
+}
+
+static int axg_tdm_formatter_power_up(struct axg_tdm_formatter *formatter,
+				      struct snd_soc_dapm_widget *w)
+{
+	struct axg_tdm_stream *ts = formatter->drv->ops->get_stream(w);
+	int ret;
+
+	/*
+	 * 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;
+
+	return 0;
+
+disable_pclk:
+	clk_disable_unprepare(formatter->pclk);
+	return ret;
+}
+
+static void axg_tdm_formatter_power_down(struct axg_tdm_formatter *formatter)
+{
+	axg_tdm_formatter_dettach(formatter);
+	clk_disable_unprepare(formatter->pclk);
+	formatter->stream = NULL;
+}
+
+int axg_tdm_formatter_event(struct snd_soc_dapm_widget *w,
+			    struct snd_kcontrol *control,
+			    int event)
+{
+	struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
+	struct axg_tdm_formatter *formatter = snd_soc_component_get_drvdata(c);
+	int ret = 0;
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		ret = axg_tdm_formatter_power_up(formatter, w);
+		break;
+
+	case SND_SOC_DAPM_PRE_PMD:
+		axg_tdm_formatter_power_down(formatter);
+		break;
+
+	default:
+		dev_err(c->dev, "Unexpected event %d\n", event);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(axg_tdm_formatter_event);
+
+int axg_tdm_formatter_probe(struct platform_device *pdev)
+{
+	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;
+
+	drv = of_device_get_match_data(dev);
+	if (!drv) {
+		dev_err(dev, "failed to match device\n");
+		return -ENODEV;
+	}
+
+	formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL);
+	if (!formatter)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, formatter);
+	formatter->drv = drv;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	regs = devm_ioremap_resource(dev, res);
+	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)) {
+		ret = PTR_ERR(formatter->pclk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get pclk: %d\n", ret);
+		return ret;
+	}
+
+	/* Formatter bit clock */
+	formatter->sclk = devm_clk_get(dev, "sclk");
+	if (IS_ERR(formatter->sclk)) {
+		ret = PTR_ERR(formatter->sclk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get sclk: %d\n", ret);
+		return ret;
+	}
+
+	/* Formatter sample clock */
+	formatter->lrclk = devm_clk_get(dev, "lrclk");
+	if (IS_ERR(formatter->lrclk)) {
+		ret = PTR_ERR(formatter->lrclk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get lrclk: %d\n", ret);
+		return ret;
+	}
+
+	/* Formatter bit clock input multiplexer */
+	formatter->sclk_sel = devm_clk_get(dev, "sclk_sel");
+	if (IS_ERR(formatter->sclk_sel)) {
+		ret = PTR_ERR(formatter->sclk_sel);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get sclk_sel: %d\n", ret);
+		return ret;
+	}
+
+	/* Formatter sample clock input multiplexer */
+	formatter->lrclk_sel = devm_clk_get(dev, "lrclk_sel");
+	if (IS_ERR(formatter->lrclk_sel)) {
+		ret = PTR_ERR(formatter->lrclk_sel);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get lrclk_sel: %d\n", ret);
+		return ret;
+	}
+
+	return devm_snd_soc_register_component(dev, drv->component_drv,
+					       NULL, 0);
+}
+EXPORT_SYMBOL_GPL(axg_tdm_formatter_probe);
+
+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;
+		}
+	}
+
+out:
+	mutex_unlock(&ts->lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(axg_tdm_stream_start);
+
+void axg_tdm_stream_stop(struct axg_tdm_stream *ts)
+{
+	struct axg_tdm_formatter *formatter;
+
+	mutex_lock(&ts->lock);
+	ts->ready = false;
+
+	/* Stop all the formatters attached to the stream */
+	list_for_each_entry(formatter, &ts->formatter_list, list) {
+		axg_tdm_formatter_disable(formatter);
+	}
+
+	mutex_unlock(&ts->lock);
+}
+EXPORT_SYMBOL_GPL(axg_tdm_stream_stop);
+
+struct axg_tdm_stream *axg_tdm_stream_alloc(struct axg_tdm_iface *iface)
+{
+	struct axg_tdm_stream *ts;
+
+	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+	if (ts) {
+		INIT_LIST_HEAD(&ts->formatter_list);
+		mutex_init(&ts->lock);
+		ts->iface = iface;
+	}
+
+	return ts;
+}
+EXPORT_SYMBOL_GPL(axg_tdm_stream_alloc);
+
+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
+	 * 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);
+
+MODULE_DESCRIPTION("Amlogic AXG TDM formatter driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/meson/axg-tdm-formatter.h b/sound/soc/meson/axg-tdm-formatter.h
new file mode 100644
index 000000000000..cf947caf3cb1
--- /dev/null
+++ b/sound/soc/meson/axg-tdm-formatter.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT)
+ *
+ * Copyright (c) 2018 Baylibre SAS.
+ * Author: Jerome Brunet <jbrunet@baylibre.com>
+ */
+
+#ifndef _MESON_AXG_TDM_FORMATTER_H
+#define _MESON_AXG_TDM_FORMATTER_H
+
+#include "axg-tdm.h"
+
+struct platform_device;
+struct regmap;
+struct snd_soc_dapm_widget;
+struct snd_kcontrol;
+
+struct axg_tdm_formatter_ops {
+	struct axg_tdm_stream *(*get_stream)(struct snd_soc_dapm_widget *w);
+	void (*enable)(struct regmap *map);
+	void (*disable)(struct regmap *map);
+	int (*prepare)(struct regmap *map, struct axg_tdm_stream *ts);
+};
+
+struct axg_tdm_formatter_driver {
+	const struct snd_soc_component_driver *component_drv;
+	const struct regmap_config *regmap_cfg;
+	const struct axg_tdm_formatter_ops *ops;
+	bool invert_sclk;
+};
+
+int axg_tdm_formatter_set_channel_masks(struct regmap *map,
+					struct axg_tdm_stream *ts,
+					unsigned int offset);
+int axg_tdm_formatter_event(struct snd_soc_dapm_widget *w,
+			    struct snd_kcontrol *control,
+			    int event);
+int axg_tdm_formatter_probe(struct platform_device *pdev);
+
+#endif /* _MESON_AXG_TDM_FORMATTER_H */
diff --git a/sound/soc/meson/axg-tdm.h b/sound/soc/meson/axg-tdm.h
new file mode 100644
index 000000000000..435d95b86457
--- /dev/null
+++ b/sound/soc/meson/axg-tdm.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT)
+ *
+ * Copyright (c) 2018 Baylibre SAS.
+ * Author: Jerome Brunet <jbrunet@baylibre.com>
+ */
+
+#ifndef _MESON_AXG_TDM_H
+#define _MESON_AXG_TDM_H
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#define AXG_TDM_NUM_LANES	4
+#define AXG_TDM_CHANNEL_MAX	128
+#define AXG_TDM_RATES		(SNDRV_PCM_RATE_5512 |		\
+				 SNDRV_PCM_RATE_8000_192000)
+#define AXG_TDM_FORMATS		(SNDRV_PCM_FMTBIT_S8 |		\
+				 SNDRV_PCM_FMTBIT_S16_LE |	\
+				 SNDRV_PCM_FMTBIT_S20_LE |	\
+				 SNDRV_PCM_FMTBIT_S24_LE |	\
+				 SNDRV_PCM_FMTBIT_S32_LE)
+
+struct axg_tdm_iface {
+	struct clk *sclk;
+	struct clk *lrclk;
+	struct clk *mclk;
+	unsigned long mclk_rate;
+
+	/* format is common to all the DAIs of the iface */
+	unsigned int fmt;
+	unsigned int slots;
+	unsigned int slot_width;
+
+	/* For component wide symmetry */
+	int rate;
+};
+
+static inline bool axg_tdm_lrclk_invert(unsigned int fmt)
+{
+	return (fmt & SND_SOC_DAIFMT_I2S) ^
+		!!(fmt & (SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_NB_IF));
+}
+
+static inline bool axg_tdm_sclk_invert(unsigned int fmt)
+{
+	return fmt & (SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_IB_NF);
+}
+
+struct axg_tdm_stream {
+	struct axg_tdm_iface *iface;
+	struct list_head formatter_list;
+	struct mutex lock;
+	unsigned int channels;
+	unsigned int width;
+	unsigned int physical_width;
+	u32 *mask;
+	bool ready;
+};
+
+struct axg_tdm_stream *axg_tdm_stream_alloc(struct axg_tdm_iface *iface);
+void axg_tdm_stream_free(struct axg_tdm_stream *ts);
+int axg_tdm_stream_start(struct axg_tdm_stream *ts);
+void axg_tdm_stream_stop(struct axg_tdm_stream *ts);
+
+static inline int axg_tdm_stream_reset(struct axg_tdm_stream *ts)
+{
+	axg_tdm_stream_stop(ts);
+	return axg_tdm_stream_start(ts);
+}
+
+#endif /* _MESON_AXG_TDM_H */
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 10/15] ASoC: meson: add tdm interface driver
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (11 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 09/15] ASoC: meson: add tdm formatter base driver Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-20 16:44   ` Applied "ASoC: meson: add tdm interface driver" to the asoc tree Mark Brown
  2018-07-17 15:36 ` [PATCH 11/15] ASoC: meson: add tdm output driver Jerome Brunet
                   ` (6 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add Amlogic's axg TDM interface driver. This driver manages the format
and clocks provided on the pads.

On this SoC, each stream direction provides 4 serial lanes. This makes
a maximum of 8 channels in i2s modes and 128 channels in DSP modes.

While each lanes operate on the same slot number (same bit clock), they
may have different TDM masks. This requires to provide a function to let
the card set the 4 masks, in lieu of the usual set_tdm_slots() callback
of the dai driver.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 sound/soc/meson/Kconfig             |   4 +
 sound/soc/meson/Makefile            |   2 +
 sound/soc/meson/axg-tdm-interface.c | 542 ++++++++++++++++++++++++++++++++++++
 sound/soc/meson/axg-tdm.h           |   4 +
 4 files changed, 552 insertions(+)
 create mode 100644 sound/soc/meson/axg-tdm-interface.c

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index 80a88689491f..869b359c2ce7 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -23,6 +23,10 @@ config SND_MESON_AXG_TDM_FORMATTER
 	tristate
 	select REGMAP_MMIO
 
+config SND_MESON_AXG_TDM_INTERFACE
+	tristate
+	select SND_MESON_AXG_TDM_FORMATTER
+
 config SND_MESON_AXG_SPDIFOUT
 	tristate "Amlogic AXG SPDIF Output Support"
 	imply SND_SOC_SPDIF
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index a06b56a1c995..1a8eb77402e3 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -4,10 +4,12 @@ snd-soc-meson-axg-fifo-objs := axg-fifo.o
 snd-soc-meson-axg-frddr-objs := axg-frddr.o
 snd-soc-meson-axg-toddr-objs := axg-toddr.o
 snd-soc-meson-axg-tdm-formatter-objs := axg-tdm-formatter.o
+snd-soc-meson-axg-tdm-interface-objs := axg-tdm-interface.o
 snd-soc-meson-axg-spdifout-objs := axg-spdifout.o
 
 obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
 obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
 obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o
 obj-$(CONFIG_SND_MESON_AXG_TDM_FORMATTER) += snd-soc-meson-axg-tdm-formatter.o
+obj-$(CONFIG_SND_MESON_AXG_TDM_INTERFACE) += snd-soc-meson-axg-tdm-interface.o
 obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o
diff --git a/sound/soc/meson/axg-tdm-interface.c b/sound/soc/meson/axg-tdm-interface.c
new file mode 100644
index 000000000000..7b8baf46d968
--- /dev/null
+++ b/sound/soc/meson/axg-tdm-interface.c
@@ -0,0 +1,542 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "axg-tdm.h"
+
+enum {
+	TDM_IFACE_PAD,
+	TDM_IFACE_LOOPBACK,
+};
+
+static unsigned int axg_tdm_slots_total(u32 *mask)
+{
+	unsigned int slots = 0;
+	int i;
+
+	if (!mask)
+		return 0;
+
+	/* Count the total number of slots provided by all 4 lanes */
+	for (i = 0; i < AXG_TDM_NUM_LANES; i++)
+		slots += hweight32(mask[i]);
+
+	return slots;
+}
+
+int axg_tdm_set_tdm_slots(struct snd_soc_dai *dai, u32 *tx_mask,
+			  u32 *rx_mask, unsigned int slots,
+			  unsigned int slot_width)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+	struct axg_tdm_stream *tx = (struct axg_tdm_stream *)
+		dai->playback_dma_data;
+	struct axg_tdm_stream *rx = (struct axg_tdm_stream *)
+		dai->capture_dma_data;
+	unsigned int tx_slots, rx_slots;
+
+	tx_slots = axg_tdm_slots_total(tx_mask);
+	rx_slots = axg_tdm_slots_total(rx_mask);
+
+	/* We should at least have a slot for a valid interface */
+	if (!tx_slots && !rx_slots) {
+		dev_err(dai->dev, "interface has no slot\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * Amend the dai driver channel number and let dpcm channel merge do
+	 * its job
+	 */
+	if (tx) {
+		tx->mask = tx_mask;
+		dai->driver->playback.channels_max = tx_slots;
+	}
+
+	if (rx) {
+		rx->mask = rx_mask;
+		dai->driver->capture.channels_max = rx_slots;
+	}
+
+	iface->slots = slots;
+
+	switch (slot_width) {
+	case 0:
+		/* defaults width to 32 if not provided */
+		iface->slot_width = 32;
+		break;
+	case 8:
+	case 16:
+	case 24:
+	case 32:
+		iface->slot_width = slot_width;
+		break;
+	default:
+		dev_err(dai->dev, "unsupported slot width: %d\n", slot_width);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(axg_tdm_set_tdm_slots);
+
+static int axg_tdm_iface_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+				    unsigned int freq, int dir)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+	int ret = -ENOTSUPP;
+
+	if (dir == SND_SOC_CLOCK_OUT && clk_id == 0) {
+		if (!iface->mclk) {
+			dev_warn(dai->dev, "master clock not provided\n");
+		} else {
+			ret = clk_set_rate(iface->mclk, freq);
+			if (!ret)
+				iface->mclk_rate = freq;
+		}
+	}
+
+	return ret;
+}
+
+static int axg_tdm_iface_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+
+	/* These modes are not supported */
+	if (fmt & (SND_SOC_DAIFMT_CBS_CFM | SND_SOC_DAIFMT_CBM_CFS)) {
+		dev_err(dai->dev, "only CBS_CFS and CBM_CFM are supported\n");
+		return -EINVAL;
+	}
+
+	/* If the TDM interface is the clock master, it requires mclk */
+	if (!iface->mclk && (fmt & SND_SOC_DAIFMT_CBS_CFS)) {
+		dev_err(dai->dev, "cpu clock master: mclk missing\n");
+		return -ENODEV;
+	}
+
+	iface->fmt = fmt;
+	return 0;
+}
+
+static int axg_tdm_iface_startup(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+	struct axg_tdm_stream *ts =
+		snd_soc_dai_get_dma_data(dai, substream);
+	int ret;
+
+	if (!axg_tdm_slots_total(ts->mask)) {
+		dev_err(dai->dev, "interface has not slots\n");
+		return -EINVAL;
+	}
+
+	/* Apply component wide rate symmetry */
+	if (dai->component->active) {
+		ret = snd_pcm_hw_constraint_single(substream->runtime,
+						   SNDRV_PCM_HW_PARAM_RATE,
+						   iface->rate);
+		if (ret < 0) {
+			dev_err(dai->dev,
+				"can't set iface rate constraint\n");
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int axg_tdm_iface_set_stream(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *params,
+				    struct snd_soc_dai *dai)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+	struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
+	unsigned int channels = params_channels(params);
+	unsigned int width = params_width(params);
+
+	/* Save rate and sample_bits for component symmetry */
+	iface->rate = params_rate(params);
+
+	/* Make sure this interface can cope with the stream */
+	if (axg_tdm_slots_total(ts->mask) < channels) {
+		dev_err(dai->dev, "not enough slots for channels\n");
+		return -EINVAL;
+	}
+
+	if (iface->slot_width < width) {
+		dev_err(dai->dev, "incompatible slots width for stream\n");
+		return -EINVAL;
+	}
+
+	/* Save the parameter for tdmout/tdmin widgets */
+	ts->physical_width = params_physical_width(params);
+	ts->width = params_width(params);
+	ts->channels = params_channels(params);
+
+	return 0;
+}
+
+static int axg_tdm_iface_set_lrclk(struct snd_soc_dai *dai,
+				   struct snd_pcm_hw_params *params)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+	unsigned int ratio_num;
+	int ret;
+
+	ret = clk_set_rate(iface->lrclk, params_rate(params));
+	if (ret) {
+		dev_err(dai->dev, "setting sample clock failed: %d\n", ret);
+		return ret;
+	}
+
+	switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_LEFT_J:
+	case SND_SOC_DAIFMT_RIGHT_J:
+		/* 50% duty cycle ratio */
+		ratio_num = 1;
+		break;
+
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		/*
+		 * A zero duty cycle ratio will result in setting the mininum
+		 * ratio possible which, for this clock, is 1 cycle of the
+		 * parent bclk clock high and the rest low, This is exactly
+		 * what we want here.
+		 */
+		ratio_num = 0;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	ret = clk_set_duty_cycle(iface->lrclk, ratio_num, 2);
+	if (ret) {
+		dev_err(dai->dev,
+			"setting sample clock duty cycle failed: %d\n", ret);
+		return ret;
+	}
+
+	/* Set sample clock inversion */
+	ret = clk_set_phase(iface->lrclk,
+			    axg_tdm_lrclk_invert(iface->fmt) ? 180 : 0);
+	if (ret) {
+		dev_err(dai->dev,
+			"setting sample clock phase failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int axg_tdm_iface_set_sclk(struct snd_soc_dai *dai,
+				  struct snd_pcm_hw_params *params)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+	unsigned long srate;
+	int ret;
+
+	srate = iface->slots * iface->slot_width * params_rate(params);
+
+	if (!iface->mclk_rate) {
+		/* If no specific mclk is requested, default to bit clock * 4 */
+		clk_set_rate(iface->mclk, 4 * srate);
+	} else {
+		/* Check if we can actually get the bit clock from mclk */
+		if (iface->mclk_rate % srate) {
+			dev_err(dai->dev,
+				"can't derive sclk %lu from mclk %lu\n",
+				srate, iface->mclk_rate);
+			return -EINVAL;
+		}
+	}
+
+	ret = clk_set_rate(iface->sclk, srate);
+	if (ret) {
+		dev_err(dai->dev, "setting bit clock failed: %d\n", ret);
+		return ret;
+	}
+
+	/* Set the bit clock inversion */
+	ret = clk_set_phase(iface->sclk,
+			    axg_tdm_sclk_invert(iface->fmt) ? 0 : 180);
+	if (ret) {
+		dev_err(dai->dev, "setting bit clock phase failed: %d\n", ret);
+		return ret;
+	}
+
+	return ret;
+}
+
+static int axg_tdm_iface_hw_params(struct snd_pcm_substream *substream,
+				   struct snd_pcm_hw_params *params,
+				   struct snd_soc_dai *dai)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+	int ret;
+
+	switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_LEFT_J:
+	case SND_SOC_DAIFMT_RIGHT_J:
+		if (iface->slots > 2) {
+			dev_err(dai->dev, "bad slot number for format: %d\n",
+				iface->slots);
+			return -EINVAL;
+		}
+		break;
+
+	case SND_SOC_DAI_FORMAT_DSP_A:
+	case SND_SOC_DAI_FORMAT_DSP_B:
+		break;
+
+	default:
+		dev_err(dai->dev, "unsupported dai format\n");
+		return -EINVAL;
+	}
+
+	ret = axg_tdm_iface_set_stream(substream, params, dai);
+	if (ret)
+		return ret;
+
+	if (iface->fmt & SND_SOC_DAIFMT_CBS_CFS) {
+		ret = axg_tdm_iface_set_sclk(dai, params);
+		if (ret)
+			return ret;
+
+		ret = axg_tdm_iface_set_lrclk(dai, params);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int axg_tdm_iface_hw_free(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
+
+	/* Stop all attached formatters */
+	axg_tdm_stream_stop(ts);
+
+	return 0;
+}
+
+static int axg_tdm_iface_prepare(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
+
+	/* Force all attached formatters to update */
+	return axg_tdm_stream_reset(ts);
+}
+
+static int axg_tdm_iface_remove_dai(struct snd_soc_dai *dai)
+{
+	if (dai->capture_dma_data)
+		axg_tdm_stream_free(dai->capture_dma_data);
+
+	if (dai->playback_dma_data)
+		axg_tdm_stream_free(dai->playback_dma_data);
+
+	return 0;
+}
+
+static int axg_tdm_iface_probe_dai(struct snd_soc_dai *dai)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+
+	if (dai->capture_widget) {
+		dai->capture_dma_data = axg_tdm_stream_alloc(iface);
+		if (!dai->capture_dma_data)
+			return -ENOMEM;
+	}
+
+	if (dai->playback_widget) {
+		dai->playback_dma_data = axg_tdm_stream_alloc(iface);
+		if (!dai->playback_dma_data) {
+			axg_tdm_iface_remove_dai(dai);
+			return -ENOMEM;
+		}
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops axg_tdm_iface_ops = {
+	.set_sysclk	= axg_tdm_iface_set_sysclk,
+	.set_fmt	= axg_tdm_iface_set_fmt,
+	.startup	= axg_tdm_iface_startup,
+	.hw_params	= axg_tdm_iface_hw_params,
+	.prepare	= axg_tdm_iface_prepare,
+	.hw_free	= axg_tdm_iface_hw_free,
+};
+
+/* TDM Backend DAIs */
+static const struct snd_soc_dai_driver axg_tdm_iface_dai_drv[] = {
+	[TDM_IFACE_PAD] = {
+		.name = "TDM Pad",
+		.playback = {
+			.stream_name	= "Playback",
+			.channels_min	= 1,
+			.channels_max	= AXG_TDM_CHANNEL_MAX,
+			.rates		= AXG_TDM_RATES,
+			.formats	= AXG_TDM_FORMATS,
+		},
+		.capture = {
+			.stream_name	= "Capture",
+			.channels_min	= 1,
+			.channels_max	= AXG_TDM_CHANNEL_MAX,
+			.rates		= AXG_TDM_RATES,
+			.formats	= AXG_TDM_FORMATS,
+		},
+		.id = TDM_IFACE_PAD,
+		.ops = &axg_tdm_iface_ops,
+		.probe = axg_tdm_iface_probe_dai,
+		.remove = axg_tdm_iface_remove_dai,
+	},
+	[TDM_IFACE_LOOPBACK] = {
+		.name = "TDM Loopback",
+		.capture = {
+			.stream_name	= "Loopback",
+			.channels_min	= 1,
+			.channels_max	= AXG_TDM_CHANNEL_MAX,
+			.rates		= AXG_TDM_RATES,
+			.formats	= AXG_TDM_FORMATS,
+		},
+		.id = TDM_IFACE_LOOPBACK,
+		.ops = &axg_tdm_iface_ops,
+		.probe = axg_tdm_iface_probe_dai,
+		.remove = axg_tdm_iface_remove_dai,
+	},
+};
+
+static int axg_tdm_iface_set_bias_level(struct snd_soc_component *component,
+					enum snd_soc_bias_level level)
+{
+	struct axg_tdm_iface *iface = snd_soc_component_get_drvdata(component);
+	enum snd_soc_bias_level now =
+		snd_soc_component_get_bias_level(component);
+	int ret = 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (now == SND_SOC_BIAS_STANDBY)
+			ret = clk_prepare_enable(iface->mclk);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (now == SND_SOC_BIAS_PREPARE)
+			clk_disable_unprepare(iface->mclk);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+	case SND_SOC_BIAS_ON:
+		break;
+	}
+
+	return ret;
+}
+
+static const struct snd_soc_component_driver axg_tdm_iface_component_drv = {
+	.set_bias_level	= axg_tdm_iface_set_bias_level,
+};
+
+static const struct of_device_id axg_tdm_iface_of_match[] = {
+	{ .compatible = "amlogic,axg-tdm-iface", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, axg_tdm_iface_of_match);
+
+static int axg_tdm_iface_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct snd_soc_dai_driver *dai_drv;
+	struct axg_tdm_iface *iface;
+	int ret, i;
+
+	iface = devm_kzalloc(dev, sizeof(*iface), GFP_KERNEL);
+	if (!iface)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, iface);
+
+	/*
+	 * Duplicate dai driver: depending on the slot masks configuration
+	 * We'll change the number of channel provided by DAI stream, so dpcm
+	 * channel merge can be done properly
+	 */
+	dai_drv = devm_kcalloc(dev, ARRAY_SIZE(axg_tdm_iface_dai_drv),
+			       sizeof(*dai_drv), GFP_KERNEL);
+	if (!dai_drv)
+		return -ENOMEM;
+
+	for (i = 0; i < ARRAY_SIZE(axg_tdm_iface_dai_drv); i++)
+		memcpy(&dai_drv[i], &axg_tdm_iface_dai_drv[i],
+		       sizeof(*dai_drv));
+
+	/* Bit clock provided on the pad */
+	iface->sclk = devm_clk_get(dev, "sclk");
+	if (IS_ERR(iface->sclk)) {
+		ret = PTR_ERR(iface->sclk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get sclk: %d\n", ret);
+		return ret;
+	}
+
+	/* Sample clock provided on the pad */
+	iface->lrclk = devm_clk_get(dev, "lrclk");
+	if (IS_ERR(iface->lrclk)) {
+		ret = PTR_ERR(iface->lrclk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get lrclk: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * mclk maybe be missing when the cpu dai is in slave mode and
+	 * the codec does not require it to provide a master clock.
+	 * At this point, ignore the error if mclk is missing. We'll
+	 * throw an error if the cpu dai is master and mclk is missing
+	 */
+	iface->mclk = devm_clk_get(dev, "mclk");
+	if (IS_ERR(iface->mclk)) {
+		ret = PTR_ERR(iface->mclk);
+		if (ret == -ENOENT) {
+			iface->mclk = NULL;
+		} else {
+			if (ret != -EPROBE_DEFER)
+				dev_err(dev, "failed to get mclk: %d\n", ret);
+			return ret;
+		}
+	}
+
+	return devm_snd_soc_register_component(dev,
+					&axg_tdm_iface_component_drv, dai_drv,
+					ARRAY_SIZE(axg_tdm_iface_dai_drv));
+}
+
+static struct platform_driver axg_tdm_iface_pdrv = {
+	.probe = axg_tdm_iface_probe,
+	.driver = {
+		.name = "axg-tdm-iface",
+		.of_match_table = axg_tdm_iface_of_match,
+	},
+};
+module_platform_driver(axg_tdm_iface_pdrv);
+
+MODULE_DESCRIPTION("Amlogic AXG TDM interface driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/meson/axg-tdm.h b/sound/soc/meson/axg-tdm.h
index 435d95b86457..e578b6f40a07 100644
--- a/sound/soc/meson/axg-tdm.h
+++ b/sound/soc/meson/axg-tdm.h
@@ -71,4 +71,8 @@ static inline int axg_tdm_stream_reset(struct axg_tdm_stream *ts)
 	return axg_tdm_stream_start(ts);
 }
 
+int axg_tdm_set_tdm_slots(struct snd_soc_dai *dai, u32 *tx_mask,
+			  u32 *rx_mask, unsigned int slots,
+			  unsigned int slot_width);
+
 #endif /* _MESON_AXG_TDM_H */
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 11/15] ASoC: meson: add tdm output driver
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (12 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 10/15] ASoC: meson: add tdm interface driver Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-20 16:43   ` Applied "ASoC: meson: add tdm output driver" to the asoc tree Mark Brown
  2018-07-17 15:36 ` [PATCH 12/15] ASoC: meson: add tdm input driver Jerome Brunet
                   ` (5 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add Amlogic's axg tdm output driver which pulls data from FRDDR fifo
and produce the TDM signals for 4 output lanes.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 sound/soc/meson/Kconfig      |   8 ++
 sound/soc/meson/Makefile     |   2 +
 sound/soc/meson/axg-tdmout.c | 259 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 269 insertions(+)
 create mode 100644 sound/soc/meson/axg-tdmout.c

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index 869b359c2ce7..08a522b77749 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -27,6 +27,14 @@ config SND_MESON_AXG_TDM_INTERFACE
 	tristate
 	select SND_MESON_AXG_TDM_FORMATTER
 
+config SND_MESON_AXG_TDMOUT
+	tristate "Amlogic AXG TDM Output Support"
+	select SND_MESON_AXG_TDM_FORMATTER
+	select SND_MESON_AXG_TDM_INTERFACE
+	help
+	  Select Y or M to add support for TDM output formatter embedded
+	  in the Amlogic AXG SoC family
+
 config SND_MESON_AXG_SPDIFOUT
 	tristate "Amlogic AXG SPDIF Output Support"
 	imply SND_SOC_SPDIF
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index 1a8eb77402e3..a665c6b76a95 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -5,6 +5,7 @@ snd-soc-meson-axg-frddr-objs := axg-frddr.o
 snd-soc-meson-axg-toddr-objs := axg-toddr.o
 snd-soc-meson-axg-tdm-formatter-objs := axg-tdm-formatter.o
 snd-soc-meson-axg-tdm-interface-objs := axg-tdm-interface.o
+snd-soc-meson-axg-tdmout-objs := axg-tdmout.o
 snd-soc-meson-axg-spdifout-objs := axg-spdifout.o
 
 obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
@@ -12,4 +13,5 @@ obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
 obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o
 obj-$(CONFIG_SND_MESON_AXG_TDM_FORMATTER) += snd-soc-meson-axg-tdm-formatter.o
 obj-$(CONFIG_SND_MESON_AXG_TDM_INTERFACE) += snd-soc-meson-axg-tdm-interface.o
+obj-$(CONFIG_SND_MESON_AXG_TDMOUT) += snd-soc-meson-axg-tdmout.o
 obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o
diff --git a/sound/soc/meson/axg-tdmout.c b/sound/soc/meson/axg-tdmout.c
new file mode 100644
index 000000000000..f73368ee1088
--- /dev/null
+++ b/sound/soc/meson/axg-tdmout.c
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "axg-tdm-formatter.h"
+
+#define TDMOUT_CTRL0			0x00
+#define  TDMOUT_CTRL0_BITNUM_MASK	GENMASK(4, 0)
+#define  TDMOUT_CTRL0_BITNUM(x)		((x) << 0)
+#define  TDMOUT_CTRL0_SLOTNUM_MASK	GENMASK(9, 5)
+#define  TDMOUT_CTRL0_SLOTNUM(x)	((x) << 5)
+#define  TDMOUT_CTRL0_INIT_BITNUM_MASK	GENMASK(19, 15)
+#define  TDMOUT_CTRL0_INIT_BITNUM(x)	((x) << 15)
+#define  TDMOUT_CTRL0_ENABLE		BIT(31)
+#define  TDMOUT_CTRL0_RST_OUT		BIT(29)
+#define  TDMOUT_CTRL0_RST_IN		BIT(28)
+#define TDMOUT_CTRL1			0x04
+#define  TDMOUT_CTRL1_TYPE_MASK		GENMASK(6, 4)
+#define  TDMOUT_CTRL1_TYPE(x)		((x) << 4)
+#define  TDMOUT_CTRL1_MSB_POS_MASK	GENMASK(12, 8)
+#define  TDMOUT_CTRL1_MSB_POS(x)	((x) << 8)
+#define  TDMOUT_CTRL1_SEL_SHIFT		24
+#define  TDMOUT_CTRL1_GAIN_EN		26
+#define  TDMOUT_CTRL1_WS_INV		BIT(28)
+#define TDMOUT_SWAP			0x08
+#define TDMOUT_MASK0			0x0c
+#define TDMOUT_MASK1			0x10
+#define TDMOUT_MASK2			0x14
+#define TDMOUT_MASK3			0x18
+#define TDMOUT_STAT			0x1c
+#define TDMOUT_GAIN0			0x20
+#define TDMOUT_GAIN1			0x24
+#define TDMOUT_MUTE_VAL			0x28
+#define TDMOUT_MUTE0			0x2c
+#define TDMOUT_MUTE1			0x30
+#define TDMOUT_MUTE2			0x34
+#define TDMOUT_MUTE3			0x38
+#define TDMOUT_MASK_VAL			0x3c
+
+static const struct regmap_config axg_tdmout_regmap_cfg = {
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_stride	= 4,
+	.max_register	= TDMOUT_MASK_VAL,
+};
+
+static const struct snd_kcontrol_new axg_tdmout_controls[] = {
+	SOC_DOUBLE("Lane 0 Volume", TDMOUT_GAIN0,  0,  8, 255, 0),
+	SOC_DOUBLE("Lane 1 Volume", TDMOUT_GAIN0, 16, 24, 255, 0),
+	SOC_DOUBLE("Lane 2 Volume", TDMOUT_GAIN1,  0,  8, 255, 0),
+	SOC_DOUBLE("Lane 3 Volume", TDMOUT_GAIN1, 16, 24, 255, 0),
+	SOC_SINGLE("Gain Enable Switch", TDMOUT_CTRL1,
+		   TDMOUT_CTRL1_GAIN_EN, 1, 0),
+};
+
+static const char * const tdmout_sel_texts[] = {
+	"IN 0", "IN 1", "IN 2",
+};
+
+static SOC_ENUM_SINGLE_DECL(axg_tdmout_sel_enum, TDMOUT_CTRL1,
+			    TDMOUT_CTRL1_SEL_SHIFT, tdmout_sel_texts);
+
+static const struct snd_kcontrol_new axg_tdmout_in_mux =
+	SOC_DAPM_ENUM("Input Source", axg_tdmout_sel_enum);
+
+static struct snd_soc_dai *
+axg_tdmout_get_be(struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_dapm_path *p = NULL;
+	struct snd_soc_dai *be;
+
+	snd_soc_dapm_widget_for_each_sink_path(w, p) {
+		if (!p->connect)
+			continue;
+
+		if (p->sink->id == snd_soc_dapm_dai_in)
+			return (struct snd_soc_dai *)p->sink->priv;
+
+		be = axg_tdmout_get_be(p->sink);
+		if (be)
+			return be;
+	}
+
+	return NULL;
+}
+
+static struct axg_tdm_stream *
+axg_tdmout_get_tdm_stream(struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_dai *be = axg_tdmout_get_be(w);
+
+	if (!be)
+		return NULL;
+
+	return be->playback_dma_data;
+}
+
+static void axg_tdmout_enable(struct regmap *map)
+{
+	/* Apply both reset */
+	regmap_update_bits(map, TDMOUT_CTRL0,
+			   TDMOUT_CTRL0_RST_OUT | TDMOUT_CTRL0_RST_IN, 0);
+
+	/* Clear out reset before in reset */
+	regmap_update_bits(map, TDMOUT_CTRL0,
+			   TDMOUT_CTRL0_RST_OUT, TDMOUT_CTRL0_RST_OUT);
+	regmap_update_bits(map, TDMOUT_CTRL0,
+			   TDMOUT_CTRL0_RST_IN,  TDMOUT_CTRL0_RST_IN);
+
+	/* Actually enable tdmout */
+	regmap_update_bits(map, TDMOUT_CTRL0,
+			   TDMOUT_CTRL0_ENABLE, TDMOUT_CTRL0_ENABLE);
+}
+
+static void axg_tdmout_disable(struct regmap *map)
+{
+	regmap_update_bits(map, TDMOUT_CTRL0, TDMOUT_CTRL0_ENABLE, 0);
+}
+
+static int axg_tdmout_prepare(struct regmap *map, struct axg_tdm_stream *ts)
+{
+	unsigned int val = 0;
+
+	/* Set the stream skew */
+	switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_DSP_A:
+		val |= TDMOUT_CTRL0_INIT_BITNUM(1);
+		break;
+
+	case SND_SOC_DAIFMT_LEFT_J:
+	case SND_SOC_DAIFMT_RIGHT_J:
+	case SND_SOC_DAIFMT_DSP_B:
+		val |= TDMOUT_CTRL0_INIT_BITNUM(2);
+		break;
+
+	default:
+		pr_err("Unsupported format: %u\n",
+		       ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+		return -EINVAL;
+	}
+
+	/* Set the slot width */
+	val |= TDMOUT_CTRL0_BITNUM(ts->iface->slot_width - 1);
+
+	/* Set the slot number */
+	val |= TDMOUT_CTRL0_SLOTNUM(ts->iface->slots - 1);
+
+	regmap_update_bits(map, TDMOUT_CTRL0,
+			   TDMOUT_CTRL0_INIT_BITNUM_MASK |
+			   TDMOUT_CTRL0_BITNUM_MASK |
+			   TDMOUT_CTRL0_SLOTNUM_MASK, val);
+
+	/* Set the sample width */
+	val = TDMOUT_CTRL1_MSB_POS(ts->width - 1);
+
+	/* FIFO data are arranged in chunks of 64bits */
+	switch (ts->physical_width) {
+	case 8:
+		/* 8 samples of 8 bits */
+		val |= TDMOUT_CTRL1_TYPE(0);
+		break;
+	case 16:
+		/* 4 samples of 16 bits - right justified */
+		val |= TDMOUT_CTRL1_TYPE(2);
+		break;
+	case 32:
+		/* 2 samples of 32 bits - right justified */
+		val |= TDMOUT_CTRL1_TYPE(4);
+		break;
+	default:
+		pr_err("Unsupported physical width: %u\n",
+		       ts->physical_width);
+		return -EINVAL;
+	}
+
+	/* If the sample clock is inverted, invert it back for the formatter */
+	if (axg_tdm_lrclk_invert(ts->iface->fmt))
+		val |= TDMOUT_CTRL1_WS_INV;
+
+	regmap_update_bits(map, TDMOUT_CTRL1,
+			   (TDMOUT_CTRL1_TYPE_MASK | TDMOUT_CTRL1_MSB_POS_MASK |
+			    TDMOUT_CTRL1_WS_INV), val);
+
+	/* Set static swap mask configuration */
+	regmap_write(map, TDMOUT_SWAP, 0x76543210);
+
+	return axg_tdm_formatter_set_channel_masks(map, ts, TDMOUT_MASK0);
+}
+
+static const struct snd_soc_dapm_widget axg_tdmout_dapm_widgets[] = {
+	SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_tdmout_in_mux),
+	SND_SOC_DAPM_PGA_E("ENC", SND_SOC_NOPM, 0, 0, NULL, 0,
+			   axg_tdm_formatter_event,
+			   (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
+	SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route axg_tdmout_dapm_routes[] = {
+	{ "SRC SEL", "IN 0", "IN 0" },
+	{ "SRC SEL", "IN 1", "IN 1" },
+	{ "SRC SEL", "IN 2", "IN 2" },
+	{ "ENC", NULL, "SRC SEL" },
+	{ "OUT", NULL, "ENC" },
+};
+
+static const struct snd_soc_component_driver axg_tdmout_component_drv = {
+	.controls		= axg_tdmout_controls,
+	.num_controls		= ARRAY_SIZE(axg_tdmout_controls),
+	.dapm_widgets		= axg_tdmout_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(axg_tdmout_dapm_widgets),
+	.dapm_routes		= axg_tdmout_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(axg_tdmout_dapm_routes),
+};
+
+static const struct axg_tdm_formatter_ops axg_tdmout_ops = {
+	.get_stream	= axg_tdmout_get_tdm_stream,
+	.prepare	= axg_tdmout_prepare,
+	.enable		= axg_tdmout_enable,
+	.disable	= axg_tdmout_disable,
+};
+
+static const struct axg_tdm_formatter_driver axg_tdmout_drv = {
+	.component_drv	= &axg_tdmout_component_drv,
+	.regmap_cfg	= &axg_tdmout_regmap_cfg,
+	.ops		= &axg_tdmout_ops,
+	.invert_sclk	= true,
+};
+
+static const struct of_device_id axg_tdmout_of_match[] = {
+	{
+		.compatible = "amlogic,axg-tdmout",
+		.data = &axg_tdmout_drv,
+	}, {}
+};
+MODULE_DEVICE_TABLE(of, axg_tdmout_of_match);
+
+static struct platform_driver axg_tdmout_pdrv = {
+	.probe = axg_tdm_formatter_probe,
+	.driver = {
+		.name = "axg-tdmout",
+		.of_match_table = axg_tdmout_of_match,
+	},
+};
+module_platform_driver(axg_tdmout_pdrv);
+
+MODULE_DESCRIPTION("Amlogic AXG TDM output formatter driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 12/15] ASoC: meson: add tdm input driver
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (13 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 11/15] ASoC: meson: add tdm output driver Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-20 16:43   ` Applied "ASoC: meson: add tdm input driver" to the asoc tree Mark Brown
  2018-07-17 15:36 ` [PATCH 13/15] ASoC: export snd_soc_of_get_slot_mask Jerome Brunet
                   ` (4 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add Amlogic's axg TDM input driver which take the TDM signal of 4 input
lanes and push the decoded audio samples to TODDR fifo

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 sound/soc/meson/Kconfig     |   8 ++
 sound/soc/meson/Makefile    |   2 +
 sound/soc/meson/axg-tdmin.c | 229 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 239 insertions(+)
 create mode 100644 sound/soc/meson/axg-tdmin.c

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index 08a522b77749..00d05df67b52 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -27,6 +27,14 @@ config SND_MESON_AXG_TDM_INTERFACE
 	tristate
 	select SND_MESON_AXG_TDM_FORMATTER
 
+config SND_MESON_AXG_TDMIN
+	tristate "Amlogic AXG TDM Input Support"
+	select SND_MESON_AXG_TDM_FORMATTER
+	select SND_MESON_AXG_TDM_INTERFACE
+	help
+	  Select Y or M to add support for TDM input formatter embedded
+	  in the Amlogic AXG SoC family
+
 config SND_MESON_AXG_TDMOUT
 	tristate "Amlogic AXG TDM Output Support"
 	select SND_MESON_AXG_TDM_FORMATTER
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index a665c6b76a95..f62833fb44d8 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -5,6 +5,7 @@ snd-soc-meson-axg-frddr-objs := axg-frddr.o
 snd-soc-meson-axg-toddr-objs := axg-toddr.o
 snd-soc-meson-axg-tdm-formatter-objs := axg-tdm-formatter.o
 snd-soc-meson-axg-tdm-interface-objs := axg-tdm-interface.o
+snd-soc-meson-axg-tdmin-objs := axg-tdmin.o
 snd-soc-meson-axg-tdmout-objs := axg-tdmout.o
 snd-soc-meson-axg-spdifout-objs := axg-spdifout.o
 
@@ -13,5 +14,6 @@ obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
 obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o
 obj-$(CONFIG_SND_MESON_AXG_TDM_FORMATTER) += snd-soc-meson-axg-tdm-formatter.o
 obj-$(CONFIG_SND_MESON_AXG_TDM_INTERFACE) += snd-soc-meson-axg-tdm-interface.o
+obj-$(CONFIG_SND_MESON_AXG_TDMIN) += snd-soc-meson-axg-tdmin.o
 obj-$(CONFIG_SND_MESON_AXG_TDMOUT) += snd-soc-meson-axg-tdmout.o
 obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o
diff --git a/sound/soc/meson/axg-tdmin.c b/sound/soc/meson/axg-tdmin.c
new file mode 100644
index 000000000000..bbac44c81688
--- /dev/null
+++ b/sound/soc/meson/axg-tdmin.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "axg-tdm-formatter.h"
+
+#define TDMIN_CTRL			0x00
+#define  TDMIN_CTRL_ENABLE		BIT(31)
+#define  TDMIN_CTRL_I2S_MODE		BIT(30)
+#define  TDMIN_CTRL_RST_OUT		BIT(29)
+#define  TDMIN_CTRL_RST_IN		BIT(28)
+#define  TDMIN_CTRL_WS_INV		BIT(25)
+#define  TDMIN_CTRL_SEL_SHIFT		20
+#define  TDMIN_CTRL_IN_BIT_SKEW_MASK	GENMASK(18, 16)
+#define  TDMIN_CTRL_IN_BIT_SKEW(x)	((x) << 16)
+#define  TDMIN_CTRL_LSB_FIRST		BIT(5)
+#define  TDMIN_CTRL_BITNUM_MASK	GENMASK(4, 0)
+#define  TDMIN_CTRL_BITNUM(x)		((x) << 0)
+#define TDMIN_SWAP			0x04
+#define TDMIN_MASK0			0x08
+#define TDMIN_MASK1			0x0c
+#define TDMIN_MASK2			0x10
+#define TDMIN_MASK3			0x14
+#define TDMIN_STAT			0x18
+#define TDMIN_MUTE_VAL			0x1c
+#define TDMIN_MUTE0			0x20
+#define TDMIN_MUTE1			0x24
+#define TDMIN_MUTE2			0x28
+#define TDMIN_MUTE3			0x2c
+
+static const struct regmap_config axg_tdmin_regmap_cfg = {
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_stride	= 4,
+	.max_register	= TDMIN_MUTE3,
+};
+
+static const char * const axg_tdmin_sel_texts[] = {
+	"IN 0", "IN 1", "IN 2", "IN 3", "IN 4", "IN 5",
+};
+
+/* Change to special mux control to reset dapm */
+static SOC_ENUM_SINGLE_DECL(axg_tdmin_sel_enum, TDMIN_CTRL,
+			    TDMIN_CTRL_SEL_SHIFT, axg_tdmin_sel_texts);
+
+static const struct snd_kcontrol_new axg_tdmin_in_mux =
+	SOC_DAPM_ENUM("Input Source", axg_tdmin_sel_enum);
+
+static struct snd_soc_dai *
+axg_tdmin_get_be(struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_dapm_path *p = NULL;
+	struct snd_soc_dai *be;
+
+	snd_soc_dapm_widget_for_each_source_path(w, p) {
+		if (!p->connect)
+			continue;
+
+		if (p->source->id == snd_soc_dapm_dai_out)
+			return (struct snd_soc_dai *)p->source->priv;
+
+		be = axg_tdmin_get_be(p->source);
+		if (be)
+			return be;
+	}
+
+	return NULL;
+}
+
+static struct axg_tdm_stream *
+axg_tdmin_get_tdm_stream(struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_dai *be = axg_tdmin_get_be(w);
+
+	if (!be)
+		return NULL;
+
+	return be->capture_dma_data;
+}
+
+static void axg_tdmin_enable(struct regmap *map)
+{
+	/* Apply both reset */
+	regmap_update_bits(map, TDMIN_CTRL,
+			   TDMIN_CTRL_RST_OUT | TDMIN_CTRL_RST_IN, 0);
+
+	/* Clear out reset before in reset */
+	regmap_update_bits(map, TDMIN_CTRL,
+			   TDMIN_CTRL_RST_OUT, TDMIN_CTRL_RST_OUT);
+	regmap_update_bits(map, TDMIN_CTRL,
+			   TDMIN_CTRL_RST_IN,  TDMIN_CTRL_RST_IN);
+
+	/* Actually enable tdmin */
+	regmap_update_bits(map, TDMIN_CTRL,
+			   TDMIN_CTRL_ENABLE, TDMIN_CTRL_ENABLE);
+}
+
+static void axg_tdmin_disable(struct regmap *map)
+{
+	regmap_update_bits(map, TDMIN_CTRL, TDMIN_CTRL_ENABLE, 0);
+}
+
+static int axg_tdmin_prepare(struct regmap *map, struct axg_tdm_stream *ts)
+{
+	unsigned int val = 0;
+
+	/* Set stream skew */
+	switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_DSP_A:
+		val |= TDMIN_CTRL_IN_BIT_SKEW(3);
+		break;
+
+	case SND_SOC_DAIFMT_LEFT_J:
+	case SND_SOC_DAIFMT_RIGHT_J:
+	case SND_SOC_DAIFMT_DSP_B:
+		val = TDMIN_CTRL_IN_BIT_SKEW(2);
+		break;
+
+	default:
+		pr_err("Unsupported format: %u\n",
+		       ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+		return -EINVAL;
+	}
+
+	/* Set stream format mode */
+	switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_LEFT_J:
+	case SND_SOC_DAIFMT_RIGHT_J:
+		val |= TDMIN_CTRL_I2S_MODE;
+		break;
+	}
+
+	/* If the sample clock is inverted, invert it back for the formatter */
+	if (axg_tdm_lrclk_invert(ts->iface->fmt))
+		val |= TDMIN_CTRL_WS_INV;
+
+	/* Set the slot width */
+	val |= TDMIN_CTRL_BITNUM(ts->iface->slot_width - 1);
+
+	/*
+	 * The following also reset LSB_FIRST which result in the formatter
+	 * placing the first bit received at bit 31
+	 */
+	regmap_update_bits(map, TDMIN_CTRL,
+			   (TDMIN_CTRL_IN_BIT_SKEW_MASK | TDMIN_CTRL_WS_INV |
+			    TDMIN_CTRL_I2S_MODE | TDMIN_CTRL_LSB_FIRST |
+			    TDMIN_CTRL_BITNUM_MASK), val);
+
+	/* Set static swap mask configuration */
+	regmap_write(map, TDMIN_SWAP, 0x76543210);
+
+	return axg_tdm_formatter_set_channel_masks(map, ts, TDMIN_MASK0);
+}
+
+static const struct snd_soc_dapm_widget axg_tdmin_dapm_widgets[] = {
+	SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 5", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_tdmin_in_mux),
+	SND_SOC_DAPM_PGA_E("DEC", SND_SOC_NOPM, 0, 0, NULL, 0,
+			   axg_tdm_formatter_event,
+			   (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
+	SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route axg_tdmin_dapm_routes[] = {
+	{ "SRC SEL", "IN 0", "IN 0" },
+	{ "SRC SEL", "IN 1", "IN 1" },
+	{ "SRC SEL", "IN 2", "IN 2" },
+	{ "SRC SEL", "IN 3", "IN 3" },
+	{ "SRC SEL", "IN 4", "IN 4" },
+	{ "SRC SEL", "IN 5", "IN 5" },
+	{ "DEC", NULL, "SRC SEL" },
+	{ "OUT", NULL, "DEC" },
+};
+
+static const struct snd_soc_component_driver axg_tdmin_component_drv = {
+	.dapm_widgets		= axg_tdmin_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(axg_tdmin_dapm_widgets),
+	.dapm_routes		= axg_tdmin_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(axg_tdmin_dapm_routes),
+};
+
+static const struct axg_tdm_formatter_ops axg_tdmin_ops = {
+	.get_stream	= axg_tdmin_get_tdm_stream,
+	.prepare	= axg_tdmin_prepare,
+	.enable		= axg_tdmin_enable,
+	.disable	= axg_tdmin_disable,
+};
+
+static const struct axg_tdm_formatter_driver axg_tdmin_drv = {
+	.component_drv	= &axg_tdmin_component_drv,
+	.regmap_cfg	= &axg_tdmin_regmap_cfg,
+	.ops		= &axg_tdmin_ops,
+	.invert_sclk	= false,
+};
+
+static const struct of_device_id axg_tdmin_of_match[] = {
+	{
+		.compatible = "amlogic,axg-tdmin",
+		.data = &axg_tdmin_drv,
+	}, {}
+};
+MODULE_DEVICE_TABLE(of, axg_tdmin_of_match);
+
+static struct platform_driver axg_tdmin_pdrv = {
+	.probe = axg_tdm_formatter_probe,
+	.driver = {
+		.name = "axg-tdmin",
+		.of_match_table = axg_tdmin_of_match,
+	},
+};
+module_platform_driver(axg_tdmin_pdrv);
+
+MODULE_DESCRIPTION("Amlogic AXG TDM input formatter driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 13/15] ASoC: export snd_soc_of_get_slot_mask
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (14 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 12/15] ASoC: meson: add tdm input driver Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-20 16:43   ` Applied "ASoC: export snd_soc_of_get_slot_mask" to the asoc tree Mark Brown
  2018-07-17 15:36 ` [PATCH 14/15] ASoC: meson: add axg sound card DT binding documentation Jerome Brunet
                   ` (3 subsequent siblings)
  19 siblings, 1 reply; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Amlogic's axg card driver can't use snd_soc_of_parse_tdm_slot()
directly because it needs to handle 4 mask for each direction.
Yet the parsing of each mask is the same, so export
snd_soc_of_get_slot_mask() to reuse the the existing code.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 include/sound/soc.h  | 3 +++
 sound/soc/soc-core.c | 7 ++++---
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/include/sound/soc.h b/include/sound/soc.h
index a4915148f739..41cec42fb456 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -1426,6 +1426,9 @@ int snd_soc_of_parse_card_name(struct snd_soc_card *card,
 			       const char *propname);
 int snd_soc_of_parse_audio_simple_widgets(struct snd_soc_card *card,
 					  const char *propname);
+int snd_soc_of_get_slot_mask(struct device_node *np,
+			     const char *prop_name,
+			     unsigned int *mask);
 int snd_soc_of_parse_tdm_slot(struct device_node *np,
 			      unsigned int *tx_mask,
 			      unsigned int *rx_mask,
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index 3be0310d5c81..1344e8e602fd 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -3366,9 +3366,9 @@ int snd_soc_of_parse_audio_simple_widgets(struct snd_soc_card *card,
 }
 EXPORT_SYMBOL_GPL(snd_soc_of_parse_audio_simple_widgets);
 
-static int snd_soc_of_get_slot_mask(struct device_node *np,
-				    const char *prop_name,
-				    unsigned int *mask)
+int snd_soc_of_get_slot_mask(struct device_node *np,
+			     const char *prop_name,
+			     unsigned int *mask)
 {
 	u32 val;
 	const __be32 *of_slot_mask = of_get_property(np, prop_name, &val);
@@ -3383,6 +3383,7 @@ static int snd_soc_of_get_slot_mask(struct device_node *np,
 
 	return val;
 }
+EXPORT_SYMBOL_GPL(snd_soc_of_get_slot_mask);
 
 int snd_soc_of_parse_tdm_slot(struct device_node *np,
 			      unsigned int *tx_mask,
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 14/15] ASoC: meson: add axg sound card DT binding documentation
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (15 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 13/15] ASoC: export snd_soc_of_get_slot_mask Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-20 16:43   ` Applied "ASoC: meson: add axg sound card DT bindings documentation" to the asoc tree Mark Brown
  2018-07-25 19:06   ` [PATCH 14/15] ASoC: meson: add axg sound card DT binding documentation Rob Herring
  2018-07-17 15:36 ` [PATCH 14/15] ASoC: meson: add axg sound card DT bindings documentation Jerome Brunet
                   ` (2 subsequent siblings)
  19 siblings, 2 replies; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add the DT binding documentation for axg sound card

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 .../bindings/sound/amlogic,axg-sound-card.txt      | 124 +++++++++++++++++++++
 1 file changed, 124 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt

diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt
new file mode 100644
index 000000000000..39e005da0407
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt
@@ -0,0 +1,124 @@
+Amlogic AXG sound card:
+
+Required properties:
+
+- compatible: "amlogic,axg-sound-card"
+- amlogic,name : User specified audio sound card name, one string
+
+Optional properties:
+
+- amlogic,aux-devs : List of phandles pointing to auxiliary devices
+- amlogic,widgets : Please refer to widgets.txt.
+- amlogic,routing : A list of the connections between audio components.
+
+Subnodes:
+
+- amlogic,dai-link: Container for dai-link level properties and the
+		    CODEC sub-nodes. There should be at least one (and
+		    probably) subnode of this type.
+
+Required dai-link properties:
+
+- sound-dai: phandle and port of the CPU DAI.
+
+Required TDM Backend dai-link properties:
+- dai-format : CPU/CODEC common audio format
+
+Optional TDM Backend dai-link properties:
+- dai-tdm-slot-rx-mask-{0,1,2,3}: Receive direction slot masks
+- dai-tdm-slot-tx-mask-{0,1,2,3}: Transmit direction slot masks
+				  When omitted, mask is assumed to have to no
+				  slots. A valid must have at one slot, so at
+				  least one these mask should be provided with
+				  an enabled slot.
+- dai-tdm-slot-num : Please refer to tdm-slot.txt.
+		     If omitted, slot number is set to accommodate the largest
+		     mask provided.
+- dai-tdm-slot-width : Please refer to tdm-slot.txt. default to 32 if omitted.
+- mclk-fs : Multiplication factor between stream rate and mclk
+
+Backend dai-link subnodes:
+
+- codec: dai-link representing backend links should have at least one subnode.
+	 One subnode for each codec of the dai-link.
+	 dai-link representing frontend links have no codec, therefore have no
+	 subnodes
+
+Required codec subnodes properties:
+
+- sound-dai: phandle and port of the CODEC DAI.
+
+Optional codec subnodes properties:
+
+- dai-tdm-slot-tx-mask : Please refer to tdm-slot.txt.
+- dai-tdm-slot-rx-mask : Please refer to tdm-slot.txt.
+
+Example:
+
+sound {
+	compatible = "amlogic,axg-sound-card";
+	amlogic,name = "AXG-S420";
+	amlogic,aux-devs = <&tdmin_a>, <&tdmout_c>;
+	amlogic,widgets = "Line", "Lineout",
+			  "Line", "Linein",
+			  "Speaker", "Speaker1 Left",
+			  "Speaker", "Speaker1 Right";
+			  "Speaker", "Speaker2 Left",
+			  "Speaker", "Speaker2 Right";
+	amlogic,routing = "TDMOUT_C IN 0", "FRDDR_A OUT 2",
+			  "SPDIFOUT IN 0", "FRDDR_A OUT 3",
+			  "TDM_C Playback", "TDMOUT_C OUT",
+			  "TDMIN_A IN 2", "TDM_C Capture",
+			  "TDMIN_A IN 5", "TDM_C Loopback",
+			  "TODDR_A IN 0", "TDMIN_A OUT",
+			  "Lineout", "Lineout AOUTL",
+			  "Lineout", "Lineout AOUTR",
+			  "Speaker1 Left", "SPK1 OUT_A",
+			  "Speaker2 Left", "SPK2 OUT_A",
+			  "Speaker1 Right", "SPK1 OUT_B",
+			  "Speaker2 Right", "SPK2 OUT_B",
+			  "Linein AINL", "Linein",
+			  "Linein AINR", "Linein";
+
+	amlogic,dai-link@0 {
+		sound-dai = <&frddr_a>;
+	};
+
+	amlogic,dai-link@1 {
+		sound-dai = <&toddr_a>;
+	};
+
+	amlogic,dai-link@2 {
+		sound-dai = <&tdmif_c>;
+		dai-format = "i2s";
+		dai-tdm-slot-tx-mask-2 = <1 1>;
+		dai-tdm-slot-tx-mask-3 = <1 1>;
+		dai-tdm-slot-rx-mask-1 = <1 1>;
+		mclk-fs = <256>;
+
+		codec@0 {
+			sound-dai = <&lineout>;
+		};
+
+		codec@1 {
+			sound-dai = <&speaker_amp1>;
+		};
+
+		codec@2 {
+			sound-dai = <&speaker_amp2>;
+		};
+
+		codec@3 {
+			sound-dai = <&linein>;
+		};
+
+	};
+
+	amlogic,dai-link@4 {
+		sound-dai = <&spdifout>;
+
+		codec {
+			sound-dai = <&spdif_dit>;
+		};
+	};
+};
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 14/15] ASoC: meson: add axg sound card DT bindings documentation
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (16 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 14/15] ASoC: meson: add axg sound card DT binding documentation Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-17 15:36 ` [PATCH 15/15] ASoC: meson: add axg sound card support Jerome Brunet
  2018-07-17 15:38 ` [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
  19 siblings, 0 replies; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add the DT bindings documentation for axg sound card

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 .../bindings/sound/amlogic,axg-sound-card.txt      | 124 +++++++++++++++++++++
 1 file changed, 124 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt

diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt
new file mode 100644
index 000000000000..39e005da0407
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt
@@ -0,0 +1,124 @@
+Amlogic AXG sound card:
+
+Required properties:
+
+- compatible: "amlogic,axg-sound-card"
+- amlogic,name : User specified audio sound card name, one string
+
+Optional properties:
+
+- amlogic,aux-devs : List of phandles pointing to auxiliary devices
+- amlogic,widgets : Please refer to widgets.txt.
+- amlogic,routing : A list of the connections between audio components.
+
+Subnodes:
+
+- amlogic,dai-link: Container for dai-link level properties and the
+		    CODEC sub-nodes. There should be at least one (and
+		    probably) subnode of this type.
+
+Required dai-link properties:
+
+- sound-dai: phandle and port of the CPU DAI.
+
+Required TDM Backend dai-link properties:
+- dai-format : CPU/CODEC common audio format
+
+Optional TDM Backend dai-link properties:
+- dai-tdm-slot-rx-mask-{0,1,2,3}: Receive direction slot masks
+- dai-tdm-slot-tx-mask-{0,1,2,3}: Transmit direction slot masks
+				  When omitted, mask is assumed to have to no
+				  slots. A valid must have at one slot, so at
+				  least one these mask should be provided with
+				  an enabled slot.
+- dai-tdm-slot-num : Please refer to tdm-slot.txt.
+		     If omitted, slot number is set to accommodate the largest
+		     mask provided.
+- dai-tdm-slot-width : Please refer to tdm-slot.txt. default to 32 if omitted.
+- mclk-fs : Multiplication factor between stream rate and mclk
+
+Backend dai-link subnodes:
+
+- codec: dai-link representing backend links should have at least one subnode.
+	 One subnode for each codec of the dai-link.
+	 dai-link representing frontend links have no codec, therefore have no
+	 subnodes
+
+Required codec subnodes properties:
+
+- sound-dai: phandle and port of the CODEC DAI.
+
+Optional codec subnodes properties:
+
+- dai-tdm-slot-tx-mask : Please refer to tdm-slot.txt.
+- dai-tdm-slot-rx-mask : Please refer to tdm-slot.txt.
+
+Example:
+
+sound {
+	compatible = "amlogic,axg-sound-card";
+	amlogic,name = "AXG-S420";
+	amlogic,aux-devs = <&tdmin_a>, <&tdmout_c>;
+	amlogic,widgets = "Line", "Lineout",
+			  "Line", "Linein",
+			  "Speaker", "Speaker1 Left",
+			  "Speaker", "Speaker1 Right";
+			  "Speaker", "Speaker2 Left",
+			  "Speaker", "Speaker2 Right";
+	amlogic,routing = "TDMOUT_C IN 0", "FRDDR_A OUT 2",
+			  "SPDIFOUT IN 0", "FRDDR_A OUT 3",
+			  "TDM_C Playback", "TDMOUT_C OUT",
+			  "TDMIN_A IN 2", "TDM_C Capture",
+			  "TDMIN_A IN 5", "TDM_C Loopback",
+			  "TODDR_A IN 0", "TDMIN_A OUT",
+			  "Lineout", "Lineout AOUTL",
+			  "Lineout", "Lineout AOUTR",
+			  "Speaker1 Left", "SPK1 OUT_A",
+			  "Speaker2 Left", "SPK2 OUT_A",
+			  "Speaker1 Right", "SPK1 OUT_B",
+			  "Speaker2 Right", "SPK2 OUT_B",
+			  "Linein AINL", "Linein",
+			  "Linein AINR", "Linein";
+
+	amlogic,dai-link@0 {
+		sound-dai = <&frddr_a>;
+	};
+
+	amlogic,dai-link@1 {
+		sound-dai = <&toddr_a>;
+	};
+
+	amlogic,dai-link@2 {
+		sound-dai = <&tdmif_c>;
+		dai-format = "i2s";
+		dai-tdm-slot-tx-mask-2 = <1 1>;
+		dai-tdm-slot-tx-mask-3 = <1 1>;
+		dai-tdm-slot-rx-mask-1 = <1 1>;
+		mclk-fs = <256>;
+
+		codec@0 {
+			sound-dai = <&lineout>;
+		};
+
+		codec@1 {
+			sound-dai = <&speaker_amp1>;
+		};
+
+		codec@2 {
+			sound-dai = <&speaker_amp2>;
+		};
+
+		codec@3 {
+			sound-dai = <&linein>;
+		};
+
+	};
+
+	amlogic,dai-link@4 {
+		sound-dai = <&spdifout>;
+
+		codec {
+			sound-dai = <&spdif_dit>;
+		};
+	};
+};
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 15/15] ASoC: meson: add axg sound card support
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (17 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 14/15] ASoC: meson: add axg sound card DT bindings documentation Jerome Brunet
@ 2018-07-17 15:36 ` Jerome Brunet
  2018-07-20 16:43   ` Applied "ASoC: meson: add axg sound card support" to the asoc tree Mark Brown
  2018-07-17 15:38 ` [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
  19 siblings, 1 reply; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:36 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel, linux-amlogic

Add the axg sound card to handle the specifities of the axg audio
sub system.

This card is required to:
 * setup the dpcm links specific to the AXG (with a cpu sound dai)
 * handle the 4 lanes masks of the tdm interfaces
 * add the loopback link when a tdm pad interface has a playback
   stream
 * handle multi-codec links

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 sound/soc/meson/Kconfig    |  11 +
 sound/soc/meson/Makefile   |   2 +
 sound/soc/meson/axg-card.c | 671 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 684 insertions(+)
 create mode 100644 sound/soc/meson/axg-card.c

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index 00d05df67b52..4cf93c05a982 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -43,6 +43,17 @@ config SND_MESON_AXG_TDMOUT
 	  Select Y or M to add support for TDM output formatter embedded
 	  in the Amlogic AXG SoC family
 
+config SND_MESON_AXG_SOUND_CARD
+	tristate "Amlogic AXG Sound Card Support"
+	select SND_MESON_AXG_TDM_INTERFACE
+	imply SND_MESON_AXG_FRDDR
+	imply SND_MESON_AXG_TODDR
+	imply SND_MESON_AXG_TDMIN
+	imply SND_MESON_AXG_TDMOUT
+	imply SND_MESON_AXG_SPDIFOUT
+	help
+	  Select Y or M to add support for the AXG SoC sound card
+
 config SND_MESON_AXG_SPDIFOUT
 	tristate "Amlogic AXG SPDIF Output Support"
 	imply SND_SOC_SPDIF
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index f62833fb44d8..c5e003b093db 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -7,6 +7,7 @@ snd-soc-meson-axg-tdm-formatter-objs := axg-tdm-formatter.o
 snd-soc-meson-axg-tdm-interface-objs := axg-tdm-interface.o
 snd-soc-meson-axg-tdmin-objs := axg-tdmin.o
 snd-soc-meson-axg-tdmout-objs := axg-tdmout.o
+snd-soc-meson-axg-sound-card-objs := axg-card.o
 snd-soc-meson-axg-spdifout-objs := axg-spdifout.o
 
 obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
@@ -16,4 +17,5 @@ obj-$(CONFIG_SND_MESON_AXG_TDM_FORMATTER) += snd-soc-meson-axg-tdm-formatter.o
 obj-$(CONFIG_SND_MESON_AXG_TDM_INTERFACE) += snd-soc-meson-axg-tdm-interface.o
 obj-$(CONFIG_SND_MESON_AXG_TDMIN) += snd-soc-meson-axg-tdmin.o
 obj-$(CONFIG_SND_MESON_AXG_TDMOUT) += snd-soc-meson-axg-tdmout.o
+obj-$(CONFIG_SND_MESON_AXG_SOUND_CARD) += snd-soc-meson-axg-sound-card.o
 obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o
diff --git a/sound/soc/meson/axg-card.c b/sound/soc/meson/axg-card.c
new file mode 100644
index 000000000000..d6d1081d94ad
--- /dev/null
+++ b/sound/soc/meson/axg-card.c
@@ -0,0 +1,671 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "axg-tdm.h"
+
+struct axg_card {
+	struct snd_soc_card card;
+	void **link_data;
+};
+
+struct axg_dai_link_tdm_mask {
+	u32 tx;
+	u32 rx;
+};
+
+struct axg_dai_link_tdm_data {
+	unsigned int mclk_fs;
+	unsigned int slots;
+	unsigned int slot_width;
+	u32 *tx_mask;
+	u32 *rx_mask;
+	struct axg_dai_link_tdm_mask *codec_masks;
+};
+
+#define PREFIX "amlogic,"
+
+static int axg_card_reallocate_links(struct axg_card *priv,
+				     unsigned int num_links)
+{
+	struct snd_soc_dai_link *links;
+	void **ldata;
+
+	links = krealloc(priv->card.dai_link,
+			 num_links * sizeof(*priv->card.dai_link),
+			 GFP_KERNEL | __GFP_ZERO);
+	ldata = krealloc(priv->link_data,
+			 num_links * sizeof(*priv->link_data),
+			 GFP_KERNEL | __GFP_ZERO);
+
+	if (!links || !ldata) {
+		dev_err(priv->card.dev, "failed to allocate links\n");
+		return -ENOMEM;
+	}
+
+	priv->card.dai_link = links;
+	priv->link_data = ldata;
+	priv->card.num_links = num_links;
+	return 0;
+}
+
+static int axg_card_parse_dai(struct snd_soc_card *card,
+			      struct device_node *node,
+			      struct device_node **dai_of_node,
+			      const char **dai_name)
+{
+	struct of_phandle_args args;
+	int ret;
+
+	if (!dai_name || !dai_of_node || !node)
+		return -EINVAL;
+
+	ret = of_parse_phandle_with_args(node, "sound-dai",
+					 "#sound-dai-cells", 0, &args);
+	if (ret) {
+		if (ret != -EPROBE_DEFER)
+			dev_err(card->dev, "can't parse dai %d\n", ret);
+		return ret;
+	}
+	*dai_of_node = args.np;
+
+	return snd_soc_get_dai_name(&args, dai_name);
+}
+
+static int axg_card_set_link_name(struct snd_soc_card *card,
+				  struct snd_soc_dai_link *link,
+				  const char *prefix)
+{
+	char *name = devm_kasprintf(card->dev, GFP_KERNEL, "%s.%s",
+				    prefix, link->cpu_of_node->full_name);
+	if (!name)
+		return -ENOMEM;
+
+	link->name = name;
+	link->stream_name = name;
+
+	return 0;
+}
+
+static void axg_card_clean_references(struct axg_card *priv)
+{
+	struct snd_soc_card *card = &priv->card;
+	struct snd_soc_dai_link *link;
+	int i, j;
+
+	if (card->dai_link) {
+		for (i = 0; i < card->num_links; i++) {
+			link = &card->dai_link[i];
+			of_node_put(link->cpu_of_node);
+			for (j = 0; j < link->num_codecs; j++)
+				of_node_put(link->codecs[j].of_node);
+		}
+	}
+
+	if (card->aux_dev) {
+		for (i = 0; i < card->num_aux_devs; i++)
+			of_node_put(card->aux_dev[i].codec_of_node);
+	}
+
+	kfree(card->dai_link);
+	kfree(priv->link_data);
+}
+
+static int axg_card_add_aux_devices(struct snd_soc_card *card)
+{
+	struct device_node *node = card->dev->of_node;
+	struct snd_soc_aux_dev *aux;
+	int num, i;
+
+	num = of_count_phandle_with_args(node, PREFIX "aux-devs", NULL);
+	if (num == -ENOENT) {
+		/*
+		 * It is ok to have no auxiliary devices but for this card it
+		 * is a strange situtation. Let's warn the about it.
+		 */
+		dev_warn(card->dev, "card has no auxiliary devices\n");
+		return 0;
+	} else if (num < 0) {
+		dev_err(card->dev, "error getting auxiliary devices: %d\n",
+			num);
+		return num;
+	}
+
+	aux = devm_kcalloc(card->dev, num, sizeof(*aux), GFP_KERNEL);
+	if (!aux)
+		return -ENOMEM;
+	card->aux_dev = aux;
+	card->num_aux_devs = num;
+
+	for (i = 0; i < card->num_aux_devs; i++, aux++) {
+		aux->codec_of_node = of_parse_phandle(node,
+						      PREFIX "aux-devs", i);
+		if (!aux->codec_of_node)
+			return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int axg_card_tdm_be_hw_params(struct snd_pcm_substream *substream,
+				     struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct axg_card *priv = snd_soc_card_get_drvdata(rtd->card);
+	struct axg_dai_link_tdm_data *be =
+		(struct axg_dai_link_tdm_data *)priv->link_data[rtd->num];
+	struct snd_soc_dai *codec_dai;
+	unsigned int mclk;
+	int ret, i;
+
+	if (be->mclk_fs) {
+		mclk = params_rate(params) * be->mclk_fs;
+
+		for (i = 0; i < rtd->num_codecs; i++) {
+			codec_dai = rtd->codec_dais[i];
+			ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
+						     SND_SOC_CLOCK_IN);
+			if (ret && ret != -ENOTSUPP)
+				return ret;
+		}
+
+		ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, 0, mclk,
+					     SND_SOC_CLOCK_OUT);
+		if (ret && ret != -ENOTSUPP)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_ops axg_card_tdm_be_ops = {
+	.hw_params = axg_card_tdm_be_hw_params,
+};
+
+static int axg_card_tdm_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct axg_card *priv = snd_soc_card_get_drvdata(rtd->card);
+	struct axg_dai_link_tdm_data *be =
+		(struct axg_dai_link_tdm_data *)priv->link_data[rtd->num];
+	struct snd_soc_dai *codec_dai;
+	int ret, i;
+
+	for (i = 0; i < rtd->num_codecs; i++) {
+		codec_dai = rtd->codec_dais[i];
+		ret = snd_soc_dai_set_tdm_slot(codec_dai,
+					       be->codec_masks[i].tx,
+					       be->codec_masks[i].rx,
+					       be->slots, be->slot_width);
+		if (ret && ret != -ENOTSUPP) {
+			dev_err(codec_dai->dev,
+				"setting tdm link slots failed\n");
+			return ret;
+		}
+	}
+
+	ret = axg_tdm_set_tdm_slots(rtd->cpu_dai, be->tx_mask, be->rx_mask,
+				    be->slots, be->slot_width);
+	if (ret) {
+		dev_err(rtd->cpu_dai->dev, "setting tdm link slots failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int axg_card_tdm_dai_lb_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct axg_card *priv = snd_soc_card_get_drvdata(rtd->card);
+	struct axg_dai_link_tdm_data *be =
+		(struct axg_dai_link_tdm_data *)priv->link_data[rtd->num];
+	int ret;
+
+	/* The loopback rx_mask is the pad tx_mask */
+	ret = axg_tdm_set_tdm_slots(rtd->cpu_dai, NULL, be->tx_mask,
+				    be->slots, be->slot_width);
+	if (ret) {
+		dev_err(rtd->cpu_dai->dev, "setting tdm link slots failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int axg_card_add_tdm_loopback(struct snd_soc_card *card,
+				     int *index)
+{
+	struct axg_card *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_dai_link *pad = &card->dai_link[*index];
+	struct snd_soc_dai_link *lb;
+	int ret;
+
+	/* extend links */
+	ret = axg_card_reallocate_links(priv, card->num_links + 1);
+	if (ret)
+		return ret;
+
+	lb = &card->dai_link[*index + 1];
+
+	lb->name = kasprintf(GFP_KERNEL, "%s-lb", pad->name);
+	if (!lb->name)
+		return -ENOMEM;
+
+	lb->stream_name = lb->name;
+	lb->cpu_of_node = pad->cpu_of_node;
+	lb->cpu_dai_name = "TDM Loopback";
+	lb->codec_name = "snd-soc-dummy";
+	lb->codec_dai_name = "snd-soc-dummy-dai";
+	lb->dpcm_capture = 1;
+	lb->no_pcm = 1;
+	lb->ops = &axg_card_tdm_be_ops;
+	lb->init = axg_card_tdm_dai_lb_init;
+
+	/* Provide the same link data to the loopback */
+	priv->link_data[*index + 1] = priv->link_data[*index];
+
+	/*
+	 * axg_card_clean_references() will iterate over this link,
+	 * make sure the node count is balanced
+	 */
+	of_node_get(lb->cpu_of_node);
+
+	/* Let add_links continue where it should */
+	*index += 1;
+
+	return 0;
+}
+
+static unsigned int axg_card_parse_daifmt(struct device_node *node,
+					  struct device_node *cpu_node)
+{
+	struct device_node *bitclkmaster = NULL;
+	struct device_node *framemaster = NULL;
+	unsigned int daifmt;
+
+	daifmt = snd_soc_of_parse_daifmt(node, PREFIX,
+					 &bitclkmaster, &framemaster);
+	daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
+
+	/* If no master is provided, default to cpu master */
+	if (!bitclkmaster || bitclkmaster == cpu_node) {
+		daifmt |= (!framemaster || framemaster == cpu_node) ?
+			SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBS_CFM;
+	} else {
+		daifmt |= (!framemaster || framemaster == cpu_node) ?
+			SND_SOC_DAIFMT_CBM_CFS : SND_SOC_DAIFMT_CBM_CFM;
+	}
+
+	of_node_put(bitclkmaster);
+	of_node_put(framemaster);
+
+	return daifmt;
+}
+
+static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card,
+					struct snd_soc_dai_link *link,
+					struct device_node *node,
+					struct axg_dai_link_tdm_data *be)
+{
+	char propname[32];
+	u32 tx, rx;
+	int i;
+
+	be->tx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES,
+				   sizeof(*be->tx_mask), GFP_KERNEL);
+	be->rx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES,
+				   sizeof(*be->rx_mask), GFP_KERNEL);
+	if (!be->tx_mask || !be->rx_mask)
+		return -ENOMEM;
+
+	for (i = 0, tx = 0; i < AXG_TDM_NUM_LANES; i++) {
+		snprintf(propname, 32, "dai-tdm-slot-tx-mask-%d", i);
+		snd_soc_of_get_slot_mask(node, propname, &be->tx_mask[i]);
+		tx = max(tx, be->tx_mask[i]);
+	}
+
+	/* Disable playback is the interface has no tx slots */
+	if (!tx)
+		link->dpcm_playback = 0;
+
+	for (i = 0, rx = 0; i < AXG_TDM_NUM_LANES; i++) {
+		snprintf(propname, 32, "dai-tdm-slot-rx-mask-%d", i);
+		snd_soc_of_get_slot_mask(node, propname, &be->rx_mask[i]);
+		rx = max(rx, be->rx_mask[i]);
+	}
+
+	/* Disable capture is the interface has no rx slots */
+	if (!rx)
+		link->dpcm_capture = 0;
+
+	/* ... but the interface should at least have one of them */
+	if (!tx && !rx) {
+		dev_err(card->dev, "tdm link has no cpu slots\n");
+		return -EINVAL;
+	}
+
+	of_property_read_u32(node, "dai-tdm-slot-num", &be->slots);
+	if (!be->slots) {
+		/*
+		 * If the slot number is not provided, set it such as it
+		 * accommodates the largest mask
+		 */
+		be->slots = fls(max(tx, rx));
+	} else if (be->slots < fls(max(tx, rx)) || be->slots > 32) {
+		/*
+		 * Error if the slots can't accommodate the largest mask or
+		 * if it is just too big
+		 */
+		dev_err(card->dev, "bad slot number\n");
+		return -EINVAL;
+	}
+
+	of_property_read_u32(node, "dai-tdm-slot-width", &be->slot_width);
+
+	return 0;
+}
+
+static int axg_card_parse_codecs_masks(struct snd_soc_card *card,
+				       struct snd_soc_dai_link *link,
+				       struct device_node *node,
+				       struct axg_dai_link_tdm_data *be)
+{
+	struct axg_dai_link_tdm_mask *codec_mask;
+	struct device_node *np;
+
+	codec_mask = devm_kcalloc(card->dev, link->num_codecs,
+				  sizeof(*codec_mask), GFP_KERNEL);
+	if (!codec_mask)
+		return -ENOMEM;
+
+	be->codec_masks = codec_mask;
+
+	for_each_child_of_node(node, np) {
+		snd_soc_of_get_slot_mask(np, "dai-tdm-slot-rx-mask",
+					 &codec_mask->rx);
+		snd_soc_of_get_slot_mask(np, "dai-tdm-slot-tx-mask",
+					 &codec_mask->tx);
+
+		codec_mask++;
+	}
+
+	return 0;
+}
+
+static int axg_card_parse_tdm(struct snd_soc_card *card,
+			      struct device_node *node,
+			      int *index)
+{
+	struct axg_card *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_dai_link *link = &card->dai_link[*index];
+	struct axg_dai_link_tdm_data *be;
+	int ret;
+
+	/* Allocate tdm link parameters */
+	be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL);
+	if (!be)
+		return -ENOMEM;
+	priv->link_data[*index] = be;
+
+	/* Setup tdm link */
+	link->ops = &axg_card_tdm_be_ops;
+	link->init = axg_card_tdm_dai_init;
+	link->dai_fmt = axg_card_parse_daifmt(node, link->cpu_of_node);
+
+	of_property_read_u32(node, "mclk-fs", &be->mclk_fs);
+
+	ret = axg_card_parse_cpu_tdm_slots(card, link, node, be);
+	if (ret) {
+		dev_err(card->dev, "error parsing tdm link slots\n");
+		return ret;
+	}
+
+	ret = axg_card_parse_codecs_masks(card, link, node, be);
+	if (ret)
+		return ret;
+
+	/* Add loopback if the pad dai has playback */
+	if (link->dpcm_playback) {
+		ret = axg_card_add_tdm_loopback(card, index);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int axg_card_set_be_link(struct snd_soc_card *card,
+				struct snd_soc_dai_link *link,
+				struct device_node *node)
+{
+	struct snd_soc_dai_link_component *codec;
+	struct device_node *np;
+	int ret, num_codecs;
+
+	link->no_pcm = 1;
+	link->dpcm_playback = 1;
+	link->dpcm_capture = 1;
+
+	num_codecs = of_get_child_count(node);
+	if (!num_codecs) {
+		dev_err(card->dev, "be link %s has no codec\n",
+			node->full_name);
+		return -EINVAL;
+	}
+
+	codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec), GFP_KERNEL);
+	if (!codec)
+		return -ENOMEM;
+
+	link->codecs = codec;
+	link->num_codecs = num_codecs;
+
+	for_each_child_of_node(node, np) {
+		ret = axg_card_parse_dai(card, np, &codec->of_node,
+					 &codec->dai_name);
+		if (ret) {
+			of_node_put(np);
+			return ret;
+		}
+
+		codec++;
+	}
+
+	ret = axg_card_set_link_name(card, link, "be");
+	if (ret)
+		dev_err(card->dev, "error setting %s link name\n", np->name);
+
+	return ret;
+}
+
+static int axg_card_set_fe_link(struct snd_soc_card *card,
+				struct snd_soc_dai_link *link,
+				bool is_playback)
+{
+	link->dynamic = 1;
+	link->dpcm_merged_format = 1;
+	link->dpcm_merged_chan = 1;
+	link->dpcm_merged_rate = 1;
+	link->codec_dai_name = "snd-soc-dummy-dai";
+	link->codec_name = "snd-soc-dummy";
+
+	if (is_playback)
+		link->dpcm_playback = 1;
+	else
+		link->dpcm_capture = 1;
+
+	return axg_card_set_link_name(card, link, "fe");
+}
+
+static int axg_card_cpu_is_capture_fe(struct device_node *np)
+{
+	return of_device_is_compatible(np, PREFIX "axg-toddr");
+}
+
+static int axg_card_cpu_is_playback_fe(struct device_node *np)
+{
+	return of_device_is_compatible(np, PREFIX "axg-frddr");
+}
+
+static int axg_card_cpu_is_tdm_iface(struct device_node *np)
+{
+	return of_device_is_compatible(np, PREFIX "axg-tdm-iface");
+}
+
+static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np,
+			     int *index)
+{
+	struct snd_soc_dai_link *dai_link = &card->dai_link[*index];
+	int ret;
+
+	ret = axg_card_parse_dai(card, np, &dai_link->cpu_of_node,
+				 &dai_link->cpu_dai_name);
+	if (ret)
+		return ret;
+
+	if (axg_card_cpu_is_playback_fe(dai_link->cpu_of_node))
+		ret = axg_card_set_fe_link(card, dai_link, true);
+	else if (axg_card_cpu_is_capture_fe(dai_link->cpu_of_node))
+		ret = axg_card_set_fe_link(card, dai_link, false);
+	else
+		ret = axg_card_set_be_link(card, dai_link, np);
+
+	if (ret)
+		return ret;
+
+	if (axg_card_cpu_is_tdm_iface(dai_link->cpu_of_node))
+		ret = axg_card_parse_tdm(card, np, index);
+
+	return ret;
+}
+
+static int axg_card_add_links(struct snd_soc_card *card)
+{
+	struct axg_card *priv = snd_soc_card_get_drvdata(card);
+	struct device_node *node = card->dev->of_node;
+	struct device_node *np;
+	int num, i, ret;
+
+	num = of_get_child_count(node);
+	if (!num) {
+		dev_err(card->dev, "card has no links\n");
+		return -EINVAL;
+	}
+
+	ret = axg_card_reallocate_links(priv, num);
+	if (ret)
+		return ret;
+
+	i = 0;
+	for_each_child_of_node(node, np) {
+		ret = axg_card_add_link(card, np, &i);
+		if (ret) {
+			of_node_put(np);
+			return ret;
+		}
+
+		i++;
+	}
+
+	return 0;
+}
+
+static int axg_card_parse_of_optional(struct snd_soc_card *card,
+				      const char *propname,
+				      int (*func)(struct snd_soc_card *c,
+						  const char *p))
+{
+	/* If property is not provided, don't fail ... */
+	if (!of_property_read_bool(card->dev->of_node, propname))
+		return 0;
+
+	/* ... but do fail if it is provided and the parsing fails */
+	return func(card, propname);
+}
+
+static const struct of_device_id axg_card_of_match[] = {
+	{ .compatible = "amlogic,axg-sound-card", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, axg_card_of_match);
+
+static int axg_card_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct axg_card *priv;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, priv);
+	snd_soc_card_set_drvdata(&priv->card, priv);
+
+	priv->card.owner = THIS_MODULE;
+	priv->card.dev = dev;
+
+	ret = snd_soc_of_parse_card_name(&priv->card, PREFIX "name");
+	if (ret < 0)
+		return ret;
+
+	ret = axg_card_parse_of_optional(&priv->card, PREFIX "routing",
+					 snd_soc_of_parse_audio_routing);
+	if (ret) {
+		dev_err(dev, "error while parsing routing\n");
+		return ret;
+	}
+
+	ret = axg_card_parse_of_optional(&priv->card, PREFIX "widgets",
+					 snd_soc_of_parse_audio_simple_widgets);
+	if (ret) {
+		dev_err(dev, "error while parsing widgets\n");
+		return ret;
+	}
+
+	ret = axg_card_add_links(&priv->card);
+	if (ret)
+		goto out_err;
+
+	ret = axg_card_add_aux_devices(&priv->card);
+	if (ret)
+		goto out_err;
+
+	ret = devm_snd_soc_register_card(dev, &priv->card);
+	if (ret)
+		goto out_err;
+
+	return 0;
+
+out_err:
+	axg_card_clean_references(priv);
+	return ret;
+}
+
+static int axg_card_remove(struct platform_device *pdev)
+{
+	struct axg_card *priv = platform_get_drvdata(pdev);
+
+	axg_card_clean_references(priv);
+
+	return 0;
+}
+
+static struct platform_driver axg_card_pdrv = {
+	.probe = axg_card_probe,
+	.remove = axg_card_remove,
+	.driver = {
+		.name = "axg-sound-card",
+		.of_match_table = axg_card_of_match,
+	},
+};
+module_platform_driver(axg_card_pdrv);
+
+MODULE_DESCRIPTION("Amlogic AXG ALSA machine driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.14.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Re: [PATCH 00/15] ASoC: meson: add axg audio subsystem support
  2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
                   ` (18 preceding siblings ...)
  2018-07-17 15:36 ` [PATCH 15/15] ASoC: meson: add axg sound card support Jerome Brunet
@ 2018-07-17 15:38 ` Jerome Brunet
  19 siblings, 0 replies; 40+ messages in thread
From: Jerome Brunet @ 2018-07-17 15:38 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione
  Cc: alsa-devel, devicetree, linux-kernel, linux-amlogic

On Tue, 2018-07-17 at 17:36 +0200, Jerome Brunet wrote:
> This patchset adds support for the audio subsystem found on Amlogic's
> axg SoC family. The first SoC of this family is the A113D/X targeted
> at smart speaker application. The G12a SoC family, Amlogic next-gen
> STB chipset, will reuse this audio subsystem with a few changes.
> 
> This patchset implements the DPCM machine driver and the different
> components it made of. Several components are still missing, such
> spdif input and pdm, and will submitted later on.
> 
> The fancier part of this patchset is probably around the TDM. Properly
> modeling the SoC architecture and its capatilities made things a little
> more complex than usual (details in the related commit descriptions)
> 
> If necessary, this patchset could broken into smaller series. For the
> intial submission, I wanted to give a global view of the subsystem.
> Hopefully, it will make it a bit easier to understand.
> 
> Jerome Brunet (15):
>   ASoC: meson: add axg fifos DT binding documentation
>   ASoC: meson: add axg fifo base driver
>   ASoC: meson: add axg frddr driver
>   ASoC: meson: add axg toddr driver
>   ASoC: meson: add axg spdif output DT bindings documentation
>   ASoC: meson: add axg spdif output
>   ASoC: meson: add axg tdm formatters DT bindings documentation
>   ASoC: meson: add axg tdm interface DT bindings documentation
>   ASoC: meson: add tdm formatter base driver
>   ASoC: meson: add tdm interface driver
>   ASoC: meson: add tdm output driver
>   ASoC: meson: add tdm input driver
>   ASoC: export snd_soc_of_get_slot_mask
>   ASoC: meson: add axg sound card DT bindings documentation
>   ASoC: meson: add axg sound card support
> 

Really sorry for the noise, messed up with format-patch.
Please ignore this series. I will resend it

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Applied "ASoC: meson: add axg tdm formatters DT bindings documentation" to the asoc tree
  2018-07-17 15:36 ` [PATCH 07/15] ASoC: meson: add axg tdm formatters DT binding documentation Jerome Brunet
@ 2018-07-18 12:25   ` Mark Brown
  0 siblings, 0 replies; 40+ messages in thread
From: Mark Brown @ 2018-07-18 12:25 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Mark Brown, Liam Girdwood, Kevin Hilman,
	Carlo Caione, devicetree, alsa-devel, linux-amlogic,
	linux-kernel, alsa-devel

The patch

   ASoC: meson: add axg tdm formatters DT bindings documentation

has been applied to the asoc tree at

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From 7713a70034f2cb54168d134ac523fdfcdda92a13 Mon Sep 17 00:00:00 2001
From: Jerome Brunet <jbrunet@baylibre.com>
Date: Tue, 17 Jul 2018 17:42:56 +0200
Subject: [PATCH] ASoC: meson: add axg tdm formatters DT bindings documentation

Add the DT bindings documentation for axg's TDM formatters: TDMIN
and TDMOUT.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 .../sound/amlogic,axg-tdm-formatters.txt      | 28 +++++++++++++++++++
 1 file changed, 28 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.txt

diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.txt
new file mode 100644
index 000000000000..1c1b7490554e
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-formatters.txt
@@ -0,0 +1,28 @@
+* Amlogic Audio TDM formatters
+
+Required properties:
+- compatible: 'amlogic,axg-tdmin' or
+	      'amlogic,axg-tdmout'
+- reg: physical base address of the controller and length of memory
+       mapped region.
+- clocks: list of clock phandle, one for each entry clock-names.
+- clock-names: should contain the following:
+  * "pclk"     : peripheral clock.
+  * "sclk"     : bit clock.
+  * "sclk_sel" : bit clock input multiplexer.
+  * "lrclk"    : sample clock
+  * "lrclk_sel": sample clock input multiplexer
+
+Example of TDMOUT_A on the A113 SoC:
+
+tdmout_a: audio-controller@500 {
+	compatible = "amlogic,axg-tdmout";
+	reg = <0x0 0x500 0x0 0x40>;
+	clocks = <&clkc_audio AUD_CLKID_TDMOUT_A>,
+		 <&clkc_audio AUD_CLKID_TDMOUT_A_SCLK>,
+		 <&clkc_audio AUD_CLKID_TDMOUT_A_SCLK_SEL>,
+		 <&clkc_audio AUD_CLKID_TDMOUT_A_LRCLK>,
+		 <&clkc_audio AUD_CLKID_TDMOUT_A_LRCLK>;
+	clock-names = "pclk", "sclk", "sclk_sel",
+		      "lrclk", "lrclk_sel";
+};
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Applied "ASoC: meson: add axg spdif output" to the asoc tree
  2018-07-17 15:36 ` [PATCH 06/15] ASoC: meson: add axg spdif output Jerome Brunet
@ 2018-07-18 12:25   ` Mark Brown
  0 siblings, 0 replies; 40+ messages in thread
From: Mark Brown @ 2018-07-18 12:25 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Mark Brown, Liam Girdwood, Kevin Hilman,
	Carlo Caione, devicetree, alsa-devel, linux-amlogic,
	linux-kernel, alsa-devel

The patch

   ASoC: meson: add axg spdif output

has been applied to the asoc tree at

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From 53eb4b7aaa045e23b6e8edb0ae0d047a4a3612ef Mon Sep 17 00:00:00 2001
From: Jerome Brunet <jbrunet@baylibre.com>
Date: Tue, 17 Jul 2018 17:42:55 +0200
Subject: [PATCH] ASoC: meson: add axg spdif output

Add support for the spdif output serializer of the axg SoC family

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 sound/soc/meson/Kconfig        |   7 +
 sound/soc/meson/Makefile       |   2 +
 sound/soc/meson/axg-spdifout.c | 456 +++++++++++++++++++++++++++++++++
 3 files changed, 465 insertions(+)
 create mode 100644 sound/soc/meson/axg-spdifout.c

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index 3916060edcae..9408214b5854 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -19,4 +19,11 @@ config SND_MESON_AXG_TODDR
 	  Select Y or M to add support for the frontend capture interfaces
 	  embedded in the Amlogic AXG SoC family
 
+config SND_MESON_AXG_SPDIFOUT
+	tristate "Amlogic AXG SPDIF Output Support"
+	imply SND_SOC_SPDIF
+	help
+	  Select Y or M to add support for SPDIF output serializer embedded
+	  in the Amlogic AXG SoC family
+
 endmenu
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index 12edf2db2a24..d51ae045ef08 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -3,7 +3,9 @@
 snd-soc-meson-axg-fifo-objs := axg-fifo.o
 snd-soc-meson-axg-frddr-objs := axg-frddr.o
 snd-soc-meson-axg-toddr-objs := axg-toddr.o
+snd-soc-meson-axg-spdifout-objs := axg-spdifout.o
 
 obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
 obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
 obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o
+obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o
diff --git a/sound/soc/meson/axg-spdifout.c b/sound/soc/meson/axg-spdifout.c
new file mode 100644
index 000000000000..9dea528053ad
--- /dev/null
+++ b/sound/soc/meson/axg-spdifout.c
@@ -0,0 +1,456 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <sound/pcm_params.h>
+#include <sound/pcm_iec958.h>
+
+/*
+ * NOTE:
+ * The meaning of bits SPDIFOUT_CTRL0_XXX_SEL is actually the opposite
+ * of what the documentation says. Manual control on V, U and C bits is
+ * applied when the related sel bits are cleared
+ */
+
+#define SPDIFOUT_STAT			0x00
+#define SPDIFOUT_GAIN0			0x04
+#define SPDIFOUT_GAIN1			0x08
+#define SPDIFOUT_CTRL0			0x0c
+#define  SPDIFOUT_CTRL0_EN		BIT(31)
+#define  SPDIFOUT_CTRL0_RST_OUT		BIT(29)
+#define  SPDIFOUT_CTRL0_RST_IN		BIT(28)
+#define  SPDIFOUT_CTRL0_USEL		BIT(26)
+#define  SPDIFOUT_CTRL0_USET		BIT(25)
+#define  SPDIFOUT_CTRL0_CHSTS_SEL	BIT(24)
+#define  SPDIFOUT_CTRL0_DATA_SEL	BIT(20)
+#define  SPDIFOUT_CTRL0_MSB_FIRST	BIT(19)
+#define  SPDIFOUT_CTRL0_VSEL		BIT(18)
+#define  SPDIFOUT_CTRL0_VSET		BIT(17)
+#define  SPDIFOUT_CTRL0_MASK_MASK	GENMASK(11, 4)
+#define  SPDIFOUT_CTRL0_MASK(x)		((x) << 4)
+#define SPDIFOUT_CTRL1			0x10
+#define  SPDIFOUT_CTRL1_MSB_POS_MASK	GENMASK(12, 8)
+#define  SPDIFOUT_CTRL1_MSB_POS(x)	((x) << 8)
+#define  SPDIFOUT_CTRL1_TYPE_MASK	GENMASK(6, 4)
+#define  SPDIFOUT_CTRL1_TYPE(x)		((x) << 4)
+#define SPDIFOUT_PREAMB			0x14
+#define SPDIFOUT_SWAP			0x18
+#define SPDIFOUT_CHSTS0			0x1c
+#define SPDIFOUT_CHSTS1			0x20
+#define SPDIFOUT_CHSTS2			0x24
+#define SPDIFOUT_CHSTS3			0x28
+#define SPDIFOUT_CHSTS4			0x2c
+#define SPDIFOUT_CHSTS5			0x30
+#define SPDIFOUT_CHSTS6			0x34
+#define SPDIFOUT_CHSTS7			0x38
+#define SPDIFOUT_CHSTS8			0x3c
+#define SPDIFOUT_CHSTS9			0x40
+#define SPDIFOUT_CHSTSA			0x44
+#define SPDIFOUT_CHSTSB			0x48
+#define SPDIFOUT_MUTE_VAL		0x4c
+
+struct axg_spdifout {
+	struct regmap *map;
+	struct clk *mclk;
+	struct clk *pclk;
+};
+
+static void axg_spdifout_enable(struct regmap *map)
+{
+	/* Apply both reset */
+	regmap_update_bits(map, SPDIFOUT_CTRL0,
+			   SPDIFOUT_CTRL0_RST_OUT | SPDIFOUT_CTRL0_RST_IN,
+			   0);
+
+	/* Clear out reset before in reset */
+	regmap_update_bits(map, SPDIFOUT_CTRL0,
+			   SPDIFOUT_CTRL0_RST_OUT, SPDIFOUT_CTRL0_RST_OUT);
+	regmap_update_bits(map, SPDIFOUT_CTRL0,
+			   SPDIFOUT_CTRL0_RST_IN,  SPDIFOUT_CTRL0_RST_IN);
+
+	/* Enable spdifout */
+	regmap_update_bits(map, SPDIFOUT_CTRL0, SPDIFOUT_CTRL0_EN,
+			   SPDIFOUT_CTRL0_EN);
+}
+
+static void axg_spdifout_disable(struct regmap *map)
+{
+	regmap_update_bits(map, SPDIFOUT_CTRL0, SPDIFOUT_CTRL0_EN, 0);
+}
+
+static int axg_spdifout_trigger(struct snd_pcm_substream *substream, int cmd,
+				struct snd_soc_dai *dai)
+{
+	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		axg_spdifout_enable(priv->map);
+		return 0;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		axg_spdifout_disable(priv->map);
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int axg_spdifout_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
+
+	/* Use spdif valid bit to perform digital mute */
+	regmap_update_bits(priv->map, SPDIFOUT_CTRL0, SPDIFOUT_CTRL0_VSET,
+			   mute ? SPDIFOUT_CTRL0_VSET : 0);
+
+	return 0;
+}
+
+static int axg_spdifout_sample_fmt(struct snd_pcm_hw_params *params,
+				   struct snd_soc_dai *dai)
+{
+	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
+	unsigned int val;
+
+	/* Set the samples spdifout will pull from the FIFO */
+	switch (params_channels(params)) {
+	case 1:
+		val = SPDIFOUT_CTRL0_MASK(0x1);
+		break;
+	case 2:
+		val = SPDIFOUT_CTRL0_MASK(0x3);
+		break;
+	default:
+		dev_err(dai->dev, "too many channels for spdif dai: %u\n",
+			params_channels(params));
+		return -EINVAL;
+	}
+
+	regmap_update_bits(priv->map, SPDIFOUT_CTRL0,
+			   SPDIFOUT_CTRL0_MASK_MASK, val);
+
+	/* FIFO data are arranged in chunks of 64bits */
+	switch (params_physical_width(params)) {
+	case 8:
+		/* 8 samples of 8 bits */
+		val = SPDIFOUT_CTRL1_TYPE(0);
+		break;
+	case 16:
+		/* 4 samples of 16 bits - right justified */
+		val = SPDIFOUT_CTRL1_TYPE(2);
+		break;
+	case 32:
+		/* 2 samples of 32 bits - right justified */
+		val = SPDIFOUT_CTRL1_TYPE(4);
+		break;
+	default:
+		dev_err(dai->dev, "Unsupported physical width: %u\n",
+			params_physical_width(params));
+		return -EINVAL;
+	}
+
+	/* Position of the MSB in FIFO samples */
+	val |= SPDIFOUT_CTRL1_MSB_POS(params_width(params) - 1);
+
+	regmap_update_bits(priv->map, SPDIFOUT_CTRL1,
+			   SPDIFOUT_CTRL1_MSB_POS_MASK |
+			   SPDIFOUT_CTRL1_TYPE_MASK, val);
+
+	regmap_update_bits(priv->map, SPDIFOUT_CTRL0,
+			   SPDIFOUT_CTRL0_MSB_FIRST | SPDIFOUT_CTRL0_DATA_SEL,
+			   0);
+
+	return 0;
+}
+
+static int axg_spdifout_set_chsts(struct snd_pcm_hw_params *params,
+				  struct snd_soc_dai *dai)
+{
+	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
+	unsigned int offset;
+	int ret;
+	u8 cs[4];
+	u32 val;
+
+	ret = snd_pcm_create_iec958_consumer_hw_params(params, cs, 4);
+	if (ret < 0) {
+		dev_err(dai->dev, "Creating IEC958 channel status failed %d\n",
+			ret);
+		return ret;
+	}
+	val = cs[0] | cs[1] << 8 | cs[2] << 16 | cs[3] << 24;
+
+	/* Setup channel status A bits [31 - 0]*/
+	regmap_write(priv->map, SPDIFOUT_CHSTS0, val);
+
+	/* Clear channel status A bits [191 - 32] */
+	for (offset = SPDIFOUT_CHSTS1; offset <= SPDIFOUT_CHSTS5;
+	     offset += regmap_get_reg_stride(priv->map))
+		regmap_write(priv->map, offset, 0);
+
+	/* Setup channel status B bits [31 - 0]*/
+	regmap_write(priv->map, SPDIFOUT_CHSTS6, val);
+
+	/* Clear channel status B bits [191 - 32] */
+	for (offset = SPDIFOUT_CHSTS7; offset <= SPDIFOUT_CHSTSB;
+	     offset += regmap_get_reg_stride(priv->map))
+		regmap_write(priv->map, offset, 0);
+
+	return 0;
+}
+
+static int axg_spdifout_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params,
+				  struct snd_soc_dai *dai)
+{
+	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
+	unsigned int rate = params_rate(params);
+	int ret;
+
+	/* 2 * 32bits per subframe * 2 channels = 128 */
+	ret = clk_set_rate(priv->mclk, rate * 128);
+	if (ret) {
+		dev_err(dai->dev, "failed to set spdif clock\n");
+		return ret;
+	}
+
+	ret = axg_spdifout_sample_fmt(params, dai);
+	if (ret) {
+		dev_err(dai->dev, "failed to setup sample format\n");
+		return ret;
+	}
+
+	ret = axg_spdifout_set_chsts(params, dai);
+	if (ret) {
+		dev_err(dai->dev, "failed to setup channel status words\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int axg_spdifout_startup(struct snd_pcm_substream *substream,
+				struct snd_soc_dai *dai)
+{
+	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
+	int ret;
+
+	/* Clock the spdif output block */
+	ret = clk_prepare_enable(priv->pclk);
+	if (ret) {
+		dev_err(dai->dev, "failed to enable pclk\n");
+		return ret;
+	}
+
+	/* Make sure the block is initially stopped */
+	axg_spdifout_disable(priv->map);
+
+	/* Insert data from bit 27 lsb first */
+	regmap_update_bits(priv->map, SPDIFOUT_CTRL0,
+			   SPDIFOUT_CTRL0_MSB_FIRST | SPDIFOUT_CTRL0_DATA_SEL,
+			   0);
+
+	/* Manual control of V, C and U, U = 0 */
+	regmap_update_bits(priv->map, SPDIFOUT_CTRL0,
+			   SPDIFOUT_CTRL0_CHSTS_SEL | SPDIFOUT_CTRL0_VSEL |
+			   SPDIFOUT_CTRL0_USEL | SPDIFOUT_CTRL0_USET,
+			   0);
+
+	/* Static SWAP configuration ATM */
+	regmap_write(priv->map, SPDIFOUT_SWAP, 0x10);
+
+	return 0;
+}
+
+static void axg_spdifout_shutdown(struct snd_pcm_substream *substream,
+				  struct snd_soc_dai *dai)
+{
+	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai);
+
+	clk_disable_unprepare(priv->pclk);
+}
+
+static const struct snd_soc_dai_ops axg_spdifout_ops = {
+	.trigger	= axg_spdifout_trigger,
+	.digital_mute	= axg_spdifout_digital_mute,
+	.hw_params	= axg_spdifout_hw_params,
+	.startup	= axg_spdifout_startup,
+	.shutdown	= axg_spdifout_shutdown,
+};
+
+static struct snd_soc_dai_driver axg_spdifout_dai_drv[] = {
+	{
+		.name = "SPDIF Output",
+		.playback = {
+			.stream_name	= "Playback",
+			.channels_min	= 1,
+			.channels_max	= 2,
+			.rates		= (SNDRV_PCM_RATE_32000  |
+					   SNDRV_PCM_RATE_44100  |
+					   SNDRV_PCM_RATE_48000  |
+					   SNDRV_PCM_RATE_88200  |
+					   SNDRV_PCM_RATE_96000  |
+					   SNDRV_PCM_RATE_176400 |
+					   SNDRV_PCM_RATE_192000),
+			.formats	= (SNDRV_PCM_FMTBIT_S8     |
+					   SNDRV_PCM_FMTBIT_S16_LE |
+					   SNDRV_PCM_FMTBIT_S20_LE |
+					   SNDRV_PCM_FMTBIT_S24_LE),
+		},
+		.ops = &axg_spdifout_ops,
+	},
+};
+
+static const char * const spdifout_sel_texts[] = {
+	"IN 0", "IN 1", "IN 2",
+};
+
+static SOC_ENUM_SINGLE_DECL(axg_spdifout_sel_enum, SPDIFOUT_CTRL1, 24,
+			    spdifout_sel_texts);
+
+static const struct snd_kcontrol_new axg_spdifout_in_mux =
+	SOC_DAPM_ENUM("Input Source", axg_spdifout_sel_enum);
+
+static const struct snd_soc_dapm_widget axg_spdifout_dapm_widgets[] = {
+	SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_spdifout_in_mux),
+};
+
+static const struct snd_soc_dapm_route axg_spdifout_dapm_routes[] = {
+	{ "SRC SEL", "IN 0", "IN 0" },
+	{ "SRC SEL", "IN 1", "IN 1" },
+	{ "SRC SEL", "IN 2", "IN 2" },
+	{ "Playback", NULL, "SRC SEL" },
+};
+
+static const struct snd_kcontrol_new axg_spdifout_controls[] = {
+	SOC_DOUBLE("Playback Volume", SPDIFOUT_GAIN0,  0,  8, 255, 0),
+	SOC_DOUBLE("Playback Switch", SPDIFOUT_CTRL0, 22, 21, 1, 1),
+	SOC_SINGLE("Playback Gain Enable Switch",
+		   SPDIFOUT_CTRL1, 26, 1, 0),
+	SOC_SINGLE("Playback Channels Mix Switch",
+		   SPDIFOUT_CTRL0, 23, 1, 0),
+};
+
+static int axg_spdifout_set_bias_level(struct snd_soc_component *component,
+				       enum snd_soc_bias_level level)
+{
+	struct axg_spdifout *priv = snd_soc_component_get_drvdata(component);
+	enum snd_soc_bias_level now =
+		snd_soc_component_get_bias_level(component);
+	int ret = 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (now == SND_SOC_BIAS_STANDBY)
+			ret = clk_prepare_enable(priv->mclk);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (now == SND_SOC_BIAS_PREPARE)
+			clk_disable_unprepare(priv->mclk);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+	case SND_SOC_BIAS_ON:
+		break;
+	}
+
+	return ret;
+}
+
+static const struct snd_soc_component_driver axg_spdifout_component_drv = {
+	.controls		= axg_spdifout_controls,
+	.num_controls		= ARRAY_SIZE(axg_spdifout_controls),
+	.dapm_widgets		= axg_spdifout_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(axg_spdifout_dapm_widgets),
+	.dapm_routes		= axg_spdifout_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(axg_spdifout_dapm_routes),
+	.set_bias_level		= axg_spdifout_set_bias_level,
+};
+
+static const struct regmap_config axg_spdifout_regmap_cfg = {
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_stride	= 4,
+	.max_register	= SPDIFOUT_MUTE_VAL,
+};
+
+static const struct of_device_id axg_spdifout_of_match[] = {
+	{ .compatible = "amlogic,axg-spdifout", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, axg_spdifout_of_match);
+
+static int axg_spdifout_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct axg_spdifout *priv;
+	struct resource *res;
+	void __iomem *regs;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, priv);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	priv->map = devm_regmap_init_mmio(dev, regs, &axg_spdifout_regmap_cfg);
+	if (IS_ERR(priv->map)) {
+		dev_err(dev, "failed to init regmap: %ld\n",
+			PTR_ERR(priv->map));
+		return PTR_ERR(priv->map);
+	}
+
+	priv->pclk = devm_clk_get(dev, "pclk");
+	if (IS_ERR(priv->pclk)) {
+		ret = PTR_ERR(priv->pclk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get pclk: %d\n", ret);
+		return ret;
+	}
+
+	priv->mclk = devm_clk_get(dev, "mclk");
+	if (IS_ERR(priv->mclk)) {
+		ret = PTR_ERR(priv->mclk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get mclk: %d\n", ret);
+		return ret;
+	}
+
+	return devm_snd_soc_register_component(dev, &axg_spdifout_component_drv,
+			axg_spdifout_dai_drv, ARRAY_SIZE(axg_spdifout_dai_drv));
+}
+
+static struct platform_driver axg_spdifout_pdrv = {
+	.probe = axg_spdifout_probe,
+	.driver = {
+		.name = "axg-spdifout",
+		.of_match_table = axg_spdifout_of_match,
+	},
+};
+module_platform_driver(axg_spdifout_pdrv);
+
+MODULE_DESCRIPTION("Amlogic AXG SPDIF Output driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Applied "ASoC: meson: add axg spdif output DT bindings documentation" to the asoc tree
  2018-07-17 15:36 ` [PATCH 05/15] ASoC: meson: add axg spdif output DT binding documentation Jerome Brunet
@ 2018-07-18 12:25   ` Mark Brown
  0 siblings, 0 replies; 40+ messages in thread
From: Mark Brown @ 2018-07-18 12:25 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Mark Brown, Liam Girdwood, Kevin Hilman,
	Carlo Caione, devicetree, alsa-devel, linux-amlogic,
	linux-kernel, alsa-devel

The patch

   ASoC: meson: add axg spdif output DT bindings documentation

has been applied to the asoc tree at

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From eb257e6607f96fd70a443750e9eaddbb49ba87ff Mon Sep 17 00:00:00 2001
From: Jerome Brunet <jbrunet@baylibre.com>
Date: Tue, 17 Jul 2018 17:42:54 +0200
Subject: [PATCH] ASoC: meson: add axg spdif output DT bindings documentation

Add the DT bindings documentation for axg's SPDIF output.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 .../bindings/sound/amlogic,axg-spdifout.txt   | 20 +++++++++++++++++++
 1 file changed, 20 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-spdifout.txt

diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-spdifout.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-spdifout.txt
new file mode 100644
index 000000000000..521c38ad89e7
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-spdifout.txt
@@ -0,0 +1,20 @@
+* Amlogic Audio SPDIF Output
+
+Required properties:
+- compatible: 'amlogic,axg-spdifout'
+- clocks: list of clock phandle, one for each entry clock-names.
+- clock-names: should contain the following:
+  * "pclk" : peripheral clock.
+  * "mclk" : master clock
+- #sound-dai-cells: must be 0.
+
+Example on the A113 SoC:
+
+spdifout: audio-controller@480 {
+	compatible = "amlogic,axg-spdifout";
+	reg = <0x0 0x480 0x0 0x50>;
+	#sound-dai-cells = <0>;
+	clocks = <&clkc_audio AUD_CLKID_SPDIFOUT>,
+		 <&clkc_audio AUD_CLKID_SPDIFOUT_CLK>;
+	clock-names = "pclk", "mclk";
+};
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Applied "ASoC: meson: add axg toddr driver" to the asoc tree
  2018-07-17 15:36 ` [PATCH 04/15] ASoC: meson: add axg toddr driver Jerome Brunet
@ 2018-07-18 12:25   ` Mark Brown
  0 siblings, 0 replies; 40+ messages in thread
From: Mark Brown @ 2018-07-18 12:25 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Mark Brown, Liam Girdwood, Kevin Hilman,
	Carlo Caione, devicetree, alsa-devel, linux-amlogic,
	linux-kernel, alsa-devel

The patch

   ASoC: meson: add axg toddr driver

has been applied to the asoc tree at

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From 7ed4877b403c9343a8e2c7581d9bcfceef0f40cf Mon Sep 17 00:00:00 2001
From: Jerome Brunet <jbrunet@baylibre.com>
Date: Tue, 17 Jul 2018 17:42:53 +0200
Subject: [PATCH] ASoC: meson: add axg toddr driver

Add the capture memory interface of Amlogic's axg SoCs.
TDM, SPDIF or PDM input devices place audio samples inside this FIFO.
The FIFO content is then pushed to DDR

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 sound/soc/meson/Kconfig     |   7 ++
 sound/soc/meson/Makefile    |   2 +
 sound/soc/meson/axg-toddr.c | 199 ++++++++++++++++++++++++++++++++++++
 3 files changed, 208 insertions(+)
 create mode 100644 sound/soc/meson/axg-toddr.c

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index cdd78f62e8d7..3916060edcae 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -12,4 +12,11 @@ config SND_MESON_AXG_FRDDR
 	  Select Y or M to add support for the frontend playback interfaces
 	  embedded in the Amlogic AXG SoC family
 
+config SND_MESON_AXG_TODDR
+	tristate "Amlogic AXG Capture FIFO support"
+	select SND_MESON_AXG_FIFO
+	help
+	  Select Y or M to add support for the frontend capture interfaces
+	  embedded in the Amlogic AXG SoC family
+
 endmenu
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index 9c5d7d4a8e33..12edf2db2a24 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -2,6 +2,8 @@
 
 snd-soc-meson-axg-fifo-objs := axg-fifo.o
 snd-soc-meson-axg-frddr-objs := axg-frddr.o
+snd-soc-meson-axg-toddr-objs := axg-toddr.o
 
 obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
 obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
+obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o
diff --git a/sound/soc/meson/axg-toddr.c b/sound/soc/meson/axg-toddr.c
new file mode 100644
index 000000000000..c2c9bb312586
--- /dev/null
+++ b/sound/soc/meson/axg-toddr.c
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+/* This driver implements the frontend capture DAI of AXG based SoCs */
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "axg-fifo.h"
+
+#define CTRL0_TODDR_SEL_RESAMPLE	BIT(30)
+#define CTRL0_TODDR_EXT_SIGNED		BIT(29)
+#define CTRL0_TODDR_PP_MODE		BIT(28)
+#define CTRL0_TODDR_TYPE_MASK		GENMASK(15, 13)
+#define CTRL0_TODDR_TYPE(x)		((x) << 13)
+#define CTRL0_TODDR_MSB_POS_MASK	GENMASK(12, 8)
+#define CTRL0_TODDR_MSB_POS(x)		((x) << 8)
+#define CTRL0_TODDR_LSB_POS_MASK	GENMASK(7, 3)
+#define CTRL0_TODDR_LSB_POS(x)		((x) << 3)
+
+static int axg_toddr_pcm_new(struct snd_soc_pcm_runtime *rtd,
+			     struct snd_soc_dai *dai)
+{
+	return axg_fifo_pcm_new(rtd, SNDRV_PCM_STREAM_CAPTURE);
+}
+
+static int axg_toddr_dai_hw_params(struct snd_pcm_substream *substream,
+				   struct snd_pcm_hw_params *params,
+				   struct snd_soc_dai *dai)
+{
+	struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
+	unsigned int type, width, msb = 31;
+
+	/*
+	 * NOTE:
+	 * Almost all backend will place the MSB at bit 31, except SPDIF Input
+	 * which will put it at index 28. When adding support for the SPDIF
+	 * Input, we'll need to find which type of backend we are connected to.
+	 */
+
+	switch (params_physical_width(params)) {
+	case 8:
+		type = 0; /* 8 samples of 8 bits */
+		break;
+	case 16:
+		type = 2; /* 4 samples of 16 bits - right justified */
+		break;
+	case 32:
+		type = 4; /* 2 samples of 32 bits - right justified */
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	width = params_width(params);
+
+	regmap_update_bits(fifo->map, FIFO_CTRL0,
+			   CTRL0_TODDR_TYPE_MASK |
+			   CTRL0_TODDR_MSB_POS_MASK |
+			   CTRL0_TODDR_LSB_POS_MASK,
+			   CTRL0_TODDR_TYPE(type) |
+			   CTRL0_TODDR_MSB_POS(msb) |
+			   CTRL0_TODDR_LSB_POS(msb - (width - 1)));
+
+	return 0;
+}
+
+static int axg_toddr_dai_startup(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
+	unsigned int fifo_threshold;
+	int ret;
+
+	/* Enable pclk to access registers and clock the fifo ip */
+	ret = clk_prepare_enable(fifo->pclk);
+	if (ret)
+		return ret;
+
+	/* Select orginal data - resampling not supported ATM */
+	regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_SEL_RESAMPLE, 0);
+
+	/* Only signed format are supported ATM */
+	regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_EXT_SIGNED,
+			   CTRL0_TODDR_EXT_SIGNED);
+
+	/* Apply single buffer mode to the interface */
+	regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_PP_MODE, 0);
+
+	/* TODDR does not have a configurable fifo depth */
+	fifo_threshold = AXG_FIFO_MIN_CNT - 1;
+	regmap_update_bits(fifo->map, FIFO_CTRL1, CTRL1_THRESHOLD_MASK,
+			   CTRL1_THRESHOLD(fifo_threshold));
+
+	return 0;
+}
+
+static void axg_toddr_dai_shutdown(struct snd_pcm_substream *substream,
+				   struct snd_soc_dai *dai)
+{
+	struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
+
+	clk_disable_unprepare(fifo->pclk);
+}
+
+static const struct snd_soc_dai_ops axg_toddr_ops = {
+	.hw_params	= axg_toddr_dai_hw_params,
+	.startup	= axg_toddr_dai_startup,
+	.shutdown	= axg_toddr_dai_shutdown,
+};
+
+static struct snd_soc_dai_driver axg_toddr_dai_drv = {
+	.name = "TODDR",
+	.capture = {
+		.stream_name	= "Capture",
+		.channels_min	= 1,
+		.channels_max	= AXG_FIFO_CH_MAX,
+		.rates		= AXG_FIFO_RATES,
+		.formats	= AXG_FIFO_FORMATS,
+	},
+	.ops		= &axg_toddr_ops,
+	.pcm_new	= axg_toddr_pcm_new,
+};
+
+static const char * const axg_toddr_sel_texts[] = {
+	"IN 0", "IN 1", "IN 2", "IN 3", "IN 4", "IN 6"
+};
+
+static const unsigned int axg_toddr_sel_values[] = {
+	0, 1, 2, 3, 4, 6
+};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(axg_toddr_sel_enum, FIFO_CTRL0,
+				  CTRL0_SEL_SHIFT, CTRL0_SEL_MASK,
+				  axg_toddr_sel_texts, axg_toddr_sel_values);
+
+static const struct snd_kcontrol_new axg_toddr_in_mux =
+	SOC_DAPM_ENUM("Input Source", axg_toddr_sel_enum);
+
+static const struct snd_soc_dapm_widget axg_toddr_dapm_widgets[] = {
+	SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_toddr_in_mux),
+	SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 6", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route axg_toddr_dapm_routes[] = {
+	{ "Capture", NULL, "SRC SEL" },
+	{ "SRC SEL", "IN 0", "IN 0" },
+	{ "SRC SEL", "IN 1", "IN 1" },
+	{ "SRC SEL", "IN 2", "IN 2" },
+	{ "SRC SEL", "IN 3", "IN 3" },
+	{ "SRC SEL", "IN 4", "IN 4" },
+	{ "SRC SEL", "IN 6", "IN 6" },
+};
+
+static const struct snd_soc_component_driver axg_toddr_component_drv = {
+	.dapm_widgets		= axg_toddr_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(axg_toddr_dapm_widgets),
+	.dapm_routes		= axg_toddr_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(axg_toddr_dapm_routes),
+	.ops			= &axg_fifo_pcm_ops
+};
+
+static const struct axg_fifo_match_data axg_toddr_match_data = {
+	.component_drv	= &axg_toddr_component_drv,
+	.dai_drv	= &axg_toddr_dai_drv
+};
+
+static const struct of_device_id axg_toddr_of_match[] = {
+	{
+		.compatible = "amlogic,axg-toddr",
+		.data = &axg_toddr_match_data,
+	}, {}
+};
+MODULE_DEVICE_TABLE(of, axg_toddr_of_match);
+
+static struct platform_driver axg_toddr_pdrv = {
+	.probe = axg_fifo_probe,
+	.driver = {
+		.name = "axg-toddr",
+		.of_match_table = axg_toddr_of_match,
+	},
+};
+module_platform_driver(axg_toddr_pdrv);
+
+MODULE_DESCRIPTION("Amlogic AXG capture fifo driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Applied "ASoC: meson: add axg frddr driver" to the asoc tree
  2018-07-17 15:36 ` [PATCH 03/15] ASoC: meson: add axg frddr driver Jerome Brunet
@ 2018-07-18 12:26   ` Mark Brown
  0 siblings, 0 replies; 40+ messages in thread
From: Mark Brown @ 2018-07-18 12:26 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Mark Brown, Liam Girdwood, Kevin Hilman,
	Carlo Caione, devicetree, alsa-devel, linux-amlogic,
	linux-kernel, alsa-devel

The patch

   ASoC: meson: add axg frddr driver

has been applied to the asoc tree at

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From 57d552e3ea76003643b2e771042659ce71bac7c2 Mon Sep 17 00:00:00 2001
From: Jerome Brunet <jbrunet@baylibre.com>
Date: Tue, 17 Jul 2018 17:42:52 +0200
Subject: [PATCH] ASoC: meson: add axg frddr driver

Add the playback memory interface of Amlogic's axg SoCs.
This device pulls data from DDR to an internal FIFO.
This FIFO is then used to feed TDM and SPDIF Output devices.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 sound/soc/meson/Kconfig     |   7 ++
 sound/soc/meson/Makefile    |   2 +
 sound/soc/meson/axg-frddr.c | 141 ++++++++++++++++++++++++++++++++++++
 3 files changed, 150 insertions(+)
 create mode 100644 sound/soc/meson/axg-frddr.c

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index c3eb5e050308..cdd78f62e8d7 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -5,4 +5,11 @@ config SND_MESON_AXG_FIFO
 	tristate
 	select REGMAP_MMIO
 
+config SND_MESON_AXG_FRDDR
+	tristate "Amlogic AXG Playback FIFO support"
+	select SND_MESON_AXG_FIFO
+	help
+	  Select Y or M to add support for the frontend playback interfaces
+	  embedded in the Amlogic AXG SoC family
+
 endmenu
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index 75289b6b3ade..9c5d7d4a8e33 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -1,5 +1,7 @@
 # SPDX-License-Identifier: (GPL-2.0 OR MIT)
 
 snd-soc-meson-axg-fifo-objs := axg-fifo.o
+snd-soc-meson-axg-frddr-objs := axg-frddr.o
 
 obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
+obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
diff --git a/sound/soc/meson/axg-frddr.c b/sound/soc/meson/axg-frddr.c
new file mode 100644
index 000000000000..a6f6f6a2eca8
--- /dev/null
+++ b/sound/soc/meson/axg-frddr.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+/* This driver implements the frontend playback DAI of AXG based SoCs */
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "axg-fifo.h"
+
+#define CTRL0_FRDDR_PP_MODE	BIT(30)
+
+static int axg_frddr_dai_startup(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
+	unsigned int fifo_depth, fifo_threshold;
+	int ret;
+
+	/* Enable pclk to access registers and clock the fifo ip */
+	ret = clk_prepare_enable(fifo->pclk);
+	if (ret)
+		return ret;
+
+	/* Apply single buffer mode to the interface */
+	regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_FRDDR_PP_MODE, 0);
+
+	/*
+	 * TODO: We could adapt the fifo depth and the fifo threshold
+	 * depending on the expected memory throughput and lantencies
+	 * For now, we'll just use the same values as the vendor kernel
+	 * Depth and threshold are zero based.
+	 */
+	fifo_depth = AXG_FIFO_MIN_CNT - 1;
+	fifo_threshold = (AXG_FIFO_MIN_CNT / 2) - 1;
+	regmap_update_bits(fifo->map, FIFO_CTRL1,
+			   CTRL1_FRDDR_DEPTH_MASK | CTRL1_THRESHOLD_MASK,
+			   CTRL1_FRDDR_DEPTH(fifo_depth) |
+			   CTRL1_THRESHOLD(fifo_threshold));
+
+	return 0;
+}
+
+static void axg_frddr_dai_shutdown(struct snd_pcm_substream *substream,
+				   struct snd_soc_dai *dai)
+{
+	struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
+
+	clk_disable_unprepare(fifo->pclk);
+}
+
+static int axg_frddr_pcm_new(struct snd_soc_pcm_runtime *rtd,
+			     struct snd_soc_dai *dai)
+{
+	return axg_fifo_pcm_new(rtd, SNDRV_PCM_STREAM_PLAYBACK);
+}
+
+static const struct snd_soc_dai_ops axg_frddr_ops = {
+	.startup	= axg_frddr_dai_startup,
+	.shutdown	= axg_frddr_dai_shutdown,
+};
+
+static struct snd_soc_dai_driver axg_frddr_dai_drv = {
+	.name = "FRDDR",
+	.playback = {
+		.stream_name	= "Playback",
+		.channels_min	= 1,
+		.channels_max	= AXG_FIFO_CH_MAX,
+		.rates		= AXG_FIFO_RATES,
+		.formats	= AXG_FIFO_FORMATS,
+	},
+	.ops		= &axg_frddr_ops,
+	.pcm_new	= axg_frddr_pcm_new,
+};
+
+static const char * const axg_frddr_sel_texts[] = {
+	"OUT 0", "OUT 1", "OUT 2", "OUT 3"
+};
+
+static SOC_ENUM_SINGLE_DECL(axg_frddr_sel_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT,
+			    axg_frddr_sel_texts);
+
+static const struct snd_kcontrol_new axg_frddr_out_demux =
+	SOC_DAPM_ENUM("Output Sink", axg_frddr_sel_enum);
+
+static const struct snd_soc_dapm_widget axg_frddr_dapm_widgets[] = {
+	SND_SOC_DAPM_DEMUX("SINK SEL", SND_SOC_NOPM, 0, 0,
+			   &axg_frddr_out_demux),
+	SND_SOC_DAPM_AIF_OUT("OUT 0", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("OUT 1", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("OUT 2", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("OUT 3", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route axg_frddr_dapm_routes[] = {
+	{ "SINK SEL", NULL, "Playback" },
+	{ "OUT 0", "OUT 0",  "SINK SEL" },
+	{ "OUT 1", "OUT 1",  "SINK SEL" },
+	{ "OUT 2", "OUT 2",  "SINK SEL" },
+	{ "OUT 3", "OUT 3",  "SINK SEL" },
+};
+
+static const struct snd_soc_component_driver axg_frddr_component_drv = {
+	.dapm_widgets		= axg_frddr_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(axg_frddr_dapm_widgets),
+	.dapm_routes		= axg_frddr_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(axg_frddr_dapm_routes),
+	.ops			= &axg_fifo_pcm_ops
+};
+
+static const struct axg_fifo_match_data axg_frddr_match_data = {
+	.component_drv	= &axg_frddr_component_drv,
+	.dai_drv	= &axg_frddr_dai_drv
+};
+
+static const struct of_device_id axg_frddr_of_match[] = {
+	{
+		.compatible = "amlogic,axg-frddr",
+		.data = &axg_frddr_match_data,
+	}, {}
+};
+MODULE_DEVICE_TABLE(of, axg_frddr_of_match);
+
+static struct platform_driver axg_frddr_pdrv = {
+	.probe = axg_fifo_probe,
+	.driver = {
+		.name = "axg-frddr",
+		.of_match_table = axg_frddr_of_match,
+	},
+};
+module_platform_driver(axg_frddr_pdrv);
+
+MODULE_DESCRIPTION("Amlogic AXG playback fifo driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Applied "ASoC: meson: add axg fifo base driver" to the asoc tree
  2018-07-17 15:36 ` [PATCH 02/15] ASoC: meson: add axg fifo base driver Jerome Brunet
@ 2018-07-18 12:26   ` Mark Brown
  0 siblings, 0 replies; 40+ messages in thread
From: Mark Brown @ 2018-07-18 12:26 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Mark Brown, Liam Girdwood, Kevin Hilman,
	Carlo Caione, devicetree, alsa-devel, linux-amlogic,
	linux-kernel, alsa-devel

The patch

   ASoC: meson: add axg fifo base driver

has been applied to the asoc tree at

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From 6dc4fa179fb86d2c986b2bc8a8377fe4d8c0428d Mon Sep 17 00:00:00 2001
From: Jerome Brunet <jbrunet@baylibre.com>
Date: Tue, 17 Jul 2018 17:42:51 +0200
Subject: [PATCH] ASoC: meson: add axg fifo base driver

Amlogic's axg SoCs have two types of fifos which are the memory
interfaces of the audio subsystem. FRDDR provides the playback
interface while TODDR provides the capture interface.

The way these fifos operate is very similar. Only a few settings
are specific to each.

They implement the same pcm driver here and the specifics of each
will be dealt with the related DAI driver.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 sound/soc/Kconfig          |   1 +
 sound/soc/Makefile         |   1 +
 sound/soc/meson/Kconfig    |   8 +
 sound/soc/meson/Makefile   |   5 +
 sound/soc/meson/axg-fifo.c | 341 +++++++++++++++++++++++++++++++++++++
 sound/soc/meson/axg-fifo.h |  80 +++++++++
 6 files changed, 436 insertions(+)
 create mode 100644 sound/soc/meson/Kconfig
 create mode 100644 sound/soc/meson/Makefile
 create mode 100644 sound/soc/meson/axg-fifo.c
 create mode 100644 sound/soc/meson/axg-fifo.h

diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 41af6b9cc350..1cf11cf51e1d 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -57,6 +57,7 @@ source "sound/soc/kirkwood/Kconfig"
 source "sound/soc/img/Kconfig"
 source "sound/soc/intel/Kconfig"
 source "sound/soc/mediatek/Kconfig"
+source "sound/soc/meson/Kconfig"
 source "sound/soc/mxs/Kconfig"
 source "sound/soc/pxa/Kconfig"
 source "sound/soc/qcom/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 06389a5385d7..62a5f87c3cfc 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -38,6 +38,7 @@ obj-$(CONFIG_SND_SOC)	+= jz4740/
 obj-$(CONFIG_SND_SOC)	+= img/
 obj-$(CONFIG_SND_SOC)	+= intel/
 obj-$(CONFIG_SND_SOC)	+= mediatek/
+obj-$(CONFIG_SND_SOC)	+= meson/
 obj-$(CONFIG_SND_SOC)	+= mxs/
 obj-$(CONFIG_SND_SOC)	+= nuc900/
 obj-$(CONFIG_SND_SOC)	+= omap/
diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
new file mode 100644
index 000000000000..c3eb5e050308
--- /dev/null
+++ b/sound/soc/meson/Kconfig
@@ -0,0 +1,8 @@
+menu "ASoC support for Amlogic platforms"
+	depends on ARCH_MESON || COMPILE_TEST
+
+config SND_MESON_AXG_FIFO
+	tristate
+	select REGMAP_MMIO
+
+endmenu
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
new file mode 100644
index 000000000000..75289b6b3ade
--- /dev/null
+++ b/sound/soc/meson/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: (GPL-2.0 OR MIT)
+
+snd-soc-meson-axg-fifo-objs := axg-fifo.o
+
+obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
diff --git a/sound/soc/meson/axg-fifo.c b/sound/soc/meson/axg-fifo.c
new file mode 100644
index 000000000000..db367d85290f
--- /dev/null
+++ b/sound/soc/meson/axg-fifo.c
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/clk.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "axg-fifo.h"
+
+/*
+ * This file implements the platform operations common to the playback and
+ * capture frontend DAI. The logic behind this two types of fifo is very
+ * similar but some difference exist.
+ * These differences the respective DAI drivers
+ */
+
+static struct snd_pcm_hardware axg_fifo_hw = {
+	.info = (SNDRV_PCM_INFO_INTERLEAVED |
+		 SNDRV_PCM_INFO_MMAP |
+		 SNDRV_PCM_INFO_MMAP_VALID |
+		 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		 SNDRV_PCM_INFO_PAUSE),
+
+	.formats = AXG_FIFO_FORMATS,
+	.rate_min = 5512,
+	.rate_max = 192000,
+	.channels_min = 1,
+	.channels_max = AXG_FIFO_CH_MAX,
+	.period_bytes_min = AXG_FIFO_MIN_DEPTH,
+	.period_bytes_max = UINT_MAX,
+	.periods_min = 2,
+	.periods_max = UINT_MAX,
+
+	/* No real justification for this */
+	.buffer_bytes_max = 1 * 1024 * 1024,
+};
+
+static struct snd_soc_dai *axg_fifo_dai(struct snd_pcm_substream *ss)
+{
+	struct snd_soc_pcm_runtime *rtd = ss->private_data;
+
+	return rtd->cpu_dai;
+}
+
+static struct axg_fifo *axg_fifo_data(struct snd_pcm_substream *ss)
+{
+	struct snd_soc_dai *dai = axg_fifo_dai(ss);
+
+	return snd_soc_dai_get_drvdata(dai);
+}
+
+static struct device *axg_fifo_dev(struct snd_pcm_substream *ss)
+{
+	struct snd_soc_dai *dai = axg_fifo_dai(ss);
+
+	return dai->dev;
+}
+
+static void __dma_enable(struct axg_fifo *fifo,  bool enable)
+{
+	regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_DMA_EN,
+			   enable ? CTRL0_DMA_EN : 0);
+}
+
+static int axg_fifo_pcm_trigger(struct snd_pcm_substream *ss, int cmd)
+{
+	struct axg_fifo *fifo = axg_fifo_data(ss);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		__dma_enable(fifo, true);
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_STOP:
+		__dma_enable(fifo, false);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t axg_fifo_pcm_pointer(struct snd_pcm_substream *ss)
+{
+	struct axg_fifo *fifo = axg_fifo_data(ss);
+	struct snd_pcm_runtime *runtime = ss->runtime;
+	unsigned int addr;
+
+	regmap_read(fifo->map, FIFO_STATUS2, &addr);
+
+	return bytes_to_frames(runtime, addr - (unsigned int)runtime->dma_addr);
+}
+
+static int axg_fifo_pcm_hw_params(struct snd_pcm_substream *ss,
+				  struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = ss->runtime;
+	struct axg_fifo *fifo = axg_fifo_data(ss);
+	dma_addr_t end_ptr;
+	unsigned int burst_num;
+	int ret;
+
+	ret = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(params));
+	if (ret < 0)
+		return ret;
+
+	/* Setup dma memory pointers */
+	end_ptr = runtime->dma_addr + runtime->dma_bytes - AXG_FIFO_BURST;
+	regmap_write(fifo->map, FIFO_START_ADDR, runtime->dma_addr);
+	regmap_write(fifo->map, FIFO_FINISH_ADDR, end_ptr);
+
+	/* Setup interrupt periodicity */
+	burst_num = params_period_bytes(params) / AXG_FIFO_BURST;
+	regmap_write(fifo->map, FIFO_INT_ADDR, burst_num);
+
+	/* Enable block count irq */
+	regmap_update_bits(fifo->map, FIFO_CTRL0,
+			   CTRL0_INT_EN(FIFO_INT_COUNT_REPEAT),
+			   CTRL0_INT_EN(FIFO_INT_COUNT_REPEAT));
+
+	return 0;
+}
+
+static int axg_fifo_pcm_hw_free(struct snd_pcm_substream *ss)
+{
+	struct axg_fifo *fifo = axg_fifo_data(ss);
+
+	/* Disable the block count irq */
+	regmap_update_bits(fifo->map, FIFO_CTRL0,
+			   CTRL0_INT_EN(FIFO_INT_COUNT_REPEAT), 0);
+
+	return snd_pcm_lib_free_pages(ss);
+}
+
+static void axg_fifo_ack_irq(struct axg_fifo *fifo, u8 mask)
+{
+	regmap_update_bits(fifo->map, FIFO_CTRL1,
+			   CTRL1_INT_CLR(FIFO_INT_MASK),
+			   CTRL1_INT_CLR(mask));
+
+	/* Clear must also be cleared */
+	regmap_update_bits(fifo->map, FIFO_CTRL1,
+			   CTRL1_INT_CLR(FIFO_INT_MASK),
+			   0);
+}
+
+static irqreturn_t axg_fifo_pcm_irq_block(int irq, void *dev_id)
+{
+	struct snd_pcm_substream *ss = dev_id;
+	struct axg_fifo *fifo = axg_fifo_data(ss);
+	unsigned int status;
+
+	regmap_read(fifo->map, FIFO_STATUS1, &status);
+
+	status = STATUS1_INT_STS(status) & FIFO_INT_MASK;
+	if (status & FIFO_INT_COUNT_REPEAT)
+		snd_pcm_period_elapsed(ss);
+	else
+		dev_dbg(axg_fifo_dev(ss), "unexpected irq - STS 0x%02x\n",
+			status);
+
+	/* Ack irqs */
+	axg_fifo_ack_irq(fifo, status);
+
+	return !status ? IRQ_NONE : IRQ_HANDLED;
+}
+
+static int axg_fifo_pcm_open(struct snd_pcm_substream *ss)
+{
+	struct axg_fifo *fifo = axg_fifo_data(ss);
+	struct device *dev = axg_fifo_dev(ss);
+	int ret;
+
+	snd_soc_set_runtime_hwparams(ss, &axg_fifo_hw);
+
+	/*
+	 * Make sure the buffer and period size are multiple of the FIFO
+	 * minimum depth size
+	 */
+	ret = snd_pcm_hw_constraint_step(ss->runtime, 0,
+					 SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+					 AXG_FIFO_MIN_DEPTH);
+	if (ret)
+		return ret;
+
+	ret = snd_pcm_hw_constraint_step(ss->runtime, 0,
+					 SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+					 AXG_FIFO_MIN_DEPTH);
+	if (ret)
+		return ret;
+
+	ret = request_irq(fifo->irq, axg_fifo_pcm_irq_block, 0,
+			  dev_name(dev), ss);
+
+	/* Enable pclk to access registers and clock the fifo ip */
+	ret = clk_prepare_enable(fifo->pclk);
+	if (ret)
+		return ret;
+
+	/* Setup status2 so it reports the memory pointer */
+	regmap_update_bits(fifo->map, FIFO_CTRL1,
+			   CTRL1_STATUS2_SEL_MASK,
+			   CTRL1_STATUS2_SEL(STATUS2_SEL_DDR_READ));
+
+	/* Make sure the dma is initially disabled */
+	__dma_enable(fifo, false);
+
+	/* Disable irqs until params are ready */
+	regmap_update_bits(fifo->map, FIFO_CTRL0,
+			   CTRL0_INT_EN(FIFO_INT_MASK), 0);
+
+	/* Clear any pending interrupt */
+	axg_fifo_ack_irq(fifo, FIFO_INT_MASK);
+
+	/* Take memory arbitror out of reset */
+	ret = reset_control_deassert(fifo->arb);
+	if (ret)
+		clk_disable_unprepare(fifo->pclk);
+
+	return ret;
+}
+
+static int axg_fifo_pcm_close(struct snd_pcm_substream *ss)
+{
+	struct axg_fifo *fifo = axg_fifo_data(ss);
+	int ret;
+
+	/* Put the memory arbitror back in reset */
+	ret = reset_control_assert(fifo->arb);
+
+	/* Disable fifo ip and register access */
+	clk_disable_unprepare(fifo->pclk);
+
+	/* remove IRQ */
+	free_irq(fifo->irq, ss);
+
+	return ret;
+}
+
+const struct snd_pcm_ops axg_fifo_pcm_ops = {
+	.open =		axg_fifo_pcm_open,
+	.close =        axg_fifo_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	axg_fifo_pcm_hw_params,
+	.hw_free =      axg_fifo_pcm_hw_free,
+	.pointer =	axg_fifo_pcm_pointer,
+	.trigger =	axg_fifo_pcm_trigger,
+};
+EXPORT_SYMBOL_GPL(axg_fifo_pcm_ops);
+
+int axg_fifo_pcm_new(struct snd_soc_pcm_runtime *rtd, unsigned int type)
+{
+	struct snd_card *card = rtd->card->snd_card;
+	size_t size = axg_fifo_hw.buffer_bytes_max;
+
+	return snd_pcm_lib_preallocate_pages(rtd->pcm->streams[type].substream,
+					     SNDRV_DMA_TYPE_DEV, card->dev,
+					     size, size);
+}
+EXPORT_SYMBOL_GPL(axg_fifo_pcm_new);
+
+static const struct regmap_config axg_fifo_regmap_cfg = {
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_stride	= 4,
+	.max_register	= FIFO_STATUS2,
+};
+
+int axg_fifo_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const struct axg_fifo_match_data *data;
+	struct axg_fifo *fifo;
+	struct resource *res;
+	void __iomem *regs;
+
+	data = of_device_get_match_data(dev);
+	if (!data) {
+		dev_err(dev, "failed to match device\n");
+		return -ENODEV;
+	}
+
+	fifo = devm_kzalloc(dev, sizeof(*fifo), GFP_KERNEL);
+	if (!fifo)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, fifo);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	fifo->map = devm_regmap_init_mmio(dev, regs, &axg_fifo_regmap_cfg);
+	if (IS_ERR(fifo->map)) {
+		dev_err(dev, "failed to init regmap: %ld\n",
+			PTR_ERR(fifo->map));
+		return PTR_ERR(fifo->map);
+	}
+
+	fifo->pclk = devm_clk_get(dev, NULL);
+	if (IS_ERR(fifo->pclk)) {
+		if (PTR_ERR(fifo->pclk) != -EPROBE_DEFER)
+			dev_err(dev, "failed to get pclk: %ld\n",
+				PTR_ERR(fifo->pclk));
+		return PTR_ERR(fifo->pclk);
+	}
+
+	fifo->arb = devm_reset_control_get_exclusive(dev, NULL);
+	if (IS_ERR(fifo->arb)) {
+		if (PTR_ERR(fifo->arb) != -EPROBE_DEFER)
+			dev_err(dev, "failed to get arb reset: %ld\n",
+				PTR_ERR(fifo->arb));
+		return PTR_ERR(fifo->arb);
+	}
+
+	fifo->irq = of_irq_get(dev->of_node, 0);
+	if (fifo->irq <= 0) {
+		dev_err(dev, "failed to get irq: %d\n", fifo->irq);
+		return fifo->irq;
+	}
+
+	return devm_snd_soc_register_component(dev, data->component_drv,
+					       data->dai_drv, 1);
+}
+EXPORT_SYMBOL_GPL(axg_fifo_probe);
+
+MODULE_DESCRIPTION("Amlogic AXG fifo driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/meson/axg-fifo.h b/sound/soc/meson/axg-fifo.h
new file mode 100644
index 000000000000..cb6c4013ca33
--- /dev/null
+++ b/sound/soc/meson/axg-fifo.h
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Copyright (c) 2018 BayLibre, SAS.
+ * Author: Jerome Brunet <jbrunet@baylibre.com>
+ */
+
+#ifndef _MESON_AXG_FIFO_H
+#define _MESON_AXG_FIFO_H
+
+struct clk;
+struct platform_device;
+struct regmap;
+struct reset_control;
+
+struct snd_soc_component_driver;
+struct snd_soc_dai;
+struct snd_soc_dai_driver;
+struct snd_pcm_ops;
+struct snd_soc_pcm_runtime;
+
+#define AXG_FIFO_CH_MAX			128
+#define AXG_FIFO_RATES			(SNDRV_PCM_RATE_5512 |		\
+					 SNDRV_PCM_RATE_8000_192000)
+#define AXG_FIFO_FORMATS		(SNDRV_PCM_FMTBIT_S8 |		\
+					 SNDRV_PCM_FMTBIT_S16_LE |	\
+					 SNDRV_PCM_FMTBIT_S20_LE |	\
+					 SNDRV_PCM_FMTBIT_S24_LE |	\
+					 SNDRV_PCM_FMTBIT_S32_LE)
+
+#define AXG_FIFO_BURST			8
+#define AXG_FIFO_MIN_CNT		64
+#define AXG_FIFO_MIN_DEPTH		(AXG_FIFO_BURST * AXG_FIFO_MIN_CNT)
+
+#define FIFO_INT_ADDR_FINISH		BIT(0)
+#define FIFO_INT_ADDR_INT		BIT(1)
+#define FIFO_INT_COUNT_REPEAT		BIT(2)
+#define FIFO_INT_COUNT_ONCE		BIT(3)
+#define FIFO_INT_FIFO_ZERO		BIT(4)
+#define FIFO_INT_FIFO_DEPTH		BIT(5)
+#define FIFO_INT_MASK			GENMASK(7, 0)
+
+#define FIFO_CTRL0			0x00
+#define  CTRL0_DMA_EN			BIT(31)
+#define  CTRL0_INT_EN(x)		((x) << 16)
+#define  CTRL0_SEL_MASK			GENMASK(2, 0)
+#define  CTRL0_SEL_SHIFT		0
+#define FIFO_CTRL1			0x04
+#define  CTRL1_INT_CLR(x)		((x) << 0)
+#define  CTRL1_STATUS2_SEL_MASK		GENMASK(11, 8)
+#define  CTRL1_STATUS2_SEL(x)		((x) << 8)
+#define   STATUS2_SEL_DDR_READ		0
+#define  CTRL1_THRESHOLD_MASK		GENMASK(23, 16)
+#define  CTRL1_THRESHOLD(x)		((x) << 16)
+#define  CTRL1_FRDDR_DEPTH_MASK		GENMASK(31, 24)
+#define  CTRL1_FRDDR_DEPTH(x)		((x) << 24)
+#define FIFO_START_ADDR			0x08
+#define FIFO_FINISH_ADDR		0x0c
+#define FIFO_INT_ADDR			0x10
+#define FIFO_STATUS1			0x14
+#define  STATUS1_INT_STS(x)		((x) << 0)
+#define FIFO_STATUS2			0x18
+
+struct axg_fifo {
+	struct regmap *map;
+	struct clk *pclk;
+	struct reset_control *arb;
+	int irq;
+};
+
+struct axg_fifo_match_data {
+	const struct snd_soc_component_driver *component_drv;
+	struct snd_soc_dai_driver *dai_drv;
+};
+
+extern const struct snd_pcm_ops axg_fifo_pcm_ops;
+
+int axg_fifo_pcm_new(struct snd_soc_pcm_runtime *rtd, unsigned int type);
+int axg_fifo_probe(struct platform_device *pdev);
+
+#endif /* _MESON_AXG_FIFO_H */
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Applied "ASoC: meson: add axg fifos DT binding documentation" to the asoc tree
  2018-07-17 15:36 ` [PATCH 01/15] ASoC: meson: add axg fifos DT binding documentation Jerome Brunet
@ 2018-07-18 12:27   ` Mark Brown
  0 siblings, 0 replies; 40+ messages in thread
From: Mark Brown @ 2018-07-18 12:27 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Mark Brown, Liam Girdwood, Kevin Hilman,
	Carlo Caione, devicetree, alsa-devel, linux-amlogic,
	linux-kernel, alsa-devel

The patch

   ASoC: meson: add axg fifos DT binding documentation

has been applied to the asoc tree at

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From e32d99af6830c9a8f37b4f2637ef0cdc60fa79fb Mon Sep 17 00:00:00 2001
From: Jerome Brunet <jbrunet@baylibre.com>
Date: Tue, 17 Jul 2018 17:42:50 +0200
Subject: [PATCH] ASoC: meson: add axg fifos DT binding documentation

Add the DT bindings documentation for axg's FIFOs: TODDR and FRDDR.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 .../bindings/sound/amlogic,axg-fifo.txt       | 23 +++++++++++++++++++
 1 file changed, 23 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-fifo.txt

diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-fifo.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-fifo.txt
new file mode 100644
index 000000000000..3dfc2515e5c6
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-fifo.txt
@@ -0,0 +1,23 @@
+* Amlogic Audio FIFO controllers
+
+Required properties:
+- compatible: 'amlogic,axg-toddr' or
+	      'amlogic,axg-frddr'
+- reg: physical base address of the controller and length of memory
+       mapped region.
+- interrupts: interrupt specifier for the fifo.
+- clocks: phandle to the fifo peripheral clock provided by the audio
+	  clock controller.
+- resets: phandle to memory ARB line provided by the arb reset controller.
+- #sound-dai-cells: must be 0.
+
+Example of FRDDR A on the A113 SoC:
+
+frddr_a: audio-controller@1c0 {
+	compatible = "amlogic,axg-frddr";
+	reg = <0x0 0x1c0 0x0 0x1c>;
+	#sound-dai-cells = <0>;
+	interrupts = <GIC_SPI 88 IRQ_TYPE_EDGE_RISING>;
+	clocks = <&clkc_audio AUD_CLKID_FRDDR_A>;
+	resets = <&arb AXG_ARB_FRDDR_A>;
+};
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Applied "ASoC: meson: add axg sound card support" to the asoc tree
  2018-07-17 15:36 ` [PATCH 15/15] ASoC: meson: add axg sound card support Jerome Brunet
@ 2018-07-20 16:43   ` Mark Brown
  0 siblings, 0 replies; 40+ messages in thread
From: Mark Brown @ 2018-07-20 16:43 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Mark Brown, Liam Girdwood, Kevin Hilman,
	Carlo Caione, devicetree, alsa-devel, linux-amlogic,
	linux-kernel, alsa-devel

The patch

   ASoC: meson: add axg sound card support

has been applied to the asoc tree at

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From 7864a79f37b55769b817d5e6c5ae0ca4bfdba93b Mon Sep 17 00:00:00 2001
From: Jerome Brunet <jbrunet@baylibre.com>
Date: Tue, 17 Jul 2018 17:43:04 +0200
Subject: [PATCH] ASoC: meson: add axg sound card support

Add the axg sound card to handle the specifities of the axg audio
sub system.

This card is required to:
 * setup the dpcm links specific to the AXG (with a cpu sound dai)
 * handle the 4 lanes masks of the tdm interfaces
 * add the loopback link when a tdm pad interface has a playback
   stream
 * handle multi-codec links

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 sound/soc/meson/Kconfig    |  11 +
 sound/soc/meson/Makefile   |   2 +
 sound/soc/meson/axg-card.c | 671 +++++++++++++++++++++++++++++++++++++
 3 files changed, 684 insertions(+)
 create mode 100644 sound/soc/meson/axg-card.c

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index 00d05df67b52..4cf93c05a982 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -43,6 +43,17 @@ config SND_MESON_AXG_TDMOUT
 	  Select Y or M to add support for TDM output formatter embedded
 	  in the Amlogic AXG SoC family
 
+config SND_MESON_AXG_SOUND_CARD
+	tristate "Amlogic AXG Sound Card Support"
+	select SND_MESON_AXG_TDM_INTERFACE
+	imply SND_MESON_AXG_FRDDR
+	imply SND_MESON_AXG_TODDR
+	imply SND_MESON_AXG_TDMIN
+	imply SND_MESON_AXG_TDMOUT
+	imply SND_MESON_AXG_SPDIFOUT
+	help
+	  Select Y or M to add support for the AXG SoC sound card
+
 config SND_MESON_AXG_SPDIFOUT
 	tristate "Amlogic AXG SPDIF Output Support"
 	imply SND_SOC_SPDIF
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index f62833fb44d8..c5e003b093db 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -7,6 +7,7 @@ snd-soc-meson-axg-tdm-formatter-objs := axg-tdm-formatter.o
 snd-soc-meson-axg-tdm-interface-objs := axg-tdm-interface.o
 snd-soc-meson-axg-tdmin-objs := axg-tdmin.o
 snd-soc-meson-axg-tdmout-objs := axg-tdmout.o
+snd-soc-meson-axg-sound-card-objs := axg-card.o
 snd-soc-meson-axg-spdifout-objs := axg-spdifout.o
 
 obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
@@ -16,4 +17,5 @@ obj-$(CONFIG_SND_MESON_AXG_TDM_FORMATTER) += snd-soc-meson-axg-tdm-formatter.o
 obj-$(CONFIG_SND_MESON_AXG_TDM_INTERFACE) += snd-soc-meson-axg-tdm-interface.o
 obj-$(CONFIG_SND_MESON_AXG_TDMIN) += snd-soc-meson-axg-tdmin.o
 obj-$(CONFIG_SND_MESON_AXG_TDMOUT) += snd-soc-meson-axg-tdmout.o
+obj-$(CONFIG_SND_MESON_AXG_SOUND_CARD) += snd-soc-meson-axg-sound-card.o
 obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o
diff --git a/sound/soc/meson/axg-card.c b/sound/soc/meson/axg-card.c
new file mode 100644
index 000000000000..d6d1081d94ad
--- /dev/null
+++ b/sound/soc/meson/axg-card.c
@@ -0,0 +1,671 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "axg-tdm.h"
+
+struct axg_card {
+	struct snd_soc_card card;
+	void **link_data;
+};
+
+struct axg_dai_link_tdm_mask {
+	u32 tx;
+	u32 rx;
+};
+
+struct axg_dai_link_tdm_data {
+	unsigned int mclk_fs;
+	unsigned int slots;
+	unsigned int slot_width;
+	u32 *tx_mask;
+	u32 *rx_mask;
+	struct axg_dai_link_tdm_mask *codec_masks;
+};
+
+#define PREFIX "amlogic,"
+
+static int axg_card_reallocate_links(struct axg_card *priv,
+				     unsigned int num_links)
+{
+	struct snd_soc_dai_link *links;
+	void **ldata;
+
+	links = krealloc(priv->card.dai_link,
+			 num_links * sizeof(*priv->card.dai_link),
+			 GFP_KERNEL | __GFP_ZERO);
+	ldata = krealloc(priv->link_data,
+			 num_links * sizeof(*priv->link_data),
+			 GFP_KERNEL | __GFP_ZERO);
+
+	if (!links || !ldata) {
+		dev_err(priv->card.dev, "failed to allocate links\n");
+		return -ENOMEM;
+	}
+
+	priv->card.dai_link = links;
+	priv->link_data = ldata;
+	priv->card.num_links = num_links;
+	return 0;
+}
+
+static int axg_card_parse_dai(struct snd_soc_card *card,
+			      struct device_node *node,
+			      struct device_node **dai_of_node,
+			      const char **dai_name)
+{
+	struct of_phandle_args args;
+	int ret;
+
+	if (!dai_name || !dai_of_node || !node)
+		return -EINVAL;
+
+	ret = of_parse_phandle_with_args(node, "sound-dai",
+					 "#sound-dai-cells", 0, &args);
+	if (ret) {
+		if (ret != -EPROBE_DEFER)
+			dev_err(card->dev, "can't parse dai %d\n", ret);
+		return ret;
+	}
+	*dai_of_node = args.np;
+
+	return snd_soc_get_dai_name(&args, dai_name);
+}
+
+static int axg_card_set_link_name(struct snd_soc_card *card,
+				  struct snd_soc_dai_link *link,
+				  const char *prefix)
+{
+	char *name = devm_kasprintf(card->dev, GFP_KERNEL, "%s.%s",
+				    prefix, link->cpu_of_node->full_name);
+	if (!name)
+		return -ENOMEM;
+
+	link->name = name;
+	link->stream_name = name;
+
+	return 0;
+}
+
+static void axg_card_clean_references(struct axg_card *priv)
+{
+	struct snd_soc_card *card = &priv->card;
+	struct snd_soc_dai_link *link;
+	int i, j;
+
+	if (card->dai_link) {
+		for (i = 0; i < card->num_links; i++) {
+			link = &card->dai_link[i];
+			of_node_put(link->cpu_of_node);
+			for (j = 0; j < link->num_codecs; j++)
+				of_node_put(link->codecs[j].of_node);
+		}
+	}
+
+	if (card->aux_dev) {
+		for (i = 0; i < card->num_aux_devs; i++)
+			of_node_put(card->aux_dev[i].codec_of_node);
+	}
+
+	kfree(card->dai_link);
+	kfree(priv->link_data);
+}
+
+static int axg_card_add_aux_devices(struct snd_soc_card *card)
+{
+	struct device_node *node = card->dev->of_node;
+	struct snd_soc_aux_dev *aux;
+	int num, i;
+
+	num = of_count_phandle_with_args(node, PREFIX "aux-devs", NULL);
+	if (num == -ENOENT) {
+		/*
+		 * It is ok to have no auxiliary devices but for this card it
+		 * is a strange situtation. Let's warn the about it.
+		 */
+		dev_warn(card->dev, "card has no auxiliary devices\n");
+		return 0;
+	} else if (num < 0) {
+		dev_err(card->dev, "error getting auxiliary devices: %d\n",
+			num);
+		return num;
+	}
+
+	aux = devm_kcalloc(card->dev, num, sizeof(*aux), GFP_KERNEL);
+	if (!aux)
+		return -ENOMEM;
+	card->aux_dev = aux;
+	card->num_aux_devs = num;
+
+	for (i = 0; i < card->num_aux_devs; i++, aux++) {
+		aux->codec_of_node = of_parse_phandle(node,
+						      PREFIX "aux-devs", i);
+		if (!aux->codec_of_node)
+			return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int axg_card_tdm_be_hw_params(struct snd_pcm_substream *substream,
+				     struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct axg_card *priv = snd_soc_card_get_drvdata(rtd->card);
+	struct axg_dai_link_tdm_data *be =
+		(struct axg_dai_link_tdm_data *)priv->link_data[rtd->num];
+	struct snd_soc_dai *codec_dai;
+	unsigned int mclk;
+	int ret, i;
+
+	if (be->mclk_fs) {
+		mclk = params_rate(params) * be->mclk_fs;
+
+		for (i = 0; i < rtd->num_codecs; i++) {
+			codec_dai = rtd->codec_dais[i];
+			ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
+						     SND_SOC_CLOCK_IN);
+			if (ret && ret != -ENOTSUPP)
+				return ret;
+		}
+
+		ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, 0, mclk,
+					     SND_SOC_CLOCK_OUT);
+		if (ret && ret != -ENOTSUPP)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_ops axg_card_tdm_be_ops = {
+	.hw_params = axg_card_tdm_be_hw_params,
+};
+
+static int axg_card_tdm_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct axg_card *priv = snd_soc_card_get_drvdata(rtd->card);
+	struct axg_dai_link_tdm_data *be =
+		(struct axg_dai_link_tdm_data *)priv->link_data[rtd->num];
+	struct snd_soc_dai *codec_dai;
+	int ret, i;
+
+	for (i = 0; i < rtd->num_codecs; i++) {
+		codec_dai = rtd->codec_dais[i];
+		ret = snd_soc_dai_set_tdm_slot(codec_dai,
+					       be->codec_masks[i].tx,
+					       be->codec_masks[i].rx,
+					       be->slots, be->slot_width);
+		if (ret && ret != -ENOTSUPP) {
+			dev_err(codec_dai->dev,
+				"setting tdm link slots failed\n");
+			return ret;
+		}
+	}
+
+	ret = axg_tdm_set_tdm_slots(rtd->cpu_dai, be->tx_mask, be->rx_mask,
+				    be->slots, be->slot_width);
+	if (ret) {
+		dev_err(rtd->cpu_dai->dev, "setting tdm link slots failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int axg_card_tdm_dai_lb_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct axg_card *priv = snd_soc_card_get_drvdata(rtd->card);
+	struct axg_dai_link_tdm_data *be =
+		(struct axg_dai_link_tdm_data *)priv->link_data[rtd->num];
+	int ret;
+
+	/* The loopback rx_mask is the pad tx_mask */
+	ret = axg_tdm_set_tdm_slots(rtd->cpu_dai, NULL, be->tx_mask,
+				    be->slots, be->slot_width);
+	if (ret) {
+		dev_err(rtd->cpu_dai->dev, "setting tdm link slots failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int axg_card_add_tdm_loopback(struct snd_soc_card *card,
+				     int *index)
+{
+	struct axg_card *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_dai_link *pad = &card->dai_link[*index];
+	struct snd_soc_dai_link *lb;
+	int ret;
+
+	/* extend links */
+	ret = axg_card_reallocate_links(priv, card->num_links + 1);
+	if (ret)
+		return ret;
+
+	lb = &card->dai_link[*index + 1];
+
+	lb->name = kasprintf(GFP_KERNEL, "%s-lb", pad->name);
+	if (!lb->name)
+		return -ENOMEM;
+
+	lb->stream_name = lb->name;
+	lb->cpu_of_node = pad->cpu_of_node;
+	lb->cpu_dai_name = "TDM Loopback";
+	lb->codec_name = "snd-soc-dummy";
+	lb->codec_dai_name = "snd-soc-dummy-dai";
+	lb->dpcm_capture = 1;
+	lb->no_pcm = 1;
+	lb->ops = &axg_card_tdm_be_ops;
+	lb->init = axg_card_tdm_dai_lb_init;
+
+	/* Provide the same link data to the loopback */
+	priv->link_data[*index + 1] = priv->link_data[*index];
+
+	/*
+	 * axg_card_clean_references() will iterate over this link,
+	 * make sure the node count is balanced
+	 */
+	of_node_get(lb->cpu_of_node);
+
+	/* Let add_links continue where it should */
+	*index += 1;
+
+	return 0;
+}
+
+static unsigned int axg_card_parse_daifmt(struct device_node *node,
+					  struct device_node *cpu_node)
+{
+	struct device_node *bitclkmaster = NULL;
+	struct device_node *framemaster = NULL;
+	unsigned int daifmt;
+
+	daifmt = snd_soc_of_parse_daifmt(node, PREFIX,
+					 &bitclkmaster, &framemaster);
+	daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
+
+	/* If no master is provided, default to cpu master */
+	if (!bitclkmaster || bitclkmaster == cpu_node) {
+		daifmt |= (!framemaster || framemaster == cpu_node) ?
+			SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBS_CFM;
+	} else {
+		daifmt |= (!framemaster || framemaster == cpu_node) ?
+			SND_SOC_DAIFMT_CBM_CFS : SND_SOC_DAIFMT_CBM_CFM;
+	}
+
+	of_node_put(bitclkmaster);
+	of_node_put(framemaster);
+
+	return daifmt;
+}
+
+static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card,
+					struct snd_soc_dai_link *link,
+					struct device_node *node,
+					struct axg_dai_link_tdm_data *be)
+{
+	char propname[32];
+	u32 tx, rx;
+	int i;
+
+	be->tx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES,
+				   sizeof(*be->tx_mask), GFP_KERNEL);
+	be->rx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES,
+				   sizeof(*be->rx_mask), GFP_KERNEL);
+	if (!be->tx_mask || !be->rx_mask)
+		return -ENOMEM;
+
+	for (i = 0, tx = 0; i < AXG_TDM_NUM_LANES; i++) {
+		snprintf(propname, 32, "dai-tdm-slot-tx-mask-%d", i);
+		snd_soc_of_get_slot_mask(node, propname, &be->tx_mask[i]);
+		tx = max(tx, be->tx_mask[i]);
+	}
+
+	/* Disable playback is the interface has no tx slots */
+	if (!tx)
+		link->dpcm_playback = 0;
+
+	for (i = 0, rx = 0; i < AXG_TDM_NUM_LANES; i++) {
+		snprintf(propname, 32, "dai-tdm-slot-rx-mask-%d", i);
+		snd_soc_of_get_slot_mask(node, propname, &be->rx_mask[i]);
+		rx = max(rx, be->rx_mask[i]);
+	}
+
+	/* Disable capture is the interface has no rx slots */
+	if (!rx)
+		link->dpcm_capture = 0;
+
+	/* ... but the interface should at least have one of them */
+	if (!tx && !rx) {
+		dev_err(card->dev, "tdm link has no cpu slots\n");
+		return -EINVAL;
+	}
+
+	of_property_read_u32(node, "dai-tdm-slot-num", &be->slots);
+	if (!be->slots) {
+		/*
+		 * If the slot number is not provided, set it such as it
+		 * accommodates the largest mask
+		 */
+		be->slots = fls(max(tx, rx));
+	} else if (be->slots < fls(max(tx, rx)) || be->slots > 32) {
+		/*
+		 * Error if the slots can't accommodate the largest mask or
+		 * if it is just too big
+		 */
+		dev_err(card->dev, "bad slot number\n");
+		return -EINVAL;
+	}
+
+	of_property_read_u32(node, "dai-tdm-slot-width", &be->slot_width);
+
+	return 0;
+}
+
+static int axg_card_parse_codecs_masks(struct snd_soc_card *card,
+				       struct snd_soc_dai_link *link,
+				       struct device_node *node,
+				       struct axg_dai_link_tdm_data *be)
+{
+	struct axg_dai_link_tdm_mask *codec_mask;
+	struct device_node *np;
+
+	codec_mask = devm_kcalloc(card->dev, link->num_codecs,
+				  sizeof(*codec_mask), GFP_KERNEL);
+	if (!codec_mask)
+		return -ENOMEM;
+
+	be->codec_masks = codec_mask;
+
+	for_each_child_of_node(node, np) {
+		snd_soc_of_get_slot_mask(np, "dai-tdm-slot-rx-mask",
+					 &codec_mask->rx);
+		snd_soc_of_get_slot_mask(np, "dai-tdm-slot-tx-mask",
+					 &codec_mask->tx);
+
+		codec_mask++;
+	}
+
+	return 0;
+}
+
+static int axg_card_parse_tdm(struct snd_soc_card *card,
+			      struct device_node *node,
+			      int *index)
+{
+	struct axg_card *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_dai_link *link = &card->dai_link[*index];
+	struct axg_dai_link_tdm_data *be;
+	int ret;
+
+	/* Allocate tdm link parameters */
+	be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL);
+	if (!be)
+		return -ENOMEM;
+	priv->link_data[*index] = be;
+
+	/* Setup tdm link */
+	link->ops = &axg_card_tdm_be_ops;
+	link->init = axg_card_tdm_dai_init;
+	link->dai_fmt = axg_card_parse_daifmt(node, link->cpu_of_node);
+
+	of_property_read_u32(node, "mclk-fs", &be->mclk_fs);
+
+	ret = axg_card_parse_cpu_tdm_slots(card, link, node, be);
+	if (ret) {
+		dev_err(card->dev, "error parsing tdm link slots\n");
+		return ret;
+	}
+
+	ret = axg_card_parse_codecs_masks(card, link, node, be);
+	if (ret)
+		return ret;
+
+	/* Add loopback if the pad dai has playback */
+	if (link->dpcm_playback) {
+		ret = axg_card_add_tdm_loopback(card, index);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int axg_card_set_be_link(struct snd_soc_card *card,
+				struct snd_soc_dai_link *link,
+				struct device_node *node)
+{
+	struct snd_soc_dai_link_component *codec;
+	struct device_node *np;
+	int ret, num_codecs;
+
+	link->no_pcm = 1;
+	link->dpcm_playback = 1;
+	link->dpcm_capture = 1;
+
+	num_codecs = of_get_child_count(node);
+	if (!num_codecs) {
+		dev_err(card->dev, "be link %s has no codec\n",
+			node->full_name);
+		return -EINVAL;
+	}
+
+	codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec), GFP_KERNEL);
+	if (!codec)
+		return -ENOMEM;
+
+	link->codecs = codec;
+	link->num_codecs = num_codecs;
+
+	for_each_child_of_node(node, np) {
+		ret = axg_card_parse_dai(card, np, &codec->of_node,
+					 &codec->dai_name);
+		if (ret) {
+			of_node_put(np);
+			return ret;
+		}
+
+		codec++;
+	}
+
+	ret = axg_card_set_link_name(card, link, "be");
+	if (ret)
+		dev_err(card->dev, "error setting %s link name\n", np->name);
+
+	return ret;
+}
+
+static int axg_card_set_fe_link(struct snd_soc_card *card,
+				struct snd_soc_dai_link *link,
+				bool is_playback)
+{
+	link->dynamic = 1;
+	link->dpcm_merged_format = 1;
+	link->dpcm_merged_chan = 1;
+	link->dpcm_merged_rate = 1;
+	link->codec_dai_name = "snd-soc-dummy-dai";
+	link->codec_name = "snd-soc-dummy";
+
+	if (is_playback)
+		link->dpcm_playback = 1;
+	else
+		link->dpcm_capture = 1;
+
+	return axg_card_set_link_name(card, link, "fe");
+}
+
+static int axg_card_cpu_is_capture_fe(struct device_node *np)
+{
+	return of_device_is_compatible(np, PREFIX "axg-toddr");
+}
+
+static int axg_card_cpu_is_playback_fe(struct device_node *np)
+{
+	return of_device_is_compatible(np, PREFIX "axg-frddr");
+}
+
+static int axg_card_cpu_is_tdm_iface(struct device_node *np)
+{
+	return of_device_is_compatible(np, PREFIX "axg-tdm-iface");
+}
+
+static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np,
+			     int *index)
+{
+	struct snd_soc_dai_link *dai_link = &card->dai_link[*index];
+	int ret;
+
+	ret = axg_card_parse_dai(card, np, &dai_link->cpu_of_node,
+				 &dai_link->cpu_dai_name);
+	if (ret)
+		return ret;
+
+	if (axg_card_cpu_is_playback_fe(dai_link->cpu_of_node))
+		ret = axg_card_set_fe_link(card, dai_link, true);
+	else if (axg_card_cpu_is_capture_fe(dai_link->cpu_of_node))
+		ret = axg_card_set_fe_link(card, dai_link, false);
+	else
+		ret = axg_card_set_be_link(card, dai_link, np);
+
+	if (ret)
+		return ret;
+
+	if (axg_card_cpu_is_tdm_iface(dai_link->cpu_of_node))
+		ret = axg_card_parse_tdm(card, np, index);
+
+	return ret;
+}
+
+static int axg_card_add_links(struct snd_soc_card *card)
+{
+	struct axg_card *priv = snd_soc_card_get_drvdata(card);
+	struct device_node *node = card->dev->of_node;
+	struct device_node *np;
+	int num, i, ret;
+
+	num = of_get_child_count(node);
+	if (!num) {
+		dev_err(card->dev, "card has no links\n");
+		return -EINVAL;
+	}
+
+	ret = axg_card_reallocate_links(priv, num);
+	if (ret)
+		return ret;
+
+	i = 0;
+	for_each_child_of_node(node, np) {
+		ret = axg_card_add_link(card, np, &i);
+		if (ret) {
+			of_node_put(np);
+			return ret;
+		}
+
+		i++;
+	}
+
+	return 0;
+}
+
+static int axg_card_parse_of_optional(struct snd_soc_card *card,
+				      const char *propname,
+				      int (*func)(struct snd_soc_card *c,
+						  const char *p))
+{
+	/* If property is not provided, don't fail ... */
+	if (!of_property_read_bool(card->dev->of_node, propname))
+		return 0;
+
+	/* ... but do fail if it is provided and the parsing fails */
+	return func(card, propname);
+}
+
+static const struct of_device_id axg_card_of_match[] = {
+	{ .compatible = "amlogic,axg-sound-card", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, axg_card_of_match);
+
+static int axg_card_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct axg_card *priv;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, priv);
+	snd_soc_card_set_drvdata(&priv->card, priv);
+
+	priv->card.owner = THIS_MODULE;
+	priv->card.dev = dev;
+
+	ret = snd_soc_of_parse_card_name(&priv->card, PREFIX "name");
+	if (ret < 0)
+		return ret;
+
+	ret = axg_card_parse_of_optional(&priv->card, PREFIX "routing",
+					 snd_soc_of_parse_audio_routing);
+	if (ret) {
+		dev_err(dev, "error while parsing routing\n");
+		return ret;
+	}
+
+	ret = axg_card_parse_of_optional(&priv->card, PREFIX "widgets",
+					 snd_soc_of_parse_audio_simple_widgets);
+	if (ret) {
+		dev_err(dev, "error while parsing widgets\n");
+		return ret;
+	}
+
+	ret = axg_card_add_links(&priv->card);
+	if (ret)
+		goto out_err;
+
+	ret = axg_card_add_aux_devices(&priv->card);
+	if (ret)
+		goto out_err;
+
+	ret = devm_snd_soc_register_card(dev, &priv->card);
+	if (ret)
+		goto out_err;
+
+	return 0;
+
+out_err:
+	axg_card_clean_references(priv);
+	return ret;
+}
+
+static int axg_card_remove(struct platform_device *pdev)
+{
+	struct axg_card *priv = platform_get_drvdata(pdev);
+
+	axg_card_clean_references(priv);
+
+	return 0;
+}
+
+static struct platform_driver axg_card_pdrv = {
+	.probe = axg_card_probe,
+	.remove = axg_card_remove,
+	.driver = {
+		.name = "axg-sound-card",
+		.of_match_table = axg_card_of_match,
+	},
+};
+module_platform_driver(axg_card_pdrv);
+
+MODULE_DESCRIPTION("Amlogic AXG ALSA machine driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Applied "ASoC: meson: add axg sound card DT bindings documentation" to the asoc tree
  2018-07-17 15:36 ` [PATCH 14/15] ASoC: meson: add axg sound card DT binding documentation Jerome Brunet
@ 2018-07-20 16:43   ` Mark Brown
  2018-07-25 19:06   ` [PATCH 14/15] ASoC: meson: add axg sound card DT binding documentation Rob Herring
  1 sibling, 0 replies; 40+ messages in thread
From: Mark Brown @ 2018-07-20 16:43 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Mark Brown, Liam Girdwood, Kevin Hilman,
	Carlo Caione, devicetree, alsa-devel, linux-amlogic,
	linux-kernel, alsa-devel

The patch

   ASoC: meson: add axg sound card DT bindings documentation

has been applied to the asoc tree at

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From 2a05c71ea17b09c88a212e8fa6be1ccddd4613ab Mon Sep 17 00:00:00 2001
From: Jerome Brunet <jbrunet@baylibre.com>
Date: Tue, 17 Jul 2018 17:43:03 +0200
Subject: [PATCH] ASoC: meson: add axg sound card DT bindings documentation

Add the DT bindings documentation for axg sound card

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 .../bindings/sound/amlogic,axg-sound-card.txt | 124 ++++++++++++++++++
 1 file changed, 124 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt

diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt
new file mode 100644
index 000000000000..39e005da0407
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt
@@ -0,0 +1,124 @@
+Amlogic AXG sound card:
+
+Required properties:
+
+- compatible: "amlogic,axg-sound-card"
+- amlogic,name : User specified audio sound card name, one string
+
+Optional properties:
+
+- amlogic,aux-devs : List of phandles pointing to auxiliary devices
+- amlogic,widgets : Please refer to widgets.txt.
+- amlogic,routing : A list of the connections between audio components.
+
+Subnodes:
+
+- amlogic,dai-link: Container for dai-link level properties and the
+		    CODEC sub-nodes. There should be at least one (and
+		    probably) subnode of this type.
+
+Required dai-link properties:
+
+- sound-dai: phandle and port of the CPU DAI.
+
+Required TDM Backend dai-link properties:
+- dai-format : CPU/CODEC common audio format
+
+Optional TDM Backend dai-link properties:
+- dai-tdm-slot-rx-mask-{0,1,2,3}: Receive direction slot masks
+- dai-tdm-slot-tx-mask-{0,1,2,3}: Transmit direction slot masks
+				  When omitted, mask is assumed to have to no
+				  slots. A valid must have at one slot, so at
+				  least one these mask should be provided with
+				  an enabled slot.
+- dai-tdm-slot-num : Please refer to tdm-slot.txt.
+		     If omitted, slot number is set to accommodate the largest
+		     mask provided.
+- dai-tdm-slot-width : Please refer to tdm-slot.txt. default to 32 if omitted.
+- mclk-fs : Multiplication factor between stream rate and mclk
+
+Backend dai-link subnodes:
+
+- codec: dai-link representing backend links should have at least one subnode.
+	 One subnode for each codec of the dai-link.
+	 dai-link representing frontend links have no codec, therefore have no
+	 subnodes
+
+Required codec subnodes properties:
+
+- sound-dai: phandle and port of the CODEC DAI.
+
+Optional codec subnodes properties:
+
+- dai-tdm-slot-tx-mask : Please refer to tdm-slot.txt.
+- dai-tdm-slot-rx-mask : Please refer to tdm-slot.txt.
+
+Example:
+
+sound {
+	compatible = "amlogic,axg-sound-card";
+	amlogic,name = "AXG-S420";
+	amlogic,aux-devs = <&tdmin_a>, <&tdmout_c>;
+	amlogic,widgets = "Line", "Lineout",
+			  "Line", "Linein",
+			  "Speaker", "Speaker1 Left",
+			  "Speaker", "Speaker1 Right";
+			  "Speaker", "Speaker2 Left",
+			  "Speaker", "Speaker2 Right";
+	amlogic,routing = "TDMOUT_C IN 0", "FRDDR_A OUT 2",
+			  "SPDIFOUT IN 0", "FRDDR_A OUT 3",
+			  "TDM_C Playback", "TDMOUT_C OUT",
+			  "TDMIN_A IN 2", "TDM_C Capture",
+			  "TDMIN_A IN 5", "TDM_C Loopback",
+			  "TODDR_A IN 0", "TDMIN_A OUT",
+			  "Lineout", "Lineout AOUTL",
+			  "Lineout", "Lineout AOUTR",
+			  "Speaker1 Left", "SPK1 OUT_A",
+			  "Speaker2 Left", "SPK2 OUT_A",
+			  "Speaker1 Right", "SPK1 OUT_B",
+			  "Speaker2 Right", "SPK2 OUT_B",
+			  "Linein AINL", "Linein",
+			  "Linein AINR", "Linein";
+
+	amlogic,dai-link@0 {
+		sound-dai = <&frddr_a>;
+	};
+
+	amlogic,dai-link@1 {
+		sound-dai = <&toddr_a>;
+	};
+
+	amlogic,dai-link@2 {
+		sound-dai = <&tdmif_c>;
+		dai-format = "i2s";
+		dai-tdm-slot-tx-mask-2 = <1 1>;
+		dai-tdm-slot-tx-mask-3 = <1 1>;
+		dai-tdm-slot-rx-mask-1 = <1 1>;
+		mclk-fs = <256>;
+
+		codec@0 {
+			sound-dai = <&lineout>;
+		};
+
+		codec@1 {
+			sound-dai = <&speaker_amp1>;
+		};
+
+		codec@2 {
+			sound-dai = <&speaker_amp2>;
+		};
+
+		codec@3 {
+			sound-dai = <&linein>;
+		};
+
+	};
+
+	amlogic,dai-link@4 {
+		sound-dai = <&spdifout>;
+
+		codec {
+			sound-dai = <&spdif_dit>;
+		};
+	};
+};
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Applied "ASoC: export snd_soc_of_get_slot_mask" to the asoc tree
  2018-07-17 15:36 ` [PATCH 13/15] ASoC: export snd_soc_of_get_slot_mask Jerome Brunet
@ 2018-07-20 16:43   ` Mark Brown
  0 siblings, 0 replies; 40+ messages in thread
From: Mark Brown @ 2018-07-20 16:43 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Mark Brown, Liam Girdwood, Kevin Hilman,
	Carlo Caione, devicetree, alsa-devel, linux-amlogic,
	linux-kernel, alsa-devel

The patch

   ASoC: export snd_soc_of_get_slot_mask

has been applied to the asoc tree at

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From cbdfab3b675f4c34258b0ec9e4707de44e1f6989 Mon Sep 17 00:00:00 2001
From: Jerome Brunet <jbrunet@baylibre.com>
Date: Tue, 17 Jul 2018 17:43:02 +0200
Subject: [PATCH] ASoC: export snd_soc_of_get_slot_mask

Amlogic's axg card driver can't use snd_soc_of_parse_tdm_slot()
directly because it needs to handle 4 mask for each direction.
Yet the parsing of each mask is the same, so export
snd_soc_of_get_slot_mask() to reuse the the existing code.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 include/sound/soc.h  | 3 +++
 sound/soc/soc-core.c | 7 ++++---
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/include/sound/soc.h b/include/sound/soc.h
index a23ecdf3eff1..ace474e8649e 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -1433,6 +1433,9 @@ int snd_soc_of_parse_card_name(struct snd_soc_card *card,
 			       const char *propname);
 int snd_soc_of_parse_audio_simple_widgets(struct snd_soc_card *card,
 					  const char *propname);
+int snd_soc_of_get_slot_mask(struct device_node *np,
+			     const char *prop_name,
+			     unsigned int *mask);
 int snd_soc_of_parse_tdm_slot(struct device_node *np,
 			      unsigned int *tx_mask,
 			      unsigned int *rx_mask,
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index 08e189485009..ad5b0ef16d82 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -3428,9 +3428,9 @@ int snd_soc_of_parse_audio_simple_widgets(struct snd_soc_card *card,
 }
 EXPORT_SYMBOL_GPL(snd_soc_of_parse_audio_simple_widgets);
 
-static int snd_soc_of_get_slot_mask(struct device_node *np,
-				    const char *prop_name,
-				    unsigned int *mask)
+int snd_soc_of_get_slot_mask(struct device_node *np,
+			     const char *prop_name,
+			     unsigned int *mask)
 {
 	u32 val;
 	const __be32 *of_slot_mask = of_get_property(np, prop_name, &val);
@@ -3445,6 +3445,7 @@ static int snd_soc_of_get_slot_mask(struct device_node *np,
 
 	return val;
 }
+EXPORT_SYMBOL_GPL(snd_soc_of_get_slot_mask);
 
 int snd_soc_of_parse_tdm_slot(struct device_node *np,
 			      unsigned int *tx_mask,
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Applied "ASoC: meson: add tdm input driver" to the asoc tree
  2018-07-17 15:36 ` [PATCH 12/15] ASoC: meson: add tdm input driver Jerome Brunet
@ 2018-07-20 16:43   ` Mark Brown
  0 siblings, 0 replies; 40+ messages in thread
From: Mark Brown @ 2018-07-20 16:43 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Mark Brown, Liam Girdwood, Kevin Hilman,
	Carlo Caione, devicetree, alsa-devel, linux-amlogic,
	linux-kernel, alsa-devel

The patch

   ASoC: meson: add tdm input driver

has been applied to the asoc tree at

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From 13a22e6a98f8b47d61948fcd095d862377b3b143 Mon Sep 17 00:00:00 2001
From: Jerome Brunet <jbrunet@baylibre.com>
Date: Tue, 17 Jul 2018 17:43:01 +0200
Subject: [PATCH] ASoC: meson: add tdm input driver

Add Amlogic's axg TDM input driver which take the TDM signal of 4 input
lanes and push the decoded audio samples to TODDR fifo

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 sound/soc/meson/Kconfig     |   8 ++
 sound/soc/meson/Makefile    |   2 +
 sound/soc/meson/axg-tdmin.c | 229 ++++++++++++++++++++++++++++++++++++
 3 files changed, 239 insertions(+)
 create mode 100644 sound/soc/meson/axg-tdmin.c

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index 08a522b77749..00d05df67b52 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -27,6 +27,14 @@ config SND_MESON_AXG_TDM_INTERFACE
 	tristate
 	select SND_MESON_AXG_TDM_FORMATTER
 
+config SND_MESON_AXG_TDMIN
+	tristate "Amlogic AXG TDM Input Support"
+	select SND_MESON_AXG_TDM_FORMATTER
+	select SND_MESON_AXG_TDM_INTERFACE
+	help
+	  Select Y or M to add support for TDM input formatter embedded
+	  in the Amlogic AXG SoC family
+
 config SND_MESON_AXG_TDMOUT
 	tristate "Amlogic AXG TDM Output Support"
 	select SND_MESON_AXG_TDM_FORMATTER
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index a665c6b76a95..f62833fb44d8 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -5,6 +5,7 @@ snd-soc-meson-axg-frddr-objs := axg-frddr.o
 snd-soc-meson-axg-toddr-objs := axg-toddr.o
 snd-soc-meson-axg-tdm-formatter-objs := axg-tdm-formatter.o
 snd-soc-meson-axg-tdm-interface-objs := axg-tdm-interface.o
+snd-soc-meson-axg-tdmin-objs := axg-tdmin.o
 snd-soc-meson-axg-tdmout-objs := axg-tdmout.o
 snd-soc-meson-axg-spdifout-objs := axg-spdifout.o
 
@@ -13,5 +14,6 @@ obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
 obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o
 obj-$(CONFIG_SND_MESON_AXG_TDM_FORMATTER) += snd-soc-meson-axg-tdm-formatter.o
 obj-$(CONFIG_SND_MESON_AXG_TDM_INTERFACE) += snd-soc-meson-axg-tdm-interface.o
+obj-$(CONFIG_SND_MESON_AXG_TDMIN) += snd-soc-meson-axg-tdmin.o
 obj-$(CONFIG_SND_MESON_AXG_TDMOUT) += snd-soc-meson-axg-tdmout.o
 obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o
diff --git a/sound/soc/meson/axg-tdmin.c b/sound/soc/meson/axg-tdmin.c
new file mode 100644
index 000000000000..bbac44c81688
--- /dev/null
+++ b/sound/soc/meson/axg-tdmin.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "axg-tdm-formatter.h"
+
+#define TDMIN_CTRL			0x00
+#define  TDMIN_CTRL_ENABLE		BIT(31)
+#define  TDMIN_CTRL_I2S_MODE		BIT(30)
+#define  TDMIN_CTRL_RST_OUT		BIT(29)
+#define  TDMIN_CTRL_RST_IN		BIT(28)
+#define  TDMIN_CTRL_WS_INV		BIT(25)
+#define  TDMIN_CTRL_SEL_SHIFT		20
+#define  TDMIN_CTRL_IN_BIT_SKEW_MASK	GENMASK(18, 16)
+#define  TDMIN_CTRL_IN_BIT_SKEW(x)	((x) << 16)
+#define  TDMIN_CTRL_LSB_FIRST		BIT(5)
+#define  TDMIN_CTRL_BITNUM_MASK	GENMASK(4, 0)
+#define  TDMIN_CTRL_BITNUM(x)		((x) << 0)
+#define TDMIN_SWAP			0x04
+#define TDMIN_MASK0			0x08
+#define TDMIN_MASK1			0x0c
+#define TDMIN_MASK2			0x10
+#define TDMIN_MASK3			0x14
+#define TDMIN_STAT			0x18
+#define TDMIN_MUTE_VAL			0x1c
+#define TDMIN_MUTE0			0x20
+#define TDMIN_MUTE1			0x24
+#define TDMIN_MUTE2			0x28
+#define TDMIN_MUTE3			0x2c
+
+static const struct regmap_config axg_tdmin_regmap_cfg = {
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_stride	= 4,
+	.max_register	= TDMIN_MUTE3,
+};
+
+static const char * const axg_tdmin_sel_texts[] = {
+	"IN 0", "IN 1", "IN 2", "IN 3", "IN 4", "IN 5",
+};
+
+/* Change to special mux control to reset dapm */
+static SOC_ENUM_SINGLE_DECL(axg_tdmin_sel_enum, TDMIN_CTRL,
+			    TDMIN_CTRL_SEL_SHIFT, axg_tdmin_sel_texts);
+
+static const struct snd_kcontrol_new axg_tdmin_in_mux =
+	SOC_DAPM_ENUM("Input Source", axg_tdmin_sel_enum);
+
+static struct snd_soc_dai *
+axg_tdmin_get_be(struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_dapm_path *p = NULL;
+	struct snd_soc_dai *be;
+
+	snd_soc_dapm_widget_for_each_source_path(w, p) {
+		if (!p->connect)
+			continue;
+
+		if (p->source->id == snd_soc_dapm_dai_out)
+			return (struct snd_soc_dai *)p->source->priv;
+
+		be = axg_tdmin_get_be(p->source);
+		if (be)
+			return be;
+	}
+
+	return NULL;
+}
+
+static struct axg_tdm_stream *
+axg_tdmin_get_tdm_stream(struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_dai *be = axg_tdmin_get_be(w);
+
+	if (!be)
+		return NULL;
+
+	return be->capture_dma_data;
+}
+
+static void axg_tdmin_enable(struct regmap *map)
+{
+	/* Apply both reset */
+	regmap_update_bits(map, TDMIN_CTRL,
+			   TDMIN_CTRL_RST_OUT | TDMIN_CTRL_RST_IN, 0);
+
+	/* Clear out reset before in reset */
+	regmap_update_bits(map, TDMIN_CTRL,
+			   TDMIN_CTRL_RST_OUT, TDMIN_CTRL_RST_OUT);
+	regmap_update_bits(map, TDMIN_CTRL,
+			   TDMIN_CTRL_RST_IN,  TDMIN_CTRL_RST_IN);
+
+	/* Actually enable tdmin */
+	regmap_update_bits(map, TDMIN_CTRL,
+			   TDMIN_CTRL_ENABLE, TDMIN_CTRL_ENABLE);
+}
+
+static void axg_tdmin_disable(struct regmap *map)
+{
+	regmap_update_bits(map, TDMIN_CTRL, TDMIN_CTRL_ENABLE, 0);
+}
+
+static int axg_tdmin_prepare(struct regmap *map, struct axg_tdm_stream *ts)
+{
+	unsigned int val = 0;
+
+	/* Set stream skew */
+	switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_DSP_A:
+		val |= TDMIN_CTRL_IN_BIT_SKEW(3);
+		break;
+
+	case SND_SOC_DAIFMT_LEFT_J:
+	case SND_SOC_DAIFMT_RIGHT_J:
+	case SND_SOC_DAIFMT_DSP_B:
+		val = TDMIN_CTRL_IN_BIT_SKEW(2);
+		break;
+
+	default:
+		pr_err("Unsupported format: %u\n",
+		       ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+		return -EINVAL;
+	}
+
+	/* Set stream format mode */
+	switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_LEFT_J:
+	case SND_SOC_DAIFMT_RIGHT_J:
+		val |= TDMIN_CTRL_I2S_MODE;
+		break;
+	}
+
+	/* If the sample clock is inverted, invert it back for the formatter */
+	if (axg_tdm_lrclk_invert(ts->iface->fmt))
+		val |= TDMIN_CTRL_WS_INV;
+
+	/* Set the slot width */
+	val |= TDMIN_CTRL_BITNUM(ts->iface->slot_width - 1);
+
+	/*
+	 * The following also reset LSB_FIRST which result in the formatter
+	 * placing the first bit received at bit 31
+	 */
+	regmap_update_bits(map, TDMIN_CTRL,
+			   (TDMIN_CTRL_IN_BIT_SKEW_MASK | TDMIN_CTRL_WS_INV |
+			    TDMIN_CTRL_I2S_MODE | TDMIN_CTRL_LSB_FIRST |
+			    TDMIN_CTRL_BITNUM_MASK), val);
+
+	/* Set static swap mask configuration */
+	regmap_write(map, TDMIN_SWAP, 0x76543210);
+
+	return axg_tdm_formatter_set_channel_masks(map, ts, TDMIN_MASK0);
+}
+
+static const struct snd_soc_dapm_widget axg_tdmin_dapm_widgets[] = {
+	SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 5", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_tdmin_in_mux),
+	SND_SOC_DAPM_PGA_E("DEC", SND_SOC_NOPM, 0, 0, NULL, 0,
+			   axg_tdm_formatter_event,
+			   (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
+	SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route axg_tdmin_dapm_routes[] = {
+	{ "SRC SEL", "IN 0", "IN 0" },
+	{ "SRC SEL", "IN 1", "IN 1" },
+	{ "SRC SEL", "IN 2", "IN 2" },
+	{ "SRC SEL", "IN 3", "IN 3" },
+	{ "SRC SEL", "IN 4", "IN 4" },
+	{ "SRC SEL", "IN 5", "IN 5" },
+	{ "DEC", NULL, "SRC SEL" },
+	{ "OUT", NULL, "DEC" },
+};
+
+static const struct snd_soc_component_driver axg_tdmin_component_drv = {
+	.dapm_widgets		= axg_tdmin_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(axg_tdmin_dapm_widgets),
+	.dapm_routes		= axg_tdmin_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(axg_tdmin_dapm_routes),
+};
+
+static const struct axg_tdm_formatter_ops axg_tdmin_ops = {
+	.get_stream	= axg_tdmin_get_tdm_stream,
+	.prepare	= axg_tdmin_prepare,
+	.enable		= axg_tdmin_enable,
+	.disable	= axg_tdmin_disable,
+};
+
+static const struct axg_tdm_formatter_driver axg_tdmin_drv = {
+	.component_drv	= &axg_tdmin_component_drv,
+	.regmap_cfg	= &axg_tdmin_regmap_cfg,
+	.ops		= &axg_tdmin_ops,
+	.invert_sclk	= false,
+};
+
+static const struct of_device_id axg_tdmin_of_match[] = {
+	{
+		.compatible = "amlogic,axg-tdmin",
+		.data = &axg_tdmin_drv,
+	}, {}
+};
+MODULE_DEVICE_TABLE(of, axg_tdmin_of_match);
+
+static struct platform_driver axg_tdmin_pdrv = {
+	.probe = axg_tdm_formatter_probe,
+	.driver = {
+		.name = "axg-tdmin",
+		.of_match_table = axg_tdmin_of_match,
+	},
+};
+module_platform_driver(axg_tdmin_pdrv);
+
+MODULE_DESCRIPTION("Amlogic AXG TDM input formatter driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Applied "ASoC: meson: add tdm output driver" to the asoc tree
  2018-07-17 15:36 ` [PATCH 11/15] ASoC: meson: add tdm output driver Jerome Brunet
@ 2018-07-20 16:43   ` Mark Brown
  0 siblings, 0 replies; 40+ messages in thread
From: Mark Brown @ 2018-07-20 16:43 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Mark Brown, Liam Girdwood, Kevin Hilman,
	Carlo Caione, devicetree, alsa-devel, linux-amlogic,
	linux-kernel, alsa-devel

The patch

   ASoC: meson: add tdm output driver

has been applied to the asoc tree at

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From c41c2a355b86368608377eaf3df442ec0f342f1e Mon Sep 17 00:00:00 2001
From: Jerome Brunet <jbrunet@baylibre.com>
Date: Tue, 17 Jul 2018 17:43:00 +0200
Subject: [PATCH] ASoC: meson: add tdm output driver

Add Amlogic's axg tdm output driver which pulls data from FRDDR fifo
and produce the TDM signals for 4 output lanes.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 sound/soc/meson/Kconfig      |   8 ++
 sound/soc/meson/Makefile     |   2 +
 sound/soc/meson/axg-tdmout.c | 259 +++++++++++++++++++++++++++++++++++
 3 files changed, 269 insertions(+)
 create mode 100644 sound/soc/meson/axg-tdmout.c

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index 869b359c2ce7..08a522b77749 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -27,6 +27,14 @@ config SND_MESON_AXG_TDM_INTERFACE
 	tristate
 	select SND_MESON_AXG_TDM_FORMATTER
 
+config SND_MESON_AXG_TDMOUT
+	tristate "Amlogic AXG TDM Output Support"
+	select SND_MESON_AXG_TDM_FORMATTER
+	select SND_MESON_AXG_TDM_INTERFACE
+	help
+	  Select Y or M to add support for TDM output formatter embedded
+	  in the Amlogic AXG SoC family
+
 config SND_MESON_AXG_SPDIFOUT
 	tristate "Amlogic AXG SPDIF Output Support"
 	imply SND_SOC_SPDIF
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index 1a8eb77402e3..a665c6b76a95 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -5,6 +5,7 @@ snd-soc-meson-axg-frddr-objs := axg-frddr.o
 snd-soc-meson-axg-toddr-objs := axg-toddr.o
 snd-soc-meson-axg-tdm-formatter-objs := axg-tdm-formatter.o
 snd-soc-meson-axg-tdm-interface-objs := axg-tdm-interface.o
+snd-soc-meson-axg-tdmout-objs := axg-tdmout.o
 snd-soc-meson-axg-spdifout-objs := axg-spdifout.o
 
 obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
@@ -12,4 +13,5 @@ obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
 obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o
 obj-$(CONFIG_SND_MESON_AXG_TDM_FORMATTER) += snd-soc-meson-axg-tdm-formatter.o
 obj-$(CONFIG_SND_MESON_AXG_TDM_INTERFACE) += snd-soc-meson-axg-tdm-interface.o
+obj-$(CONFIG_SND_MESON_AXG_TDMOUT) += snd-soc-meson-axg-tdmout.o
 obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o
diff --git a/sound/soc/meson/axg-tdmout.c b/sound/soc/meson/axg-tdmout.c
new file mode 100644
index 000000000000..f73368ee1088
--- /dev/null
+++ b/sound/soc/meson/axg-tdmout.c
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "axg-tdm-formatter.h"
+
+#define TDMOUT_CTRL0			0x00
+#define  TDMOUT_CTRL0_BITNUM_MASK	GENMASK(4, 0)
+#define  TDMOUT_CTRL0_BITNUM(x)		((x) << 0)
+#define  TDMOUT_CTRL0_SLOTNUM_MASK	GENMASK(9, 5)
+#define  TDMOUT_CTRL0_SLOTNUM(x)	((x) << 5)
+#define  TDMOUT_CTRL0_INIT_BITNUM_MASK	GENMASK(19, 15)
+#define  TDMOUT_CTRL0_INIT_BITNUM(x)	((x) << 15)
+#define  TDMOUT_CTRL0_ENABLE		BIT(31)
+#define  TDMOUT_CTRL0_RST_OUT		BIT(29)
+#define  TDMOUT_CTRL0_RST_IN		BIT(28)
+#define TDMOUT_CTRL1			0x04
+#define  TDMOUT_CTRL1_TYPE_MASK		GENMASK(6, 4)
+#define  TDMOUT_CTRL1_TYPE(x)		((x) << 4)
+#define  TDMOUT_CTRL1_MSB_POS_MASK	GENMASK(12, 8)
+#define  TDMOUT_CTRL1_MSB_POS(x)	((x) << 8)
+#define  TDMOUT_CTRL1_SEL_SHIFT		24
+#define  TDMOUT_CTRL1_GAIN_EN		26
+#define  TDMOUT_CTRL1_WS_INV		BIT(28)
+#define TDMOUT_SWAP			0x08
+#define TDMOUT_MASK0			0x0c
+#define TDMOUT_MASK1			0x10
+#define TDMOUT_MASK2			0x14
+#define TDMOUT_MASK3			0x18
+#define TDMOUT_STAT			0x1c
+#define TDMOUT_GAIN0			0x20
+#define TDMOUT_GAIN1			0x24
+#define TDMOUT_MUTE_VAL			0x28
+#define TDMOUT_MUTE0			0x2c
+#define TDMOUT_MUTE1			0x30
+#define TDMOUT_MUTE2			0x34
+#define TDMOUT_MUTE3			0x38
+#define TDMOUT_MASK_VAL			0x3c
+
+static const struct regmap_config axg_tdmout_regmap_cfg = {
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_stride	= 4,
+	.max_register	= TDMOUT_MASK_VAL,
+};
+
+static const struct snd_kcontrol_new axg_tdmout_controls[] = {
+	SOC_DOUBLE("Lane 0 Volume", TDMOUT_GAIN0,  0,  8, 255, 0),
+	SOC_DOUBLE("Lane 1 Volume", TDMOUT_GAIN0, 16, 24, 255, 0),
+	SOC_DOUBLE("Lane 2 Volume", TDMOUT_GAIN1,  0,  8, 255, 0),
+	SOC_DOUBLE("Lane 3 Volume", TDMOUT_GAIN1, 16, 24, 255, 0),
+	SOC_SINGLE("Gain Enable Switch", TDMOUT_CTRL1,
+		   TDMOUT_CTRL1_GAIN_EN, 1, 0),
+};
+
+static const char * const tdmout_sel_texts[] = {
+	"IN 0", "IN 1", "IN 2",
+};
+
+static SOC_ENUM_SINGLE_DECL(axg_tdmout_sel_enum, TDMOUT_CTRL1,
+			    TDMOUT_CTRL1_SEL_SHIFT, tdmout_sel_texts);
+
+static const struct snd_kcontrol_new axg_tdmout_in_mux =
+	SOC_DAPM_ENUM("Input Source", axg_tdmout_sel_enum);
+
+static struct snd_soc_dai *
+axg_tdmout_get_be(struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_dapm_path *p = NULL;
+	struct snd_soc_dai *be;
+
+	snd_soc_dapm_widget_for_each_sink_path(w, p) {
+		if (!p->connect)
+			continue;
+
+		if (p->sink->id == snd_soc_dapm_dai_in)
+			return (struct snd_soc_dai *)p->sink->priv;
+
+		be = axg_tdmout_get_be(p->sink);
+		if (be)
+			return be;
+	}
+
+	return NULL;
+}
+
+static struct axg_tdm_stream *
+axg_tdmout_get_tdm_stream(struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_dai *be = axg_tdmout_get_be(w);
+
+	if (!be)
+		return NULL;
+
+	return be->playback_dma_data;
+}
+
+static void axg_tdmout_enable(struct regmap *map)
+{
+	/* Apply both reset */
+	regmap_update_bits(map, TDMOUT_CTRL0,
+			   TDMOUT_CTRL0_RST_OUT | TDMOUT_CTRL0_RST_IN, 0);
+
+	/* Clear out reset before in reset */
+	regmap_update_bits(map, TDMOUT_CTRL0,
+			   TDMOUT_CTRL0_RST_OUT, TDMOUT_CTRL0_RST_OUT);
+	regmap_update_bits(map, TDMOUT_CTRL0,
+			   TDMOUT_CTRL0_RST_IN,  TDMOUT_CTRL0_RST_IN);
+
+	/* Actually enable tdmout */
+	regmap_update_bits(map, TDMOUT_CTRL0,
+			   TDMOUT_CTRL0_ENABLE, TDMOUT_CTRL0_ENABLE);
+}
+
+static void axg_tdmout_disable(struct regmap *map)
+{
+	regmap_update_bits(map, TDMOUT_CTRL0, TDMOUT_CTRL0_ENABLE, 0);
+}
+
+static int axg_tdmout_prepare(struct regmap *map, struct axg_tdm_stream *ts)
+{
+	unsigned int val = 0;
+
+	/* Set the stream skew */
+	switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_DSP_A:
+		val |= TDMOUT_CTRL0_INIT_BITNUM(1);
+		break;
+
+	case SND_SOC_DAIFMT_LEFT_J:
+	case SND_SOC_DAIFMT_RIGHT_J:
+	case SND_SOC_DAIFMT_DSP_B:
+		val |= TDMOUT_CTRL0_INIT_BITNUM(2);
+		break;
+
+	default:
+		pr_err("Unsupported format: %u\n",
+		       ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+		return -EINVAL;
+	}
+
+	/* Set the slot width */
+	val |= TDMOUT_CTRL0_BITNUM(ts->iface->slot_width - 1);
+
+	/* Set the slot number */
+	val |= TDMOUT_CTRL0_SLOTNUM(ts->iface->slots - 1);
+
+	regmap_update_bits(map, TDMOUT_CTRL0,
+			   TDMOUT_CTRL0_INIT_BITNUM_MASK |
+			   TDMOUT_CTRL0_BITNUM_MASK |
+			   TDMOUT_CTRL0_SLOTNUM_MASK, val);
+
+	/* Set the sample width */
+	val = TDMOUT_CTRL1_MSB_POS(ts->width - 1);
+
+	/* FIFO data are arranged in chunks of 64bits */
+	switch (ts->physical_width) {
+	case 8:
+		/* 8 samples of 8 bits */
+		val |= TDMOUT_CTRL1_TYPE(0);
+		break;
+	case 16:
+		/* 4 samples of 16 bits - right justified */
+		val |= TDMOUT_CTRL1_TYPE(2);
+		break;
+	case 32:
+		/* 2 samples of 32 bits - right justified */
+		val |= TDMOUT_CTRL1_TYPE(4);
+		break;
+	default:
+		pr_err("Unsupported physical width: %u\n",
+		       ts->physical_width);
+		return -EINVAL;
+	}
+
+	/* If the sample clock is inverted, invert it back for the formatter */
+	if (axg_tdm_lrclk_invert(ts->iface->fmt))
+		val |= TDMOUT_CTRL1_WS_INV;
+
+	regmap_update_bits(map, TDMOUT_CTRL1,
+			   (TDMOUT_CTRL1_TYPE_MASK | TDMOUT_CTRL1_MSB_POS_MASK |
+			    TDMOUT_CTRL1_WS_INV), val);
+
+	/* Set static swap mask configuration */
+	regmap_write(map, TDMOUT_SWAP, 0x76543210);
+
+	return axg_tdm_formatter_set_channel_masks(map, ts, TDMOUT_MASK0);
+}
+
+static const struct snd_soc_dapm_widget axg_tdmout_dapm_widgets[] = {
+	SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_tdmout_in_mux),
+	SND_SOC_DAPM_PGA_E("ENC", SND_SOC_NOPM, 0, 0, NULL, 0,
+			   axg_tdm_formatter_event,
+			   (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
+	SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route axg_tdmout_dapm_routes[] = {
+	{ "SRC SEL", "IN 0", "IN 0" },
+	{ "SRC SEL", "IN 1", "IN 1" },
+	{ "SRC SEL", "IN 2", "IN 2" },
+	{ "ENC", NULL, "SRC SEL" },
+	{ "OUT", NULL, "ENC" },
+};
+
+static const struct snd_soc_component_driver axg_tdmout_component_drv = {
+	.controls		= axg_tdmout_controls,
+	.num_controls		= ARRAY_SIZE(axg_tdmout_controls),
+	.dapm_widgets		= axg_tdmout_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(axg_tdmout_dapm_widgets),
+	.dapm_routes		= axg_tdmout_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(axg_tdmout_dapm_routes),
+};
+
+static const struct axg_tdm_formatter_ops axg_tdmout_ops = {
+	.get_stream	= axg_tdmout_get_tdm_stream,
+	.prepare	= axg_tdmout_prepare,
+	.enable		= axg_tdmout_enable,
+	.disable	= axg_tdmout_disable,
+};
+
+static const struct axg_tdm_formatter_driver axg_tdmout_drv = {
+	.component_drv	= &axg_tdmout_component_drv,
+	.regmap_cfg	= &axg_tdmout_regmap_cfg,
+	.ops		= &axg_tdmout_ops,
+	.invert_sclk	= true,
+};
+
+static const struct of_device_id axg_tdmout_of_match[] = {
+	{
+		.compatible = "amlogic,axg-tdmout",
+		.data = &axg_tdmout_drv,
+	}, {}
+};
+MODULE_DEVICE_TABLE(of, axg_tdmout_of_match);
+
+static struct platform_driver axg_tdmout_pdrv = {
+	.probe = axg_tdm_formatter_probe,
+	.driver = {
+		.name = "axg-tdmout",
+		.of_match_table = axg_tdmout_of_match,
+	},
+};
+module_platform_driver(axg_tdmout_pdrv);
+
+MODULE_DESCRIPTION("Amlogic AXG TDM output formatter driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Applied "ASoC: meson: add tdm interface driver" to the asoc tree
  2018-07-17 15:36 ` [PATCH 10/15] ASoC: meson: add tdm interface driver Jerome Brunet
@ 2018-07-20 16:44   ` Mark Brown
  0 siblings, 0 replies; 40+ messages in thread
From: Mark Brown @ 2018-07-20 16:44 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Mark Brown, Liam Girdwood, Kevin Hilman,
	Carlo Caione, devicetree, alsa-devel, linux-amlogic,
	linux-kernel, alsa-devel

The patch

   ASoC: meson: add tdm interface driver

has been applied to the asoc tree at

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From d60e4f1e4be5e2dfb55fb084b119aed094227a35 Mon Sep 17 00:00:00 2001
From: Jerome Brunet <jbrunet@baylibre.com>
Date: Tue, 17 Jul 2018 17:42:59 +0200
Subject: [PATCH] ASoC: meson: add tdm interface driver

Add Amlogic's axg TDM interface driver. This driver manages the format
and clocks provided on the pads.

On this SoC, each stream direction provides 4 serial lanes. This makes
a maximum of 8 channels in i2s modes and 128 channels in DSP modes.

While each lanes operate on the same slot number (same bit clock), they
may have different TDM masks. This requires to provide a function to let
the card set the 4 masks, in lieu of the usual set_tdm_slots() callback
of the dai driver.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 sound/soc/meson/Kconfig             |   4 +
 sound/soc/meson/Makefile            |   2 +
 sound/soc/meson/axg-tdm-interface.c | 542 ++++++++++++++++++++++++++++
 sound/soc/meson/axg-tdm.h           |   4 +
 4 files changed, 552 insertions(+)
 create mode 100644 sound/soc/meson/axg-tdm-interface.c

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index 80a88689491f..869b359c2ce7 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -23,6 +23,10 @@ config SND_MESON_AXG_TDM_FORMATTER
 	tristate
 	select REGMAP_MMIO
 
+config SND_MESON_AXG_TDM_INTERFACE
+	tristate
+	select SND_MESON_AXG_TDM_FORMATTER
+
 config SND_MESON_AXG_SPDIFOUT
 	tristate "Amlogic AXG SPDIF Output Support"
 	imply SND_SOC_SPDIF
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index a06b56a1c995..1a8eb77402e3 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -4,10 +4,12 @@ snd-soc-meson-axg-fifo-objs := axg-fifo.o
 snd-soc-meson-axg-frddr-objs := axg-frddr.o
 snd-soc-meson-axg-toddr-objs := axg-toddr.o
 snd-soc-meson-axg-tdm-formatter-objs := axg-tdm-formatter.o
+snd-soc-meson-axg-tdm-interface-objs := axg-tdm-interface.o
 snd-soc-meson-axg-spdifout-objs := axg-spdifout.o
 
 obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
 obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
 obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o
 obj-$(CONFIG_SND_MESON_AXG_TDM_FORMATTER) += snd-soc-meson-axg-tdm-formatter.o
+obj-$(CONFIG_SND_MESON_AXG_TDM_INTERFACE) += snd-soc-meson-axg-tdm-interface.o
 obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o
diff --git a/sound/soc/meson/axg-tdm-interface.c b/sound/soc/meson/axg-tdm-interface.c
new file mode 100644
index 000000000000..7b8baf46d968
--- /dev/null
+++ b/sound/soc/meson/axg-tdm-interface.c
@@ -0,0 +1,542 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "axg-tdm.h"
+
+enum {
+	TDM_IFACE_PAD,
+	TDM_IFACE_LOOPBACK,
+};
+
+static unsigned int axg_tdm_slots_total(u32 *mask)
+{
+	unsigned int slots = 0;
+	int i;
+
+	if (!mask)
+		return 0;
+
+	/* Count the total number of slots provided by all 4 lanes */
+	for (i = 0; i < AXG_TDM_NUM_LANES; i++)
+		slots += hweight32(mask[i]);
+
+	return slots;
+}
+
+int axg_tdm_set_tdm_slots(struct snd_soc_dai *dai, u32 *tx_mask,
+			  u32 *rx_mask, unsigned int slots,
+			  unsigned int slot_width)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+	struct axg_tdm_stream *tx = (struct axg_tdm_stream *)
+		dai->playback_dma_data;
+	struct axg_tdm_stream *rx = (struct axg_tdm_stream *)
+		dai->capture_dma_data;
+	unsigned int tx_slots, rx_slots;
+
+	tx_slots = axg_tdm_slots_total(tx_mask);
+	rx_slots = axg_tdm_slots_total(rx_mask);
+
+	/* We should at least have a slot for a valid interface */
+	if (!tx_slots && !rx_slots) {
+		dev_err(dai->dev, "interface has no slot\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * Amend the dai driver channel number and let dpcm channel merge do
+	 * its job
+	 */
+	if (tx) {
+		tx->mask = tx_mask;
+		dai->driver->playback.channels_max = tx_slots;
+	}
+
+	if (rx) {
+		rx->mask = rx_mask;
+		dai->driver->capture.channels_max = rx_slots;
+	}
+
+	iface->slots = slots;
+
+	switch (slot_width) {
+	case 0:
+		/* defaults width to 32 if not provided */
+		iface->slot_width = 32;
+		break;
+	case 8:
+	case 16:
+	case 24:
+	case 32:
+		iface->slot_width = slot_width;
+		break;
+	default:
+		dev_err(dai->dev, "unsupported slot width: %d\n", slot_width);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(axg_tdm_set_tdm_slots);
+
+static int axg_tdm_iface_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+				    unsigned int freq, int dir)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+	int ret = -ENOTSUPP;
+
+	if (dir == SND_SOC_CLOCK_OUT && clk_id == 0) {
+		if (!iface->mclk) {
+			dev_warn(dai->dev, "master clock not provided\n");
+		} else {
+			ret = clk_set_rate(iface->mclk, freq);
+			if (!ret)
+				iface->mclk_rate = freq;
+		}
+	}
+
+	return ret;
+}
+
+static int axg_tdm_iface_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+
+	/* These modes are not supported */
+	if (fmt & (SND_SOC_DAIFMT_CBS_CFM | SND_SOC_DAIFMT_CBM_CFS)) {
+		dev_err(dai->dev, "only CBS_CFS and CBM_CFM are supported\n");
+		return -EINVAL;
+	}
+
+	/* If the TDM interface is the clock master, it requires mclk */
+	if (!iface->mclk && (fmt & SND_SOC_DAIFMT_CBS_CFS)) {
+		dev_err(dai->dev, "cpu clock master: mclk missing\n");
+		return -ENODEV;
+	}
+
+	iface->fmt = fmt;
+	return 0;
+}
+
+static int axg_tdm_iface_startup(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+	struct axg_tdm_stream *ts =
+		snd_soc_dai_get_dma_data(dai, substream);
+	int ret;
+
+	if (!axg_tdm_slots_total(ts->mask)) {
+		dev_err(dai->dev, "interface has not slots\n");
+		return -EINVAL;
+	}
+
+	/* Apply component wide rate symmetry */
+	if (dai->component->active) {
+		ret = snd_pcm_hw_constraint_single(substream->runtime,
+						   SNDRV_PCM_HW_PARAM_RATE,
+						   iface->rate);
+		if (ret < 0) {
+			dev_err(dai->dev,
+				"can't set iface rate constraint\n");
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int axg_tdm_iface_set_stream(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *params,
+				    struct snd_soc_dai *dai)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+	struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
+	unsigned int channels = params_channels(params);
+	unsigned int width = params_width(params);
+
+	/* Save rate and sample_bits for component symmetry */
+	iface->rate = params_rate(params);
+
+	/* Make sure this interface can cope with the stream */
+	if (axg_tdm_slots_total(ts->mask) < channels) {
+		dev_err(dai->dev, "not enough slots for channels\n");
+		return -EINVAL;
+	}
+
+	if (iface->slot_width < width) {
+		dev_err(dai->dev, "incompatible slots width for stream\n");
+		return -EINVAL;
+	}
+
+	/* Save the parameter for tdmout/tdmin widgets */
+	ts->physical_width = params_physical_width(params);
+	ts->width = params_width(params);
+	ts->channels = params_channels(params);
+
+	return 0;
+}
+
+static int axg_tdm_iface_set_lrclk(struct snd_soc_dai *dai,
+				   struct snd_pcm_hw_params *params)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+	unsigned int ratio_num;
+	int ret;
+
+	ret = clk_set_rate(iface->lrclk, params_rate(params));
+	if (ret) {
+		dev_err(dai->dev, "setting sample clock failed: %d\n", ret);
+		return ret;
+	}
+
+	switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_LEFT_J:
+	case SND_SOC_DAIFMT_RIGHT_J:
+		/* 50% duty cycle ratio */
+		ratio_num = 1;
+		break;
+
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		/*
+		 * A zero duty cycle ratio will result in setting the mininum
+		 * ratio possible which, for this clock, is 1 cycle of the
+		 * parent bclk clock high and the rest low, This is exactly
+		 * what we want here.
+		 */
+		ratio_num = 0;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	ret = clk_set_duty_cycle(iface->lrclk, ratio_num, 2);
+	if (ret) {
+		dev_err(dai->dev,
+			"setting sample clock duty cycle failed: %d\n", ret);
+		return ret;
+	}
+
+	/* Set sample clock inversion */
+	ret = clk_set_phase(iface->lrclk,
+			    axg_tdm_lrclk_invert(iface->fmt) ? 180 : 0);
+	if (ret) {
+		dev_err(dai->dev,
+			"setting sample clock phase failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int axg_tdm_iface_set_sclk(struct snd_soc_dai *dai,
+				  struct snd_pcm_hw_params *params)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+	unsigned long srate;
+	int ret;
+
+	srate = iface->slots * iface->slot_width * params_rate(params);
+
+	if (!iface->mclk_rate) {
+		/* If no specific mclk is requested, default to bit clock * 4 */
+		clk_set_rate(iface->mclk, 4 * srate);
+	} else {
+		/* Check if we can actually get the bit clock from mclk */
+		if (iface->mclk_rate % srate) {
+			dev_err(dai->dev,
+				"can't derive sclk %lu from mclk %lu\n",
+				srate, iface->mclk_rate);
+			return -EINVAL;
+		}
+	}
+
+	ret = clk_set_rate(iface->sclk, srate);
+	if (ret) {
+		dev_err(dai->dev, "setting bit clock failed: %d\n", ret);
+		return ret;
+	}
+
+	/* Set the bit clock inversion */
+	ret = clk_set_phase(iface->sclk,
+			    axg_tdm_sclk_invert(iface->fmt) ? 0 : 180);
+	if (ret) {
+		dev_err(dai->dev, "setting bit clock phase failed: %d\n", ret);
+		return ret;
+	}
+
+	return ret;
+}
+
+static int axg_tdm_iface_hw_params(struct snd_pcm_substream *substream,
+				   struct snd_pcm_hw_params *params,
+				   struct snd_soc_dai *dai)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+	int ret;
+
+	switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_LEFT_J:
+	case SND_SOC_DAIFMT_RIGHT_J:
+		if (iface->slots > 2) {
+			dev_err(dai->dev, "bad slot number for format: %d\n",
+				iface->slots);
+			return -EINVAL;
+		}
+		break;
+
+	case SND_SOC_DAI_FORMAT_DSP_A:
+	case SND_SOC_DAI_FORMAT_DSP_B:
+		break;
+
+	default:
+		dev_err(dai->dev, "unsupported dai format\n");
+		return -EINVAL;
+	}
+
+	ret = axg_tdm_iface_set_stream(substream, params, dai);
+	if (ret)
+		return ret;
+
+	if (iface->fmt & SND_SOC_DAIFMT_CBS_CFS) {
+		ret = axg_tdm_iface_set_sclk(dai, params);
+		if (ret)
+			return ret;
+
+		ret = axg_tdm_iface_set_lrclk(dai, params);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int axg_tdm_iface_hw_free(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
+
+	/* Stop all attached formatters */
+	axg_tdm_stream_stop(ts);
+
+	return 0;
+}
+
+static int axg_tdm_iface_prepare(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
+
+	/* Force all attached formatters to update */
+	return axg_tdm_stream_reset(ts);
+}
+
+static int axg_tdm_iface_remove_dai(struct snd_soc_dai *dai)
+{
+	if (dai->capture_dma_data)
+		axg_tdm_stream_free(dai->capture_dma_data);
+
+	if (dai->playback_dma_data)
+		axg_tdm_stream_free(dai->playback_dma_data);
+
+	return 0;
+}
+
+static int axg_tdm_iface_probe_dai(struct snd_soc_dai *dai)
+{
+	struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai);
+
+	if (dai->capture_widget) {
+		dai->capture_dma_data = axg_tdm_stream_alloc(iface);
+		if (!dai->capture_dma_data)
+			return -ENOMEM;
+	}
+
+	if (dai->playback_widget) {
+		dai->playback_dma_data = axg_tdm_stream_alloc(iface);
+		if (!dai->playback_dma_data) {
+			axg_tdm_iface_remove_dai(dai);
+			return -ENOMEM;
+		}
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops axg_tdm_iface_ops = {
+	.set_sysclk	= axg_tdm_iface_set_sysclk,
+	.set_fmt	= axg_tdm_iface_set_fmt,
+	.startup	= axg_tdm_iface_startup,
+	.hw_params	= axg_tdm_iface_hw_params,
+	.prepare	= axg_tdm_iface_prepare,
+	.hw_free	= axg_tdm_iface_hw_free,
+};
+
+/* TDM Backend DAIs */
+static const struct snd_soc_dai_driver axg_tdm_iface_dai_drv[] = {
+	[TDM_IFACE_PAD] = {
+		.name = "TDM Pad",
+		.playback = {
+			.stream_name	= "Playback",
+			.channels_min	= 1,
+			.channels_max	= AXG_TDM_CHANNEL_MAX,
+			.rates		= AXG_TDM_RATES,
+			.formats	= AXG_TDM_FORMATS,
+		},
+		.capture = {
+			.stream_name	= "Capture",
+			.channels_min	= 1,
+			.channels_max	= AXG_TDM_CHANNEL_MAX,
+			.rates		= AXG_TDM_RATES,
+			.formats	= AXG_TDM_FORMATS,
+		},
+		.id = TDM_IFACE_PAD,
+		.ops = &axg_tdm_iface_ops,
+		.probe = axg_tdm_iface_probe_dai,
+		.remove = axg_tdm_iface_remove_dai,
+	},
+	[TDM_IFACE_LOOPBACK] = {
+		.name = "TDM Loopback",
+		.capture = {
+			.stream_name	= "Loopback",
+			.channels_min	= 1,
+			.channels_max	= AXG_TDM_CHANNEL_MAX,
+			.rates		= AXG_TDM_RATES,
+			.formats	= AXG_TDM_FORMATS,
+		},
+		.id = TDM_IFACE_LOOPBACK,
+		.ops = &axg_tdm_iface_ops,
+		.probe = axg_tdm_iface_probe_dai,
+		.remove = axg_tdm_iface_remove_dai,
+	},
+};
+
+static int axg_tdm_iface_set_bias_level(struct snd_soc_component *component,
+					enum snd_soc_bias_level level)
+{
+	struct axg_tdm_iface *iface = snd_soc_component_get_drvdata(component);
+	enum snd_soc_bias_level now =
+		snd_soc_component_get_bias_level(component);
+	int ret = 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (now == SND_SOC_BIAS_STANDBY)
+			ret = clk_prepare_enable(iface->mclk);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (now == SND_SOC_BIAS_PREPARE)
+			clk_disable_unprepare(iface->mclk);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+	case SND_SOC_BIAS_ON:
+		break;
+	}
+
+	return ret;
+}
+
+static const struct snd_soc_component_driver axg_tdm_iface_component_drv = {
+	.set_bias_level	= axg_tdm_iface_set_bias_level,
+};
+
+static const struct of_device_id axg_tdm_iface_of_match[] = {
+	{ .compatible = "amlogic,axg-tdm-iface", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, axg_tdm_iface_of_match);
+
+static int axg_tdm_iface_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct snd_soc_dai_driver *dai_drv;
+	struct axg_tdm_iface *iface;
+	int ret, i;
+
+	iface = devm_kzalloc(dev, sizeof(*iface), GFP_KERNEL);
+	if (!iface)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, iface);
+
+	/*
+	 * Duplicate dai driver: depending on the slot masks configuration
+	 * We'll change the number of channel provided by DAI stream, so dpcm
+	 * channel merge can be done properly
+	 */
+	dai_drv = devm_kcalloc(dev, ARRAY_SIZE(axg_tdm_iface_dai_drv),
+			       sizeof(*dai_drv), GFP_KERNEL);
+	if (!dai_drv)
+		return -ENOMEM;
+
+	for (i = 0; i < ARRAY_SIZE(axg_tdm_iface_dai_drv); i++)
+		memcpy(&dai_drv[i], &axg_tdm_iface_dai_drv[i],
+		       sizeof(*dai_drv));
+
+	/* Bit clock provided on the pad */
+	iface->sclk = devm_clk_get(dev, "sclk");
+	if (IS_ERR(iface->sclk)) {
+		ret = PTR_ERR(iface->sclk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get sclk: %d\n", ret);
+		return ret;
+	}
+
+	/* Sample clock provided on the pad */
+	iface->lrclk = devm_clk_get(dev, "lrclk");
+	if (IS_ERR(iface->lrclk)) {
+		ret = PTR_ERR(iface->lrclk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get lrclk: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * mclk maybe be missing when the cpu dai is in slave mode and
+	 * the codec does not require it to provide a master clock.
+	 * At this point, ignore the error if mclk is missing. We'll
+	 * throw an error if the cpu dai is master and mclk is missing
+	 */
+	iface->mclk = devm_clk_get(dev, "mclk");
+	if (IS_ERR(iface->mclk)) {
+		ret = PTR_ERR(iface->mclk);
+		if (ret == -ENOENT) {
+			iface->mclk = NULL;
+		} else {
+			if (ret != -EPROBE_DEFER)
+				dev_err(dev, "failed to get mclk: %d\n", ret);
+			return ret;
+		}
+	}
+
+	return devm_snd_soc_register_component(dev,
+					&axg_tdm_iface_component_drv, dai_drv,
+					ARRAY_SIZE(axg_tdm_iface_dai_drv));
+}
+
+static struct platform_driver axg_tdm_iface_pdrv = {
+	.probe = axg_tdm_iface_probe,
+	.driver = {
+		.name = "axg-tdm-iface",
+		.of_match_table = axg_tdm_iface_of_match,
+	},
+};
+module_platform_driver(axg_tdm_iface_pdrv);
+
+MODULE_DESCRIPTION("Amlogic AXG TDM interface driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/meson/axg-tdm.h b/sound/soc/meson/axg-tdm.h
index 435d95b86457..e578b6f40a07 100644
--- a/sound/soc/meson/axg-tdm.h
+++ b/sound/soc/meson/axg-tdm.h
@@ -71,4 +71,8 @@ static inline int axg_tdm_stream_reset(struct axg_tdm_stream *ts)
 	return axg_tdm_stream_start(ts);
 }
 
+int axg_tdm_set_tdm_slots(struct snd_soc_dai *dai, u32 *tx_mask,
+			  u32 *rx_mask, unsigned int slots,
+			  unsigned int slot_width);
+
 #endif /* _MESON_AXG_TDM_H */
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Applied "ASoC: meson: add tdm formatter base driver" to the asoc tree
  2018-07-17 15:36 ` [PATCH 09/15] ASoC: meson: add tdm formatter base driver Jerome Brunet
@ 2018-07-20 16:44   ` Mark Brown
  0 siblings, 0 replies; 40+ messages in thread
From: Mark Brown @ 2018-07-20 16:44 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Mark Brown, Liam Girdwood, Kevin Hilman,
	Carlo Caione, devicetree, alsa-devel, linux-amlogic,
	linux-kernel, alsa-devel

The patch

   ASoC: meson: add tdm formatter base driver

has been applied to the asoc tree at

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From 1a11d88f499ceb69e9b4098ddc36866820335a54 Mon Sep 17 00:00:00 2001
From: Jerome Brunet <jbrunet@baylibre.com>
Date: Tue, 17 Jul 2018 17:42:58 +0200
Subject: [PATCH] ASoC: meson: add tdm formatter base driver

Add Amlogic's axg TDM core driver. On this SoC, tdm is bit more
complex than usual, mainly because the different TDM input decoders can
be attached to any of TDM pad interface, including the output pads.

For the this, TDM on this SoC is modeled like this:
- TDM interface provides the DAIs the codecs will be attached to.
  The main responsibility of this driver is to manage the pad format
  and the TDM clock rates.
- TDM Formatters: These are the entities which are actually dealing with
  the TDM signal. TDMOUT produce a TDM signal from the audio sample
  provided by FRDDR using the clocks provided the TDM interface. TDMIN
  feeds TODDR with audio sample using the clocks and TDM signal provided
  by the TDM Interface.
- TDM Streams: This provides the link between 1 DAI stream of the TDM
  interface and one (or more) TDM formatters.

This driver provides the TDM formatter and TDM stream operations.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 sound/soc/meson/Kconfig             |   4 +
 sound/soc/meson/Makefile            |   2 +
 sound/soc/meson/axg-tdm-formatter.c | 381 ++++++++++++++++++++++++++++
 sound/soc/meson/axg-tdm-formatter.h |  39 +++
 sound/soc/meson/axg-tdm.h           |  74 ++++++
 5 files changed, 500 insertions(+)
 create mode 100644 sound/soc/meson/axg-tdm-formatter.c
 create mode 100644 sound/soc/meson/axg-tdm-formatter.h
 create mode 100644 sound/soc/meson/axg-tdm.h

diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig
index 9408214b5854..80a88689491f 100644
--- a/sound/soc/meson/Kconfig
+++ b/sound/soc/meson/Kconfig
@@ -19,6 +19,10 @@ config SND_MESON_AXG_TODDR
 	  Select Y or M to add support for the frontend capture interfaces
 	  embedded in the Amlogic AXG SoC family
 
+config SND_MESON_AXG_TDM_FORMATTER
+	tristate
+	select REGMAP_MMIO
+
 config SND_MESON_AXG_SPDIFOUT
 	tristate "Amlogic AXG SPDIF Output Support"
 	imply SND_SOC_SPDIF
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index d51ae045ef08..a06b56a1c995 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -3,9 +3,11 @@
 snd-soc-meson-axg-fifo-objs := axg-fifo.o
 snd-soc-meson-axg-frddr-objs := axg-frddr.o
 snd-soc-meson-axg-toddr-objs := axg-toddr.o
+snd-soc-meson-axg-tdm-formatter-objs := axg-tdm-formatter.o
 snd-soc-meson-axg-spdifout-objs := axg-spdifout.o
 
 obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o
 obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o
 obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o
+obj-$(CONFIG_SND_MESON_AXG_TDM_FORMATTER) += snd-soc-meson-axg-tdm-formatter.o
 obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o
diff --git a/sound/soc/meson/axg-tdm-formatter.c b/sound/soc/meson/axg-tdm-formatter.c
new file mode 100644
index 000000000000..43e390f9358a
--- /dev/null
+++ b/sound/soc/meson/axg-tdm-formatter.c
@@ -0,0 +1,381 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2018 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+
+#include "axg-tdm-formatter.h"
+
+struct axg_tdm_formatter {
+	struct list_head list;
+	struct axg_tdm_stream *stream;
+	const struct axg_tdm_formatter_driver *drv;
+	struct clk *pclk;
+	struct clk *sclk;
+	struct clk *lrclk;
+	struct clk *sclk_sel;
+	struct clk *lrclk_sel;
+	bool enabled;
+	struct regmap *map;
+};
+
+int axg_tdm_formatter_set_channel_masks(struct regmap *map,
+					struct axg_tdm_stream *ts,
+					unsigned int offset)
+{
+	unsigned int val, ch = ts->channels;
+	unsigned long mask;
+	int i, j;
+
+	/*
+	 * Distribute the channels of the stream over the available slots
+	 * of each TDM lane
+	 */
+	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;
+		}
+
+		regmap_write(map, offset, val);
+		offset += regmap_get_reg_stride(map);
+	}
+
+	/*
+	 * 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;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(axg_tdm_formatter_set_channel_masks);
+
+static int axg_tdm_formatter_enable(struct axg_tdm_formatter *formatter)
+{
+	struct axg_tdm_stream *ts = formatter->stream;
+	bool invert = formatter->drv->invert_sclk;
+	int ret;
+
+	/* Do nothing if the formatter is already enabled */
+	if (formatter->enabled)
+		return 0;
+
+	/*
+	 * If sclk is inverted, invert it back and provide the inversion
+	 * required by the formatter
+	 */
+	invert ^= axg_tdm_sclk_invert(ts->iface->fmt);
+	ret = clk_set_phase(formatter->sclk, invert ? 180 : 0);
+	if (ret)
+		return ret;
+
+	/* Setup the stream parameter in the formatter */
+	ret = formatter->drv->ops->prepare(formatter->map, 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;
+	}
+
+	/* Finally, actually enable the formatter */
+	formatter->drv->ops->enable(formatter->map);
+	formatter->enabled = true;
+
+	return 0;
+}
+
+static void axg_tdm_formatter_disable(struct axg_tdm_formatter *formatter)
+{
+	/* Do nothing if the formatter is already disabled */
+	if (!formatter->enabled)
+		return;
+
+	formatter->drv->ops->disable(formatter->map);
+	clk_disable_unprepare(formatter->lrclk);
+	clk_disable_unprepare(formatter->sclk);
+	formatter->enabled = false;
+}
+
+static int 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;
+		}
+	}
+
+	list_add_tail(&formatter->list, &ts->formatter_list);
+out:
+	mutex_unlock(&ts->lock);
+	return ret;
+}
+
+static void axg_tdm_formatter_dettach(struct axg_tdm_formatter *formatter)
+{
+	struct axg_tdm_stream *ts = formatter->stream;
+
+	mutex_lock(&ts->lock);
+	list_del(&formatter->list);
+	mutex_unlock(&ts->lock);
+
+	axg_tdm_formatter_disable(formatter);
+}
+
+static int axg_tdm_formatter_power_up(struct axg_tdm_formatter *formatter,
+				      struct snd_soc_dapm_widget *w)
+{
+	struct axg_tdm_stream *ts = formatter->drv->ops->get_stream(w);
+	int ret;
+
+	/*
+	 * 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;
+
+	return 0;
+
+disable_pclk:
+	clk_disable_unprepare(formatter->pclk);
+	return ret;
+}
+
+static void axg_tdm_formatter_power_down(struct axg_tdm_formatter *formatter)
+{
+	axg_tdm_formatter_dettach(formatter);
+	clk_disable_unprepare(formatter->pclk);
+	formatter->stream = NULL;
+}
+
+int axg_tdm_formatter_event(struct snd_soc_dapm_widget *w,
+			    struct snd_kcontrol *control,
+			    int event)
+{
+	struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
+	struct axg_tdm_formatter *formatter = snd_soc_component_get_drvdata(c);
+	int ret = 0;
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		ret = axg_tdm_formatter_power_up(formatter, w);
+		break;
+
+	case SND_SOC_DAPM_PRE_PMD:
+		axg_tdm_formatter_power_down(formatter);
+		break;
+
+	default:
+		dev_err(c->dev, "Unexpected event %d\n", event);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(axg_tdm_formatter_event);
+
+int axg_tdm_formatter_probe(struct platform_device *pdev)
+{
+	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;
+
+	drv = of_device_get_match_data(dev);
+	if (!drv) {
+		dev_err(dev, "failed to match device\n");
+		return -ENODEV;
+	}
+
+	formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL);
+	if (!formatter)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, formatter);
+	formatter->drv = drv;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	regs = devm_ioremap_resource(dev, res);
+	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)) {
+		ret = PTR_ERR(formatter->pclk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get pclk: %d\n", ret);
+		return ret;
+	}
+
+	/* Formatter bit clock */
+	formatter->sclk = devm_clk_get(dev, "sclk");
+	if (IS_ERR(formatter->sclk)) {
+		ret = PTR_ERR(formatter->sclk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get sclk: %d\n", ret);
+		return ret;
+	}
+
+	/* Formatter sample clock */
+	formatter->lrclk = devm_clk_get(dev, "lrclk");
+	if (IS_ERR(formatter->lrclk)) {
+		ret = PTR_ERR(formatter->lrclk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get lrclk: %d\n", ret);
+		return ret;
+	}
+
+	/* Formatter bit clock input multiplexer */
+	formatter->sclk_sel = devm_clk_get(dev, "sclk_sel");
+	if (IS_ERR(formatter->sclk_sel)) {
+		ret = PTR_ERR(formatter->sclk_sel);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get sclk_sel: %d\n", ret);
+		return ret;
+	}
+
+	/* Formatter sample clock input multiplexer */
+	formatter->lrclk_sel = devm_clk_get(dev, "lrclk_sel");
+	if (IS_ERR(formatter->lrclk_sel)) {
+		ret = PTR_ERR(formatter->lrclk_sel);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get lrclk_sel: %d\n", ret);
+		return ret;
+	}
+
+	return devm_snd_soc_register_component(dev, drv->component_drv,
+					       NULL, 0);
+}
+EXPORT_SYMBOL_GPL(axg_tdm_formatter_probe);
+
+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;
+		}
+	}
+
+out:
+	mutex_unlock(&ts->lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(axg_tdm_stream_start);
+
+void axg_tdm_stream_stop(struct axg_tdm_stream *ts)
+{
+	struct axg_tdm_formatter *formatter;
+
+	mutex_lock(&ts->lock);
+	ts->ready = false;
+
+	/* Stop all the formatters attached to the stream */
+	list_for_each_entry(formatter, &ts->formatter_list, list) {
+		axg_tdm_formatter_disable(formatter);
+	}
+
+	mutex_unlock(&ts->lock);
+}
+EXPORT_SYMBOL_GPL(axg_tdm_stream_stop);
+
+struct axg_tdm_stream *axg_tdm_stream_alloc(struct axg_tdm_iface *iface)
+{
+	struct axg_tdm_stream *ts;
+
+	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+	if (ts) {
+		INIT_LIST_HEAD(&ts->formatter_list);
+		mutex_init(&ts->lock);
+		ts->iface = iface;
+	}
+
+	return ts;
+}
+EXPORT_SYMBOL_GPL(axg_tdm_stream_alloc);
+
+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
+	 * 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);
+
+MODULE_DESCRIPTION("Amlogic AXG TDM formatter driver");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/meson/axg-tdm-formatter.h b/sound/soc/meson/axg-tdm-formatter.h
new file mode 100644
index 000000000000..cf947caf3cb1
--- /dev/null
+++ b/sound/soc/meson/axg-tdm-formatter.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT)
+ *
+ * Copyright (c) 2018 Baylibre SAS.
+ * Author: Jerome Brunet <jbrunet@baylibre.com>
+ */
+
+#ifndef _MESON_AXG_TDM_FORMATTER_H
+#define _MESON_AXG_TDM_FORMATTER_H
+
+#include "axg-tdm.h"
+
+struct platform_device;
+struct regmap;
+struct snd_soc_dapm_widget;
+struct snd_kcontrol;
+
+struct axg_tdm_formatter_ops {
+	struct axg_tdm_stream *(*get_stream)(struct snd_soc_dapm_widget *w);
+	void (*enable)(struct regmap *map);
+	void (*disable)(struct regmap *map);
+	int (*prepare)(struct regmap *map, struct axg_tdm_stream *ts);
+};
+
+struct axg_tdm_formatter_driver {
+	const struct snd_soc_component_driver *component_drv;
+	const struct regmap_config *regmap_cfg;
+	const struct axg_tdm_formatter_ops *ops;
+	bool invert_sclk;
+};
+
+int axg_tdm_formatter_set_channel_masks(struct regmap *map,
+					struct axg_tdm_stream *ts,
+					unsigned int offset);
+int axg_tdm_formatter_event(struct snd_soc_dapm_widget *w,
+			    struct snd_kcontrol *control,
+			    int event);
+int axg_tdm_formatter_probe(struct platform_device *pdev);
+
+#endif /* _MESON_AXG_TDM_FORMATTER_H */
diff --git a/sound/soc/meson/axg-tdm.h b/sound/soc/meson/axg-tdm.h
new file mode 100644
index 000000000000..435d95b86457
--- /dev/null
+++ b/sound/soc/meson/axg-tdm.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT)
+ *
+ * Copyright (c) 2018 Baylibre SAS.
+ * Author: Jerome Brunet <jbrunet@baylibre.com>
+ */
+
+#ifndef _MESON_AXG_TDM_H
+#define _MESON_AXG_TDM_H
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#define AXG_TDM_NUM_LANES	4
+#define AXG_TDM_CHANNEL_MAX	128
+#define AXG_TDM_RATES		(SNDRV_PCM_RATE_5512 |		\
+				 SNDRV_PCM_RATE_8000_192000)
+#define AXG_TDM_FORMATS		(SNDRV_PCM_FMTBIT_S8 |		\
+				 SNDRV_PCM_FMTBIT_S16_LE |	\
+				 SNDRV_PCM_FMTBIT_S20_LE |	\
+				 SNDRV_PCM_FMTBIT_S24_LE |	\
+				 SNDRV_PCM_FMTBIT_S32_LE)
+
+struct axg_tdm_iface {
+	struct clk *sclk;
+	struct clk *lrclk;
+	struct clk *mclk;
+	unsigned long mclk_rate;
+
+	/* format is common to all the DAIs of the iface */
+	unsigned int fmt;
+	unsigned int slots;
+	unsigned int slot_width;
+
+	/* For component wide symmetry */
+	int rate;
+};
+
+static inline bool axg_tdm_lrclk_invert(unsigned int fmt)
+{
+	return (fmt & SND_SOC_DAIFMT_I2S) ^
+		!!(fmt & (SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_NB_IF));
+}
+
+static inline bool axg_tdm_sclk_invert(unsigned int fmt)
+{
+	return fmt & (SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_IB_NF);
+}
+
+struct axg_tdm_stream {
+	struct axg_tdm_iface *iface;
+	struct list_head formatter_list;
+	struct mutex lock;
+	unsigned int channels;
+	unsigned int width;
+	unsigned int physical_width;
+	u32 *mask;
+	bool ready;
+};
+
+struct axg_tdm_stream *axg_tdm_stream_alloc(struct axg_tdm_iface *iface);
+void axg_tdm_stream_free(struct axg_tdm_stream *ts);
+int axg_tdm_stream_start(struct axg_tdm_stream *ts);
+void axg_tdm_stream_stop(struct axg_tdm_stream *ts);
+
+static inline int axg_tdm_stream_reset(struct axg_tdm_stream *ts)
+{
+	axg_tdm_stream_stop(ts);
+	return axg_tdm_stream_start(ts);
+}
+
+#endif /* _MESON_AXG_TDM_H */
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Applied "ASoC: meson: add axg tdm interface DT bindings documentation" to the asoc tree
  2018-07-17 15:36 ` [PATCH 08/15] ASoC: meson: add axg tdm interface DT binding documentation Jerome Brunet
@ 2018-07-20 16:45   ` Mark Brown
  0 siblings, 0 replies; 40+ messages in thread
From: Mark Brown @ 2018-07-20 16:45 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Mark Brown, Liam Girdwood, Kevin Hilman,
	Carlo Caione, devicetree, alsa-devel, linux-amlogic,
	linux-kernel, alsa-devel

The patch

   ASoC: meson: add axg tdm interface DT bindings documentation

has been applied to the asoc tree at

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From 9e960c0298b5811e5a2c1ebceea6aa3b7bbc61c6 Mon Sep 17 00:00:00 2001
From: Jerome Brunet <jbrunet@baylibre.com>
Date: Tue, 17 Jul 2018 17:42:57 +0200
Subject: [PATCH] ASoC: meson: add axg tdm interface DT bindings documentation

Add the DT bindings documentation for axg's TDM interfaces

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 .../bindings/sound/amlogic,axg-tdm-iface.txt  | 22 +++++++++++++++++++
 1 file changed, 22 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt

diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt
new file mode 100644
index 000000000000..cabfb26a5f22
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt
@@ -0,0 +1,22 @@
+* Amlogic Audio TDM Interfaces
+
+Required properties:
+- compatible: 'amlogic,axg-tdm-iface'
+- clocks: list of clock phandle, one for each entry clock-names.
+- clock-names: should contain the following:
+  * "sclk" : bit clock.
+  * "lrclk": sample clock
+  * "mclk" : master clock
+	     -> optional if the interface is in clock slave mode.
+- #sound-dai-cells: must be 0.
+
+Example of TDM_A on the A113 SoC:
+
+tdmif_a: audio-controller@0 {
+	compatible = "amlogic,axg-tdm-iface";
+	#sound-dai-cells = <0>;
+	clocks = <&clkc_audio AUD_CLKID_MST_A_MCLK>,
+		 <&clkc_audio AUD_CLKID_MST_A_SCLK>,
+		 <&clkc_audio AUD_CLKID_MST_A_LRCLK>;
+	clock-names = "mclk", "sclk", "lrclk";
+};
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Re: [PATCH 14/15] ASoC: meson: add axg sound card DT binding documentation
  2018-07-17 15:36 ` [PATCH 14/15] ASoC: meson: add axg sound card DT binding documentation Jerome Brunet
  2018-07-20 16:43   ` Applied "ASoC: meson: add axg sound card DT bindings documentation" to the asoc tree Mark Brown
@ 2018-07-25 19:06   ` Rob Herring
  2018-07-25 21:12     ` jbrunet
  2018-07-26  7:40     ` jbrunet
  1 sibling, 2 replies; 40+ messages in thread
From: Rob Herring @ 2018-07-25 19:06 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione,
	alsa-devel, devicetree, linux-kernel, linux-amlogic

On Tue, Jul 17, 2018 at 05:36:41PM +0200, Jerome Brunet wrote:
> Add the DT binding documentation for axg sound card

I see Mark already applied, but...

> 
> Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
> ---
>  .../bindings/sound/amlogic,axg-sound-card.txt      | 124 +++++++++++++++++++++
>  1 file changed, 124 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt
> 
> diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt
> new file mode 100644
> index 000000000000..39e005da0407
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt
> @@ -0,0 +1,124 @@
> +Amlogic AXG sound card:
> +
> +Required properties:
> +
> +- compatible: "amlogic,axg-sound-card"
> +- amlogic,name : User specified audio sound card name, one string

Just using "model" for new bindings.

> +
> +Optional properties:
> +
> +- amlogic,aux-devs : List of phandles pointing to auxiliary devices
> +- amlogic,widgets : Please refer to widgets.txt.
> +- amlogic,routing : A list of the connections between audio components.

audio-routing IIRC

> +
> +Subnodes:
> +
> +- amlogic,dai-link: Container for dai-link level properties and the
> +		    CODEC sub-nodes. There should be at least one (and
> +		    probably) subnode of this type.

Just 'dai-link'

> +
> +Required dai-link properties:
> +
> +- sound-dai: phandle and port of the CPU DAI.
> +
> +Required TDM Backend dai-link properties:
> +- dai-format : CPU/CODEC common audio format
> +
> +Optional TDM Backend dai-link properties:
> +- dai-tdm-slot-rx-mask-{0,1,2,3}: Receive direction slot masks
> +- dai-tdm-slot-tx-mask-{0,1,2,3}: Transmit direction slot masks
> +				  When omitted, mask is assumed to have to no
> +				  slots. A valid must have at one slot, so at
> +				  least one these mask should be provided with
> +				  an enabled slot.
> +- dai-tdm-slot-num : Please refer to tdm-slot.txt.
> +		     If omitted, slot number is set to accommodate the largest
> +		     mask provided.
> +- dai-tdm-slot-width : Please refer to tdm-slot.txt. default to 32 if omitted.
> +- mclk-fs : Multiplication factor between stream rate and mclk
> +
> +Backend dai-link subnodes:
> +
> +- codec: dai-link representing backend links should have at least one subnode.
> +	 One subnode for each codec of the dai-link.
> +	 dai-link representing frontend links have no codec, therefore have no
> +	 subnodes
> +
> +Required codec subnodes properties:
> +
> +- sound-dai: phandle and port of the CODEC DAI.
> +
> +Optional codec subnodes properties:
> +
> +- dai-tdm-slot-tx-mask : Please refer to tdm-slot.txt.
> +- dai-tdm-slot-rx-mask : Please refer to tdm-slot.txt.
> +
> +Example:
> +
> +sound {
> +	compatible = "amlogic,axg-sound-card";
> +	amlogic,name = "AXG-S420";
> +	amlogic,aux-devs = <&tdmin_a>, <&tdmout_c>;
> +	amlogic,widgets = "Line", "Lineout",
> +			  "Line", "Linein",
> +			  "Speaker", "Speaker1 Left",
> +			  "Speaker", "Speaker1 Right";
> +			  "Speaker", "Speaker2 Left",
> +			  "Speaker", "Speaker2 Right";
> +	amlogic,routing = "TDMOUT_C IN 0", "FRDDR_A OUT 2",
> +			  "SPDIFOUT IN 0", "FRDDR_A OUT 3",
> +			  "TDM_C Playback", "TDMOUT_C OUT",
> +			  "TDMIN_A IN 2", "TDM_C Capture",
> +			  "TDMIN_A IN 5", "TDM_C Loopback",
> +			  "TODDR_A IN 0", "TDMIN_A OUT",
> +			  "Lineout", "Lineout AOUTL",
> +			  "Lineout", "Lineout AOUTR",
> +			  "Speaker1 Left", "SPK1 OUT_A",
> +			  "Speaker2 Left", "SPK2 OUT_A",
> +			  "Speaker1 Right", "SPK1 OUT_B",
> +			  "Speaker2 Right", "SPK2 OUT_B",
> +			  "Linein AINL", "Linein",
> +			  "Linein AINR", "Linein";
> +
> +	amlogic,dai-link@0 {
> +		sound-dai = <&frddr_a>;
> +	};
> +
> +	amlogic,dai-link@1 {
> +		sound-dai = <&toddr_a>;
> +	};
> +
> +	amlogic,dai-link@2 {
> +		sound-dai = <&tdmif_c>;
> +		dai-format = "i2s";
> +		dai-tdm-slot-tx-mask-2 = <1 1>;
> +		dai-tdm-slot-tx-mask-3 = <1 1>;
> +		dai-tdm-slot-rx-mask-1 = <1 1>;
> +		mclk-fs = <256>;
> +
> +		codec@0 {
> +			sound-dai = <&lineout>;
> +		};
> +
> +		codec@1 {
> +			sound-dai = <&speaker_amp1>;
> +		};
> +
> +		codec@2 {
> +			sound-dai = <&speaker_amp2>;
> +		};
> +
> +		codec@3 {
> +			sound-dai = <&linein>;
> +		};
> +
> +	};
> +
> +	amlogic,dai-link@4 {
> +		sound-dai = <&spdifout>;
> +
> +		codec {
> +			sound-dai = <&spdif_dit>;
> +		};
> +	};
> +};
> -- 
> 2.14.4
> 
> --
> To unsubscribe from this list: send the line "unsubscribe devicetree" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 14/15] ASoC: meson: add axg sound card DT binding documentation
  2018-07-25 19:06   ` [PATCH 14/15] ASoC: meson: add axg sound card DT binding documentation Rob Herring
@ 2018-07-25 21:12     ` jbrunet
  2018-07-26  7:40     ` jbrunet
  1 sibling, 0 replies; 40+ messages in thread
From: jbrunet @ 2018-07-25 21:12 UTC (permalink / raw)
  To: Rob Herring
  Cc: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione,
	alsa-devel, devicetree, linux-kernel, linux-amlogic

On Wed, 2018-07-25 at 13:06 -0600, Rob Herring wrote:
> On Tue, Jul 17, 2018 at 05:36:41PM +0200, Jerome Brunet wrote:
> > Add the DT binding documentation for axg sound card
> 
> I see Mark already applied, but...

It's very recent, no DT using it yet.

I can easily have your 3 comments fixed if it is OK with Mark. I'll submit
something for this tomorrow.


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 14/15] ASoC: meson: add axg sound card DT binding documentation
  2018-07-25 19:06   ` [PATCH 14/15] ASoC: meson: add axg sound card DT binding documentation Rob Herring
  2018-07-25 21:12     ` jbrunet
@ 2018-07-26  7:40     ` jbrunet
  2018-07-26 13:20       ` Rob Herring
  1 sibling, 1 reply; 40+ messages in thread
From: jbrunet @ 2018-07-26  7:40 UTC (permalink / raw)
  To: Rob Herring
  Cc: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione,
	alsa-devel, devicetree, linux-kernel, linux-amlogic

On Wed, 2018-07-25 at 13:06 -0600, Rob Herring wrote:
> > +- amlogic,aux-devs : List of phandles pointing to auxiliary devices
> > +- amlogic,widgets : Please refer to widgets.txt.
> > +- amlogic,routing : A list of the connections between audio components.
> 
> audio-routing IIRC

Wouldn't it make sense to do the same for widgets and aux-devs then ?

audio-widgets ?
audio-aux-devs ?


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 14/15] ASoC: meson: add axg sound card DT binding documentation
  2018-07-26  7:40     ` jbrunet
@ 2018-07-26 13:20       ` Rob Herring
  0 siblings, 0 replies; 40+ messages in thread
From: Rob Herring @ 2018-07-26 13:20 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Mark Brown, Liam Girdwood, Kevin Hilman, Carlo Caione,
	Linux-ALSA, devicetree, linux-kernel, linux-amlogic

On Thu, Jul 26, 2018 at 1:40 AM <jbrunet@baylibre.com> wrote:
>
> On Wed, 2018-07-25 at 13:06 -0600, Rob Herring wrote:
> > > +- amlogic,aux-devs : List of phandles pointing to auxiliary devices
> > > +- amlogic,widgets : Please refer to widgets.txt.
> > > +- amlogic,routing : A list of the connections between audio components.
> >
> > audio-routing IIRC
>
> Wouldn't it make sense to do the same for widgets and aux-devs then ?
>
> audio-widgets ?
> audio-aux-devs ?

Probably so. Those aren't as common.

Rob

^ permalink raw reply	[flat|nested] 40+ messages in thread

end of thread, other threads:[~2018-07-26 13:20 UTC | newest]

Thread overview: 40+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-07-17 15:36 [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet
2018-07-17 15:36 ` [PATCH 01/15] ASoC: meson: add axg fifos DT binding documentation Jerome Brunet
2018-07-18 12:27   ` Applied "ASoC: meson: add axg fifos DT binding documentation" to the asoc tree Mark Brown
2018-07-17 15:36 ` [PATCH 02/15] ASoC: meson: add axg fifo base driver Jerome Brunet
2018-07-18 12:26   ` Applied "ASoC: meson: add axg fifo base driver" to the asoc tree Mark Brown
2018-07-17 15:36 ` [PATCH 03/15] ASoC: meson: add axg frddr driver Jerome Brunet
2018-07-18 12:26   ` Applied "ASoC: meson: add axg frddr driver" to the asoc tree Mark Brown
2018-07-17 15:36 ` [PATCH 04/15] ASoC: meson: add axg toddr driver Jerome Brunet
2018-07-18 12:25   ` Applied "ASoC: meson: add axg toddr driver" to the asoc tree Mark Brown
2018-07-17 15:36 ` [PATCH 05/15] ASoC: meson: add axg spdif output DT binding documentation Jerome Brunet
2018-07-18 12:25   ` Applied "ASoC: meson: add axg spdif output DT bindings documentation" to the asoc tree Mark Brown
2018-07-17 15:36 ` [PATCH 05/15] ASoC: meson: add axg spdif output DT bindings documentation Jerome Brunet
2018-07-17 15:36 ` [PATCH 06/15] ASoC: meson: add axg spdif output Jerome Brunet
2018-07-18 12:25   ` Applied "ASoC: meson: add axg spdif output" to the asoc tree Mark Brown
2018-07-17 15:36 ` [PATCH 07/15] ASoC: meson: add axg tdm formatters DT binding documentation Jerome Brunet
2018-07-18 12:25   ` Applied "ASoC: meson: add axg tdm formatters DT bindings documentation" to the asoc tree Mark Brown
2018-07-17 15:36 ` [PATCH 07/15] ASoC: meson: add axg tdm formatters DT bindings documentation Jerome Brunet
2018-07-17 15:36 ` [PATCH 08/15] ASoC: meson: add axg tdm interface DT binding documentation Jerome Brunet
2018-07-20 16:45   ` Applied "ASoC: meson: add axg tdm interface DT bindings documentation" to the asoc tree Mark Brown
2018-07-17 15:36 ` [PATCH 08/15] ASoC: meson: add axg tdm interface DT bindings documentation Jerome Brunet
2018-07-17 15:36 ` [PATCH 09/15] ASoC: meson: add tdm formatter base driver Jerome Brunet
2018-07-20 16:44   ` Applied "ASoC: meson: add tdm formatter base driver" to the asoc tree Mark Brown
2018-07-17 15:36 ` [PATCH 10/15] ASoC: meson: add tdm interface driver Jerome Brunet
2018-07-20 16:44   ` Applied "ASoC: meson: add tdm interface driver" to the asoc tree Mark Brown
2018-07-17 15:36 ` [PATCH 11/15] ASoC: meson: add tdm output driver Jerome Brunet
2018-07-20 16:43   ` Applied "ASoC: meson: add tdm output driver" to the asoc tree Mark Brown
2018-07-17 15:36 ` [PATCH 12/15] ASoC: meson: add tdm input driver Jerome Brunet
2018-07-20 16:43   ` Applied "ASoC: meson: add tdm input driver" to the asoc tree Mark Brown
2018-07-17 15:36 ` [PATCH 13/15] ASoC: export snd_soc_of_get_slot_mask Jerome Brunet
2018-07-20 16:43   ` Applied "ASoC: export snd_soc_of_get_slot_mask" to the asoc tree Mark Brown
2018-07-17 15:36 ` [PATCH 14/15] ASoC: meson: add axg sound card DT binding documentation Jerome Brunet
2018-07-20 16:43   ` Applied "ASoC: meson: add axg sound card DT bindings documentation" to the asoc tree Mark Brown
2018-07-25 19:06   ` [PATCH 14/15] ASoC: meson: add axg sound card DT binding documentation Rob Herring
2018-07-25 21:12     ` jbrunet
2018-07-26  7:40     ` jbrunet
2018-07-26 13:20       ` Rob Herring
2018-07-17 15:36 ` [PATCH 14/15] ASoC: meson: add axg sound card DT bindings documentation Jerome Brunet
2018-07-17 15:36 ` [PATCH 15/15] ASoC: meson: add axg sound card support Jerome Brunet
2018-07-20 16:43   ` Applied "ASoC: meson: add axg sound card support" to the asoc tree Mark Brown
2018-07-17 15:38 ` [PATCH 00/15] ASoC: meson: add axg audio subsystem support Jerome Brunet

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).