The HDMI TX Parallel Audio Interface (HTX_PAI) is a digital module that
acts as the bridge between the Audio Subsystem to the HDMI TX Controller.
This IP block is found in the HDMI subsystem of the i.MX8MP SoC.
Data received from the audio subsystem can have an arbitrary component
ordering. The HTX_PAI block has integrated muxing options to select which
sections of the 32-bit input data word will be mapped to each IEC60958
field. The HTX_PAI_FIELD_CTRL register contains mux selects to
individually select P,C,U,V,Data, and Preamble.
Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
---
drivers/gpu/drm/bridge/imx/Kconfig | 7 +
drivers/gpu/drm/bridge/imx/Makefile | 1 +
drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c | 134 +++++++++++++++++++
include/drm/bridge/dw_hdmi.h | 6 +
4 files changed, 148 insertions(+)
create mode 100644 drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c
diff --git a/drivers/gpu/drm/bridge/imx/Kconfig b/drivers/gpu/drm/bridge/imx/Kconfig
index 9a480c6abb85..d95fa84a8dcd 100644
--- a/drivers/gpu/drm/bridge/imx/Kconfig
+++ b/drivers/gpu/drm/bridge/imx/Kconfig
@@ -24,6 +24,13 @@ config DRM_IMX8MP_DW_HDMI_BRIDGE
Choose this to enable support for the internal HDMI encoder found
on the i.MX8MP SoC.
+config DRM_IMX8MP_HDMI_PAI
+ tristate "Freescale i.MX8MP HDMI PAI bridge support"
+ depends on OF
+ help
+ Choose this to enable support for the internal HDMI TX Parallel
+ Audio Interface found on the Freescale i.MX8MP SoC.
+
config DRM_IMX8MP_HDMI_PVI
tristate "Freescale i.MX8MP HDMI PVI bridge support"
depends on OF
diff --git a/drivers/gpu/drm/bridge/imx/Makefile b/drivers/gpu/drm/bridge/imx/Makefile
index dd5d48584806..8d01fda25451 100644
--- a/drivers/gpu/drm/bridge/imx/Makefile
+++ b/drivers/gpu/drm/bridge/imx/Makefile
@@ -1,6 +1,7 @@
obj-$(CONFIG_DRM_IMX_LDB_HELPER) += imx-ldb-helper.o
obj-$(CONFIG_DRM_IMX_LEGACY_BRIDGE) += imx-legacy-bridge.o
obj-$(CONFIG_DRM_IMX8MP_DW_HDMI_BRIDGE) += imx8mp-hdmi-tx.o
+obj-$(CONFIG_DRM_IMX8MP_HDMI_PAI) += imx8mp-hdmi-pai.o
obj-$(CONFIG_DRM_IMX8MP_HDMI_PVI) += imx8mp-hdmi-pvi.o
obj-$(CONFIG_DRM_IMX8QM_LDB) += imx8qm-ldb.o
obj-$(CONFIG_DRM_IMX8QXP_LDB) += imx8qxp-ldb.o
diff --git a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c
new file mode 100644
index 000000000000..f09ee2622e57
--- /dev/null
+++ b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2025 NXP
+ */
+
+#include <drm/bridge/dw_hdmi.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+#define HTX_PAI_CTRL 0x00
+#define HTX_PAI_CTRL_EXT 0x04
+#define HTX_PAI_FIELD_CTRL 0x08
+#define HTX_PAI_STAT 0x0c
+#define HTX_PAI_IRQ_NOMASK 0x10
+#define HTX_PAI_IRQ_MASKED 0x14
+#define HTX_PAI_IRQ_MASK 0x18
+
+#define CTRL_ENABLE BIT(0)
+
+#define CTRL_EXT_WTMK_HIGH_MASK GENMASK(31, 24)
+#define CTRL_EXT_WTMK_HIGH (0x3 << 24)
+#define CTRL_EXT_WTMK_LOW_MASK GENMASK(23, 16)
+#define CTRL_EXT_WTMK_LOW (0x3 << 16)
+#define CTRL_EXT_NUM_CH_MASK GENMASK(10, 8)
+#define CTRL_EXT_NUM_CH_SHIFT 8
+
+#define FIELD_CTRL_B_FILT BIT(31)
+#define FIELD_CTRL_PARITY_EN BIT(30)
+#define FIELD_CTRL_END_SEL BIT(29)
+#define FIELD_CTRL_PRE_SEL GENMASK(28, 24)
+#define FIELD_CTRL_PRE_SEL_SHIFT 24
+#define FIELD_CTRL_D_SEL GENMASK(23, 20)
+#define FIELD_CTRL_D_SEL_SHIFT 20
+#define FIELD_CTRL_V_SEL GENMASK(19, 15)
+#define FIELD_CTRL_V_SEL_SHIFT 15
+#define FIELD_CTRL_U_SEL GENMASK(14, 10)
+#define FIELD_CTRL_U_SEL_SHIFT 10
+#define FIELD_CTRL_C_SEL GENMASK(9, 5)
+#define FIELD_CTRL_C_SEL_SHIFT 5
+#define FIELD_CTRL_P_SEL GENMASK(4, 0)
+#define FIELD_CTRL_P_SEL_SHIFT 0
+
+struct imx8mp_hdmi_pai {
+ struct device *dev;
+ void __iomem *base;
+};
+
+static void imx8mp_hdmi_pai_enable(struct dw_hdmi *dw_hdmi, int channel,
+ int width, int rate, int non_pcm)
+{
+ const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi);
+ struct imx8mp_hdmi_pai *hdmi_pai = (struct imx8mp_hdmi_pai *)pdata->priv_audio;
+ int val;
+
+ /* PAI set */
+ val = CTRL_EXT_WTMK_HIGH | CTRL_EXT_WTMK_LOW;
+ val |= ((channel - 1) << CTRL_EXT_NUM_CH_SHIFT);
+ writel(val, hdmi_pai->base + HTX_PAI_CTRL_EXT);
+
+ /* IEC60958 format */
+ val = 31 << FIELD_CTRL_P_SEL_SHIFT;
+ val |= 30 << FIELD_CTRL_C_SEL_SHIFT;
+ val |= 29 << FIELD_CTRL_U_SEL_SHIFT;
+ val |= 28 << FIELD_CTRL_V_SEL_SHIFT;
+ val |= 4 << FIELD_CTRL_D_SEL_SHIFT;
+ val |= 0 << FIELD_CTRL_PRE_SEL_SHIFT;
+
+ writel(val, hdmi_pai->base + HTX_PAI_FIELD_CTRL);
+ /* PAI start running */
+ writel(CTRL_ENABLE, hdmi_pai->base + HTX_PAI_CTRL);
+}
+
+static void imx8mp_hdmi_pai_disable(struct dw_hdmi *dw_hdmi)
+{
+ const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi);
+ struct imx8mp_hdmi_pai *hdmi_pai = (struct imx8mp_hdmi_pai *)pdata->priv_audio;
+
+ /* Stop PAI */
+ writel(0, hdmi_pai->base + HTX_PAI_CTRL);
+}
+
+static int imx8mp_hdmi_pai_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dw_hdmi_plat_data *plat_data;
+ struct imx8mp_hdmi_pai *hdmi_pai;
+ struct device_node *remote;
+ struct platform_device *hdmi_tx;
+ struct resource *res;
+
+ hdmi_pai = devm_kzalloc(dev, sizeof(*hdmi_pai), GFP_KERNEL);
+ if (!hdmi_pai)
+ return -ENOMEM;
+
+ hdmi_pai->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(hdmi_pai->base))
+ return PTR_ERR(hdmi_pai->base);
+
+ hdmi_pai->dev = dev;
+
+ remote = of_graph_get_remote_node(pdev->dev.of_node, 0, -1);
+ if (!remote)
+ return -EINVAL;
+
+ hdmi_tx = of_find_device_by_node(remote);
+ if (!hdmi_tx)
+ return -EINVAL;
+
+ plat_data = platform_get_drvdata(hdmi_tx);
+ plat_data->enable_audio = imx8mp_hdmi_pai_enable;
+ plat_data->disable_audio = imx8mp_hdmi_pai_disable;
+ plat_data->priv_audio = hdmi_pai;
+
+ return 0;
+}
+
+static const struct of_device_id imx8mp_hdmi_pai_of_table[] = {
+ { .compatible = "fsl,imx8mp-hdmi-pai" },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx8mp_hdmi_pai_of_table);
+
+static struct platform_driver imx8mp_hdmi_pai_platform_driver = {
+ .probe = imx8mp_hdmi_pai_probe,
+ .driver = {
+ .name = "imx8mp-hdmi-pai",
+ .of_match_table = imx8mp_hdmi_pai_of_table,
+ },
+};
+module_platform_driver(imx8mp_hdmi_pai_platform_driver);
+
+MODULE_DESCRIPTION("i.MX8MP HDMI PAI driver");
+MODULE_LICENSE("GPL");
diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h
index a56a3519a22a..9ca70ce80cc5 100644
--- a/include/drm/bridge/dw_hdmi.h
+++ b/include/drm/bridge/dw_hdmi.h
@@ -143,6 +143,12 @@ struct dw_hdmi_plat_data {
const struct drm_display_info *info,
const struct drm_display_mode *mode);
+ /*
+ * priv_audio is specially used for additional audio device to get
+ * driver data through this dw_hdmi_plat_data.
+ */
+ void *priv_audio;
+
/* Platform-specific audio enable/disable (optional) */
void (*enable_audio)(struct dw_hdmi *hdmi, int channel,
int width, int rate, int non_pcm);
--
2.34.1
Hi Shengjiu, On 07/18/2025, Shengjiu Wang wrote: > The HDMI TX Parallel Audio Interface (HTX_PAI) is a digital module that > acts as the bridge between the Audio Subsystem to the HDMI TX Controller. > This IP block is found in the HDMI subsystem of the i.MX8MP SoC. > > Data received from the audio subsystem can have an arbitrary component > ordering. The HTX_PAI block has integrated muxing options to select which > sections of the 32-bit input data word will be mapped to each IEC60958 > field. The HTX_PAI_FIELD_CTRL register contains mux selects to > individually select P,C,U,V,Data, and Preamble. > > Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com> > --- > drivers/gpu/drm/bridge/imx/Kconfig | 7 + > drivers/gpu/drm/bridge/imx/Makefile | 1 + > drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c | 134 +++++++++++++++++++ > include/drm/bridge/dw_hdmi.h | 6 + > 4 files changed, 148 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c > > diff --git a/drivers/gpu/drm/bridge/imx/Kconfig b/drivers/gpu/drm/bridge/imx/Kconfig > index 9a480c6abb85..d95fa84a8dcd 100644 > --- a/drivers/gpu/drm/bridge/imx/Kconfig > +++ b/drivers/gpu/drm/bridge/imx/Kconfig > @@ -24,6 +24,13 @@ config DRM_IMX8MP_DW_HDMI_BRIDGE > Choose this to enable support for the internal HDMI encoder found > on the i.MX8MP SoC. > > +config DRM_IMX8MP_HDMI_PAI > + tristate "Freescale i.MX8MP HDMI PAI bridge support" > + depends on OF > + help > + Choose this to enable support for the internal HDMI TX Parallel > + Audio Interface found on the Freescale i.MX8MP SoC. Should DRM_IMX8MP_DW_HDMI_BRIDGE imply DRM_IMX8MP_HDMI_PAI as it implies DRM_IMX8MP_HDMI_PVI and PHY_FSL_SAMSUNG_HDMI_PHY? > + > config DRM_IMX8MP_HDMI_PVI > tristate "Freescale i.MX8MP HDMI PVI bridge support" > depends on OF > diff --git a/drivers/gpu/drm/bridge/imx/Makefile b/drivers/gpu/drm/bridge/imx/Makefile > index dd5d48584806..8d01fda25451 100644 > --- a/drivers/gpu/drm/bridge/imx/Makefile > +++ b/drivers/gpu/drm/bridge/imx/Makefile > @@ -1,6 +1,7 @@ > obj-$(CONFIG_DRM_IMX_LDB_HELPER) += imx-ldb-helper.o > obj-$(CONFIG_DRM_IMX_LEGACY_BRIDGE) += imx-legacy-bridge.o > obj-$(CONFIG_DRM_IMX8MP_DW_HDMI_BRIDGE) += imx8mp-hdmi-tx.o > +obj-$(CONFIG_DRM_IMX8MP_HDMI_PAI) += imx8mp-hdmi-pai.o > obj-$(CONFIG_DRM_IMX8MP_HDMI_PVI) += imx8mp-hdmi-pvi.o > obj-$(CONFIG_DRM_IMX8QM_LDB) += imx8qm-ldb.o > obj-$(CONFIG_DRM_IMX8QXP_LDB) += imx8qxp-ldb.o > diff --git a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c > new file mode 100644 > index 000000000000..f09ee2622e57 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c > @@ -0,0 +1,134 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright 2025 NXP > + */ > + > +#include <drm/bridge/dw_hdmi.h> Usually, linux/*.h header files come before drm/*.h header files. > +#include <linux/module.h> > +#include <linux/of_platform.h> > +#include <linux/platform_device.h> > + > +#define HTX_PAI_CTRL 0x00 > +#define HTX_PAI_CTRL_EXT 0x04 > +#define HTX_PAI_FIELD_CTRL 0x08 > +#define HTX_PAI_STAT 0x0c > +#define HTX_PAI_IRQ_NOMASK 0x10 > +#define HTX_PAI_IRQ_MASKED 0x14 > +#define HTX_PAI_IRQ_MASK 0x18 The above 4 registers are unused. Drop. > + > +#define CTRL_ENABLE BIT(0) Drop CTRL_ prefix. Same for the other bits/fields. Define this bit under register HTX_PAI_CTRL. Same for bits/fields of the other registers. > + > +#define CTRL_EXT_WTMK_HIGH_MASK GENMASK(31, 24) > +#define CTRL_EXT_WTMK_HIGH (0x3 << 24) Add a parameter for the macro and use FIELD_PREP. Same for WTMK_LOW and NUM_CH. #define WTMK_HIGH(n) FIELD_PREP(WTMK_HIGH_MASK, (n)) > +#define CTRL_EXT_WTMK_LOW_MASK GENMASK(23, 16) > +#define CTRL_EXT_WTMK_LOW (0x3 << 16) > +#define CTRL_EXT_NUM_CH_MASK GENMASK(10, 8) > +#define CTRL_EXT_NUM_CH_SHIFT 8 This is not needed if FIELD_PREP is used. > + > +#define FIELD_CTRL_B_FILT BIT(31) > +#define FIELD_CTRL_PARITY_EN BIT(30) > +#define FIELD_CTRL_END_SEL BIT(29) > +#define FIELD_CTRL_PRE_SEL GENMASK(28, 24) > +#define FIELD_CTRL_PRE_SEL_SHIFT 24 > +#define FIELD_CTRL_D_SEL GENMASK(23, 20) > +#define FIELD_CTRL_D_SEL_SHIFT 20 > +#define FIELD_CTRL_V_SEL GENMASK(19, 15) > +#define FIELD_CTRL_V_SEL_SHIFT 15 > +#define FIELD_CTRL_U_SEL GENMASK(14, 10) > +#define FIELD_CTRL_U_SEL_SHIFT 10 > +#define FIELD_CTRL_C_SEL GENMASK(9, 5) > +#define FIELD_CTRL_C_SEL_SHIFT 5 > +#define FIELD_CTRL_P_SEL GENMASK(4, 0) > +#define FIELD_CTRL_P_SEL_SHIFT 0 > + > +struct imx8mp_hdmi_pai { > + struct device *dev; > + void __iomem *base; > +}; > + > +static void imx8mp_hdmi_pai_enable(struct dw_hdmi *dw_hdmi, int channel, > + int width, int rate, int non_pcm) > +{ > + const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi); > + struct imx8mp_hdmi_pai *hdmi_pai = (struct imx8mp_hdmi_pai *)pdata->priv_audio; > + int val; > + > + /* PAI set */ /* PAI set control extended */ > + val = CTRL_EXT_WTMK_HIGH | CTRL_EXT_WTMK_LOW; > + val |= ((channel - 1) << CTRL_EXT_NUM_CH_SHIFT); > + writel(val, hdmi_pai->base + HTX_PAI_CTRL_EXT); Can you use regmap API? > + > + /* IEC60958 format */ > + val = 31 << FIELD_CTRL_P_SEL_SHIFT; > + val |= 30 << FIELD_CTRL_C_SEL_SHIFT; > + val |= 29 << FIELD_CTRL_U_SEL_SHIFT; > + val |= 28 << FIELD_CTRL_V_SEL_SHIFT; > + val |= 4 << FIELD_CTRL_D_SEL_SHIFT; > + val |= 0 << FIELD_CTRL_PRE_SEL_SHIFT; > + Nit: remove this blank line. > + writel(val, hdmi_pai->base + HTX_PAI_FIELD_CTRL); Nit: add a blank line here. > + /* PAI start running */ > + writel(CTRL_ENABLE, hdmi_pai->base + HTX_PAI_CTRL); > +} > + > +static void imx8mp_hdmi_pai_disable(struct dw_hdmi *dw_hdmi) > +{ > + const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi); > + struct imx8mp_hdmi_pai *hdmi_pai = (struct imx8mp_hdmi_pai *)pdata->priv_audio; > + > + /* Stop PAI */ > + writel(0, hdmi_pai->base + HTX_PAI_CTRL); > +} > + > +static int imx8mp_hdmi_pai_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct dw_hdmi_plat_data *plat_data; > + struct imx8mp_hdmi_pai *hdmi_pai; > + struct device_node *remote; > + struct platform_device *hdmi_tx; > + struct resource *res; > + > + hdmi_pai = devm_kzalloc(dev, sizeof(*hdmi_pai), GFP_KERNEL); > + if (!hdmi_pai) > + return -ENOMEM; > + > + hdmi_pai->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); > + if (IS_ERR(hdmi_pai->base)) > + return PTR_ERR(hdmi_pai->base); > + > + hdmi_pai->dev = dev; > + > + remote = of_graph_get_remote_node(pdev->dev.of_node, 0, -1); > + if (!remote) > + return -EINVAL; > + > + hdmi_tx = of_find_device_by_node(remote); > + if (!hdmi_tx) > + return -EINVAL; > + > + plat_data = platform_get_drvdata(hdmi_tx); > + plat_data->enable_audio = imx8mp_hdmi_pai_enable; > + plat_data->disable_audio = imx8mp_hdmi_pai_disable; {enable,disable}_audio callbacks could be set too late... You are trying to probe this driver after imx8mp_hdmi_tx is probed, i.e., after dw_hdmi_probe() is called in imx8mp_dw_hdmi_probe(). Note that after dw_hdmi_probe() is called, the audio device could be functional soon, while this probe is called asynchronously. Also, what if imx8mp_hdmi_pai module is removed while imx8mp_hdmi_tx is running? Leaking {enable,disable}_audio callbacks? I think that you may try to use component helper to take imx8mp_hdmi_tx as an aggregate driver and this driver as a component driver. After the component is bound, you may set {enable,disable}_audio callbacks in imx8mp_hdmi_tx before calling dw_hdmi_probe(). And, you need to export imx8mp_hdmi_pai_{enable,disable} symbols. > + plat_data->priv_audio = hdmi_pai; > + > + return 0; > +} > + > +static const struct of_device_id imx8mp_hdmi_pai_of_table[] = { > + { .compatible = "fsl,imx8mp-hdmi-pai" }, > + { /* Sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, imx8mp_hdmi_pai_of_table); > + > +static struct platform_driver imx8mp_hdmi_pai_platform_driver = { > + .probe = imx8mp_hdmi_pai_probe, > + .driver = { > + .name = "imx8mp-hdmi-pai", > + .of_match_table = imx8mp_hdmi_pai_of_table, > + }, > +}; > +module_platform_driver(imx8mp_hdmi_pai_platform_driver); > + > +MODULE_DESCRIPTION("i.MX8MP HDMI PAI driver"); > +MODULE_LICENSE("GPL"); > diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h > index a56a3519a22a..9ca70ce80cc5 100644 > --- a/include/drm/bridge/dw_hdmi.h > +++ b/include/drm/bridge/dw_hdmi.h > @@ -143,6 +143,12 @@ struct dw_hdmi_plat_data { > const struct drm_display_info *info, > const struct drm_display_mode *mode); > > + /* > + * priv_audio is specially used for additional audio device to get > + * driver data through this dw_hdmi_plat_data. > + */ > + void *priv_audio; > + > /* Platform-specific audio enable/disable (optional) */ > void (*enable_audio)(struct dw_hdmi *hdmi, int channel, > int width, int rate, int non_pcm); -- Regards, Liu Ying
On Tue, Jul 22, 2025 at 4:47 PM Liu Ying <victor.liu@nxp.com> wrote: > > Hi Shengjiu, > > On 07/18/2025, Shengjiu Wang wrote: > > The HDMI TX Parallel Audio Interface (HTX_PAI) is a digital module that > > acts as the bridge between the Audio Subsystem to the HDMI TX Controller. > > This IP block is found in the HDMI subsystem of the i.MX8MP SoC. > > > > Data received from the audio subsystem can have an arbitrary component > > ordering. The HTX_PAI block has integrated muxing options to select which > > sections of the 32-bit input data word will be mapped to each IEC60958 > > field. The HTX_PAI_FIELD_CTRL register contains mux selects to > > individually select P,C,U,V,Data, and Preamble. > > > > Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com> > > --- > > drivers/gpu/drm/bridge/imx/Kconfig | 7 + > > drivers/gpu/drm/bridge/imx/Makefile | 1 + > > drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c | 134 +++++++++++++++++++ > > include/drm/bridge/dw_hdmi.h | 6 + > > 4 files changed, 148 insertions(+) > > create mode 100644 drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c > > > > diff --git a/drivers/gpu/drm/bridge/imx/Kconfig b/drivers/gpu/drm/bridge/imx/Kconfig > > index 9a480c6abb85..d95fa84a8dcd 100644 > > --- a/drivers/gpu/drm/bridge/imx/Kconfig > > +++ b/drivers/gpu/drm/bridge/imx/Kconfig > > @@ -24,6 +24,13 @@ config DRM_IMX8MP_DW_HDMI_BRIDGE > > Choose this to enable support for the internal HDMI encoder found > > on the i.MX8MP SoC. > > > > +config DRM_IMX8MP_HDMI_PAI > > + tristate "Freescale i.MX8MP HDMI PAI bridge support" > > + depends on OF > > + help > > + Choose this to enable support for the internal HDMI TX Parallel > > + Audio Interface found on the Freescale i.MX8MP SoC. > > Should DRM_IMX8MP_DW_HDMI_BRIDGE imply DRM_IMX8MP_HDMI_PAI as it implies > DRM_IMX8MP_HDMI_PVI and PHY_FSL_SAMSUNG_HDMI_PHY? yes. > > > + > > config DRM_IMX8MP_HDMI_PVI > > tristate "Freescale i.MX8MP HDMI PVI bridge support" > > depends on OF > > diff --git a/drivers/gpu/drm/bridge/imx/Makefile b/drivers/gpu/drm/bridge/imx/Makefile > > index dd5d48584806..8d01fda25451 100644 > > --- a/drivers/gpu/drm/bridge/imx/Makefile > > +++ b/drivers/gpu/drm/bridge/imx/Makefile > > @@ -1,6 +1,7 @@ > > obj-$(CONFIG_DRM_IMX_LDB_HELPER) += imx-ldb-helper.o > > obj-$(CONFIG_DRM_IMX_LEGACY_BRIDGE) += imx-legacy-bridge.o > > obj-$(CONFIG_DRM_IMX8MP_DW_HDMI_BRIDGE) += imx8mp-hdmi-tx.o > > +obj-$(CONFIG_DRM_IMX8MP_HDMI_PAI) += imx8mp-hdmi-pai.o > > obj-$(CONFIG_DRM_IMX8MP_HDMI_PVI) += imx8mp-hdmi-pvi.o > > obj-$(CONFIG_DRM_IMX8QM_LDB) += imx8qm-ldb.o > > obj-$(CONFIG_DRM_IMX8QXP_LDB) += imx8qxp-ldb.o > > diff --git a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c > > new file mode 100644 > > index 000000000000..f09ee2622e57 > > --- /dev/null > > +++ b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c > > @@ -0,0 +1,134 @@ > > +// SPDX-License-Identifier: GPL-2.0+ > > +/* > > + * Copyright 2025 NXP > > + */ > > + > > +#include <drm/bridge/dw_hdmi.h> > > Usually, linux/*.h header files come before drm/*.h header files. > > > +#include <linux/module.h> > > +#include <linux/of_platform.h> > > +#include <linux/platform_device.h> > > + > > +#define HTX_PAI_CTRL 0x00 > > +#define HTX_PAI_CTRL_EXT 0x04 > > +#define HTX_PAI_FIELD_CTRL 0x08 > > +#define HTX_PAI_STAT 0x0c > > +#define HTX_PAI_IRQ_NOMASK 0x10 > > +#define HTX_PAI_IRQ_MASKED 0x14 > > +#define HTX_PAI_IRQ_MASK 0x18 > > The above 4 registers are unused. Drop. > > > + > > +#define CTRL_ENABLE BIT(0) > > Drop CTRL_ prefix. Same for the other bits/fields. > > Define this bit under register HTX_PAI_CTRL. Same for bits/fields of > the other registers. Ok, will update them. > > > + > > +#define CTRL_EXT_WTMK_HIGH_MASK GENMASK(31, 24) > > +#define CTRL_EXT_WTMK_HIGH (0x3 << 24) > > Add a parameter for the macro and use FIELD_PREP. > Same for WTMK_LOW and NUM_CH. > > #define WTMK_HIGH(n) FIELD_PREP(WTMK_HIGH_MASK, (n)) > > > +#define CTRL_EXT_WTMK_LOW_MASK GENMASK(23, 16) > > +#define CTRL_EXT_WTMK_LOW (0x3 << 16) > > +#define CTRL_EXT_NUM_CH_MASK GENMASK(10, 8) > > +#define CTRL_EXT_NUM_CH_SHIFT 8 > > This is not needed if FIELD_PREP is used. > > > + > > +#define FIELD_CTRL_B_FILT BIT(31) > > +#define FIELD_CTRL_PARITY_EN BIT(30) > > +#define FIELD_CTRL_END_SEL BIT(29) > > +#define FIELD_CTRL_PRE_SEL GENMASK(28, 24) > > +#define FIELD_CTRL_PRE_SEL_SHIFT 24 > > +#define FIELD_CTRL_D_SEL GENMASK(23, 20) > > +#define FIELD_CTRL_D_SEL_SHIFT 20 > > +#define FIELD_CTRL_V_SEL GENMASK(19, 15) > > +#define FIELD_CTRL_V_SEL_SHIFT 15 > > +#define FIELD_CTRL_U_SEL GENMASK(14, 10) > > +#define FIELD_CTRL_U_SEL_SHIFT 10 > > +#define FIELD_CTRL_C_SEL GENMASK(9, 5) > > +#define FIELD_CTRL_C_SEL_SHIFT 5 > > +#define FIELD_CTRL_P_SEL GENMASK(4, 0) > > +#define FIELD_CTRL_P_SEL_SHIFT 0 > > + > > +struct imx8mp_hdmi_pai { > > + struct device *dev; > > + void __iomem *base; > > +}; > > + > > +static void imx8mp_hdmi_pai_enable(struct dw_hdmi *dw_hdmi, int channel, > > + int width, int rate, int non_pcm) > > +{ > > + const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi); > > + struct imx8mp_hdmi_pai *hdmi_pai = (struct imx8mp_hdmi_pai *)pdata->priv_audio; > > + int val; > > + > > + /* PAI set */ > > /* PAI set control extended */ > > > + val = CTRL_EXT_WTMK_HIGH | CTRL_EXT_WTMK_LOW; > > + val |= ((channel - 1) << CTRL_EXT_NUM_CH_SHIFT); > > + writel(val, hdmi_pai->base + HTX_PAI_CTRL_EXT); > > Can you use regmap API? yes. > > > + > > + /* IEC60958 format */ > > + val = 31 << FIELD_CTRL_P_SEL_SHIFT; > > + val |= 30 << FIELD_CTRL_C_SEL_SHIFT; > > + val |= 29 << FIELD_CTRL_U_SEL_SHIFT; > > + val |= 28 << FIELD_CTRL_V_SEL_SHIFT; > > + val |= 4 << FIELD_CTRL_D_SEL_SHIFT; > > + val |= 0 << FIELD_CTRL_PRE_SEL_SHIFT; > > + > > Nit: remove this blank line. > > > + writel(val, hdmi_pai->base + HTX_PAI_FIELD_CTRL); > > Nit: add a blank line here. > > > + /* PAI start running */ > > + writel(CTRL_ENABLE, hdmi_pai->base + HTX_PAI_CTRL); > > +} > > + > > +static void imx8mp_hdmi_pai_disable(struct dw_hdmi *dw_hdmi) > > +{ > > + const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi); > > + struct imx8mp_hdmi_pai *hdmi_pai = (struct imx8mp_hdmi_pai *)pdata->priv_audio; > > + > > + /* Stop PAI */ > > + writel(0, hdmi_pai->base + HTX_PAI_CTRL); > > +} > > + > > +static int imx8mp_hdmi_pai_probe(struct platform_device *pdev) > > +{ > > + struct device *dev = &pdev->dev; > > + struct dw_hdmi_plat_data *plat_data; > > + struct imx8mp_hdmi_pai *hdmi_pai; > > + struct device_node *remote; > > + struct platform_device *hdmi_tx; > > + struct resource *res; > > + > > + hdmi_pai = devm_kzalloc(dev, sizeof(*hdmi_pai), GFP_KERNEL); > > + if (!hdmi_pai) > > + return -ENOMEM; > > + > > + hdmi_pai->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); > > + if (IS_ERR(hdmi_pai->base)) > > + return PTR_ERR(hdmi_pai->base); > > + > > + hdmi_pai->dev = dev; > > + > > + remote = of_graph_get_remote_node(pdev->dev.of_node, 0, -1); > > + if (!remote) > > + return -EINVAL; > > + > > + hdmi_tx = of_find_device_by_node(remote); > > + if (!hdmi_tx) > > + return -EINVAL; > > + > > + plat_data = platform_get_drvdata(hdmi_tx); > > + plat_data->enable_audio = imx8mp_hdmi_pai_enable; > > + plat_data->disable_audio = imx8mp_hdmi_pai_disable; > > {enable,disable}_audio callbacks could be set too late... > You are trying to probe this driver after imx8mp_hdmi_tx is probed, > i.e., after dw_hdmi_probe() is called in imx8mp_dw_hdmi_probe(). > Note that after dw_hdmi_probe() is called, the audio device could be > functional soon, while this probe is called asynchronously. > > Also, what if imx8mp_hdmi_pai module is removed while imx8mp_hdmi_tx > is running? Leaking {enable,disable}_audio callbacks? > > I think that you may try to use component helper to take imx8mp_hdmi_tx > as an aggregate driver and this driver as a component driver. After > the component is bound, you may set {enable,disable}_audio callbacks > in imx8mp_hdmi_tx before calling dw_hdmi_probe(). > And, you need to export imx8mp_hdmi_pai_{enable,disable} symbols. yes, will use component helper. with component helper, we can assign the {enable, disable}_audio in .bind() callback, not matter which driver probe first. best regards Shengjiu Wang > > > + plat_data->priv_audio = hdmi_pai; > > + > > + return 0; > > +} > > + > > +static const struct of_device_id imx8mp_hdmi_pai_of_table[] = { > > + { .compatible = "fsl,imx8mp-hdmi-pai" }, > > + { /* Sentinel */ } > > +}; > > +MODULE_DEVICE_TABLE(of, imx8mp_hdmi_pai_of_table); > > + > > +static struct platform_driver imx8mp_hdmi_pai_platform_driver = { > > + .probe = imx8mp_hdmi_pai_probe, > > + .driver = { > > + .name = "imx8mp-hdmi-pai", > > + .of_match_table = imx8mp_hdmi_pai_of_table, > > + }, > > +}; > > +module_platform_driver(imx8mp_hdmi_pai_platform_driver); > > + > > +MODULE_DESCRIPTION("i.MX8MP HDMI PAI driver"); > > +MODULE_LICENSE("GPL"); > > diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h > > index a56a3519a22a..9ca70ce80cc5 100644 > > --- a/include/drm/bridge/dw_hdmi.h > > +++ b/include/drm/bridge/dw_hdmi.h > > @@ -143,6 +143,12 @@ struct dw_hdmi_plat_data { > > const struct drm_display_info *info, > > const struct drm_display_mode *mode); > > > > + /* > > + * priv_audio is specially used for additional audio device to get > > + * driver data through this dw_hdmi_plat_data. > > + */ > > + void *priv_audio; > > + > > /* Platform-specific audio enable/disable (optional) */ > > void (*enable_audio)(struct dw_hdmi *hdmi, int channel, > > int width, int rate, int non_pcm); > > -- > Regards, > Liu Ying
Hi, thanks for sending this path. Am Freitag, 18. Juli 2025, 12:11:48 CEST schrieb Shengjiu Wang: > The HDMI TX Parallel Audio Interface (HTX_PAI) is a digital module that > acts as the bridge between the Audio Subsystem to the HDMI TX Controller. > This IP block is found in the HDMI subsystem of the i.MX8MP SoC. > > Data received from the audio subsystem can have an arbitrary component > ordering. The HTX_PAI block has integrated muxing options to select which > sections of the 32-bit input data word will be mapped to each IEC60958 > field. The HTX_PAI_FIELD_CTRL register contains mux selects to > individually select P,C,U,V,Data, and Preamble. > > Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com> > --- > drivers/gpu/drm/bridge/imx/Kconfig | 7 + > drivers/gpu/drm/bridge/imx/Makefile | 1 + > drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c | 134 +++++++++++++++++++ > include/drm/bridge/dw_hdmi.h | 6 + > 4 files changed, 148 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c > > diff --git a/drivers/gpu/drm/bridge/imx/Kconfig b/drivers/gpu/drm/bridge/imx/Kconfig > index 9a480c6abb85..d95fa84a8dcd 100644 > --- a/drivers/gpu/drm/bridge/imx/Kconfig > +++ b/drivers/gpu/drm/bridge/imx/Kconfig > @@ -24,6 +24,13 @@ config DRM_IMX8MP_DW_HDMI_BRIDGE > Choose this to enable support for the internal HDMI encoder found > on the i.MX8MP SoC. > > +config DRM_IMX8MP_HDMI_PAI > + tristate "Freescale i.MX8MP HDMI PAI bridge support" > + depends on OF > + help > + Choose this to enable support for the internal HDMI TX Parallel > + Audio Interface found on the Freescale i.MX8MP SoC. > + > config DRM_IMX8MP_HDMI_PVI > tristate "Freescale i.MX8MP HDMI PVI bridge support" > depends on OF > diff --git a/drivers/gpu/drm/bridge/imx/Makefile b/drivers/gpu/drm/bridge/imx/Makefile > index dd5d48584806..8d01fda25451 100644 > --- a/drivers/gpu/drm/bridge/imx/Makefile > +++ b/drivers/gpu/drm/bridge/imx/Makefile > @@ -1,6 +1,7 @@ > obj-$(CONFIG_DRM_IMX_LDB_HELPER) += imx-ldb-helper.o > obj-$(CONFIG_DRM_IMX_LEGACY_BRIDGE) += imx-legacy-bridge.o > obj-$(CONFIG_DRM_IMX8MP_DW_HDMI_BRIDGE) += imx8mp-hdmi-tx.o > +obj-$(CONFIG_DRM_IMX8MP_HDMI_PAI) += imx8mp-hdmi-pai.o > obj-$(CONFIG_DRM_IMX8MP_HDMI_PVI) += imx8mp-hdmi-pvi.o > obj-$(CONFIG_DRM_IMX8QM_LDB) += imx8qm-ldb.o > obj-$(CONFIG_DRM_IMX8QXP_LDB) += imx8qxp-ldb.o > diff --git a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c > new file mode 100644 > index 000000000000..f09ee2622e57 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c > @@ -0,0 +1,134 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright 2025 NXP > + */ > + > +#include <drm/bridge/dw_hdmi.h> > +#include <linux/module.h> > +#include <linux/of_platform.h> > +#include <linux/platform_device.h> > + > +#define HTX_PAI_CTRL 0x00 > +#define HTX_PAI_CTRL_EXT 0x04 > +#define HTX_PAI_FIELD_CTRL 0x08 > +#define HTX_PAI_STAT 0x0c > +#define HTX_PAI_IRQ_NOMASK 0x10 > +#define HTX_PAI_IRQ_MASKED 0x14 > +#define HTX_PAI_IRQ_MASK 0x18 > + > +#define CTRL_ENABLE BIT(0) > + > +#define CTRL_EXT_WTMK_HIGH_MASK GENMASK(31, 24) > +#define CTRL_EXT_WTMK_HIGH (0x3 << 24) > +#define CTRL_EXT_WTMK_LOW_MASK GENMASK(23, 16) > +#define CTRL_EXT_WTMK_LOW (0x3 << 16) > +#define CTRL_EXT_NUM_CH_MASK GENMASK(10, 8) > +#define CTRL_EXT_NUM_CH_SHIFT 8 > + > +#define FIELD_CTRL_B_FILT BIT(31) > +#define FIELD_CTRL_PARITY_EN BIT(30) > +#define FIELD_CTRL_END_SEL BIT(29) > +#define FIELD_CTRL_PRE_SEL GENMASK(28, 24) > +#define FIELD_CTRL_PRE_SEL_SHIFT 24 > +#define FIELD_CTRL_D_SEL GENMASK(23, 20) > +#define FIELD_CTRL_D_SEL_SHIFT 20 > +#define FIELD_CTRL_V_SEL GENMASK(19, 15) > +#define FIELD_CTRL_V_SEL_SHIFT 15 > +#define FIELD_CTRL_U_SEL GENMASK(14, 10) > +#define FIELD_CTRL_U_SEL_SHIFT 10 > +#define FIELD_CTRL_C_SEL GENMASK(9, 5) > +#define FIELD_CTRL_C_SEL_SHIFT 5 > +#define FIELD_CTRL_P_SEL GENMASK(4, 0) > +#define FIELD_CTRL_P_SEL_SHIFT 0 > + > +struct imx8mp_hdmi_pai { > + struct device *dev; > + void __iomem *base; > +}; > + > +static void imx8mp_hdmi_pai_enable(struct dw_hdmi *dw_hdmi, int channel, > + int width, int rate, int non_pcm) > +{ > + const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi); > + struct imx8mp_hdmi_pai *hdmi_pai = (struct imx8mp_hdmi_pai *)pdata->priv_audio; > + int val; > + > + /* PAI set */ > + val = CTRL_EXT_WTMK_HIGH | CTRL_EXT_WTMK_LOW; > + val |= ((channel - 1) << CTRL_EXT_NUM_CH_SHIFT); > + writel(val, hdmi_pai->base + HTX_PAI_CTRL_EXT); > + > + /* IEC60958 format */ > + val = 31 << FIELD_CTRL_P_SEL_SHIFT; > + val |= 30 << FIELD_CTRL_C_SEL_SHIFT; > + val |= 29 << FIELD_CTRL_U_SEL_SHIFT; > + val |= 28 << FIELD_CTRL_V_SEL_SHIFT; > + val |= 4 << FIELD_CTRL_D_SEL_SHIFT; > + val |= 0 << FIELD_CTRL_PRE_SEL_SHIFT; You can use FIELD_PREP_CONST() for these shifts as the GENMASK are already available. But where do these numbers come from? I can see that downstream kernel sets these bits depending on audio config being passed. > + > + writel(val, hdmi_pai->base + HTX_PAI_FIELD_CTRL); > + /* PAI start running */ > + writel(CTRL_ENABLE, hdmi_pai->base + HTX_PAI_CTRL); > +} > + > +static void imx8mp_hdmi_pai_disable(struct dw_hdmi *dw_hdmi) > +{ > + const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi); > + struct imx8mp_hdmi_pai *hdmi_pai = (struct imx8mp_hdmi_pai *)pdata->priv_audio; > + > + /* Stop PAI */ > + writel(0, hdmi_pai->base + HTX_PAI_CTRL); > +} > + > +static int imx8mp_hdmi_pai_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct dw_hdmi_plat_data *plat_data; > + struct imx8mp_hdmi_pai *hdmi_pai; > + struct device_node *remote; > + struct platform_device *hdmi_tx; > + struct resource *res; > + > + hdmi_pai = devm_kzalloc(dev, sizeof(*hdmi_pai), GFP_KERNEL); > + if (!hdmi_pai) > + return -ENOMEM; > + > + hdmi_pai->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); > + if (IS_ERR(hdmi_pai->base)) > + return PTR_ERR(hdmi_pai->base); > + > + hdmi_pai->dev = dev; > + > + remote = of_graph_get_remote_node(pdev->dev.of_node, 0, -1); > + if (!remote) > + return -EINVAL; > + > + hdmi_tx = of_find_device_by_node(remote); > + if (!hdmi_tx) > + return -EINVAL; > + > + plat_data = platform_get_drvdata(hdmi_tx); How do you ensure hdmi_tx has been probed so that drvdata is set? Also hdmi_tx needs to be dropped. > + plat_data->enable_audio = imx8mp_hdmi_pai_enable; > + plat_data->disable_audio = imx8mp_hdmi_pai_disable; > + plat_data->priv_audio = hdmi_pai; How do you connect this device to aud2htx? Best regards, Alexander > + > + return 0; > +} > + > +static const struct of_device_id imx8mp_hdmi_pai_of_table[] = { > + { .compatible = "fsl,imx8mp-hdmi-pai" }, > + { /* Sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, imx8mp_hdmi_pai_of_table); > + > +static struct platform_driver imx8mp_hdmi_pai_platform_driver = { > + .probe = imx8mp_hdmi_pai_probe, > + .driver = { > + .name = "imx8mp-hdmi-pai", > + .of_match_table = imx8mp_hdmi_pai_of_table, > + }, > +}; > +module_platform_driver(imx8mp_hdmi_pai_platform_driver); > + > +MODULE_DESCRIPTION("i.MX8MP HDMI PAI driver"); > +MODULE_LICENSE("GPL"); > diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h > index a56a3519a22a..9ca70ce80cc5 100644 > --- a/include/drm/bridge/dw_hdmi.h > +++ b/include/drm/bridge/dw_hdmi.h > @@ -143,6 +143,12 @@ struct dw_hdmi_plat_data { > const struct drm_display_info *info, > const struct drm_display_mode *mode); > > + /* > + * priv_audio is specially used for additional audio device to get > + * driver data through this dw_hdmi_plat_data. > + */ > + void *priv_audio; > + > /* Platform-specific audio enable/disable (optional) */ > void (*enable_audio)(struct dw_hdmi *hdmi, int channel, > int width, int rate, int non_pcm); > -- TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany Amtsgericht München, HRB 105018 Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider http://www.tq-group.com/
On Fri, Jul 18, 2025 at 7:51 PM Alexander Stein <alexander.stein@ew.tq-group.com> wrote: > > Hi, > > thanks for sending this path. > > Am Freitag, 18. Juli 2025, 12:11:48 CEST schrieb Shengjiu Wang: > > The HDMI TX Parallel Audio Interface (HTX_PAI) is a digital module that > > acts as the bridge between the Audio Subsystem to the HDMI TX Controller. > > This IP block is found in the HDMI subsystem of the i.MX8MP SoC. > > > > Data received from the audio subsystem can have an arbitrary component > > ordering. The HTX_PAI block has integrated muxing options to select which > > sections of the 32-bit input data word will be mapped to each IEC60958 > > field. The HTX_PAI_FIELD_CTRL register contains mux selects to > > individually select P,C,U,V,Data, and Preamble. > > > > Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com> > > --- > > drivers/gpu/drm/bridge/imx/Kconfig | 7 + > > drivers/gpu/drm/bridge/imx/Makefile | 1 + > > drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c | 134 +++++++++++++++++++ > > include/drm/bridge/dw_hdmi.h | 6 + > > 4 files changed, 148 insertions(+) > > create mode 100644 drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c > > > > diff --git a/drivers/gpu/drm/bridge/imx/Kconfig b/drivers/gpu/drm/bridge/imx/Kconfig > > index 9a480c6abb85..d95fa84a8dcd 100644 > > --- a/drivers/gpu/drm/bridge/imx/Kconfig > > +++ b/drivers/gpu/drm/bridge/imx/Kconfig > > @@ -24,6 +24,13 @@ config DRM_IMX8MP_DW_HDMI_BRIDGE > > Choose this to enable support for the internal HDMI encoder found > > on the i.MX8MP SoC. > > > > +config DRM_IMX8MP_HDMI_PAI > > + tristate "Freescale i.MX8MP HDMI PAI bridge support" > > + depends on OF > > + help > > + Choose this to enable support for the internal HDMI TX Parallel > > + Audio Interface found on the Freescale i.MX8MP SoC. > > + > > config DRM_IMX8MP_HDMI_PVI > > tristate "Freescale i.MX8MP HDMI PVI bridge support" > > depends on OF > > diff --git a/drivers/gpu/drm/bridge/imx/Makefile b/drivers/gpu/drm/bridge/imx/Makefile > > index dd5d48584806..8d01fda25451 100644 > > --- a/drivers/gpu/drm/bridge/imx/Makefile > > +++ b/drivers/gpu/drm/bridge/imx/Makefile > > @@ -1,6 +1,7 @@ > > obj-$(CONFIG_DRM_IMX_LDB_HELPER) += imx-ldb-helper.o > > obj-$(CONFIG_DRM_IMX_LEGACY_BRIDGE) += imx-legacy-bridge.o > > obj-$(CONFIG_DRM_IMX8MP_DW_HDMI_BRIDGE) += imx8mp-hdmi-tx.o > > +obj-$(CONFIG_DRM_IMX8MP_HDMI_PAI) += imx8mp-hdmi-pai.o > > obj-$(CONFIG_DRM_IMX8MP_HDMI_PVI) += imx8mp-hdmi-pvi.o > > obj-$(CONFIG_DRM_IMX8QM_LDB) += imx8qm-ldb.o > > obj-$(CONFIG_DRM_IMX8QXP_LDB) += imx8qxp-ldb.o > > diff --git a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c > > new file mode 100644 > > index 000000000000..f09ee2622e57 > > --- /dev/null > > +++ b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c > > @@ -0,0 +1,134 @@ > > +// SPDX-License-Identifier: GPL-2.0+ > > +/* > > + * Copyright 2025 NXP > > + */ > > + > > +#include <drm/bridge/dw_hdmi.h> > > +#include <linux/module.h> > > +#include <linux/of_platform.h> > > +#include <linux/platform_device.h> > > + > > +#define HTX_PAI_CTRL 0x00 > > +#define HTX_PAI_CTRL_EXT 0x04 > > +#define HTX_PAI_FIELD_CTRL 0x08 > > +#define HTX_PAI_STAT 0x0c > > +#define HTX_PAI_IRQ_NOMASK 0x10 > > +#define HTX_PAI_IRQ_MASKED 0x14 > > +#define HTX_PAI_IRQ_MASK 0x18 > > + > > +#define CTRL_ENABLE BIT(0) > > + > > +#define CTRL_EXT_WTMK_HIGH_MASK GENMASK(31, 24) > > +#define CTRL_EXT_WTMK_HIGH (0x3 << 24) > > +#define CTRL_EXT_WTMK_LOW_MASK GENMASK(23, 16) > > +#define CTRL_EXT_WTMK_LOW (0x3 << 16) > > +#define CTRL_EXT_NUM_CH_MASK GENMASK(10, 8) > > +#define CTRL_EXT_NUM_CH_SHIFT 8 > > + > > +#define FIELD_CTRL_B_FILT BIT(31) > > +#define FIELD_CTRL_PARITY_EN BIT(30) > > +#define FIELD_CTRL_END_SEL BIT(29) > > +#define FIELD_CTRL_PRE_SEL GENMASK(28, 24) > > +#define FIELD_CTRL_PRE_SEL_SHIFT 24 > > +#define FIELD_CTRL_D_SEL GENMASK(23, 20) > > +#define FIELD_CTRL_D_SEL_SHIFT 20 > > +#define FIELD_CTRL_V_SEL GENMASK(19, 15) > > +#define FIELD_CTRL_V_SEL_SHIFT 15 > > +#define FIELD_CTRL_U_SEL GENMASK(14, 10) > > +#define FIELD_CTRL_U_SEL_SHIFT 10 > > +#define FIELD_CTRL_C_SEL GENMASK(9, 5) > > +#define FIELD_CTRL_C_SEL_SHIFT 5 > > +#define FIELD_CTRL_P_SEL GENMASK(4, 0) > > +#define FIELD_CTRL_P_SEL_SHIFT 0 > > + > > +struct imx8mp_hdmi_pai { > > + struct device *dev; > > + void __iomem *base; > > +}; > > + > > +static void imx8mp_hdmi_pai_enable(struct dw_hdmi *dw_hdmi, int channel, > > + int width, int rate, int non_pcm) > > +{ > > + const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi); > > + struct imx8mp_hdmi_pai *hdmi_pai = (struct imx8mp_hdmi_pai *)pdata->priv_audio; > > + int val; > > + > > + /* PAI set */ > > + val = CTRL_EXT_WTMK_HIGH | CTRL_EXT_WTMK_LOW; > > + val |= ((channel - 1) << CTRL_EXT_NUM_CH_SHIFT); > > + writel(val, hdmi_pai->base + HTX_PAI_CTRL_EXT); > > + > > + /* IEC60958 format */ > > + val = 31 << FIELD_CTRL_P_SEL_SHIFT; > > + val |= 30 << FIELD_CTRL_C_SEL_SHIFT; > > + val |= 29 << FIELD_CTRL_U_SEL_SHIFT; > > + val |= 28 << FIELD_CTRL_V_SEL_SHIFT; > > + val |= 4 << FIELD_CTRL_D_SEL_SHIFT; > > + val |= 0 << FIELD_CTRL_PRE_SEL_SHIFT; > > You can use FIELD_PREP_CONST() for these shifts as the GENMASK are > already available. Ok. > But where do these numbers come from? I can see that downstream kernel > sets these bits depending on audio config being passed. These numbers are defined in standard IEC958 spec. The implementation in downstream kernel may not good enough, which can't distinguish between the raw PCM and IEC958 PCM. Even raw PCM can work, but in RM, the supported format is IEC958. So later we need to update the aud2htx driver to only support IEC958 format. Make the alignment between PAI driver and aud2htx driver. > > > + > > + writel(val, hdmi_pai->base + HTX_PAI_FIELD_CTRL); > > + /* PAI start running */ > > + writel(CTRL_ENABLE, hdmi_pai->base + HTX_PAI_CTRL); > > +} > > + > > +static void imx8mp_hdmi_pai_disable(struct dw_hdmi *dw_hdmi) > > +{ > > + const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi); > > + struct imx8mp_hdmi_pai *hdmi_pai = (struct imx8mp_hdmi_pai *)pdata->priv_audio; > > + > > + /* Stop PAI */ > > + writel(0, hdmi_pai->base + HTX_PAI_CTRL); > > +} > > + > > +static int imx8mp_hdmi_pai_probe(struct platform_device *pdev) > > +{ > > + struct device *dev = &pdev->dev; > > + struct dw_hdmi_plat_data *plat_data; > > + struct imx8mp_hdmi_pai *hdmi_pai; > > + struct device_node *remote; > > + struct platform_device *hdmi_tx; > > + struct resource *res; > > + > > + hdmi_pai = devm_kzalloc(dev, sizeof(*hdmi_pai), GFP_KERNEL); > > + if (!hdmi_pai) > > + return -ENOMEM; > > + > > + hdmi_pai->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); > > + if (IS_ERR(hdmi_pai->base)) > > + return PTR_ERR(hdmi_pai->base); > > + > > + hdmi_pai->dev = dev; > > + > > + remote = of_graph_get_remote_node(pdev->dev.of_node, 0, -1); > > + if (!remote) > > + return -EINVAL; > > + > > + hdmi_tx = of_find_device_by_node(remote); > > + if (!hdmi_tx) > > + return -EINVAL; > > + > > + plat_data = platform_get_drvdata(hdmi_tx); > > How do you ensure hdmi_tx has been probed so that drvdata is set? > Also hdmi_tx needs to be dropped. Good catch. need to update to hdmi_tx = of_find_device_by_node(remote); of_node_put(remote); if (!hdmi_tx) return -ENODEV; plat_data = platform_get_drvdata(hdmi_tx); put_device(&hdmi_tx->dev); if (!plat_data) return -EPROBE_DEFER; > > > + plat_data->enable_audio = imx8mp_hdmi_pai_enable; > > + plat_data->disable_audio = imx8mp_hdmi_pai_disable; > > + plat_data->priv_audio = hdmi_pai; > > How do you connect this device to aud2htx? aud2htx -> hdmi_codec -> dw-hdmi-gp-audio hdmi_codec is sound/soc/codecs/hdmi-codec.c gp-audio will call this plat_data->enable(disable)_audio(). So need to register the plat_data->enable(disable)_audio in this driver. Best regards Shengjiu Wang > > Best regards, > Alexander > > > + > > + return 0; > > +} > > + > > +static const struct of_device_id imx8mp_hdmi_pai_of_table[] = { > > + { .compatible = "fsl,imx8mp-hdmi-pai" }, > > + { /* Sentinel */ } > > +}; > > +MODULE_DEVICE_TABLE(of, imx8mp_hdmi_pai_of_table); > > + > > +static struct platform_driver imx8mp_hdmi_pai_platform_driver = { > > + .probe = imx8mp_hdmi_pai_probe, > > + .driver = { > > + .name = "imx8mp-hdmi-pai", > > + .of_match_table = imx8mp_hdmi_pai_of_table, > > + }, > > +}; > > +module_platform_driver(imx8mp_hdmi_pai_platform_driver); > > + > > +MODULE_DESCRIPTION("i.MX8MP HDMI PAI driver"); > > +MODULE_LICENSE("GPL"); > > diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h > > index a56a3519a22a..9ca70ce80cc5 100644 > > --- a/include/drm/bridge/dw_hdmi.h > > +++ b/include/drm/bridge/dw_hdmi.h > > @@ -143,6 +143,12 @@ struct dw_hdmi_plat_data { > > const struct drm_display_info *info, > > const struct drm_display_mode *mode); > > > > + /* > > + * priv_audio is specially used for additional audio device to get > > + * driver data through this dw_hdmi_plat_data. > > + */ > > + void *priv_audio; > > + > > /* Platform-specific audio enable/disable (optional) */ > > void (*enable_audio)(struct dw_hdmi *hdmi, int channel, > > int width, int rate, int non_pcm); > > > > > -- > TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany > Amtsgericht München, HRB 105018 > Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider > http://www.tq-group.com/ > >
Hi Shengjiu, On 07/21/2025, Shengjiu Wang wrote: > On Fri, Jul 18, 2025 at 7:51 PM Alexander Stein > <alexander.stein@ew.tq-group.com> wrote: [...] >> Am Freitag, 18. Juli 2025, 12:11:48 CEST schrieb Shengjiu Wang: [...] >>> + /* IEC60958 format */ >>> + val = 31 << FIELD_CTRL_P_SEL_SHIFT; >>> + val |= 30 << FIELD_CTRL_C_SEL_SHIFT; >>> + val |= 29 << FIELD_CTRL_U_SEL_SHIFT; >>> + val |= 28 << FIELD_CTRL_V_SEL_SHIFT; >>> + val |= 4 << FIELD_CTRL_D_SEL_SHIFT; >>> + val |= 0 << FIELD_CTRL_PRE_SEL_SHIFT; [...] >> But where do these numbers come from? I can see that downstream kernel >> sets these bits depending on audio config being passed. > > These numbers are defined in standard IEC958 spec. Can these be defined by macros, even in a certain common header file, include/sound/asoundef.h? -- Regards, Liu Ying
On Tue, Jul 22, 2025 at 3:50 PM Liu Ying <victor.liu@nxp.com> wrote: > > Hi Shengjiu, > > On 07/21/2025, Shengjiu Wang wrote: > > On Fri, Jul 18, 2025 at 7:51 PM Alexander Stein > > <alexander.stein@ew.tq-group.com> wrote: > > [...] > > >> Am Freitag, 18. Juli 2025, 12:11:48 CEST schrieb Shengjiu Wang: > > [...] > > >>> + /* IEC60958 format */ > >>> + val = 31 << FIELD_CTRL_P_SEL_SHIFT; > >>> + val |= 30 << FIELD_CTRL_C_SEL_SHIFT; > >>> + val |= 29 << FIELD_CTRL_U_SEL_SHIFT; > >>> + val |= 28 << FIELD_CTRL_V_SEL_SHIFT; > >>> + val |= 4 << FIELD_CTRL_D_SEL_SHIFT; > >>> + val |= 0 << FIELD_CTRL_PRE_SEL_SHIFT; > > [...] > > >> But where do these numbers come from? I can see that downstream kernel > >> sets these bits depending on audio config being passed. > > > > These numbers are defined in standard IEC958 spec. > > Can these be defined by macros, even in a certain common header file, > include/sound/asoundef.h? yes, then will include ALSA maintainer for the change. > > -- > Regards, > Liu Ying
© 2016 - 2025 Red Hat, Inc.