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

drm: bridge: dw_hdmi: Audio: Add General Parallel Audio (GPA) driver

General Parallel Audio (GPA) interface is one of the supported
audio interface for synopsys HDMI module, which has verified for
i.MX8MPlus platform.
This is initial version for GPA.

Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
Signed-off-by: Sandor Yu <Sandor.yu@nxp.com>
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
Signed-off-by: Robert Foss <robert.foss@linaro.org>
Link: https://patchwork.freedesktop.org/patch/msgid/f21ba3e8c4d9d028ac74c6f3c588ddbffe739399.1649989179.git.Sandor.yu@nxp.com

authored by

Sandor Yu and committed by
Robert Foss
d970ce30 8fb241e2

+358 -4
+10
drivers/gpu/drm/bridge/synopsys/Kconfig
··· 25 25 Support the I2S Audio interface which is part of the Synopsys 26 26 Designware HDMI block. 27 27 28 + config DRM_DW_HDMI_GP_AUDIO 29 + tristate "Synopsys Designware GP Audio interface" 30 + depends on DRM_DW_HDMI && SND 31 + select SND_PCM 32 + select SND_PCM_ELD 33 + select SND_PCM_IEC958 34 + help 35 + Support the GP Audio interface which is part of the Synopsys 36 + Designware HDMI block. 37 + 28 38 config DRM_DW_HDMI_CEC 29 39 tristate "Synopsis Designware CEC interface" 30 40 depends on DRM_DW_HDMI
+1
drivers/gpu/drm/bridge/synopsys/Makefile
··· 1 1 # SPDX-License-Identifier: GPL-2.0-only 2 2 obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o 3 3 obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o 4 + obj-$(CONFIG_DRM_DW_HDMI_GP_AUDIO) += dw-hdmi-gp-audio.o 4 5 obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o 5 6 obj-$(CONFIG_DRM_DW_HDMI_CEC) += dw-hdmi-cec.o 6 7
+199
drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c
··· 1 + // SPDX-License-Identifier: (GPL-2.0+ OR MIT) 2 + /* 3 + * dw-hdmi-gp-audio.c 4 + * 5 + * Copyright 2020-2022 NXP 6 + */ 7 + #include <linux/io.h> 8 + #include <linux/interrupt.h> 9 + #include <linux/module.h> 10 + #include <linux/platform_device.h> 11 + #include <linux/dmaengine.h> 12 + #include <linux/dma-mapping.h> 13 + #include <drm/bridge/dw_hdmi.h> 14 + #include <drm/drm_edid.h> 15 + #include <drm/drm_connector.h> 16 + 17 + #include <sound/hdmi-codec.h> 18 + #include <sound/asoundef.h> 19 + #include <sound/core.h> 20 + #include <sound/initval.h> 21 + #include <sound/pcm.h> 22 + #include <sound/pcm_drm_eld.h> 23 + #include <sound/pcm_iec958.h> 24 + #include <sound/dmaengine_pcm.h> 25 + 26 + #include "dw-hdmi-audio.h" 27 + 28 + #define DRIVER_NAME "dw-hdmi-gp-audio" 29 + #define DRV_NAME "hdmi-gp-audio" 30 + 31 + struct snd_dw_hdmi { 32 + struct dw_hdmi_audio_data data; 33 + struct platform_device *audio_pdev; 34 + unsigned int pos; 35 + }; 36 + 37 + struct dw_hdmi_channel_conf { 38 + u8 conf1; 39 + u8 ca; 40 + }; 41 + 42 + /* 43 + * The default mapping of ALSA channels to HDMI channels and speaker 44 + * allocation bits. Note that we can't do channel remapping here - 45 + * channels must be in the same order. 46 + * 47 + * Mappings for alsa-lib pcm/surround*.conf files: 48 + * 49 + * Front Sur4.0 Sur4.1 Sur5.0 Sur5.1 Sur7.1 50 + * Channels 2 4 6 6 6 8 51 + * 52 + * Our mapping from ALSA channel to CEA686D speaker name and HDMI channel: 53 + * 54 + * Number of ALSA channels 55 + * ALSA Channel 2 3 4 5 6 7 8 56 + * 0 FL:0 = = = = = = 57 + * 1 FR:1 = = = = = = 58 + * 2 FC:3 RL:4 LFE:2 = = = 59 + * 3 RR:5 RL:4 FC:3 = = 60 + * 4 RR:5 RL:4 = = 61 + * 5 RR:5 = = 62 + * 6 RC:6 = 63 + * 7 RLC/FRC RLC/FRC 64 + */ 65 + static struct dw_hdmi_channel_conf default_hdmi_channel_config[7] = { 66 + { 0x03, 0x00 }, /* FL,FR */ 67 + { 0x0b, 0x02 }, /* FL,FR,FC */ 68 + { 0x33, 0x08 }, /* FL,FR,RL,RR */ 69 + { 0x37, 0x09 }, /* FL,FR,LFE,RL,RR */ 70 + { 0x3f, 0x0b }, /* FL,FR,LFE,FC,RL,RR */ 71 + { 0x7f, 0x0f }, /* FL,FR,LFE,FC,RL,RR,RC */ 72 + { 0xff, 0x13 }, /* FL,FR,LFE,FC,RL,RR,[FR]RC,[FR]LC */ 73 + }; 74 + 75 + static int audio_hw_params(struct device *dev, void *data, 76 + struct hdmi_codec_daifmt *daifmt, 77 + struct hdmi_codec_params *params) 78 + { 79 + struct snd_dw_hdmi *dw = dev_get_drvdata(dev); 80 + int ret = 0; 81 + u8 ca; 82 + 83 + dw_hdmi_set_sample_rate(dw->data.hdmi, params->sample_rate); 84 + 85 + ca = default_hdmi_channel_config[params->channels - 2].ca; 86 + 87 + dw_hdmi_set_channel_count(dw->data.hdmi, params->channels); 88 + dw_hdmi_set_channel_allocation(dw->data.hdmi, ca); 89 + 90 + dw_hdmi_set_sample_non_pcm(dw->data.hdmi, 91 + params->iec.status[0] & IEC958_AES0_NONAUDIO); 92 + dw_hdmi_set_sample_width(dw->data.hdmi, params->sample_width); 93 + 94 + return ret; 95 + } 96 + 97 + static void audio_shutdown(struct device *dev, void *data) 98 + { 99 + } 100 + 101 + static int audio_mute_stream(struct device *dev, void *data, 102 + bool enable, int direction) 103 + { 104 + struct snd_dw_hdmi *dw = dev_get_drvdata(dev); 105 + int ret = 0; 106 + 107 + if (!enable) 108 + dw_hdmi_audio_enable(dw->data.hdmi); 109 + else 110 + dw_hdmi_audio_disable(dw->data.hdmi); 111 + 112 + return ret; 113 + } 114 + 115 + static int audio_get_eld(struct device *dev, void *data, 116 + u8 *buf, size_t len) 117 + { 118 + struct dw_hdmi_audio_data *audio = data; 119 + u8 *eld; 120 + 121 + eld = audio->get_eld(audio->hdmi); 122 + if (eld) 123 + memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len)); 124 + else 125 + /* Pass en empty ELD if connector not available */ 126 + memset(buf, 0, len); 127 + 128 + return 0; 129 + } 130 + 131 + static int audio_hook_plugged_cb(struct device *dev, void *data, 132 + hdmi_codec_plugged_cb fn, 133 + struct device *codec_dev) 134 + { 135 + struct snd_dw_hdmi *dw = dev_get_drvdata(dev); 136 + 137 + return dw_hdmi_set_plugged_cb(dw->data.hdmi, fn, codec_dev); 138 + } 139 + 140 + static const struct hdmi_codec_ops audio_codec_ops = { 141 + .hw_params = audio_hw_params, 142 + .audio_shutdown = audio_shutdown, 143 + .mute_stream = audio_mute_stream, 144 + .get_eld = audio_get_eld, 145 + .hook_plugged_cb = audio_hook_plugged_cb, 146 + }; 147 + 148 + static int snd_dw_hdmi_probe(struct platform_device *pdev) 149 + { 150 + struct dw_hdmi_audio_data *data = pdev->dev.platform_data; 151 + struct snd_dw_hdmi *dw; 152 + 153 + const struct hdmi_codec_pdata codec_data = { 154 + .i2s = 1, 155 + .spdif = 0, 156 + .ops = &audio_codec_ops, 157 + .max_i2s_channels = 8, 158 + .data = data, 159 + }; 160 + 161 + dw = devm_kzalloc(&pdev->dev, sizeof(*dw), GFP_KERNEL); 162 + if (!dw) 163 + return -ENOMEM; 164 + 165 + dw->data = *data; 166 + 167 + platform_set_drvdata(pdev, dw); 168 + 169 + dw->audio_pdev = platform_device_register_data(&pdev->dev, 170 + HDMI_CODEC_DRV_NAME, 1, 171 + &codec_data, 172 + sizeof(codec_data)); 173 + 174 + return PTR_ERR_OR_ZERO(dw->audio_pdev); 175 + } 176 + 177 + static int snd_dw_hdmi_remove(struct platform_device *pdev) 178 + { 179 + struct snd_dw_hdmi *dw = platform_get_drvdata(pdev); 180 + 181 + platform_device_unregister(dw->audio_pdev); 182 + 183 + return 0; 184 + } 185 + 186 + static struct platform_driver snd_dw_hdmi_driver = { 187 + .probe = snd_dw_hdmi_probe, 188 + .remove = snd_dw_hdmi_remove, 189 + .driver = { 190 + .name = DRIVER_NAME, 191 + }, 192 + }; 193 + 194 + module_platform_driver(snd_dw_hdmi_driver); 195 + 196 + MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>"); 197 + MODULE_DESCRIPTION("Synopsys Designware HDMI GPA ALSA interface"); 198 + MODULE_LICENSE("GPL"); 199 + MODULE_ALIAS("platform:" DRIVER_NAME);
+130 -2
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
··· 191 191 192 192 spinlock_t audio_lock; 193 193 struct mutex audio_mutex; 194 + unsigned int sample_non_pcm; 195 + unsigned int sample_width; 194 196 unsigned int sample_rate; 197 + unsigned int channels; 195 198 unsigned int audio_cts; 196 199 unsigned int audio_n; 197 200 bool audio_enable; ··· 592 589 n = 4096; 593 590 else if (pixel_clk == 74176000 || pixel_clk == 148352000) 594 591 n = 11648; 592 + else if (pixel_clk == 297000000) 593 + n = 3072; 595 594 else 596 595 n = 4096; 597 596 n *= mult; ··· 606 601 n = 17836; 607 602 else if (pixel_clk == 148352000) 608 603 n = 8918; 604 + else if (pixel_clk == 297000000) 605 + n = 4704; 609 606 else 610 607 n = 6272; 611 608 n *= mult; ··· 622 615 n = 11648; 623 616 else if (pixel_clk == 148352000) 624 617 n = 5824; 618 + else if (pixel_clk == 297000000) 619 + n = 5120; 625 620 else 626 621 n = 6144; 627 622 n *= mult; ··· 668 659 669 660 config3 = hdmi_readb(hdmi, HDMI_CONFIG3_ID); 670 661 671 - /* Only compute CTS when using internal AHB audio */ 672 - if (config3 & HDMI_CONFIG3_AHBAUDDMA) { 662 + /* Compute CTS when using internal AHB audio or General Parallel audio*/ 663 + if ((config3 & HDMI_CONFIG3_AHBAUDDMA) || (config3 & HDMI_CONFIG3_GPAUD)) { 673 664 /* 674 665 * Compute the CTS value from the N value. Note that CTS and N 675 666 * can be up to 20 bits in total, so we need 64-bit math. Also ··· 711 702 mutex_unlock(&hdmi->audio_mutex); 712 703 } 713 704 705 + void dw_hdmi_set_sample_width(struct dw_hdmi *hdmi, unsigned int width) 706 + { 707 + mutex_lock(&hdmi->audio_mutex); 708 + hdmi->sample_width = width; 709 + mutex_unlock(&hdmi->audio_mutex); 710 + } 711 + EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_width); 712 + 713 + void dw_hdmi_set_sample_non_pcm(struct dw_hdmi *hdmi, unsigned int non_pcm) 714 + { 715 + mutex_lock(&hdmi->audio_mutex); 716 + hdmi->sample_non_pcm = non_pcm; 717 + mutex_unlock(&hdmi->audio_mutex); 718 + } 719 + EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_non_pcm); 720 + 714 721 void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate) 715 722 { 716 723 mutex_lock(&hdmi->audio_mutex); ··· 742 717 u8 layout; 743 718 744 719 mutex_lock(&hdmi->audio_mutex); 720 + hdmi->channels = cnt; 745 721 746 722 /* 747 723 * For >2 channel PCM audio, we need to select layout 1 ··· 789 763 return NULL; 790 764 791 765 return hdmi->curr_conn->eld; 766 + } 767 + 768 + static void dw_hdmi_gp_audio_enable(struct dw_hdmi *hdmi) 769 + { 770 + const struct dw_hdmi_plat_data *pdata = hdmi->plat_data; 771 + int sample_freq = 0x2, org_sample_freq = 0xD; 772 + int ch_mask = BIT(hdmi->channels) - 1; 773 + 774 + switch (hdmi->sample_rate) { 775 + case 32000: 776 + sample_freq = 0x03; 777 + org_sample_freq = 0x0C; 778 + break; 779 + case 44100: 780 + sample_freq = 0x00; 781 + org_sample_freq = 0x0F; 782 + break; 783 + case 48000: 784 + sample_freq = 0x02; 785 + org_sample_freq = 0x0D; 786 + break; 787 + case 88200: 788 + sample_freq = 0x08; 789 + org_sample_freq = 0x07; 790 + break; 791 + case 96000: 792 + sample_freq = 0x0A; 793 + org_sample_freq = 0x05; 794 + break; 795 + case 176400: 796 + sample_freq = 0x0C; 797 + org_sample_freq = 0x03; 798 + break; 799 + case 192000: 800 + sample_freq = 0x0E; 801 + org_sample_freq = 0x01; 802 + break; 803 + default: 804 + break; 805 + } 806 + 807 + hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); 808 + hdmi_enable_audio_clk(hdmi, true); 809 + 810 + hdmi_writeb(hdmi, 0x1, HDMI_FC_AUDSCHNLS0); 811 + hdmi_writeb(hdmi, hdmi->channels, HDMI_FC_AUDSCHNLS2); 812 + hdmi_writeb(hdmi, 0x22, HDMI_FC_AUDSCHNLS3); 813 + hdmi_writeb(hdmi, 0x22, HDMI_FC_AUDSCHNLS4); 814 + hdmi_writeb(hdmi, 0x11, HDMI_FC_AUDSCHNLS5); 815 + hdmi_writeb(hdmi, 0x11, HDMI_FC_AUDSCHNLS6); 816 + hdmi_writeb(hdmi, (0x3 << 4) | sample_freq, HDMI_FC_AUDSCHNLS7); 817 + hdmi_writeb(hdmi, (org_sample_freq << 4) | 0xb, HDMI_FC_AUDSCHNLS8); 818 + 819 + hdmi_writeb(hdmi, ch_mask, HDMI_GP_CONF1); 820 + hdmi_writeb(hdmi, 0x02, HDMI_GP_CONF2); 821 + hdmi_writeb(hdmi, 0x01, HDMI_GP_CONF0); 822 + 823 + hdmi_modb(hdmi, 0x3, 0x3, HDMI_FC_DATAUTO3); 824 + 825 + /* hbr */ 826 + if (hdmi->sample_rate == 192000 && hdmi->channels == 8 && 827 + hdmi->sample_width == 32 && hdmi->sample_non_pcm) 828 + hdmi_modb(hdmi, 0x01, 0x01, HDMI_GP_CONF2); 829 + 830 + if (pdata->enable_audio) 831 + pdata->enable_audio(hdmi, 832 + hdmi->channels, 833 + hdmi->sample_width, 834 + hdmi->sample_rate, 835 + hdmi->sample_non_pcm); 836 + } 837 + 838 + static void dw_hdmi_gp_audio_disable(struct dw_hdmi *hdmi) 839 + { 840 + const struct dw_hdmi_plat_data *pdata = hdmi->plat_data; 841 + 842 + hdmi_set_cts_n(hdmi, hdmi->audio_cts, 0); 843 + 844 + hdmi_modb(hdmi, 0, 0x3, HDMI_FC_DATAUTO3); 845 + if (pdata->disable_audio) 846 + pdata->disable_audio(hdmi); 847 + 848 + hdmi_enable_audio_clk(hdmi, false); 792 849 } 793 850 794 851 static void dw_hdmi_ahb_audio_enable(struct dw_hdmi *hdmi) ··· 3367 3258 hdmi->plat_data = plat_data; 3368 3259 hdmi->dev = dev; 3369 3260 hdmi->sample_rate = 48000; 3261 + hdmi->channels = 2; 3370 3262 hdmi->disabled = true; 3371 3263 hdmi->rxsense = true; 3372 3264 hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE); ··· 3587 3477 hdmi->disable_audio = dw_hdmi_i2s_audio_disable; 3588 3478 3589 3479 pdevinfo.name = "dw-hdmi-i2s-audio"; 3480 + pdevinfo.data = &audio; 3481 + pdevinfo.size_data = sizeof(audio); 3482 + pdevinfo.dma_mask = DMA_BIT_MASK(32); 3483 + hdmi->audio = platform_device_register_full(&pdevinfo); 3484 + } else if (iores && config3 & HDMI_CONFIG3_GPAUD) { 3485 + struct dw_hdmi_audio_data audio; 3486 + 3487 + audio.phys = iores->start; 3488 + audio.base = hdmi->regs; 3489 + audio.irq = irq; 3490 + audio.hdmi = hdmi; 3491 + audio.get_eld = hdmi_audio_get_eld; 3492 + 3493 + hdmi->enable_audio = dw_hdmi_gp_audio_enable; 3494 + hdmi->disable_audio = dw_hdmi_gp_audio_disable; 3495 + 3496 + pdevinfo.name = "dw-hdmi-gp-audio"; 3497 + pdevinfo.id = PLATFORM_DEVID_NONE; 3590 3498 pdevinfo.data = &audio; 3591 3499 pdevinfo.size_data = sizeof(audio); 3592 3500 pdevinfo.dma_mask = DMA_BIT_MASK(32);
+11 -2
drivers/gpu/drm/bridge/synopsys/dw-hdmi.h
··· 158 158 #define HDMI_FC_SPDDEVICEINF 0x1062 159 159 #define HDMI_FC_AUDSCONF 0x1063 160 160 #define HDMI_FC_AUDSSTAT 0x1064 161 - #define HDMI_FC_AUDSCHNLS7 0x106e 162 - #define HDMI_FC_AUDSCHNLS8 0x106f 161 + #define HDMI_FC_AUDSV 0x1065 162 + #define HDMI_FC_AUDSU 0x1066 163 + #define HDMI_FC_AUDSCHNLS0 0x1067 164 + #define HDMI_FC_AUDSCHNLS1 0x1068 165 + #define HDMI_FC_AUDSCHNLS2 0x1069 166 + #define HDMI_FC_AUDSCHNLS3 0x106A 167 + #define HDMI_FC_AUDSCHNLS4 0x106B 168 + #define HDMI_FC_AUDSCHNLS5 0x106C 169 + #define HDMI_FC_AUDSCHNLS6 0x106D 170 + #define HDMI_FC_AUDSCHNLS7 0x106E 171 + #define HDMI_FC_AUDSCHNLS8 0x106F 163 172 #define HDMI_FC_DATACH0FILL 0x1070 164 173 #define HDMI_FC_DATACH1FILL 0x1071 165 174 #define HDMI_FC_DATACH2FILL 0x1072
+7
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 + /* Platform-specific audio enable/disable (optional) */ 147 + void (*enable_audio)(struct dw_hdmi *hdmi, int channel, 148 + int width, int rate, int non_pcm); 149 + void (*disable_audio)(struct dw_hdmi *hdmi); 150 + 146 151 /* Vendor PHY support */ 147 152 const struct dw_hdmi_phy_ops *phy_ops; 148 153 const char *phy_name; ··· 178 173 179 174 int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn, 180 175 struct device *codec_dev); 176 + void dw_hdmi_set_sample_non_pcm(struct dw_hdmi *hdmi, unsigned int non_pcm); 177 + void dw_hdmi_set_sample_width(struct dw_hdmi *hdmi, unsigned int width); 181 178 void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate); 182 179 void dw_hdmi_set_channel_count(struct dw_hdmi *hdmi, unsigned int cnt); 183 180 void dw_hdmi_set_channel_status(struct dw_hdmi *hdmi, u8 *channel_status);