Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm/bridge: imx: add driver for HDMI TX Parallel Audio Interface

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.

Use component helper so that imx8mp-hdmi-tx will be aggregate driver,
imx8mp-hdmi-pai will be component driver, then imx8mp-hdmi-pai can use
bind() ops to get the plat_data from imx8mp-hdmi-tx device.

Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
Reviewed-by: Liu Ying <victor.liu@nxp.com>
Tested-by: Alexander Stein <alexander.stein@ew.tq-group.com>
Signed-off-by: Liu Ying <victor.liu@nxp.com>
Link: https://lore.kernel.org/r/20250923053001.2678596-6-shengjiu.wang@nxp.com

authored by

Shengjiu Wang and committed by
Liu Ying
0205fae6 80c5d144

+236 -5
+11
drivers/gpu/drm/bridge/imx/Kconfig
··· 18 18 depends on OF 19 19 depends on COMMON_CLK 20 20 select DRM_DW_HDMI 21 + imply DRM_IMX8MP_HDMI_PAI 21 22 imply DRM_IMX8MP_HDMI_PVI 22 23 imply PHY_FSL_SAMSUNG_HDMI_PHY 23 24 help 24 25 Choose this to enable support for the internal HDMI encoder found 25 26 on the i.MX8MP SoC. 27 + 28 + config DRM_IMX8MP_HDMI_PAI 29 + tristate "Freescale i.MX8MP HDMI PAI bridge support" 30 + depends on OF 31 + select DRM_DW_HDMI 32 + select REGMAP 33 + select REGMAP_MMIO 34 + help 35 + Choose this to enable support for the internal HDMI TX Parallel 36 + Audio Interface found on the Freescale i.MX8MP SoC. 26 37 27 38 config DRM_IMX8MP_HDMI_PVI 28 39 tristate "Freescale i.MX8MP HDMI PVI bridge support"
+1
drivers/gpu/drm/bridge/imx/Makefile
··· 1 1 obj-$(CONFIG_DRM_IMX_LDB_HELPER) += imx-ldb-helper.o 2 2 obj-$(CONFIG_DRM_IMX_LEGACY_BRIDGE) += imx-legacy-bridge.o 3 3 obj-$(CONFIG_DRM_IMX8MP_DW_HDMI_BRIDGE) += imx8mp-hdmi-tx.o 4 + obj-$(CONFIG_DRM_IMX8MP_HDMI_PAI) += imx8mp-hdmi-pai.o 4 5 obj-$(CONFIG_DRM_IMX8MP_HDMI_PVI) += imx8mp-hdmi-pvi.o 5 6 obj-$(CONFIG_DRM_IMX8QM_LDB) += imx8qm-ldb.o 6 7 obj-$(CONFIG_DRM_IMX8QXP_LDB) += imx8qxp-ldb.o
+158
drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c
··· 1 + // SPDX-License-Identifier: GPL-2.0+ 2 + /* 3 + * Copyright 2025 NXP 4 + */ 5 + 6 + #include <linux/bitfield.h> 7 + #include <linux/component.h> 8 + #include <linux/module.h> 9 + #include <linux/of_platform.h> 10 + #include <linux/platform_device.h> 11 + #include <linux/regmap.h> 12 + #include <drm/bridge/dw_hdmi.h> 13 + #include <sound/asoundef.h> 14 + 15 + #define HTX_PAI_CTRL 0x00 16 + #define ENABLE BIT(0) 17 + 18 + #define HTX_PAI_CTRL_EXT 0x04 19 + #define WTMK_HIGH_MASK GENMASK(31, 24) 20 + #define WTMK_LOW_MASK GENMASK(23, 16) 21 + #define NUM_CH_MASK GENMASK(10, 8) 22 + #define WTMK_HIGH(n) FIELD_PREP(WTMK_HIGH_MASK, (n)) 23 + #define WTMK_LOW(n) FIELD_PREP(WTMK_LOW_MASK, (n)) 24 + #define NUM_CH(n) FIELD_PREP(NUM_CH_MASK, (n) - 1) 25 + 26 + #define HTX_PAI_FIELD_CTRL 0x08 27 + #define PRE_SEL GENMASK(28, 24) 28 + #define D_SEL GENMASK(23, 20) 29 + #define V_SEL GENMASK(19, 15) 30 + #define U_SEL GENMASK(14, 10) 31 + #define C_SEL GENMASK(9, 5) 32 + #define P_SEL GENMASK(4, 0) 33 + 34 + struct imx8mp_hdmi_pai { 35 + struct regmap *regmap; 36 + }; 37 + 38 + static void imx8mp_hdmi_pai_enable(struct dw_hdmi *dw_hdmi, int channel, 39 + int width, int rate, int non_pcm, 40 + int iec958) 41 + { 42 + const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi); 43 + struct imx8mp_hdmi_pai *hdmi_pai = pdata->priv_audio; 44 + int val; 45 + 46 + /* PAI set control extended */ 47 + val = WTMK_HIGH(3) | WTMK_LOW(3); 48 + val |= NUM_CH(channel); 49 + regmap_write(hdmi_pai->regmap, HTX_PAI_CTRL_EXT, val); 50 + 51 + /* IEC60958 format */ 52 + if (iec958) { 53 + val = FIELD_PREP_CONST(P_SEL, 54 + __bf_shf(IEC958_SUBFRAME_PARITY)); 55 + val |= FIELD_PREP_CONST(C_SEL, 56 + __bf_shf(IEC958_SUBFRAME_CHANNEL_STATUS)); 57 + val |= FIELD_PREP_CONST(U_SEL, 58 + __bf_shf(IEC958_SUBFRAME_USER_DATA)); 59 + val |= FIELD_PREP_CONST(V_SEL, 60 + __bf_shf(IEC958_SUBFRAME_VALIDITY)); 61 + val |= FIELD_PREP_CONST(D_SEL, 62 + __bf_shf(IEC958_SUBFRAME_SAMPLE_24_MASK)); 63 + val |= FIELD_PREP_CONST(PRE_SEL, 64 + __bf_shf(IEC958_SUBFRAME_PREAMBLE_MASK)); 65 + } else { 66 + /* 67 + * The allowed PCM widths are 24bit and 32bit, as they are supported 68 + * by aud2htx module. 69 + * for 24bit, D_SEL = 0, select all the bits. 70 + * for 32bit, D_SEL = 8, select 24bit in MSB. 71 + */ 72 + val = FIELD_PREP(D_SEL, width - 24); 73 + } 74 + 75 + regmap_write(hdmi_pai->regmap, HTX_PAI_FIELD_CTRL, val); 76 + 77 + /* PAI start running */ 78 + regmap_write(hdmi_pai->regmap, HTX_PAI_CTRL, ENABLE); 79 + } 80 + 81 + static void imx8mp_hdmi_pai_disable(struct dw_hdmi *dw_hdmi) 82 + { 83 + const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi); 84 + struct imx8mp_hdmi_pai *hdmi_pai = pdata->priv_audio; 85 + 86 + /* Stop PAI */ 87 + regmap_write(hdmi_pai->regmap, HTX_PAI_CTRL, 0); 88 + } 89 + 90 + static const struct regmap_config imx8mp_hdmi_pai_regmap_config = { 91 + .reg_bits = 32, 92 + .reg_stride = 4, 93 + .val_bits = 32, 94 + .max_register = HTX_PAI_FIELD_CTRL, 95 + }; 96 + 97 + static int imx8mp_hdmi_pai_bind(struct device *dev, struct device *master, void *data) 98 + { 99 + struct platform_device *pdev = to_platform_device(dev); 100 + struct dw_hdmi_plat_data *plat_data = data; 101 + struct imx8mp_hdmi_pai *hdmi_pai; 102 + struct resource *res; 103 + void __iomem *base; 104 + 105 + hdmi_pai = devm_kzalloc(dev, sizeof(*hdmi_pai), GFP_KERNEL); 106 + if (!hdmi_pai) 107 + return -ENOMEM; 108 + 109 + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); 110 + if (IS_ERR(base)) 111 + return PTR_ERR(base); 112 + 113 + hdmi_pai->regmap = devm_regmap_init_mmio_clk(dev, "apb", base, 114 + &imx8mp_hdmi_pai_regmap_config); 115 + if (IS_ERR(hdmi_pai->regmap)) { 116 + dev_err(dev, "regmap init failed\n"); 117 + return PTR_ERR(hdmi_pai->regmap); 118 + } 119 + 120 + plat_data->enable_audio = imx8mp_hdmi_pai_enable; 121 + plat_data->disable_audio = imx8mp_hdmi_pai_disable; 122 + plat_data->priv_audio = hdmi_pai; 123 + 124 + return 0; 125 + } 126 + 127 + static const struct component_ops imx8mp_hdmi_pai_ops = { 128 + .bind = imx8mp_hdmi_pai_bind, 129 + }; 130 + 131 + static int imx8mp_hdmi_pai_probe(struct platform_device *pdev) 132 + { 133 + return component_add(&pdev->dev, &imx8mp_hdmi_pai_ops); 134 + } 135 + 136 + static void imx8mp_hdmi_pai_remove(struct platform_device *pdev) 137 + { 138 + component_del(&pdev->dev, &imx8mp_hdmi_pai_ops); 139 + } 140 + 141 + static const struct of_device_id imx8mp_hdmi_pai_of_table[] = { 142 + { .compatible = "fsl,imx8mp-hdmi-pai" }, 143 + { /* Sentinel */ } 144 + }; 145 + MODULE_DEVICE_TABLE(of, imx8mp_hdmi_pai_of_table); 146 + 147 + static struct platform_driver imx8mp_hdmi_pai_platform_driver = { 148 + .probe = imx8mp_hdmi_pai_probe, 149 + .remove = imx8mp_hdmi_pai_remove, 150 + .driver = { 151 + .name = "imx8mp-hdmi-pai", 152 + .of_match_table = imx8mp_hdmi_pai_of_table, 153 + }, 154 + }; 155 + module_platform_driver(imx8mp_hdmi_pai_platform_driver); 156 + 157 + MODULE_DESCRIPTION("i.MX8MP HDMI PAI driver"); 158 + MODULE_LICENSE("GPL");
+60 -5
drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c
··· 5 5 */ 6 6 7 7 #include <linux/clk.h> 8 + #include <linux/component.h> 8 9 #include <linux/mod_devicetable.h> 9 10 #include <linux/module.h> 10 11 #include <linux/platform_device.h> 11 12 #include <drm/bridge/dw_hdmi.h> 12 13 #include <drm/drm_modes.h> 14 + #include <drm/drm_of.h> 13 15 14 16 struct imx8mp_hdmi { 15 17 struct dw_hdmi_plat_data plat_data; ··· 81 79 .update_hpd = dw_hdmi_phy_update_hpd, 82 80 }; 83 81 82 + static int imx8mp_dw_hdmi_bind(struct device *dev) 83 + { 84 + struct platform_device *pdev = to_platform_device(dev); 85 + struct imx8mp_hdmi *hdmi = dev_get_drvdata(dev); 86 + int ret; 87 + 88 + ret = component_bind_all(dev, &hdmi->plat_data); 89 + if (ret) 90 + return dev_err_probe(dev, ret, "component_bind_all failed!\n"); 91 + 92 + hdmi->dw_hdmi = dw_hdmi_probe(pdev, &hdmi->plat_data); 93 + if (IS_ERR(hdmi->dw_hdmi)) { 94 + component_unbind_all(dev, &hdmi->plat_data); 95 + return PTR_ERR(hdmi->dw_hdmi); 96 + } 97 + 98 + return 0; 99 + } 100 + 101 + static void imx8mp_dw_hdmi_unbind(struct device *dev) 102 + { 103 + struct imx8mp_hdmi *hdmi = dev_get_drvdata(dev); 104 + 105 + dw_hdmi_remove(hdmi->dw_hdmi); 106 + 107 + component_unbind_all(dev, &hdmi->plat_data); 108 + } 109 + 110 + static const struct component_master_ops imx8mp_dw_hdmi_ops = { 111 + .bind = imx8mp_dw_hdmi_bind, 112 + .unbind = imx8mp_dw_hdmi_unbind, 113 + }; 114 + 84 115 static int imx8mp_dw_hdmi_probe(struct platform_device *pdev) 85 116 { 86 117 struct device *dev = &pdev->dev; 87 118 struct dw_hdmi_plat_data *plat_data; 119 + struct component_match *match = NULL; 120 + struct device_node *remote; 88 121 struct imx8mp_hdmi *hdmi; 89 122 90 123 hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); ··· 139 102 plat_data->priv_data = hdmi; 140 103 plat_data->phy_force_vendor = true; 141 104 142 - hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data); 143 - if (IS_ERR(hdmi->dw_hdmi)) 144 - return PTR_ERR(hdmi->dw_hdmi); 145 - 146 105 platform_set_drvdata(pdev, hdmi); 106 + 107 + /* port@2 is for hdmi_pai device */ 108 + remote = of_graph_get_remote_node(pdev->dev.of_node, 2, 0); 109 + if (!remote) { 110 + hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data); 111 + if (IS_ERR(hdmi->dw_hdmi)) 112 + return PTR_ERR(hdmi->dw_hdmi); 113 + } else { 114 + drm_of_component_match_add(dev, &match, component_compare_of, remote); 115 + 116 + of_node_put(remote); 117 + 118 + return component_master_add_with_match(dev, &imx8mp_dw_hdmi_ops, match); 119 + } 147 120 148 121 return 0; 149 122 } ··· 161 114 static void imx8mp_dw_hdmi_remove(struct platform_device *pdev) 162 115 { 163 116 struct imx8mp_hdmi *hdmi = platform_get_drvdata(pdev); 117 + struct device_node *remote; 164 118 165 - dw_hdmi_remove(hdmi->dw_hdmi); 119 + remote = of_graph_get_remote_node(pdev->dev.of_node, 2, 0); 120 + if (remote) { 121 + of_node_put(remote); 122 + 123 + component_master_del(&pdev->dev, &imx8mp_dw_hdmi_ops); 124 + } else { 125 + dw_hdmi_remove(hdmi->dw_hdmi); 126 + } 166 127 } 167 128 168 129 static int imx8mp_dw_hdmi_pm_suspend(struct device *dev)
+6
include/drm/bridge/dw_hdmi.h
··· 143 143 const struct drm_display_info *info, 144 144 const struct drm_display_mode *mode); 145 145 146 + /* 147 + * priv_audio is specially used for additional audio device to get 148 + * driver data through this dw_hdmi_plat_data. 149 + */ 150 + void *priv_audio; 151 + 146 152 /* Platform-specific audio enable/disable (optional) */ 147 153 void (*enable_audio)(struct dw_hdmi *hdmi, int channel, 148 154 int width, int rate, int non_pcm, int iec958);