10205fae6SShengjiu Wang // SPDX-License-Identifier: GPL-2.0+
20205fae6SShengjiu Wang /*
30205fae6SShengjiu Wang * Copyright 2025 NXP
40205fae6SShengjiu Wang */
50205fae6SShengjiu Wang
60205fae6SShengjiu Wang #include <linux/bitfield.h>
70205fae6SShengjiu Wang #include <linux/component.h>
80205fae6SShengjiu Wang #include <linux/module.h>
90205fae6SShengjiu Wang #include <linux/of_platform.h>
100205fae6SShengjiu Wang #include <linux/platform_device.h>
11*40b24d9cSShengjiu Wang #include <linux/pm_runtime.h>
120205fae6SShengjiu Wang #include <linux/regmap.h>
130205fae6SShengjiu Wang #include <drm/bridge/dw_hdmi.h>
140205fae6SShengjiu Wang #include <sound/asoundef.h>
150205fae6SShengjiu Wang
160205fae6SShengjiu Wang #define HTX_PAI_CTRL 0x00
170205fae6SShengjiu Wang #define ENABLE BIT(0)
180205fae6SShengjiu Wang
190205fae6SShengjiu Wang #define HTX_PAI_CTRL_EXT 0x04
200205fae6SShengjiu Wang #define WTMK_HIGH_MASK GENMASK(31, 24)
210205fae6SShengjiu Wang #define WTMK_LOW_MASK GENMASK(23, 16)
220205fae6SShengjiu Wang #define NUM_CH_MASK GENMASK(10, 8)
230205fae6SShengjiu Wang #define WTMK_HIGH(n) FIELD_PREP(WTMK_HIGH_MASK, (n))
240205fae6SShengjiu Wang #define WTMK_LOW(n) FIELD_PREP(WTMK_LOW_MASK, (n))
250205fae6SShengjiu Wang #define NUM_CH(n) FIELD_PREP(NUM_CH_MASK, (n) - 1)
260205fae6SShengjiu Wang
270205fae6SShengjiu Wang #define HTX_PAI_FIELD_CTRL 0x08
280205fae6SShengjiu Wang #define PRE_SEL GENMASK(28, 24)
290205fae6SShengjiu Wang #define D_SEL GENMASK(23, 20)
300205fae6SShengjiu Wang #define V_SEL GENMASK(19, 15)
310205fae6SShengjiu Wang #define U_SEL GENMASK(14, 10)
320205fae6SShengjiu Wang #define C_SEL GENMASK(9, 5)
330205fae6SShengjiu Wang #define P_SEL GENMASK(4, 0)
340205fae6SShengjiu Wang
350205fae6SShengjiu Wang struct imx8mp_hdmi_pai {
360205fae6SShengjiu Wang struct regmap *regmap;
37*40b24d9cSShengjiu Wang struct device *dev;
380205fae6SShengjiu Wang };
390205fae6SShengjiu Wang
imx8mp_hdmi_pai_enable(struct dw_hdmi * dw_hdmi,int channel,int width,int rate,int non_pcm,int iec958)400205fae6SShengjiu Wang static void imx8mp_hdmi_pai_enable(struct dw_hdmi *dw_hdmi, int channel,
410205fae6SShengjiu Wang int width, int rate, int non_pcm,
420205fae6SShengjiu Wang int iec958)
430205fae6SShengjiu Wang {
440205fae6SShengjiu Wang const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi);
450205fae6SShengjiu Wang struct imx8mp_hdmi_pai *hdmi_pai = pdata->priv_audio;
460205fae6SShengjiu Wang int val;
470205fae6SShengjiu Wang
48*40b24d9cSShengjiu Wang if (pm_runtime_resume_and_get(hdmi_pai->dev) < 0)
49*40b24d9cSShengjiu Wang return;
50*40b24d9cSShengjiu Wang
510205fae6SShengjiu Wang /* PAI set control extended */
520205fae6SShengjiu Wang val = WTMK_HIGH(3) | WTMK_LOW(3);
530205fae6SShengjiu Wang val |= NUM_CH(channel);
540205fae6SShengjiu Wang regmap_write(hdmi_pai->regmap, HTX_PAI_CTRL_EXT, val);
550205fae6SShengjiu Wang
560205fae6SShengjiu Wang /* IEC60958 format */
570205fae6SShengjiu Wang if (iec958) {
580205fae6SShengjiu Wang val = FIELD_PREP_CONST(P_SEL,
590205fae6SShengjiu Wang __bf_shf(IEC958_SUBFRAME_PARITY));
600205fae6SShengjiu Wang val |= FIELD_PREP_CONST(C_SEL,
610205fae6SShengjiu Wang __bf_shf(IEC958_SUBFRAME_CHANNEL_STATUS));
620205fae6SShengjiu Wang val |= FIELD_PREP_CONST(U_SEL,
630205fae6SShengjiu Wang __bf_shf(IEC958_SUBFRAME_USER_DATA));
640205fae6SShengjiu Wang val |= FIELD_PREP_CONST(V_SEL,
650205fae6SShengjiu Wang __bf_shf(IEC958_SUBFRAME_VALIDITY));
660205fae6SShengjiu Wang val |= FIELD_PREP_CONST(D_SEL,
670205fae6SShengjiu Wang __bf_shf(IEC958_SUBFRAME_SAMPLE_24_MASK));
680205fae6SShengjiu Wang val |= FIELD_PREP_CONST(PRE_SEL,
690205fae6SShengjiu Wang __bf_shf(IEC958_SUBFRAME_PREAMBLE_MASK));
700205fae6SShengjiu Wang } else {
710205fae6SShengjiu Wang /*
720205fae6SShengjiu Wang * The allowed PCM widths are 24bit and 32bit, as they are supported
730205fae6SShengjiu Wang * by aud2htx module.
740205fae6SShengjiu Wang * for 24bit, D_SEL = 0, select all the bits.
750205fae6SShengjiu Wang * for 32bit, D_SEL = 8, select 24bit in MSB.
760205fae6SShengjiu Wang */
770205fae6SShengjiu Wang val = FIELD_PREP(D_SEL, width - 24);
780205fae6SShengjiu Wang }
790205fae6SShengjiu Wang
800205fae6SShengjiu Wang regmap_write(hdmi_pai->regmap, HTX_PAI_FIELD_CTRL, val);
810205fae6SShengjiu Wang
820205fae6SShengjiu Wang /* PAI start running */
830205fae6SShengjiu Wang regmap_write(hdmi_pai->regmap, HTX_PAI_CTRL, ENABLE);
840205fae6SShengjiu Wang }
850205fae6SShengjiu Wang
imx8mp_hdmi_pai_disable(struct dw_hdmi * dw_hdmi)860205fae6SShengjiu Wang static void imx8mp_hdmi_pai_disable(struct dw_hdmi *dw_hdmi)
870205fae6SShengjiu Wang {
880205fae6SShengjiu Wang const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi);
890205fae6SShengjiu Wang struct imx8mp_hdmi_pai *hdmi_pai = pdata->priv_audio;
900205fae6SShengjiu Wang
910205fae6SShengjiu Wang /* Stop PAI */
920205fae6SShengjiu Wang regmap_write(hdmi_pai->regmap, HTX_PAI_CTRL, 0);
93*40b24d9cSShengjiu Wang
94*40b24d9cSShengjiu Wang pm_runtime_put_sync(hdmi_pai->dev);
950205fae6SShengjiu Wang }
960205fae6SShengjiu Wang
970205fae6SShengjiu Wang static const struct regmap_config imx8mp_hdmi_pai_regmap_config = {
980205fae6SShengjiu Wang .reg_bits = 32,
990205fae6SShengjiu Wang .reg_stride = 4,
1000205fae6SShengjiu Wang .val_bits = 32,
1010205fae6SShengjiu Wang .max_register = HTX_PAI_FIELD_CTRL,
1020205fae6SShengjiu Wang };
1030205fae6SShengjiu Wang
imx8mp_hdmi_pai_bind(struct device * dev,struct device * master,void * data)1040205fae6SShengjiu Wang static int imx8mp_hdmi_pai_bind(struct device *dev, struct device *master, void *data)
1050205fae6SShengjiu Wang {
1060205fae6SShengjiu Wang struct platform_device *pdev = to_platform_device(dev);
1070205fae6SShengjiu Wang struct dw_hdmi_plat_data *plat_data = data;
1080205fae6SShengjiu Wang struct imx8mp_hdmi_pai *hdmi_pai;
1090205fae6SShengjiu Wang struct resource *res;
1100205fae6SShengjiu Wang void __iomem *base;
111*40b24d9cSShengjiu Wang int ret;
1120205fae6SShengjiu Wang
1130205fae6SShengjiu Wang hdmi_pai = devm_kzalloc(dev, sizeof(*hdmi_pai), GFP_KERNEL);
1140205fae6SShengjiu Wang if (!hdmi_pai)
1150205fae6SShengjiu Wang return -ENOMEM;
1160205fae6SShengjiu Wang
1170205fae6SShengjiu Wang base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
1180205fae6SShengjiu Wang if (IS_ERR(base))
1190205fae6SShengjiu Wang return PTR_ERR(base);
1200205fae6SShengjiu Wang
1210205fae6SShengjiu Wang hdmi_pai->regmap = devm_regmap_init_mmio_clk(dev, "apb", base,
1220205fae6SShengjiu Wang &imx8mp_hdmi_pai_regmap_config);
1230205fae6SShengjiu Wang if (IS_ERR(hdmi_pai->regmap)) {
1240205fae6SShengjiu Wang dev_err(dev, "regmap init failed\n");
1250205fae6SShengjiu Wang return PTR_ERR(hdmi_pai->regmap);
1260205fae6SShengjiu Wang }
1270205fae6SShengjiu Wang
1280205fae6SShengjiu Wang plat_data->enable_audio = imx8mp_hdmi_pai_enable;
1290205fae6SShengjiu Wang plat_data->disable_audio = imx8mp_hdmi_pai_disable;
1300205fae6SShengjiu Wang plat_data->priv_audio = hdmi_pai;
1310205fae6SShengjiu Wang
132*40b24d9cSShengjiu Wang hdmi_pai->dev = dev;
133*40b24d9cSShengjiu Wang ret = devm_pm_runtime_enable(dev);
134*40b24d9cSShengjiu Wang if (ret < 0) {
135*40b24d9cSShengjiu Wang dev_err(dev, "failed to enable PM runtime: %d\n", ret);
136*40b24d9cSShengjiu Wang return ret;
137*40b24d9cSShengjiu Wang }
138*40b24d9cSShengjiu Wang
1390205fae6SShengjiu Wang return 0;
1400205fae6SShengjiu Wang }
1410205fae6SShengjiu Wang
1420205fae6SShengjiu Wang static const struct component_ops imx8mp_hdmi_pai_ops = {
1430205fae6SShengjiu Wang .bind = imx8mp_hdmi_pai_bind,
1440205fae6SShengjiu Wang };
1450205fae6SShengjiu Wang
imx8mp_hdmi_pai_probe(struct platform_device * pdev)1460205fae6SShengjiu Wang static int imx8mp_hdmi_pai_probe(struct platform_device *pdev)
1470205fae6SShengjiu Wang {
1480205fae6SShengjiu Wang return component_add(&pdev->dev, &imx8mp_hdmi_pai_ops);
1490205fae6SShengjiu Wang }
1500205fae6SShengjiu Wang
imx8mp_hdmi_pai_remove(struct platform_device * pdev)1510205fae6SShengjiu Wang static void imx8mp_hdmi_pai_remove(struct platform_device *pdev)
1520205fae6SShengjiu Wang {
1530205fae6SShengjiu Wang component_del(&pdev->dev, &imx8mp_hdmi_pai_ops);
1540205fae6SShengjiu Wang }
1550205fae6SShengjiu Wang
1560205fae6SShengjiu Wang static const struct of_device_id imx8mp_hdmi_pai_of_table[] = {
1570205fae6SShengjiu Wang { .compatible = "fsl,imx8mp-hdmi-pai" },
1580205fae6SShengjiu Wang { /* Sentinel */ }
1590205fae6SShengjiu Wang };
1600205fae6SShengjiu Wang MODULE_DEVICE_TABLE(of, imx8mp_hdmi_pai_of_table);
1610205fae6SShengjiu Wang
1620205fae6SShengjiu Wang static struct platform_driver imx8mp_hdmi_pai_platform_driver = {
1630205fae6SShengjiu Wang .probe = imx8mp_hdmi_pai_probe,
1640205fae6SShengjiu Wang .remove = imx8mp_hdmi_pai_remove,
1650205fae6SShengjiu Wang .driver = {
1660205fae6SShengjiu Wang .name = "imx8mp-hdmi-pai",
1670205fae6SShengjiu Wang .of_match_table = imx8mp_hdmi_pai_of_table,
1680205fae6SShengjiu Wang },
1690205fae6SShengjiu Wang };
1700205fae6SShengjiu Wang module_platform_driver(imx8mp_hdmi_pai_platform_driver);
1710205fae6SShengjiu Wang
1720205fae6SShengjiu Wang MODULE_DESCRIPTION("i.MX8MP HDMI PAI driver");
1730205fae6SShengjiu Wang MODULE_LICENSE("GPL");
174