2020-09-16 09:19:44

by Viorel Suman (OSS)

[permalink] [raw]
Subject: [PATCH 0/2] DAI driver for new XCVR IP

From: Viorel Suman <[email protected]>

DAI driver for new XCVR IP found in i.MX8MP.

Viorel Suman (2):
ASoC: fsl_xcvr: Add XCVR ASoC CPU DAI driver
ASoC: dt-bindings: fsl_xcvr: Add document for XCVR

.../devicetree/bindings/sound/fsl,xcvr.yaml | 104 ++
sound/soc/fsl/Kconfig | 10 +
sound/soc/fsl/Makefile | 2 +
sound/soc/fsl/fsl_xcvr.c | 1352 ++++++++++++++++++++
sound/soc/fsl/fsl_xcvr.h | 266 ++++
5 files changed, 1734 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/fsl,xcvr.yaml
create mode 100644 sound/soc/fsl/fsl_xcvr.c
create mode 100644 sound/soc/fsl/fsl_xcvr.h

--
2.7.4


2020-09-16 09:20:17

by Viorel Suman (OSS)

[permalink] [raw]
Subject: [PATCH 2/2] ASoC: dt-bindings: fsl_xcvr: Add document for XCVR

From: Viorel Suman <[email protected]>

XCVR (Audio Transceiver) is a new IP module found on i.MX8MP.

Signed-off-by: Viorel Suman <[email protected]>
---
.../devicetree/bindings/sound/fsl,xcvr.yaml | 104 +++++++++++++++++++++
1 file changed, 104 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/fsl,xcvr.yaml

diff --git a/Documentation/devicetree/bindings/sound/fsl,xcvr.yaml b/Documentation/devicetree/bindings/sound/fsl,xcvr.yaml
new file mode 100644
index 00000000..d4bb792
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/fsl,xcvr.yaml
@@ -0,0 +1,104 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/fsl,xcvr.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NXP Audio Transceiver (XCVR) Controller
+
+maintainers:
+ - Viorel Suman <[email protected]>
+
+properties:
+ $nodename:
+ pattern: "^xcvr@.*"
+
+ compatible:
+ const: fsl,imx8mp-xcvr
+
+ reg:
+ items:
+ - description: 20K RAM for code and data
+ - description: registers space
+ - description: RX FIFO address
+ - description: TX FIFO address
+
+ reg-names:
+ items:
+ - const: ram
+ - const: regs
+ - const: rxfifo
+ - const: txfifo
+
+ interrupts:
+ maxItems: 3
+
+ clocks:
+ items:
+ - description: Peripheral clock
+ - description: PHY clock
+ - description: SPBA clock
+ - description: PLL clock
+
+ clock-names:
+ items:
+ - const: ipg
+ - const: phy
+ - const: spba
+ - const: pll_ipg
+
+ dmas:
+ maxItems: 2
+
+ dma-names:
+ items:
+ - const: rx
+ - const: tx
+
+ firmware-name:
+ $ref: /schemas/types.yaml#/definitions/string
+ const: imx/xcvr/xcvr-imx8mp.bin
+ description: |
+ Should contain the name of the default firmware image
+ file located on the firmware search path
+
+ resets:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - interrupts
+ - clocks
+ - clock-names
+ - dmas
+ - dma-names
+ - firmware-name
+ - resets
+
+examples:
+ - |
+ #include <dt-bindings/clock/imx8mp-clock.h>
+ #include <dt-bindings/reset/imx8mp-reset.h>
+
+ xcvr: xcvr@30cc0000 {
+ compatible = "fsl,imx8mp-xcvr";
+ reg = <0x30cc0000 0x800>,
+ <0x30cc0800 0x400>,
+ <0x30cc0c00 0x080>,
+ <0x30cc0e00 0x080>;
+ reg-names = "ram", "regs", "rxfifo", "txfifo";
+ interrupts = <GIC_SPI 128 IRQ_TYPE_LEVEL_HIGH>, /* XCVR IRQ 0 */
+ <GIC_SPI 129 IRQ_TYPE_LEVEL_HIGH>, /* XCVR IRQ 1 */
+ <GIC_SPI 146 IRQ_TYPE_LEVEL_HIGH>; /* XCVR PHY - SPDIF wakeup IRQ */
+ clocks = <&audiomix_clk IMX8MP_CLK_AUDIOMIX_EARC_IPG>,
+ <&audiomix_clk IMX8MP_CLK_AUDIOMIX_EARC_PHY>,
+ <&audiomix_clk IMX8MP_CLK_AUDIOMIX_SPBA2_ROOT>,
+ <&audiomix_clk IMX8MP_CLK_AUDIOMIX_AUDPLL_ROOT>;
+ clock-names = "ipg", "phy", "spba", "pll_ipg";
+ dmas = <&sdma2 30 2 0>, <&sdma2 31 2 0>;
+ dma-names = "rx", "tx";
+ firmware-name = "imx/xcvr/xcvr-imx8mp.bin";
+ resets = <&audiomix_reset 0>;
+ };
--
2.7.4

2020-09-16 09:22:15

by Viorel Suman (OSS)

[permalink] [raw]
Subject: [PATCH 1/2] ASoC: fsl_xcvr: Add XCVR ASoC CPU DAI driver

From: Viorel Suman <[email protected]>

XCVR (Audio Transceiver) is a on-chip functional module found
on i.MX8MP. It support HDMI2.1 eARC, HDMI1.4 ARC and SPDIF.

Signed-off-by: Viorel Suman <[email protected]>
---
sound/soc/fsl/Kconfig | 10 +
sound/soc/fsl/Makefile | 2 +
sound/soc/fsl/fsl_xcvr.c | 1352 ++++++++++++++++++++++++++++++++++++++++++++++
sound/soc/fsl/fsl_xcvr.h | 266 +++++++++
4 files changed, 1630 insertions(+)
create mode 100644 sound/soc/fsl/fsl_xcvr.c
create mode 100644 sound/soc/fsl/fsl_xcvr.h

diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index 3f76ff7..d04b64d 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -95,6 +95,16 @@ config SND_SOC_FSL_EASRC
destination sample rate. It is a new design module compare with the
old ASRC.

+config SND_SOC_FSL_XCVR
+ tristate "NXP Audio Transceiver (XCVR) module support"
+ select REGMAP_MMIO
+ select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ help
+ Say Y if you want to add Audio Transceiver (XCVR) support for NXP
+ iMX CPUs. XCVR is a digital module that supports HDMI2.1 eARC,
+ HDMI1.4 ARC and SPDIF.
+
config SND_SOC_FSL_UTILS
tristate

diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index b835eeb..1d2231f 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -25,6 +25,7 @@ snd-soc-fsl-utils-objs := fsl_utils.o
snd-soc-fsl-dma-objs := fsl_dma.o
snd-soc-fsl-mqs-objs := fsl_mqs.o
snd-soc-fsl-easrc-objs := fsl_easrc.o
+snd-soc-fsl-xcvr-objs := fsl_xcvr.o

obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o
obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o
@@ -38,6 +39,7 @@ obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o
obj-$(CONFIG_SND_SOC_FSL_MQS) += snd-soc-fsl-mqs.o
obj-$(CONFIG_SND_SOC_FSL_EASRC) += snd-soc-fsl-easrc.o
obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o
+obj-$(CONFIG_SND_SOC_FSL_XCVR) += snd-soc-fsl-xcvr.o

# MPC5200 Platform Support
obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c
new file mode 100644
index 00000000..2e66f22
--- /dev/null
+++ b/sound/soc/fsl/fsl_xcvr.c
@@ -0,0 +1,1352 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2019 NXP
+
+#include <linux/bitrev.h>
+#include <linux/clk.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_iec958.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_xcvr.h"
+#include "imx-pcm.h"
+
+#define FSL_XCVR_CAPDS_SIZE 256
+
+struct fsl_xcvr {
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct clk *ipg_clk;
+ struct clk *pll_ipg_clk;
+ struct clk *phy_clk;
+ struct clk *spba_clk;
+ struct reset_control *reset;
+ const char *fw_name;
+ u8 streams;
+ u32 mode;
+ u32 arc_mode;
+ void __iomem *ram_addr;
+ struct snd_dmaengine_dai_dma_data dma_prms_rx;
+ struct snd_dmaengine_dai_dma_data dma_prms_tx;
+ struct snd_aes_iec958 rx_iec958;
+ struct snd_aes_iec958 tx_iec958;
+ u8 cap_ds[FSL_XCVR_CAPDS_SIZE];
+};
+
+static const struct fsl_xcvr_pll_conf {
+ u8 mfi; /* min=0x18, max=0x38 */
+ u32 mfn; /* signed int, 2's compl., min=0x3FFF0000, max=0x00010000 */
+ u32 mfd; /* unsigned int */
+ u32 fout; /* Fout = Fref*(MFI + MFN/MFD), Fref is 24MHz */
+} fsl_xcvr_pll_cfg[] = {
+ { .mfi = 54, .mfn = 1, .mfd = 6, .fout = 1300000000, }, /* 1.3 GHz */
+ { .mfi = 32, .mfn = 96, .mfd = 125, .fout = 786432000, }, /* 8000 Hz */
+ { .mfi = 30, .mfn = 66, .mfd = 625, .fout = 722534400, }, /* 11025 Hz */
+ { .mfi = 29, .mfn = 1, .mfd = 6, .fout = 700000000, }, /* 700 MHz */
+};
+
+static const u32 fsl_xcvr_earc_channels[] = { 1, 2, 8, 16, 32, }; /* one bit 6, 12 ? */
+static const struct snd_pcm_hw_constraint_list fsl_xcvr_earc_channels_constr = {
+ .count = ARRAY_SIZE(fsl_xcvr_earc_channels),
+ .list = fsl_xcvr_earc_channels,
+};
+
+static const u32 fsl_xcvr_earc_rates[] = {
+ 32000, 44100, 48000, 64000, 88200, 96000,
+ 128000, 176400, 192000, 256000, 352800, 384000,
+ 512000, 705600, 768000, 1024000, 1411200, 1536000,
+};
+static const struct snd_pcm_hw_constraint_list fsl_xcvr_earc_rates_constr = {
+ .count = ARRAY_SIZE(fsl_xcvr_earc_rates),
+ .list = fsl_xcvr_earc_rates,
+};
+
+static const u32 fsl_xcvr_spdif_channels[] = { 2, };
+static const struct snd_pcm_hw_constraint_list fsl_xcvr_spdif_channels_constr = {
+ .count = ARRAY_SIZE(fsl_xcvr_spdif_channels),
+ .list = fsl_xcvr_spdif_channels,
+};
+
+static const u32 fsl_xcvr_spdif_rates[] = {
+ 32000, 44100, 48000, 88200, 96000, 176400, 192000,
+};
+static const struct snd_pcm_hw_constraint_list fsl_xcvr_spdif_rates_constr = {
+ .count = ARRAY_SIZE(fsl_xcvr_spdif_rates),
+ .list = fsl_xcvr_spdif_rates,
+};
+
+static int fsl_xcvr_arc_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+
+ xcvr->arc_mode = snd_soc_enum_item_to_val(e, item[0]);
+
+ return 0;
+}
+
+static int fsl_xcvr_arc_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ ucontrol->value.enumerated.item[0] = xcvr->arc_mode;
+
+ return 0;
+}
+
+static const int fsl_xcvr_phy_arc_cfg[] = {
+ FSL_XCVR_PHY_CTRL_ARC_MODE_SE_EN, FSL_XCVR_PHY_CTRL_ARC_MODE_CM_EN,
+};
+
+static const char * const fsl_xcvr_arc_mode[] = { "Single Ended", "Common", };
+static const struct soc_enum fsl_xcvr_arc_mode_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(fsl_xcvr_arc_mode), fsl_xcvr_arc_mode);
+static struct snd_kcontrol_new fsl_xcvr_arc_mode_kctl =
+ SOC_ENUM_EXT("ARC Mode", fsl_xcvr_arc_mode_enum,
+ fsl_xcvr_arc_mode_get, fsl_xcvr_arc_mode_put);
+
+/* Capabilities data structure, bytes */
+static int fsl_xcvr_type_capds_bytes_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+ uinfo->count = FSL_XCVR_CAPDS_SIZE;
+
+ return 0;
+}
+
+static int fsl_xcvr_capds_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ memcpy(ucontrol->value.bytes.data, xcvr->cap_ds, FSL_XCVR_CAPDS_SIZE);
+
+ return 0;
+}
+
+static int fsl_xcvr_capds_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ memcpy(xcvr->cap_ds, ucontrol->value.bytes.data, FSL_XCVR_CAPDS_SIZE);
+
+ return 0;
+}
+
+static struct snd_kcontrol_new fsl_xcvr_earc_capds_kctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "Capabilities Data Structure",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = fsl_xcvr_type_capds_bytes_info,
+ .get = fsl_xcvr_capds_get,
+ .put = fsl_xcvr_capds_put,
+};
+
+static int fsl_xcvr_activate_ctl(struct snd_soc_dai *dai, const char *name,
+ bool active)
+{
+ struct snd_soc_card *card = dai->component->card;
+ struct snd_kcontrol *kctl;
+ bool enabled;
+
+ kctl = snd_soc_card_get_kcontrol(card, name);
+ if (kctl == NULL)
+ return -ENOENT;
+
+ enabled = ((kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_WRITE) != 0);
+ if (active == enabled)
+ return 0; /* nothing to do */
+
+ if (active)
+ kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_WRITE;
+ else
+ kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_WRITE;
+
+ snd_ctl_notify(card->snd_card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id);
+
+ return 1;
+}
+
+static int fsl_xcvr_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ struct snd_soc_card *card = dai->component->card;
+ struct snd_soc_pcm_runtime *rtd;
+
+ xcvr->mode = snd_soc_enum_item_to_val(e, item[0]);
+
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_arc_mode_kctl.name,
+ (xcvr->mode == FSL_XCVR_MODE_ARC));
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_earc_capds_kctl.name,
+ (xcvr->mode == FSL_XCVR_MODE_EARC));
+ /* Allow playback for SPDIF only */
+ rtd = snd_soc_get_pcm_runtime(card, card->dai_link);
+ rtd->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count =
+ (xcvr->mode == FSL_XCVR_MODE_SPDIF ? 1 : 0);
+ return 0;
+}
+
+static int fsl_xcvr_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ ucontrol->value.enumerated.item[0] = xcvr->mode;
+
+ return 0;
+}
+
+static const char * const fsl_xcvr_mode[] = { "SPDIF", "ARC RX", "eARC", };
+static const struct soc_enum fsl_xcvr_mode_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(fsl_xcvr_mode), fsl_xcvr_mode);
+static struct snd_kcontrol_new fsl_xcvr_mode_kctl =
+ SOC_ENUM_EXT("XCVR Mode", fsl_xcvr_mode_enum,
+ fsl_xcvr_mode_get, fsl_xcvr_mode_put);
+
+/** phy: true => phy, false => pll */
+static int fsl_xcvr_ai_write(struct fsl_xcvr *xcvr, u8 reg, u32 data, bool phy)
+{
+ u32 val, idx, tidx;
+
+ idx = BIT(phy ? 26 : 24);
+ tidx = BIT(phy ? 27 : 25);
+
+ regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_CLR, 0xFF);
+ regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_SET, reg);
+ regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_WDATA, data);
+ regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_TOG, idx);
+
+ do {
+ regmap_read(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL, &val);
+ } while ((val & idx) != ((val & tidx) >> 1));
+
+ return 0;
+}
+
+static int fsl_xcvr_en_phy_pll(struct fsl_xcvr *xcvr, u32 freq, bool tx)
+{
+ struct device *dev = &xcvr->pdev->dev;
+ u32 i, div = 0, log2;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(fsl_xcvr_pll_cfg); i++) {
+ if (fsl_xcvr_pll_cfg[i].fout % freq == 0) {
+ div = fsl_xcvr_pll_cfg[i].fout / freq;
+ break;
+ }
+ }
+
+ if (!div || i >= ARRAY_SIZE(fsl_xcvr_pll_cfg))
+ return -EINVAL;
+
+ log2 = ilog2(div);
+
+ /* Release AI interface from reset */
+ ret = regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_SET,
+ FSL_XCVR_PHY_AI_CTRL_AI_RESETN);
+ if (ret < 0) {
+ dev_err(dev, "Error while setting IER0: %d\n", ret);
+ return ret;
+ }
+
+ /* PLL: BANDGAP_SET: EN_VBG (enable bandgap) */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_BANDGAP_SET,
+ FSL_XCVR_PLL_BANDGAP_EN_VBG, 0);
+
+ /* PLL: CTRL0: DIV_INTEGER */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0, fsl_xcvr_pll_cfg[i].mfi, 0);
+ /* PLL: NUMERATOR: MFN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_NUM, fsl_xcvr_pll_cfg[i].mfn, 0);
+ /* PLL: DENOMINATOR: MFD */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_DEN, fsl_xcvr_pll_cfg[i].mfd, 0);
+ /* PLL: CTRL0_SET: HOLD_RING_OFF, POWER_UP */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET,
+ FSL_XCVR_PLL_CTRL0_HROFF | FSL_XCVR_PLL_CTRL0_PWP, 0);
+ udelay(25);
+ /* PLL: CTRL0: Clear Hold Ring Off */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_CLR,
+ FSL_XCVR_PLL_CTRL0_HROFF, 0);
+ udelay(100);
+ if (tx) { /* TX is enabled for SPDIF only */
+ /* PLL: POSTDIV: PDIV0 */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_PDIV,
+ FSL_XCVR_PLL_PDIVx(log2, 0), 0);
+ /* PLL: CTRL_SET: CLKMUX0_EN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET,
+ FSL_XCVR_PLL_CTRL0_CM0_EN, 0);
+ } else if (xcvr->mode == FSL_XCVR_MODE_EARC) { /* eARC RX */
+ /* PLL: POSTDIV: PDIV1 */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_PDIV,
+ FSL_XCVR_PLL_PDIVx(log2, 1), 0);
+ /* PLL: CTRL_SET: CLKMUX1_EN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET,
+ FSL_XCVR_PLL_CTRL0_CM1_EN, 0);
+ } else { /* SPDIF / ARC RX */
+ /* PLL: POSTDIV: PDIV2 */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_PDIV,
+ FSL_XCVR_PLL_PDIVx(log2, 2), 0);
+ /* PLL: CTRL_SET: CLKMUX2_EN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET,
+ FSL_XCVR_PLL_CTRL0_CM2_EN, 0);
+ }
+
+ if (xcvr->mode == FSL_XCVR_MODE_EARC) { /* eARC mode */
+ /* PHY: CTRL_SET: TX_DIFF_OE, PHY_EN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET,
+ FSL_XCVR_PHY_CTRL_TSDIFF_OE |
+ FSL_XCVR_PHY_CTRL_PHY_EN, 1);
+ /* PHY: CTRL2_SET: EARC_TX_MODE */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL2_SET,
+ FSL_XCVR_PHY_CTRL2_EARC_TXMS, 1);
+ } else if (!tx) { /* SPDIF / ARC RX mode */
+ if (xcvr->mode == FSL_XCVR_MODE_SPDIF)
+ /* PHY: CTRL_SET: SPDIF_EN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET,
+ FSL_XCVR_PHY_CTRL_SPDIF_EN, 1);
+ else /* PHY: CTRL_SET: ARC RX setup */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET,
+ FSL_XCVR_PHY_CTRL_PHY_EN |
+ FSL_XCVR_PHY_CTRL_RX_CM_EN |
+ fsl_xcvr_phy_arc_cfg[xcvr->arc_mode], 1);
+ }
+
+ dev_dbg(dev, "PLL Fexp: %u, Fout: %u, mfi: %u, mfn: %u, mfd: %d, div: %u, pdiv0: %u\n",
+ freq, fsl_xcvr_pll_cfg[i].fout, fsl_xcvr_pll_cfg[i].mfi,
+ fsl_xcvr_pll_cfg[i].mfn, fsl_xcvr_pll_cfg[i].mfd, div, log2);
+ return 0;
+}
+
+static int fsl_xcvr_en_aud_pll(struct fsl_xcvr *xcvr, u32 freq)
+{
+ struct device *dev = &xcvr->pdev->dev;
+ int ret;
+
+ clk_disable_unprepare(xcvr->phy_clk);
+ ret = clk_set_rate(xcvr->phy_clk, freq);
+ if (ret < 0) {
+ dev_err(dev, "Error while setting AUD PLL rate: %d\n", ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(xcvr->phy_clk);
+ if (ret) {
+ dev_err(dev, "failed to start PHY clock: %d\n", ret);
+ return ret;
+ }
+
+ /* Release AI interface from reset */
+ ret = regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_SET,
+ FSL_XCVR_PHY_AI_CTRL_AI_RESETN);
+ if (ret < 0) {
+ dev_err(dev, "Error while setting IER0: %d\n", ret);
+ return ret;
+ }
+
+ if (xcvr->mode == FSL_XCVR_MODE_EARC) { /* eARC mode */
+ /* PHY: CTRL_SET: TX_DIFF_OE, PHY_EN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET,
+ FSL_XCVR_PHY_CTRL_TSDIFF_OE |
+ FSL_XCVR_PHY_CTRL_PHY_EN, 1);
+ /* PHY: CTRL2_SET: EARC_TX_MODE */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL2_SET,
+ FSL_XCVR_PHY_CTRL2_EARC_TXMS, 1);
+ } else { /* SPDIF mode */
+ /* PHY: CTRL_SET: TX_CLK_AUD_SS | SPDIF_EN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET,
+ FSL_XCVR_PHY_CTRL_TX_CLK_AUD_SS |
+ FSL_XCVR_PHY_CTRL_SPDIF_EN, 1);
+ }
+
+ dev_dbg(dev, "PLL Fexp: %u\n", freq);
+
+ return 0;
+}
+
+#define FSL_XCVR_SPDIF_RX_FREQ 175000000
+static int fsl_xcvr_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ u32 m_ctl = 0, v_ctl = 0;
+ u32 r = substream->runtime->rate, ch = substream->runtime->channels;
+ u32 fout = 32 * r * ch * 10 * 2;
+ int ret = 0;
+
+ switch (xcvr->mode) {
+ case FSL_XCVR_MODE_SPDIF:
+ case FSL_XCVR_MODE_ARC:
+ if (tx) {
+ ret = fsl_xcvr_en_aud_pll(xcvr, fout);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to set TX freq %u: %d\n",
+ fout, ret);
+ return ret;
+ }
+
+ ret = regmap_write(xcvr->regmap, FSL_XCVR_TX_DPTH_CTRL_SET,
+ FSL_XCVR_TX_DPTH_CTRL_FRM_FMT);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to set TX_DPTH: %d\n", ret);
+ return ret;
+ }
+
+ /**
+ * set SPDIF MODE - this flag is used to gate
+ * SPDIF output, useless for SPDIF RX
+ */
+ m_ctl |= FSL_XCVR_EXT_CTRL_SPDIF_MODE;
+ v_ctl |= FSL_XCVR_EXT_CTRL_SPDIF_MODE;
+ } else {
+ /**
+ * Clear RX FIFO, flip RX FIFO bits,
+ * disable eARC related HW mode detects
+ */
+ ret = regmap_write(xcvr->regmap, FSL_XCVR_RX_DPTH_CTRL_SET,
+ FSL_XCVR_RX_DPTH_CTRL_STORE_FMT |
+ FSL_XCVR_RX_DPTH_CTRL_CLR_RX_FIFO |
+ FSL_XCVR_RX_DPTH_CTRL_COMP |
+ FSL_XCVR_RX_DPTH_CTRL_LAYB_CTRL);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to set RX_DPTH: %d\n", ret);
+ return ret;
+ }
+
+ ret = fsl_xcvr_en_phy_pll(xcvr, FSL_XCVR_SPDIF_RX_FREQ, tx);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to set RX freq %u: %d\n",
+ FSL_XCVR_SPDIF_RX_FREQ, ret);
+ return ret;
+ }
+ }
+ break;
+ case FSL_XCVR_MODE_EARC:
+ if (!tx) {
+ /** Clear RX FIFO, flip RX FIFO bits */
+ ret = regmap_write(xcvr->regmap, FSL_XCVR_RX_DPTH_CTRL_SET,
+ FSL_XCVR_RX_DPTH_CTRL_STORE_FMT |
+ FSL_XCVR_RX_DPTH_CTRL_CLR_RX_FIFO);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to set RX_DPTH: %d\n", ret);
+ return ret;
+ }
+
+ /** Enable eARC related HW mode detects */
+ ret = regmap_write(xcvr->regmap, FSL_XCVR_RX_DPTH_CTRL_CLR,
+ FSL_XCVR_RX_DPTH_CTRL_COMP |
+ FSL_XCVR_RX_DPTH_CTRL_LAYB_CTRL);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to clr TX_DPTH: %d\n", ret);
+ return ret;
+ }
+ }
+
+ /* clear CMDC RESET */
+ m_ctl |= FSL_XCVR_EXT_CTRL_CMDC_RESET(tx);
+ /* set TX_RX_MODE */
+ m_ctl |= FSL_XCVR_EXT_CTRL_TX_RX_MODE;
+ v_ctl |= (tx ? FSL_XCVR_EXT_CTRL_TX_RX_MODE : 0);
+ break;
+ }
+
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0,
+ FSL_XCVR_IRQ_EARC_ALL, FSL_XCVR_IRQ_EARC_ALL);
+ if (ret < 0) {
+ dev_err(dai->dev, "Error while setting IER0: %d\n", ret);
+ return ret;
+ }
+
+ /* clear DPATH RESET */
+ m_ctl |= FSL_XCVR_EXT_CTRL_DPTH_RESET(tx);
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, m_ctl, v_ctl);
+ if (ret < 0) {
+ dev_err(dai->dev, "Error while setting EXT_CTRL: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fsl_xcvr_constr(const struct snd_pcm_substream *substream,
+ const struct snd_pcm_hw_constraint_list *channels,
+ const struct snd_pcm_hw_constraint_list *rates)
+{
+ struct snd_pcm_runtime *rt = substream->runtime;
+ int ret;
+
+ ret = snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ channels);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE,
+ rates);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int fsl_xcvr_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ int ret = 0;
+
+ if (xcvr->streams & BIT(substream->stream)) {
+ dev_err(dai->dev, "%sX busy\n", tx ? "T" : "R");
+ return -EBUSY;
+ }
+
+ switch (xcvr->mode) {
+ case FSL_XCVR_MODE_SPDIF:
+ case FSL_XCVR_MODE_ARC:
+ ret = fsl_xcvr_constr(substream, &fsl_xcvr_spdif_channels_constr,
+ &fsl_xcvr_spdif_rates_constr);
+ break;
+ case FSL_XCVR_MODE_EARC:
+ ret = fsl_xcvr_constr(substream, &fsl_xcvr_earc_channels_constr,
+ &fsl_xcvr_earc_rates_constr);
+ break;
+ }
+ if (ret < 0)
+ return ret;
+
+ xcvr->streams |= BIT(substream->stream);
+
+ /* Disable XCVR controls if there is stream started */
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_mode_kctl.name, false);
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_arc_mode_kctl.name, false);
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_earc_capds_kctl.name, false);
+
+ return 0;
+}
+
+static void fsl_xcvr_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ u32 mask = 0, val = 0;
+ int ret;
+
+ xcvr->streams &= ~BIT(substream->stream);
+
+ /* Enable XCVR controls if there is no stream started */
+ if (!xcvr->streams) {
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_mode_kctl.name, true);
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_arc_mode_kctl.name,
+ (xcvr->mode == FSL_XCVR_MODE_ARC));
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_earc_capds_kctl.name,
+ (xcvr->mode == FSL_XCVR_MODE_EARC));
+
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0,
+ FSL_XCVR_IRQ_EARC_ALL, 0);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to set IER0: %d\n", ret);
+ return;
+ }
+
+ /* clear SPDIF MODE */
+ if (xcvr->mode == FSL_XCVR_MODE_SPDIF)
+ mask |= FSL_XCVR_EXT_CTRL_SPDIF_MODE;
+ }
+
+ if (xcvr->mode == FSL_XCVR_MODE_EARC) {
+ /* set CMDC RESET */
+ mask |= FSL_XCVR_EXT_CTRL_CMDC_RESET(tx);
+ val |= FSL_XCVR_EXT_CTRL_CMDC_RESET(tx);
+ }
+
+ /* set DPATH RESET */
+ mask |= FSL_XCVR_EXT_CTRL_DPTH_RESET(tx);
+ val |= FSL_XCVR_EXT_CTRL_DPTH_RESET(tx);
+
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, mask, val);
+ if (ret < 0) {
+ dev_err(dai->dev, "Err setting DPATH RESET: %d\n", ret);
+ return;
+ }
+}
+
+static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ int ret;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (tx) {
+ switch (xcvr->mode) {
+ case FSL_XCVR_MODE_EARC:
+ /* set isr_cmdc_tx_en, w1c */
+ ret = regmap_write(xcvr->regmap,
+ FSL_XCVR_ISR_SET,
+ FSL_XCVR_ISR_CMDC_TX_EN);
+ if (ret < 0) {
+ dev_err(dai->dev, "err updating isr %d\n", ret);
+ return ret;
+ }
+ fallthrough;
+ case FSL_XCVR_MODE_SPDIF:
+ ret = regmap_write(xcvr->regmap,
+ FSL_XCVR_TX_DPTH_CTRL_SET,
+ FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to start DATA_TX: %d\n", ret);
+ return ret;
+ }
+ break;
+ }
+ }
+
+ /* enable DMA RD/WR */
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
+ FSL_XCVR_EXT_CTRL_DMA_DIS(tx), 0);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to enable DMA: %d\n", ret);
+ return ret;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ /* disable DMA RD/WR */
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
+ FSL_XCVR_EXT_CTRL_DMA_DIS(tx),
+ FSL_XCVR_EXT_CTRL_DMA_DIS(tx));
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to disable DMA: %d\n", ret);
+ return ret;
+ }
+
+ if (tx) {
+ switch (xcvr->mode) {
+ case FSL_XCVR_MODE_SPDIF:
+ ret = regmap_write(xcvr->regmap,
+ FSL_XCVR_TX_DPTH_CTRL_CLR,
+ FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to stop DATA_TX: %d\n", ret);
+ return ret;
+ }
+ fallthrough;
+ case FSL_XCVR_MODE_EARC:
+ /* clear ISR_CMDC_TX_EN, W1C */
+ ret = regmap_write(xcvr->regmap,
+ FSL_XCVR_ISR_CLR,
+ FSL_XCVR_ISR_CMDC_TX_EN);
+ if (ret < 0) {
+ dev_err(dai->dev,
+ "Err updating ISR %d\n", ret);
+ return ret;
+ }
+ break;
+ }
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr)
+{
+ struct device *dev = &xcvr->pdev->dev;
+ const struct firmware *fw;
+ int ret = 0, rem, off, out, page = 0, size = FSL_XCVR_REG_OFFSET;
+ u32 mask, val;
+
+ ret = request_firmware(&fw, xcvr->fw_name, dev);
+ if (ret) {
+ dev_err(dev, "failed to request firmware.\n");
+ return ret;
+ }
+
+ rem = fw->size;
+
+ /* RAM is 20KiB => max 10 pages 2KiB each */
+ for (page = 0; page < 10; page++) {
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
+ FSL_XCVR_EXT_CTRL_PAGE_MASK,
+ FSL_XCVR_EXT_CTRL_PAGE(page));
+ if (ret < 0) {
+ dev_err(dev, "FW: failed to set page %d, err=%d\n",
+ page, ret);
+ goto err_firmware;
+ }
+
+ off = page * size;
+ out = min(rem, size);
+ /* IPG clock is assumed to be running, otherwise it will hang */
+ if (out > 0) {
+ /* write firmware into code memory */
+ memcpy_toio(xcvr->ram_addr, fw->data + off, out);
+ rem -= out;
+ if (rem == 0) {
+ /* last part of firmware written */
+ /* clean remaining part of code memory page */
+ memset_io(xcvr->ram_addr + out, 0, size - out);
+ }
+ } else {
+ /* clean current page, including data memory */
+ memset_io(xcvr->ram_addr, 0, size);
+ }
+ };
+
+err_firmware:
+ release_firmware(fw);
+ if (ret < 0)
+ return ret;
+
+ /* configure watermarks */
+ mask = FSL_XCVR_EXT_CTRL_RX_FWM_MASK | FSL_XCVR_EXT_CTRL_TX_FWM_MASK;
+ val = FSL_XCVR_EXT_CTRL_RX_FWM(FSL_XCVR_FIFO_WMK_RX);
+ val |= FSL_XCVR_EXT_CTRL_TX_FWM(FSL_XCVR_FIFO_WMK_TX);
+ /* disable DMA RD/WR */
+ mask |= FSL_XCVR_EXT_CTRL_DMA_RD_DIS | FSL_XCVR_EXT_CTRL_DMA_WR_DIS;
+ val |= FSL_XCVR_EXT_CTRL_DMA_RD_DIS | FSL_XCVR_EXT_CTRL_DMA_WR_DIS;
+ /* Data RAM is 4KiB, last two pages: 8 and 9. Select page 8. */
+ mask |= FSL_XCVR_EXT_CTRL_PAGE_MASK;
+ val |= FSL_XCVR_EXT_CTRL_PAGE(8);
+
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, mask, val);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set watermarks: %d\n", ret);
+ return ret;
+ }
+
+ /* Store Capabilities Data Structure into Data RAM */
+ memcpy_toio(xcvr->ram_addr + FSL_XCVR_CAP_DATA_STR, xcvr->cap_ds,
+ FSL_XCVR_CAPDS_SIZE);
+
+ return 0;
+}
+
+static int fsl_xcvr_type_iec958_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+
+ return 0;
+}
+
+static int fsl_xcvr_type_iec958_bytes_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+ uinfo->count = sizeof_field(struct snd_aes_iec958, status);
+
+ return 0;
+}
+
+static int fsl_xcvr_rx_cs_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ memcpy(ucontrol->value.iec958.status, xcvr->rx_iec958.status, 24);
+
+ return 0;
+}
+
+static int fsl_xcvr_tx_cs_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ memcpy(ucontrol->value.iec958.status, xcvr->tx_iec958.status, 24);
+
+ return 0;
+}
+
+static int fsl_xcvr_tx_cs_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ memcpy(xcvr->tx_iec958.status, ucontrol->value.iec958.status, 24);
+
+ return 0;
+}
+
+static struct snd_kcontrol_new fsl_xcvr_rx_ctls[] = {
+ /* Channel status controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .info = fsl_xcvr_type_iec958_info,
+ .get = fsl_xcvr_rx_cs_get,
+ },
+ /* Capture channel status, bytes */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "Capture Channel Status",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .info = fsl_xcvr_type_iec958_bytes_info,
+ .get = fsl_xcvr_rx_cs_get,
+ },
+};
+
+static struct snd_kcontrol_new fsl_xcvr_tx_ctls[] = {
+ /* Channel status controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = fsl_xcvr_type_iec958_info,
+ .get = fsl_xcvr_tx_cs_get,
+ .put = fsl_xcvr_tx_cs_put,
+ },
+ /* Playback channel status, bytes */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "Playback Channel Status",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = fsl_xcvr_type_iec958_bytes_info,
+ .get = fsl_xcvr_tx_cs_get,
+ .put = fsl_xcvr_tx_cs_put,
+ },
+};
+
+static struct snd_soc_dai_ops fsl_xcvr_dai_ops = {
+ .prepare = fsl_xcvr_prepare,
+ .startup = fsl_xcvr_startup,
+ .shutdown = fsl_xcvr_shutdown,
+ .trigger = fsl_xcvr_trigger,
+};
+
+static int fsl_xcvr_dai_probe(struct snd_soc_dai *dai)
+{
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ snd_soc_dai_init_dma_data(dai, &xcvr->dma_prms_tx, &xcvr->dma_prms_rx);
+ snd_soc_dai_set_drvdata(dai, xcvr);
+
+ snd_soc_add_dai_controls(dai, &fsl_xcvr_mode_kctl, 1);
+ snd_soc_add_dai_controls(dai, &fsl_xcvr_arc_mode_kctl, 1);
+ snd_soc_add_dai_controls(dai, &fsl_xcvr_earc_capds_kctl, 1);
+ snd_soc_add_dai_controls(dai, fsl_xcvr_tx_ctls,
+ ARRAY_SIZE(fsl_xcvr_tx_ctls));
+ snd_soc_add_dai_controls(dai, fsl_xcvr_rx_ctls,
+ ARRAY_SIZE(fsl_xcvr_rx_ctls));
+ return 0;
+}
+
+static struct snd_soc_dai_driver fsl_xcvr_dai = {
+ .probe = fsl_xcvr_dai_probe,
+ .ops = &fsl_xcvr_dai_ops,
+ .playback = {
+ .stream_name = "CPU-Playback",
+ .channels_min = 1,
+ .channels_max = 32,
+ .rate_min = 32000,
+ .rate_max = 1536000,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,
+ },
+ .capture = {
+ .stream_name = "CPU-Capture",
+ .channels_min = 1,
+ .channels_max = 32,
+ .rate_min = 32000,
+ .rate_max = 1536000,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,
+ },
+};
+
+static const struct snd_soc_component_driver fsl_xcvr_comp = {
+ .name = "fsl-xcvr-dai",
+};
+
+static const struct reg_default fsl_xcvr_reg_defaults[] = {
+ { FSL_XCVR_VERSION, 0x00000000 },
+ { FSL_XCVR_EXT_CTRL, 0xF8204040 },
+ { FSL_XCVR_EXT_STATUS, 0x00000000 },
+ { FSL_XCVR_EXT_IER0, 0x00000000 },
+ { FSL_XCVR_EXT_IER1, 0x00000000 },
+ { FSL_XCVR_EXT_ISR, 0x00000000 },
+ { FSL_XCVR_EXT_ISR_SET, 0x00000000 },
+ { FSL_XCVR_EXT_ISR_CLR, 0x00000000 },
+ { FSL_XCVR_EXT_ISR_TOG, 0x00000000 },
+ { FSL_XCVR_IER, 0x00000000 },
+ { FSL_XCVR_ISR, 0x00000000 },
+ { FSL_XCVR_ISR_SET, 0x00000000 },
+ { FSL_XCVR_ISR_CLR, 0x00000000 },
+ { FSL_XCVR_ISR_TOG, 0x00000000 },
+ { FSL_XCVR_RX_DPTH_CTRL, 0x00002C89 },
+ { FSL_XCVR_RX_DPTH_CTRL_SET, 0x00002C89 },
+ { FSL_XCVR_RX_DPTH_CTRL_CLR, 0x00002C89 },
+ { FSL_XCVR_RX_DPTH_CTRL_TOG, 0x00002C89 },
+ { FSL_XCVR_TX_DPTH_CTRL, 0x00000000 },
+ { FSL_XCVR_TX_DPTH_CTRL_SET, 0x00000000 },
+ { FSL_XCVR_TX_DPTH_CTRL_CLR, 0x00000000 },
+ { FSL_XCVR_TX_DPTH_CTRL_TOG, 0x00000000 },
+ { FSL_XCVR_TX_CS_DATA_0, 0x00000000 },
+ { FSL_XCVR_TX_CS_DATA_1, 0x00000000 },
+ { FSL_XCVR_TX_CS_DATA_2, 0x00000000 },
+ { FSL_XCVR_TX_CS_DATA_3, 0x00000000 },
+ { FSL_XCVR_TX_CS_DATA_4, 0x00000000 },
+ { FSL_XCVR_TX_CS_DATA_5, 0x00000000 },
+ { FSL_XCVR_DEBUG_REG_0, 0x00000000 },
+ { FSL_XCVR_DEBUG_REG_1, 0x00000000 },
+};
+
+static bool fsl_xcvr_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case FSL_XCVR_VERSION:
+ case FSL_XCVR_EXT_CTRL:
+ case FSL_XCVR_EXT_STATUS:
+ case FSL_XCVR_EXT_IER0:
+ case FSL_XCVR_EXT_IER1:
+ case FSL_XCVR_EXT_ISR:
+ case FSL_XCVR_EXT_ISR_SET:
+ case FSL_XCVR_EXT_ISR_CLR:
+ case FSL_XCVR_EXT_ISR_TOG:
+ case FSL_XCVR_IER:
+ case FSL_XCVR_ISR:
+ case FSL_XCVR_ISR_SET:
+ case FSL_XCVR_ISR_CLR:
+ case FSL_XCVR_ISR_TOG:
+ case FSL_XCVR_PHY_AI_CTRL:
+ case FSL_XCVR_PHY_AI_CTRL_SET:
+ case FSL_XCVR_PHY_AI_CTRL_CLR:
+ case FSL_XCVR_PHY_AI_CTRL_TOG:
+ case FSL_XCVR_PHY_AI_RDATA:
+ case FSL_XCVR_CLK_CTRL:
+ case FSL_XCVR_RX_DPTH_CTRL:
+ case FSL_XCVR_RX_DPTH_CTRL_SET:
+ case FSL_XCVR_RX_DPTH_CTRL_CLR:
+ case FSL_XCVR_RX_DPTH_CTRL_TOG:
+ case FSL_XCVR_TX_DPTH_CTRL:
+ case FSL_XCVR_TX_DPTH_CTRL_SET:
+ case FSL_XCVR_TX_DPTH_CTRL_CLR:
+ case FSL_XCVR_TX_DPTH_CTRL_TOG:
+ case FSL_XCVR_TX_CS_DATA_0:
+ case FSL_XCVR_TX_CS_DATA_1:
+ case FSL_XCVR_TX_CS_DATA_2:
+ case FSL_XCVR_TX_CS_DATA_3:
+ case FSL_XCVR_TX_CS_DATA_4:
+ case FSL_XCVR_TX_CS_DATA_5:
+ case FSL_XCVR_DEBUG_REG_0:
+ case FSL_XCVR_DEBUG_REG_1:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_xcvr_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case FSL_XCVR_EXT_CTRL:
+ case FSL_XCVR_EXT_IER0:
+ case FSL_XCVR_EXT_IER1:
+ case FSL_XCVR_EXT_ISR:
+ case FSL_XCVR_EXT_ISR_SET:
+ case FSL_XCVR_EXT_ISR_CLR:
+ case FSL_XCVR_EXT_ISR_TOG:
+ case FSL_XCVR_IER:
+ case FSL_XCVR_ISR_SET:
+ case FSL_XCVR_ISR_CLR:
+ case FSL_XCVR_ISR_TOG:
+ case FSL_XCVR_PHY_AI_CTRL:
+ case FSL_XCVR_PHY_AI_CTRL_SET:
+ case FSL_XCVR_PHY_AI_CTRL_CLR:
+ case FSL_XCVR_PHY_AI_CTRL_TOG:
+ case FSL_XCVR_PHY_AI_WDATA:
+ case FSL_XCVR_CLK_CTRL:
+ case FSL_XCVR_RX_DPTH_CTRL:
+ case FSL_XCVR_RX_DPTH_CTRL_SET:
+ case FSL_XCVR_RX_DPTH_CTRL_CLR:
+ case FSL_XCVR_RX_DPTH_CTRL_TOG:
+ case FSL_XCVR_TX_DPTH_CTRL_SET:
+ case FSL_XCVR_TX_DPTH_CTRL_CLR:
+ case FSL_XCVR_TX_DPTH_CTRL_TOG:
+ case FSL_XCVR_TX_CS_DATA_0:
+ case FSL_XCVR_TX_CS_DATA_1:
+ case FSL_XCVR_TX_CS_DATA_2:
+ case FSL_XCVR_TX_CS_DATA_3:
+ case FSL_XCVR_TX_CS_DATA_4:
+ case FSL_XCVR_TX_CS_DATA_5:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_xcvr_volatile_reg(struct device *dev, unsigned int reg)
+{
+ return fsl_xcvr_readable_reg(dev, reg);
+}
+
+static const struct regmap_config fsl_xcvr_regmap_cfg = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = FSL_XCVR_MAX_REG,
+ .reg_defaults = fsl_xcvr_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(fsl_xcvr_reg_defaults),
+ .readable_reg = fsl_xcvr_readable_reg,
+ .volatile_reg = fsl_xcvr_volatile_reg,
+ .writeable_reg = fsl_xcvr_writeable_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static irqreturn_t irq0_isr(int irq, void *devid)
+{
+ struct fsl_xcvr *xcvr = (struct fsl_xcvr *)devid;
+ struct device *dev = &xcvr->pdev->dev;
+ struct regmap *regmap = xcvr->regmap;
+ void __iomem *reg_ctrl, *reg_buff;
+ u32 isr, val, i;
+
+ regmap_read(regmap, FSL_XCVR_EXT_ISR, &isr);
+ regmap_write(regmap, FSL_XCVR_EXT_ISR_CLR, isr);
+
+ if (isr & FSL_XCVR_IRQ_NEW_CS) {
+ dev_dbg(dev, "Received new CS block\n");
+ /* Data RAM is 4KiB, last two pages: 8 and 9. Select page 8. */
+ regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
+ FSL_XCVR_EXT_CTRL_PAGE_MASK,
+ FSL_XCVR_EXT_CTRL_PAGE(8));
+
+ /* Find updated CS buffer */
+ reg_ctrl = xcvr->ram_addr + FSL_XCVR_RX_CS_CTRL_0;
+ reg_buff = xcvr->ram_addr + FSL_XCVR_RX_CS_BUFF_0;
+ memcpy_fromio(&val, reg_ctrl, sizeof(val));
+ if (!val) {
+ reg_ctrl = xcvr->ram_addr + FSL_XCVR_RX_CS_CTRL_1;
+ reg_buff = xcvr->ram_addr + FSL_XCVR_RX_CS_BUFF_1;
+ memcpy_fromio(&val, reg_ctrl, sizeof(val));
+ }
+
+ if (val) {
+ /* copy CS buffer */
+ memcpy_fromio(&xcvr->rx_iec958.status, reg_buff,
+ sizeof(xcvr->rx_iec958.status));
+ for (i = 0; i < 6; i++) {
+ val = *(u32 *)(xcvr->rx_iec958.status + i*4);
+ *(u32 *)(xcvr->rx_iec958.status + i*4) =
+ bitrev32(val);
+ }
+ /* clear CS control register */
+ memset_io(reg_ctrl, 0, sizeof(val));
+ }
+ }
+ if (isr & FSL_XCVR_IRQ_NEW_UD)
+ dev_dbg(dev, "Received new UD block\n");
+ if (isr & FSL_XCVR_IRQ_MUTE)
+ dev_dbg(dev, "HW mute bit detected\n");
+ if (isr & FSL_XCVR_IRQ_FIFO_UOFL_ERR)
+ dev_dbg(dev, "RX/TX FIFO full/empty\n");
+ if (isr & FSL_XCVR_IRQ_ARC_MODE)
+ dev_dbg(dev, "CMDC SM falls out of eARC mode\n");
+ if (isr & FSL_XCVR_IRQ_DMA_RD_REQ)
+ dev_dbg(dev, "DMA read request\n");
+ if (isr & FSL_XCVR_IRQ_DMA_WR_REQ)
+ dev_dbg(dev, "DMA write request\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t irq1_isr(int irq, void *devid)
+{
+ struct fsl_xcvr *xcvr = (struct fsl_xcvr *)devid;
+ struct device *dev = &xcvr->pdev->dev;
+
+ dev_dbg(dev, "irq[1]: %d\n", irq);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t irq2_isr(int irq, void *devid)
+{
+ struct fsl_xcvr *xcvr = (struct fsl_xcvr *)devid;
+ struct device *dev = &xcvr->pdev->dev;
+
+ dev_dbg(dev, "irq[2]: %d\n", irq);
+
+ return IRQ_HANDLED;
+}
+
+static const struct of_device_id fsl_xcvr_dt_ids[] = {
+ { .compatible = "fsl,imx8mp-xcvr", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, fsl_xcvr_dt_ids);
+
+static int fsl_xcvr_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ const struct of_device_id *of_id;
+ struct fsl_xcvr *xcvr;
+ struct resource *ram_res, *regs_res, *rx_res, *tx_res;
+ void __iomem *regs;
+ int ret, irq;
+
+ of_id = of_match_device(fsl_xcvr_dt_ids, dev);
+ if (!of_id)
+ return -EINVAL;
+
+ xcvr = devm_kzalloc(dev, sizeof(*xcvr), GFP_KERNEL);
+ if (!xcvr)
+ return -ENOMEM;
+
+ xcvr->pdev = pdev;
+ xcvr->ipg_clk = devm_clk_get(dev, "ipg");
+ if (IS_ERR(xcvr->ipg_clk)) {
+ dev_err(dev, "failed to get ipg clock\n");
+ return PTR_ERR(xcvr->ipg_clk);
+ }
+
+ xcvr->phy_clk = devm_clk_get(dev, "phy");
+ if (IS_ERR(xcvr->phy_clk)) {
+ dev_err(dev, "failed to get phy clock\n");
+ return PTR_ERR(xcvr->phy_clk);
+ }
+
+ xcvr->spba_clk = devm_clk_get(dev, "spba");
+ if (IS_ERR(xcvr->spba_clk)) {
+ dev_err(dev, "failed to get spba clock\n");
+ return PTR_ERR(xcvr->spba_clk);
+ }
+
+ xcvr->pll_ipg_clk = devm_clk_get(dev, "pll_ipg");
+ if (IS_ERR(xcvr->pll_ipg_clk)) {
+ dev_err(dev, "failed to get pll_ipg clock\n");
+ return PTR_ERR(xcvr->pll_ipg_clk);
+ }
+
+ ram_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ram");
+ xcvr->ram_addr = devm_ioremap_resource(dev, ram_res);
+ if (IS_ERR(xcvr->ram_addr))
+ return PTR_ERR(xcvr->ram_addr);
+
+ regs_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
+ regs = devm_ioremap_resource(dev, regs_res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ xcvr->regmap = devm_regmap_init_mmio_clk(dev, NULL, regs,
+ &fsl_xcvr_regmap_cfg);
+ if (IS_ERR(xcvr->regmap)) {
+ dev_err(dev, "failed to init XCVR regmap: %ld\n",
+ PTR_ERR(xcvr->regmap));
+ return PTR_ERR(xcvr->regmap);
+ }
+
+ xcvr->reset = of_reset_control_get(np, NULL);
+
+ ret = of_property_read_string(np, "firmware-name", &xcvr->fw_name);
+ if (ret) {
+ dev_err(dev, "failed to get fsl,xcvr-fw: %d\n", ret);
+ return ret;
+ }
+
+ /* get IRQs */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(dev, "no irq[0]: %d\n", irq);
+ return irq;
+ }
+
+ ret = devm_request_irq(dev, irq, irq0_isr, 0, pdev->name, xcvr);
+ if (ret) {
+ dev_err(dev, "failed to claim IRQ0: %i\n", ret);
+ return ret;
+ }
+
+ irq = platform_get_irq(pdev, 1);
+ if (irq < 0) {
+ dev_err(dev, "no irq[1]: %d\n", irq);
+ return irq;
+ }
+
+ ret = devm_request_irq(dev, irq, irq1_isr, 0, pdev->name, xcvr);
+ if (ret) {
+ dev_err(dev, "failed to claim IRQ1: %i\n", ret);
+ return ret;
+ }
+
+ irq = platform_get_irq(pdev, 2);
+ if (irq < 0) {
+ dev_err(dev, "no irq[2]: %d\n", irq);
+ return irq;
+ }
+
+ ret = devm_request_irq(dev, irq, irq2_isr, 0, pdev->name, xcvr);
+ if (ret) {
+ dev_err(dev, "failed to claim IRQ2: %i\n", ret);
+ return ret;
+ }
+
+ rx_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rxfifo");
+ tx_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "txfifo");
+ xcvr->dma_prms_rx.chan_name = "rx";
+ xcvr->dma_prms_tx.chan_name = "tx";
+ xcvr->dma_prms_rx.addr = rx_res->start;
+ xcvr->dma_prms_tx.addr = tx_res->start;
+ xcvr->dma_prms_rx.maxburst = FSL_XCVR_MAXBURST_RX;
+ xcvr->dma_prms_tx.maxburst = FSL_XCVR_MAXBURST_TX;
+
+ platform_set_drvdata(pdev, xcvr);
+ pm_runtime_enable(dev);
+ regcache_cache_only(xcvr->regmap, true);
+
+ ret = devm_snd_soc_register_component(dev, &fsl_xcvr_comp,
+ &fsl_xcvr_dai, 1);
+ if (ret) {
+ dev_err(dev, "failed to register component %s\n",
+ fsl_xcvr_comp.name);
+ return ret;
+ }
+
+ ret = devm_snd_dmaengine_pcm_register(dev, NULL, 0);
+ if (ret)
+ dev_err(dev, "failed to pcm register\n");
+
+ return ret;
+}
+
+#ifdef CONFIG_PM
+static int fsl_xcvr_runtime_suspend(struct device *dev)
+{
+ struct fsl_xcvr *xcvr = dev_get_drvdata(dev);
+ int ret;
+
+ /* Assert M0+ reset */
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
+ FSL_XCVR_EXT_CTRL_CORE_RESET,
+ FSL_XCVR_EXT_CTRL_CORE_RESET);
+ if (ret < 0)
+ dev_err(dev, "Failed to assert M0+ core: %d\n", ret);
+
+ regcache_cache_only(xcvr->regmap, true);
+
+ clk_disable_unprepare(xcvr->spba_clk);
+ clk_disable_unprepare(xcvr->phy_clk);
+ clk_disable_unprepare(xcvr->pll_ipg_clk);
+ clk_disable_unprepare(xcvr->ipg_clk);
+
+ return 0;
+}
+
+static int fsl_xcvr_runtime_resume(struct device *dev)
+{
+ struct fsl_xcvr *xcvr = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(xcvr->ipg_clk);
+ if (ret) {
+ dev_err(dev, "failed to start IPG clock.\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(xcvr->pll_ipg_clk);
+ if (ret) {
+ dev_err(dev, "failed to start PLL IPG clock.\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(xcvr->phy_clk);
+ if (ret) {
+ dev_err(dev, "failed to start PHY clock: %d\n", ret);
+ clk_disable_unprepare(xcvr->ipg_clk);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(xcvr->spba_clk);
+ if (ret) {
+ dev_err(dev, "failed to start SPBA clock.\n");
+ clk_disable_unprepare(xcvr->phy_clk);
+ clk_disable_unprepare(xcvr->ipg_clk);
+ return ret;
+ }
+
+ regcache_cache_only(xcvr->regmap, false);
+ regcache_mark_dirty(xcvr->regmap);
+ ret = regcache_sync(xcvr->regmap);
+
+ if (ret) {
+ dev_err(dev, "failed to sync regcache.\n");
+ return ret;
+ }
+
+ reset_control_assert(xcvr->reset);
+ reset_control_deassert(xcvr->reset);
+
+ ret = fsl_xcvr_load_firmware(xcvr);
+ if (ret) {
+ dev_err(dev, "failed to load firmware.\n");
+ return ret;
+ }
+
+ /* Release M0+ reset */
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
+ FSL_XCVR_EXT_CTRL_CORE_RESET, 0);
+ if (ret < 0) {
+ dev_err(dev, "M0+ core release failed: %d\n", ret);
+ return ret;
+ }
+ mdelay(50);
+
+ return 0;
+}
+#endif /* CONFIG_PM*/
+
+static const struct dev_pm_ops fsl_xcvr_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_xcvr_runtime_suspend,
+ fsl_xcvr_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver fsl_xcvr_driver = {
+ .probe = fsl_xcvr_probe,
+ .driver = {
+ .name = "fsl,imx8mp-audio-xcvr",
+ .pm = &fsl_xcvr_pm_ops,
+ .of_match_table = fsl_xcvr_dt_ids,
+ },
+};
+module_platform_driver(fsl_xcvr_driver);
+
+MODULE_AUTHOR("Viorel Suman <[email protected]>");
+MODULE_DESCRIPTION("NXP Audio Transceiver (XCVR) driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_xcvr.h b/sound/soc/fsl/fsl_xcvr.h
new file mode 100644
index 00000000..7f2853c
--- /dev/null
+++ b/sound/soc/fsl/fsl_xcvr.h
@@ -0,0 +1,266 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * NXP XCVR ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ * Copyright 2019 NXP
+ */
+
+#ifndef __FSL_XCVR_H
+#define __FSL_XCVR_H
+
+#define FSL_XCVR_MODE_SPDIF 0
+#define FSL_XCVR_MODE_ARC 1
+#define FSL_XCVR_MODE_EARC 2
+
+/* XCVR Registers */
+#define FSL_XCVR_REG_OFFSET 0x800 /* regs offset */
+#define FSL_XCVR_FIFO_SIZE 0x80 /* 128 */
+#define FSL_XCVR_FIFO_WMK_RX (FSL_XCVR_FIFO_SIZE >> 1) /* 64 */
+#define FSL_XCVR_FIFO_WMK_TX (FSL_XCVR_FIFO_SIZE >> 1) /* 64 */
+#define FSL_XCVR_MAXBURST_RX (FSL_XCVR_FIFO_WMK_RX >> 2) /* 16 */
+#define FSL_XCVR_MAXBURST_TX (FSL_XCVR_FIFO_WMK_TX >> 2) /* 16 */
+
+#define FSL_XCVR_RX_FIFO_ADDR 0x0C00
+#define FSL_XCVR_TX_FIFO_ADDR 0x0E00
+
+#define FSL_XCVR_VERSION 0x00 /* Version */
+#define FSL_XCVR_EXT_CTRL 0x10 /* Control */
+#define FSL_XCVR_EXT_STATUS 0x20 /* Status */
+#define FSL_XCVR_EXT_IER0 0x30 /* Interrupt en 0 */
+#define FSL_XCVR_EXT_IER1 0x40 /* Interrupt en 1 */
+#define FSL_XCVR_EXT_ISR 0x50 /* Interrupt status */
+#define FSL_XCVR_EXT_ISR_SET 0x54 /* Interrupt status */
+#define FSL_XCVR_EXT_ISR_CLR 0x58 /* Interrupt status */
+#define FSL_XCVR_EXT_ISR_TOG 0x5C /* Interrupt status */
+#define FSL_XCVR_IER 0x70 /* Interrupt en for M0+ */
+#define FSL_XCVR_ISR 0x80 /* Interrupt status */
+#define FSL_XCVR_ISR_SET 0x84 /* Interrupt status set */
+#define FSL_XCVR_ISR_CLR 0x88 /* Interrupt status clear */
+#define FSL_XCVR_ISR_TOG 0x8C /* Interrupt status toggle */
+#define FSL_XCVR_PHY_AI_CTRL 0x90
+#define FSL_XCVR_PHY_AI_CTRL_SET 0x94
+#define FSL_XCVR_PHY_AI_CTRL_CLR 0x98
+#define FSL_XCVR_PHY_AI_CTRL_TOG 0x9C
+#define FSL_XCVR_PHY_AI_WDATA 0xA0
+#define FSL_XCVR_PHY_AI_RDATA 0xA4
+#define FSL_XCVR_CLK_CTRL 0xB0
+#define FSL_XCVR_RX_DPTH_CTRL 0x180 /* RX datapath ctrl reg */
+#define FSL_XCVR_RX_DPTH_CTRL_SET 0x184
+#define FSL_XCVR_RX_DPTH_CTRL_CLR 0x188
+#define FSL_XCVR_RX_DPTH_CTRL_TOG 0x18c
+
+#define FSL_XCVR_TX_DPTH_CTRL 0x220 /* TX datapath ctrl reg */
+#define FSL_XCVR_TX_DPTH_CTRL_SET 0x224
+#define FSL_XCVR_TX_DPTH_CTRL_CLR 0x228
+#define FSL_XCVR_TX_DPTH_CTRL_TOG 0x22C
+#define FSL_XCVR_TX_CS_DATA_0 0x230 /* TX channel status bits regs */
+#define FSL_XCVR_TX_CS_DATA_1 0x234
+#define FSL_XCVR_TX_CS_DATA_2 0x238
+#define FSL_XCVR_TX_CS_DATA_3 0x23C
+#define FSL_XCVR_TX_CS_DATA_4 0x240
+#define FSL_XCVR_TX_CS_DATA_5 0x244
+#define FSL_XCVR_DEBUG_REG_0 0x2E0
+#define FSL_XCVR_DEBUG_REG_1 0x2F0
+
+#define FSL_XCVR_MAX_REG FSL_XCVR_DEBUG_REG_1
+
+#define FSL_XCVR_EXT_CTRL_CORE_RESET BIT(31)
+
+#define FSL_XCVR_EXT_CTRL_RX_CMDC_RESET BIT(30)
+#define FSL_XCVR_EXT_CTRL_TX_CMDC_RESET BIT(29)
+#define FSL_XCVR_EXT_CTRL_CMDC_RESET(t) (t ? BIT(29) : BIT(30))
+
+#define FSL_XCVR_EXT_CTRL_RX_DPTH_RESET BIT(28)
+#define FSL_XCVR_EXT_CTRL_TX_DPTH_RESET BIT(27)
+#define FSL_XCVR_EXT_CTRL_DPTH_RESET(t) (t ? BIT(27) : BIT(28))
+
+#define FSL_XCVR_EXT_CTRL_TX_RX_MODE BIT(26)
+#define FSL_XCVR_EXT_CTRL_DMA_RD_DIS BIT(25)
+#define FSL_XCVR_EXT_CTRL_DMA_WR_DIS BIT(24)
+#define FSL_XCVR_EXT_CTRL_DMA_DIS(t) (t ? BIT(24) : BIT(25))
+#define FSL_XCVR_EXT_CTRL_SPDIF_MODE BIT(23)
+#define FSL_XCVR_EXT_CTRL_SLEEP_MODE BIT(21)
+
+#define FSL_XCVR_EXT_CTRL_TX_FWM_SHFT 0
+#define FSL_XCVR_EXT_CTRL_TX_FWM_MASK GENMASK(6, 0)
+#define FSL_XCVR_EXT_CTRL_TX_FWM(i) (((i) << FSL_XCVR_EXT_CTRL_TX_FWM_SHFT) \
+ & FSL_XCVR_EXT_CTRL_TX_FWM_MASK)
+#define FSL_XCVR_EXT_CTRL_RX_FWM_SHFT 8
+#define FSL_XCVR_EXT_CTRL_RX_FWM_MASK GENMASK(14, 8)
+#define FSL_XCVR_EXT_CTRL_RX_FWM(i) (((i) << FSL_XCVR_EXT_CTRL_RX_FWM_SHFT) \
+ & FSL_XCVR_EXT_CTRL_RX_FWM_MASK)
+#define FSL_XCVR_EXT_CTRL_PAGE_SHFT 16
+#define FSL_XCVR_EXT_CTRL_PAGE_MASK GENMASK(19, 16)
+#define FSL_XCVR_EXT_CTRL_PAGE(i) (((i) << FSL_XCVR_EXT_CTRL_PAGE_SHFT) \
+ & FSL_XCVR_EXT_CTRL_PAGE_MASK)
+
+#define FSL_XCVR_EXT_STUS_NT_FIFO_ENTR GENMASK(7, 0)
+#define FSL_XCVR_EXT_STUS_NR_FIFO_ENTR GENMASK(15, 8)
+#define FSL_XCVR_EXT_STUS_CM0_SLEEPING BIT(16)
+#define FSL_XCVR_EXT_STUS_CM0_DEEP_SLP BIT(17)
+#define FSL_XCVR_EXT_STUS_CM0_SLP_HACK BIT(18)
+#define FSL_XCVR_EXT_STUS_RX_CMDC_RSTO BIT(23)
+#define FSL_XCVR_EXT_STUS_TX_CMDC_RSTO BIT(24)
+#define FSL_XCVR_EXT_STUS_RX_CMDC_COTO BIT(25)
+#define FSL_XCVR_EXT_STUS_TX_CMDC_COTO BIT(26)
+#define FSL_XCVR_EXT_STUS_HB_STATUS BIT(27)
+#define FSL_XCVR_EXT_STUS_NEW_UD4_REC BIT(28)
+#define FSL_XCVR_EXT_STUS_NEW_UD5_REC BIT(29)
+#define FSL_XCVR_EXT_STUS_NEW_UD6_REC BIT(30)
+#define FSL_XCVR_EXT_STUS_HPD_INPUT BIT(31)
+
+#define FSL_XCVR_IRQ_NEW_CS BIT(0)
+#define FSL_XCVR_IRQ_NEW_UD BIT(1)
+#define FSL_XCVR_IRQ_MUTE BIT(2)
+#define FSL_XCVR_IRQ_CMDC_RESP_TO BIT(3)
+#define FSL_XCVR_IRQ_ECC_ERR BIT(4)
+#define FSL_XCVR_IRQ_PREAMBLE_MISMATCH BIT(5)
+#define FSL_XCVR_IRQ_FIFO_UOFL_ERR BIT(6)
+#define FSL_XCVR_IRQ_HOST_WAKEUP BIT(7)
+#define FSL_XCVR_IRQ_HOST_OHPD BIT(8)
+#define FSL_XCVR_IRQ_DMAC_NO_DATA_REC BIT(9)
+#define FSL_XCVR_IRQ_DMAC_FMT_CHG_DET BIT(10)
+#define FSL_XCVR_IRQ_HB_STATE_CHG BIT(11)
+#define FSL_XCVR_IRQ_CMDC_STATUS_UPD BIT(12)
+#define FSL_XCVR_IRQ_TEMP_UPD BIT(13)
+#define FSL_XCVR_IRQ_DMA_RD_REQ BIT(14)
+#define FSL_XCVR_IRQ_DMA_WR_REQ BIT(15)
+#define FSL_XCVR_IRQ_DMAC_BME_BIT_ERR BIT(16)
+#define FSL_XCVR_IRQ_PREAMBLE_MATCH BIT(17)
+#define FSL_XCVR_IRQ_M_W_PRE_MISMATCH BIT(18)
+#define FSL_XCVR_IRQ_B_PRE_MISMATCH BIT(19)
+#define FSL_XCVR_IRQ_UNEXP_PRE_REC BIT(20)
+#define FSL_XCVR_IRQ_ARC_MODE BIT(21)
+#define FSL_XCVR_IRQ_CH_UD_OFLOW BIT(22)
+#define FSL_XCVR_IRQ_EARC_ALL (FSL_XCVR_IRQ_NEW_CS | \
+ FSL_XCVR_IRQ_NEW_UD | \
+ FSL_XCVR_IRQ_MUTE | \
+ FSL_XCVR_IRQ_FIFO_UOFL_ERR | \
+ FSL_XCVR_IRQ_HOST_WAKEUP | \
+ FSL_XCVR_IRQ_ARC_MODE)
+
+#define FSL_XCVR_ISR_CMDC_TX_EN BIT(3)
+#define FSL_XCVR_ISR_HPD_TGL BIT(15)
+#define FSL_XCVR_ISR_DMAC_SPARE_INT BIT(19)
+#define FSL_XCVR_ISR_SET_SPDIF_RX_INT BIT(20)
+#define FSL_XCVR_ISR_SET_SPDIF_TX_INT BIT(21)
+#define FSL_XCVR_ISR_SET_SPDIF_MODE(t) (t ? BIT(21) : BIT(20))
+#define FSL_XCVR_ISR_SET_ARC_CM_INT BIT(22)
+#define FSL_XCVR_ISR_SET_ARC_SE_INT BIT(23)
+
+#define FSL_XCVR_PHY_AI_ADDR_MASK GENMASK(7, 0)
+#define FSL_XCVR_PHY_AI_RESETN BIT(15)
+#define FSL_XCVR_PHY_AI_TOG_PLL BIT(24)
+#define FSL_XCVR_PHY_AI_TOG_DONE_PLL BIT(25)
+#define FSL_XCVR_PHY_AI_TOG_PHY BIT(26)
+#define FSL_XCVR_PHY_AI_TOG_DONE_PHY BIT(27)
+#define FSL_XCVR_PHY_AI_RW_MASK BIT(31)
+
+#define FSL_XCVR_RX_DPTH_CTRL_PAPB_FIFO_STATUS BIT(0)
+#define FSL_XCVR_RX_DPTH_CTRL_DIS_PRE_ERR_CHK BIT(1)
+#define FSL_XCVR_RX_DPTH_CTRL_DIS_NOD_REC_CHK BIT(2)
+#define FSL_XCVR_RX_DPTH_CTRL_ECC_VUC_BIT_CHK BIT(3)
+#define FSL_XCVR_RX_DPTH_CTRL_EN_CMP_PAR_CALC BIT(4)
+#define FSL_XCVR_RX_DPTH_CTRL_RST_PKT_CNT_FIFO BIT(5)
+#define FSL_XCVR_RX_DPTH_CTRL_STORE_FMT BIT(6)
+#define FSL_XCVR_RX_DPTH_CTRL_EN_PAR_CALC BIT(7)
+#define FSL_XCVR_RX_DPTH_CTRL_UDR BIT(8)
+#define FSL_XCVR_RX_DPTH_CTRL_CSR BIT(9)
+#define FSL_XCVR_RX_DPTH_CTRL_UDA BIT(10)
+#define FSL_XCVR_RX_DPTH_CTRL_CSA BIT(11)
+#define FSL_XCVR_RX_DPTH_CTRL_CLR_RX_FIFO BIT(12)
+#define FSL_XCVR_RX_DPTH_CTRL_DIS_B_PRE_ERR_CHK BIT(13)
+#define FSL_XCVR_RX_DPTH_CTRL_PABS BIT(19)
+#define FSL_XCVR_RX_DPTH_CTRL_DTS_CDS BIT(20)
+#define FSL_XCVR_RX_DPTH_CTRL_BLKC BIT(21)
+#define FSL_XCVR_RX_DPTH_CTRL_MUTE_CTRL BIT(22)
+#define FSL_XCVR_RX_DPTH_CTRL_MUTE_MODE BIT(23)
+#define FSL_XCVR_RX_DPTH_CTRL_FMT_CHG_CTRL BIT(24)
+#define FSL_XCVR_RX_DPTH_CTRL_FMT_CHG_MODE BIT(25)
+#define FSL_XCVR_RX_DPTH_CTRL_LAYB_CTRL BIT(26)
+#define FSL_XCVR_RX_DPTH_CTRL_LAYB_MODE BIT(27)
+#define FSL_XCVR_RX_DPTH_CTRL_PRC BIT(28)
+#define FSL_XCVR_RX_DPTH_CTRL_COMP BIT(29)
+#define FSL_XCVR_RX_DPTH_CTRL_FSM GENMASK(31, 30)
+
+#define FSL_XCVR_TX_DPTH_CTRL_CS_ACK BIT(0)
+#define FSL_XCVR_TX_DPTH_CTRL_UD_ACK BIT(1)
+#define FSL_XCVR_TX_DPTH_CTRL_CS_MOD BIT(2)
+#define FSL_XCVR_TX_DPTH_CTRL_UD_MOD BIT(3)
+#define FSL_XCVR_TX_DPTH_CTRL_VLD_MOD BIT(4)
+#define FSL_XCVR_TX_DPTH_CTRL_FRM_VLD BIT(5)
+#define FSL_XCVR_TX_DPTH_CTRL_EN_PARITY BIT(6)
+#define FSL_XCVR_TX_DPTH_CTRL_EN_PREAMBLE BIT(7)
+#define FSL_XCVR_TX_DPTH_CTRL_EN_ECC_INTER BIT(8)
+#define FSL_XCVR_TX_DPTH_CTRL_BYPASS_FEM BIT(10)
+#define FSL_XCVR_TX_DPTH_CTRL_FRM_FMT BIT(11)
+#define FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX BIT(14)
+#define FSL_XCVR_TX_DPTH_CTRL_ADD_CYC_TX_OE_STR BIT(15)
+#define FSL_XCVR_TX_DPTH_CTRL_ADD_CYC_TX_OE_END BIT(16)
+#define FSL_XCVR_TX_DPTH_CTRL_CLK_RATIO BIT(29)
+#define FSL_XCVR_TX_DPTH_CTRL_TM_NO_PRE_BME GENMASK(31, 30)
+
+#define FSL_XCVR_PHY_AI_CTRL_AI_RESETN BIT(15)
+
+#define FSL_XCVR_PLL_CTRL0 0x00
+#define FSL_XCVR_PLL_CTRL0_SET 0x04
+#define FSL_XCVR_PLL_CTRL0_CLR 0x08
+#define FSL_XCVR_PLL_NUM 0x20
+#define FSL_XCVR_PLL_DEN 0x30
+#define FSL_XCVR_PLL_PDIV 0x40
+#define FSL_XCVR_PLL_BANDGAP_SET 0x54
+#define FSL_XCVR_PHY_CTRL 0x00
+#define FSL_XCVR_PHY_CTRL_SET 0x04
+#define FSL_XCVR_PHY_CTRL_CLR 0x08
+#define FSL_XCVR_PHY_CTRL2 0x70
+#define FSL_XCVR_PHY_CTRL2_SET 0x74
+#define FSL_XCVR_PHY_CTRL2_CLR 0x78
+
+#define FSL_XCVR_PLL_BANDGAP_EN_VBG BIT(0)
+#define FSL_XCVR_PLL_CTRL0_HROFF BIT(13)
+#define FSL_XCVR_PLL_CTRL0_PWP BIT(14)
+#define FSL_XCVR_PLL_CTRL0_CM0_EN BIT(24)
+#define FSL_XCVR_PLL_CTRL0_CM1_EN BIT(25)
+#define FSL_XCVR_PLL_CTRL0_CM2_EN BIT(26)
+#define FSL_XCVR_PLL_PDIVx(v, i) ((v & 0x7) << (4 * i))
+
+#define FSL_XCVR_PHY_CTRL_PHY_EN BIT(0)
+#define FSL_XCVR_PHY_CTRL_RX_CM_EN BIT(1)
+#define FSL_XCVR_PHY_CTRL_TSDIFF_OE BIT(5)
+#define FSL_XCVR_PHY_CTRL_SPDIF_EN BIT(8)
+#define FSL_XCVR_PHY_CTRL_ARC_MODE_SE_EN BIT(9)
+#define FSL_XCVR_PHY_CTRL_ARC_MODE_CM_EN BIT(10)
+#define FSL_XCVR_PHY_CTRL_TX_CLK_MASK GENMASK(26, 25)
+#define FSL_XCVR_PHY_CTRL_TX_CLK_HDMI_SS BIT(25)
+#define FSL_XCVR_PHY_CTRL_TX_CLK_AUD_SS BIT(26)
+#define FSL_XCVR_PHY_CTRL2_EARC_TXMS BIT(14)
+
+#define FSL_XCVR_CS_DATA_0_FS_MASK GENMASK(31, 24)
+#define FSL_XCVR_CS_DATA_0_FS_32000 0x3000000
+#define FSL_XCVR_CS_DATA_0_FS_44100 0x0000000
+#define FSL_XCVR_CS_DATA_0_FS_48000 0x2000000
+#define FSL_XCVR_CS_DATA_0_FS_64000 0xB000000
+#define FSL_XCVR_CS_DATA_0_FS_88200 0x8000000
+#define FSL_XCVR_CS_DATA_0_FS_96000 0xA000000
+#define FSL_XCVR_CS_DATA_0_FS_176400 0xC000000
+#define FSL_XCVR_CS_DATA_0_FS_192000 0xE000000
+
+#define FSL_XCVR_CS_DATA_0_CH_MASK 0x3A
+#define FSL_XCVR_CS_DATA_0_CH_U2LPCM 0x00
+#define FSL_XCVR_CS_DATA_0_CH_UMLPCM 0x20
+#define FSL_XCVR_CS_DATA_0_CH_U1BAUD 0x30
+
+#define FSL_XCVR_CS_DATA_1_CH_MASK 0xF000
+#define FSL_XCVR_CS_DATA_1_CH_2 0x0000
+#define FSL_XCVR_CS_DATA_1_CH_8 0x7000
+#define FSL_XCVR_CS_DATA_1_CH_16 0xB000
+#define FSL_XCVR_CS_DATA_1_CH_32 0x3000
+
+/* Data memory structures */
+#define FSL_XCVR_RX_CS_CTRL_0 0x20 /* First RX CS control register */
+#define FSL_XCVR_RX_CS_CTRL_1 0x24 /* Second RX CS control register */
+#define FSL_XCVR_RX_CS_BUFF_0 0x80 /* First RX CS buffer */
+#define FSL_XCVR_RX_CS_BUFF_1 0xA0 /* Second RX CS buffer */
+#define FSL_XCVR_CAP_DATA_STR 0x300 /* Capabilities data structure */
+
+#endif /* __FSL_XCVR_H */
--
2.7.4

2020-09-17 07:19:44

by Nicolin Chen

[permalink] [raw]
Subject: Re: [PATCH 1/2] ASoC: fsl_xcvr: Add XCVR ASoC CPU DAI driver

Hi Viorel,

It looks pretty clean to me, though some small comments inline.

On Wed, Sep 16, 2020 at 12:17:55PM +0300, Viorel Suman (OSS) wrote:
> From: Viorel Suman <[email protected]>
>
> XCVR (Audio Transceiver) is a on-chip functional module found
> on i.MX8MP. It support HDMI2.1 eARC, HDMI1.4 ARC and SPDIF.
>
> Signed-off-by: Viorel Suman <[email protected]>

> +static const u32 fsl_xcvr_earc_channels[] = { 1, 2, 8, 16, 32, }; /* one bit 6, 12 ? */

What's the meaning of the comments?

> +static const int fsl_xcvr_phy_arc_cfg[] = {
> + FSL_XCVR_PHY_CTRL_ARC_MODE_SE_EN, FSL_XCVR_PHY_CTRL_ARC_MODE_CM_EN,
> +};

Nit: better be u32 vs. int?

> +/** phy: true => phy, false => pll */
> +static int fsl_xcvr_ai_write(struct fsl_xcvr *xcvr, u8 reg, u32 data, bool phy)
> +{
> + u32 val, idx, tidx;
> +
> + idx = BIT(phy ? 26 : 24);
> + tidx = BIT(phy ? 27 : 25);
> +
> + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_CLR, 0xFF);
> + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_SET, reg);
> + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_WDATA, data);
> + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_TOG, idx);
> +
> + do {
> + regmap_read(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL, &val);
> + } while ((val & idx) != ((val & tidx) >> 1));

Might regmap_read_poll_timeout() be better? And it seems to poll
intentionally with no sleep nor timeout -- would be nice to have
a line of comments to explain why.

> > +static int fsl_xcvr_runtime_resume(struct device *dev)
> +{
> + struct fsl_xcvr *xcvr = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = clk_prepare_enable(xcvr->ipg_clk);
> + if (ret) {
> + dev_err(dev, "failed to start IPG clock.\n");
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(xcvr->pll_ipg_clk);
> + if (ret) {
> + dev_err(dev, "failed to start PLL IPG clock.\n");

Should it disable ipg_clk?

> + return ret;
> + }
> +
> + ret = clk_prepare_enable(xcvr->phy_clk);
> + if (ret) {
> + dev_err(dev, "failed to start PHY clock: %d\n", ret);
> + clk_disable_unprepare(xcvr->ipg_clk);

Should it disable pll_ipg_clk?

> + return ret;
> + }
> +
> + ret = clk_prepare_enable(xcvr->spba_clk);
> + if (ret) {
> + dev_err(dev, "failed to start SPBA clock.\n");
> + clk_disable_unprepare(xcvr->phy_clk);
> + clk_disable_unprepare(xcvr->ipg_clk);

Ditto

> + return ret;
> + }
> +
> + regcache_cache_only(xcvr->regmap, false);
> + regcache_mark_dirty(xcvr->regmap);
> + ret = regcache_sync(xcvr->regmap);
> +
> + if (ret) {
> + dev_err(dev, "failed to sync regcache.\n");
> + return ret;

What about those clocks? Probably better to have some error-out
labels at the end of the function?

> + }
> +
> + reset_control_assert(xcvr->reset);
> + reset_control_deassert(xcvr->reset);
> +
> + ret = fsl_xcvr_load_firmware(xcvr);
> + if (ret) {
> + dev_err(dev, "failed to load firmware.\n");
> + return ret;

Ditto

> + }
> +
> + /* Release M0+ reset */
> + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
> + FSL_XCVR_EXT_CTRL_CORE_RESET, 0);
> + if (ret < 0) {
> + dev_err(dev, "M0+ core release failed: %d\n", ret);
> + return ret;

Ditto

> + }
> + mdelay(50);

Any reason to use mdelay over msleep for a 50ms wait? May add a
line of comments if mdelay is a must?

2020-09-17 14:00:27

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 1/2] ASoC: fsl_xcvr: Add XCVR ASoC CPU DAI driver

On Wed, Sep 16, 2020 at 12:17:55PM +0300, Viorel Suman (OSS) wrote:

This looks mostly good, a few smallish things below but nothing major.

> +static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr)
> +{
> + struct device *dev = &xcvr->pdev->dev;
> + const struct firmware *fw;
> + int ret = 0, rem, off, out, page = 0, size = FSL_XCVR_REG_OFFSET;
> + u32 mask, val;
> +
> + ret = request_firmware(&fw, xcvr->fw_name, dev);
> + if (ret) {
> + dev_err(dev, "failed to request firmware.\n");
> + return ret;
> + }
> +
> + rem = fw->size;

It would be good to see some explicit validation of the image size, at
least printing an error message if the image is bigger than can be
loaded. The code should be safe in that it won't overflow the device
region it's writing to but it feels like it'd be better to tell people
if we spot a problem rather than just silently truncating the file.

> + /* RAM is 20KiB => max 10 pages 2KiB each */
> + for (page = 0; page < 10; page++) {
> + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
> + FSL_XCVR_EXT_CTRL_PAGE_MASK,
> + FSL_XCVR_EXT_CTRL_PAGE(page));

regmap does have paging support, though given that this is currently the
only place where paging is used this probably doesn't matter too much.

> +static irqreturn_t irq0_isr(int irq, void *devid)
> +{
> + struct fsl_xcvr *xcvr = (struct fsl_xcvr *)devid;
> + struct device *dev = &xcvr->pdev->dev;
> + struct regmap *regmap = xcvr->regmap;
> + void __iomem *reg_ctrl, *reg_buff;
> + u32 isr, val, i;
> +
> + regmap_read(regmap, FSL_XCVR_EXT_ISR, &isr);
> + regmap_write(regmap, FSL_XCVR_EXT_ISR_CLR, isr);

This will unconditionally clear any interrupts, even those we don't
understand - it might be better to only clear bits that are supported so
the IRQ core can complain if there's something unexpected showing up.

> + if (isr & FSL_XCVR_IRQ_FIFO_UOFL_ERR)
> + dev_dbg(dev, "RX/TX FIFO full/empty\n");

Should this be dev_err()?

> +static irqreturn_t irq1_isr(int irq, void *devid)
> +{
> + struct fsl_xcvr *xcvr = (struct fsl_xcvr *)devid;
> + struct device *dev = &xcvr->pdev->dev;
> +
> + dev_dbg(dev, "irq[1]: %d\n", irq);
> +
> + return IRQ_HANDLED;
> +}

Is there any value in even requesting this and irq2 given the lack of
meaningful handling?


Attachments:
(No filename) (2.29 kB)
signature.asc (499.00 B)
Download all attachments

2020-09-18 14:25:01

by Viorel Suman (OSS)

[permalink] [raw]
Subject: RE: [PATCH 1/2] ASoC: fsl_xcvr: Add XCVR ASoC CPU DAI driver

Hi Nicolin,

Thank you for your review.

> > +static const u32 fsl_xcvr_earc_channels[] = { 1, 2, 8, 16, 32, }; /*
> > +one bit 6, 12 ? */
>
> What's the meaning of the comments?

Just a thought noted as comment. HDMI2.1 spec defines 6- and 12-channels layout when
one bit audio stream is transmitted - I was wandering how can this be enforced. Is a @todo like of comment.

>
> > +static const int fsl_xcvr_phy_arc_cfg[] = {
> > + FSL_XCVR_PHY_CTRL_ARC_MODE_SE_EN,
> FSL_XCVR_PHY_CTRL_ARC_MODE_CM_EN,
> > +};
>
> Nit: better be u32 vs. int?

Yes, will fix it in v2.

>
> > +/** phy: true => phy, false => pll */ static int
> > +fsl_xcvr_ai_write(struct fsl_xcvr *xcvr, u8 reg, u32 data, bool phy)
> > +{
> > + u32 val, idx, tidx;
> > +
> > + idx = BIT(phy ? 26 : 24);
> > + tidx = BIT(phy ? 27 : 25);
> > +
> > + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_CLR, 0xFF);
> > + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_SET, reg);
> > + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_WDATA, data);
> > + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_TOG, idx);
> > +
> > + do {
> > + regmap_read(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL, &val);
> > + } while ((val & idx) != ((val & tidx) >> 1));
>
> Might regmap_read_poll_timeout() be better? And it seems to poll intentionally
> with no sleep nor timeout -- would be nice to have a line of comments to explain
> why.

No particular reason to do it with no sleep or timeout here, will check and fix it in v2.

>
> > > +static int fsl_xcvr_runtime_resume(struct device *dev)
> > +{
> > + struct fsl_xcvr *xcvr = dev_get_drvdata(dev);
> > + int ret;
> > +
> > + ret = clk_prepare_enable(xcvr->ipg_clk);
> > + if (ret) {
> > + dev_err(dev, "failed to start IPG clock.\n");
> > + return ret;
> > + }
> > +
> > + ret = clk_prepare_enable(xcvr->pll_ipg_clk);
> > + if (ret) {
> > + dev_err(dev, "failed to start PLL IPG clock.\n");
>
> Should it disable ipg_clk?

Yes, thank you, will fix in v2.

>
> > + return ret;
> > + }
> > +
> > + ret = clk_prepare_enable(xcvr->phy_clk);
> > + if (ret) {
> > + dev_err(dev, "failed to start PHY clock: %d\n", ret);
> > + clk_disable_unprepare(xcvr->ipg_clk);
>
> Should it disable pll_ipg_clk?

Yes, will fix in v2.

>
> > + return ret;
> > + }
> > +
> > + ret = clk_prepare_enable(xcvr->spba_clk);
> > + if (ret) {
> > + dev_err(dev, "failed to start SPBA clock.\n");
> > + clk_disable_unprepare(xcvr->phy_clk);
> > + clk_disable_unprepare(xcvr->ipg_clk);
>
> Ditto

Ok.

>
> > + return ret;
> > + }
> > +
> > + regcache_cache_only(xcvr->regmap, false);
> > + regcache_mark_dirty(xcvr->regmap);
> > + ret = regcache_sync(xcvr->regmap);
> > +
> > + if (ret) {
> > + dev_err(dev, "failed to sync regcache.\n");
> > + return ret;
>
> What about those clocks? Probably better to have some error-out labels at the
> end of the function?

Make sense, will fix in v2.

>
> > + }
> > +
> > + reset_control_assert(xcvr->reset);
> > + reset_control_deassert(xcvr->reset);
> > +
> > + ret = fsl_xcvr_load_firmware(xcvr);
> > + if (ret) {
> > + dev_err(dev, "failed to load firmware.\n");
> > + return ret;
>
> Ditto
>
> > + }
> > +
> > + /* Release M0+ reset */
> > + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
> > + FSL_XCVR_EXT_CTRL_CORE_RESET, 0);
> > + if (ret < 0) {
> > + dev_err(dev, "M0+ core release failed: %d\n", ret);
> > + return ret;
>
> Ditto
>
> > + }
> > + mdelay(50);
>
> Any reason to use mdelay over msleep for a 50ms wait? May add a line of
> comments if mdelay is a must?

No particular reason, will fix it in v2.

Thank you,
Viorel

2020-09-18 15:06:38

by Viorel Suman (OSS)

[permalink] [raw]
Subject: RE: [PATCH 1/2] ASoC: fsl_xcvr: Add XCVR ASoC CPU DAI driver

Hi Mark,

Thank you for your review.

> On Wed, Sep 16, 2020 at 12:17:55PM +0300, Viorel Suman (OSS) wrote:
> > +static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) {
> > + struct device *dev = &xcvr->pdev->dev;
> > + const struct firmware *fw;
> > + int ret = 0, rem, off, out, page = 0, size = FSL_XCVR_REG_OFFSET;
> > + u32 mask, val;
> > +
> > + ret = request_firmware(&fw, xcvr->fw_name, dev);
> > + if (ret) {
> > + dev_err(dev, "failed to request firmware.\n");
> > + return ret;
> > + }
> > +
> > + rem = fw->size;
>
> It would be good to see some explicit validation of the image size, at least
> printing an error message if the image is bigger than can be loaded. The code
> should be safe in that it won't overflow the device region it's writing to but it
> feels like it'd be better to tell people if we spot a problem rather than just silently
> truncating the file.

Make sense, will improve this part in the next version.

> > +static irqreturn_t irq0_isr(int irq, void *devid) {
> > + struct fsl_xcvr *xcvr = (struct fsl_xcvr *)devid;
> > + struct device *dev = &xcvr->pdev->dev;
> > + struct regmap *regmap = xcvr->regmap;
> > + void __iomem *reg_ctrl, *reg_buff;
> > + u32 isr, val, i;
> > +
> > + regmap_read(regmap, FSL_XCVR_EXT_ISR, &isr);
> > + regmap_write(regmap, FSL_XCVR_EXT_ISR_CLR, isr);
>
> This will unconditionally clear any interrupts, even those we don't understand - it
> might be better to only clear bits that are supported so the IRQ core can
> complain if there's something unexpected showing up.

The ARM core registers itself in "fsl_xcvr_prepare" (the code below) just for a subset of all supported interrupts:
=====
ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0,
FSL_XCVR_IRQ_EARC_ALL, FSL_XCVR_IRQ_EARC_ALL);
=====
FSL_XCVR_IRQ_EARC_ALL - this mask represents all the interrupts we are interested in and we handle in interrupt handler,
But this is just a subset of all interrupts the M0+ core is able to assert. Not very intuitive, I think I need to reword it somehow.

> > + if (isr & FSL_XCVR_IRQ_FIFO_UOFL_ERR)
> > + dev_dbg(dev, "RX/TX FIFO full/empty\n");
>
> Should this be dev_err()?

The interrupt may be asserted right before DMA starts to fill the TX FIFO if I recall correctly.
I've added it just to debug the IP behavior, will check and change it to err it in next version if it is the case.

> > +static irqreturn_t irq1_isr(int irq, void *devid) {
> > + struct fsl_xcvr *xcvr = (struct fsl_xcvr *)devid;
> > + struct device *dev = &xcvr->pdev->dev;
> > +
> > + dev_dbg(dev, "irq[1]: %d\n", irq);
> > +
> > + return IRQ_HANDLED;
> > +}
>
> Is there any value in even requesting this and irq2 given the lack of meaningful
> handling?

No, will remove it in v2.

Thank you,
Viorel

2020-09-18 15:22:38

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 1/2] ASoC: fsl_xcvr: Add XCVR ASoC CPU DAI driver

On Fri, Sep 18, 2020 at 03:02:39PM +0000, Viorel Suman (OSS) wrote:

Please fix your mail client to word wrap within paragraphs at something
substantially less than 80 columns. Doing this makes your messages much
easier to read and reply to.

> > > + regmap_read(regmap, FSL_XCVR_EXT_ISR, &isr);
> > > + regmap_write(regmap, FSL_XCVR_EXT_ISR_CLR, isr);

> > This will unconditionally clear any interrupts, even those we don't understand - it
> > might be better to only clear bits that are supported so the IRQ core can
> > complain if there's something unexpected showing up.

> The ARM core registers itself in "fsl_xcvr_prepare" (the code below) just for a subset of all supported interrupts:
> =====
> ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0,
> FSL_XCVR_IRQ_EARC_ALL, FSL_XCVR_IRQ_EARC_ALL);
> =====
> FSL_XCVR_IRQ_EARC_ALL - this mask represents all the interrupts we are interested in and we handle in interrupt handler,
> But this is just a subset of all interrupts the M0+ core is able to assert. Not very intuitive, I think I need to reword it somehow.

That's not the issue, the issue is that if we get into the ISR we just
ack all the bits that are flagged by the hardware regardless of if we
actually handled them. This won't work if there are ever systems that
share the interrupt and it works against safety/debugging features that
the interrupt has in case something goes wrong and we get spurious
interrupts.

> > > + if (isr & FSL_XCVR_IRQ_FIFO_UOFL_ERR)
> > > + dev_dbg(dev, "RX/TX FIFO full/empty\n");

> > Should this be dev_err()?

> The interrupt may be asserted right before DMA starts to fill the TX FIFO if I recall correctly.
> I've added it just to debug the IP behavior, will check and change it to err it in next version if it is the case.

If it does come up normally then a comment or something to explain why
this happens normally would probably be good.


Attachments:
(No filename) (1.91 kB)
signature.asc (499.00 B)
Download all attachments

2020-09-18 15:36:52

by Viorel Suman (OSS)

[permalink] [raw]
Subject: RE: [PATCH 1/2] ASoC: fsl_xcvr: Add XCVR ASoC CPU DAI driver

> On Fri, Sep 18, 2020 at 03:02:39PM +0000, Viorel Suman (OSS) wrote:
>
> Please fix your mail client to word wrap within paragraphs at something
> substantially less than 80 columns. Doing this makes your messages much easier
> to read and reply to.
>

My bad, will do.

> > > > + regmap_read(regmap, FSL_XCVR_EXT_ISR, &isr);
> > > > + regmap_write(regmap, FSL_XCVR_EXT_ISR_CLR, isr);
>
> > > This will unconditionally clear any interrupts, even those we don't
> > > understand - it might be better to only clear bits that are
> > > supported so the IRQ core can complain if there's something unexpected
> showing up.
>
> > The ARM core registers itself in "fsl_xcvr_prepare" (the code below) just for a
> subset of all supported interrupts:
> > =====
> > ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0,
> > FSL_XCVR_IRQ_EARC_ALL,
> FSL_XCVR_IRQ_EARC_ALL); =====
> > FSL_XCVR_IRQ_EARC_ALL - this mask represents all the interrupts we are
> > interested in and we handle in interrupt handler, But this is just a subset of all
> interrupts the M0+ core is able to assert. Not very intuitive, I think I need to
> reword it somehow.
>
> That's not the issue, the issue is that if we get into the ISR we just ack all the bits
> that are flagged by the hardware regardless of if we actually handled them. This
> won't work if there are ever systems that share the interrupt and it works
> against safety/debugging features that the interrupt has in case something goes
> wrong and we get spurious interrupts.
>

Thank you for explanation, will fix it in next version.

> > > > + if (isr & FSL_XCVR_IRQ_FIFO_UOFL_ERR)
> > > > + dev_dbg(dev, "RX/TX FIFO full/empty\n");
>
> > > Should this be dev_err()?
>
> > The interrupt may be asserted right before DMA starts to fill the TX FIFO if I
> recall correctly.
> > I've added it just to debug the IP behavior, will check and change it to err it in
> next version if it is the case.
>
> If it does come up normally then a comment or something to explain why this
> happens normally would probably be good.

Sure, ok.

2020-09-18 16:10:32

by Timur Tabi

[permalink] [raw]
Subject: Re: [PATCH 1/2] ASoC: fsl_xcvr: Add XCVR ASoC CPU DAI driver

On 9/18/20 9:21 AM, Viorel Suman (OSS) wrote:
>>> +static const u32 fsl_xcvr_earc_channels[] = { 1, 2, 8, 16, 32, }; /*
>>> +one bit 6, 12 ? */
>> What's the meaning of the comments?
> Just a thought noted as comment. HDMI2.1 spec defines 6- and 12-channels layout when
> one bit audio stream is transmitted - I was wandering how can this be enforced. Is a @todo like of comment.

Please don't add comments that other developers could never understand.

The text that you just wrote here would be a great starting point for a
better comment.

2020-09-18 17:26:34

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH 2/2] ASoC: dt-bindings: fsl_xcvr: Add document for XCVR

On Wed, 16 Sep 2020 12:17:56 +0300, Viorel Suman (OSS) wrote:
> From: Viorel Suman <[email protected]>
>
> XCVR (Audio Transceiver) is a new IP module found on i.MX8MP.
>
> Signed-off-by: Viorel Suman <[email protected]>
> ---
> .../devicetree/bindings/sound/fsl,xcvr.yaml | 104 +++++++++++++++++++++
> 1 file changed, 104 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/sound/fsl,xcvr.yaml
>


My bot found errors running 'make dt_binding_check' on your patch:

Error: Documentation/devicetree/bindings/sound/fsl,xcvr.example.dts:29.30-31 syntax error
FATAL ERROR: Unable to parse input tree
make[1]: *** [scripts/Makefile.lib:342: Documentation/devicetree/bindings/sound/fsl,xcvr.example.dt.yaml] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:1366: dt_binding_check] Error 2


See https://patchwork.ozlabs.org/patch/1365072

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure dt-schema is up to date:

pip3 install git+https://github.com/devicetree-org/dt-schema.git@master --upgrade

Please check and re-submit.