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 u8 ca; 81 82 dw_hdmi_set_sample_rate(dw->data.hdmi, params->sample_rate); 83 84 ca = default_hdmi_channel_config[params->channels - 2].ca; 85 86 dw_hdmi_set_channel_count(dw->data.hdmi, params->channels); 87 dw_hdmi_set_channel_allocation(dw->data.hdmi, ca); 88 89 dw_hdmi_set_sample_non_pcm(dw->data.hdmi, 90 params->iec.status[0] & IEC958_AES0_NONAUDIO); 91 dw_hdmi_set_sample_width(dw->data.hdmi, params->sample_width); 92 93 if (daifmt->bit_fmt == SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE) 94 dw_hdmi_set_sample_iec958(dw->data.hdmi, 1); 95 else 96 dw_hdmi_set_sample_iec958(dw->data.hdmi, 0); 97 98 return 0; 99 } 100 101 static void audio_shutdown(struct device *dev, void *data) 102 { 103 } 104 105 static int audio_mute_stream(struct device *dev, void *data, 106 bool enable, int direction) 107 { 108 struct snd_dw_hdmi *dw = dev_get_drvdata(dev); 109 110 if (!enable) 111 dw_hdmi_audio_enable(dw->data.hdmi); 112 else 113 dw_hdmi_audio_disable(dw->data.hdmi); 114 115 return 0; 116 } 117 118 static int audio_get_eld(struct device *dev, void *data, 119 u8 *buf, size_t len) 120 { 121 struct dw_hdmi_audio_data *audio = data; 122 u8 *eld; 123 124 eld = audio->get_eld(audio->hdmi); 125 if (eld) 126 memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len)); 127 else 128 /* Pass en empty ELD if connector not available */ 129 memset(buf, 0, len); 130 131 return 0; 132 } 133 134 static int audio_hook_plugged_cb(struct device *dev, void *data, 135 hdmi_codec_plugged_cb fn, 136 struct device *codec_dev) 137 { 138 struct snd_dw_hdmi *dw = dev_get_drvdata(dev); 139 140 return dw_hdmi_set_plugged_cb(dw->data.hdmi, fn, codec_dev); 141 } 142 143 static const struct hdmi_codec_ops audio_codec_ops = { 144 .hw_params = audio_hw_params, 145 .audio_shutdown = audio_shutdown, 146 .mute_stream = audio_mute_stream, 147 .get_eld = audio_get_eld, 148 .hook_plugged_cb = audio_hook_plugged_cb, 149 }; 150 151 static int snd_dw_hdmi_probe(struct platform_device *pdev) 152 { 153 struct dw_hdmi_audio_data *data = pdev->dev.platform_data; 154 struct snd_dw_hdmi *dw; 155 156 const struct hdmi_codec_pdata codec_data = { 157 .i2s = 1, 158 .spdif = 0, 159 .ops = &audio_codec_ops, 160 .max_i2s_channels = 8, 161 .data = data, 162 }; 163 164 dw = devm_kzalloc(&pdev->dev, sizeof(*dw), GFP_KERNEL); 165 if (!dw) 166 return -ENOMEM; 167 168 dw->data = *data; 169 170 platform_set_drvdata(pdev, dw); 171 172 dw->audio_pdev = platform_device_register_data(&pdev->dev, 173 HDMI_CODEC_DRV_NAME, 1, 174 &codec_data, 175 sizeof(codec_data)); 176 177 return PTR_ERR_OR_ZERO(dw->audio_pdev); 178 } 179 180 static void snd_dw_hdmi_remove(struct platform_device *pdev) 181 { 182 struct snd_dw_hdmi *dw = platform_get_drvdata(pdev); 183 184 platform_device_unregister(dw->audio_pdev); 185 } 186 187 static struct platform_driver snd_dw_hdmi_driver = { 188 .probe = snd_dw_hdmi_probe, 189 .remove = snd_dw_hdmi_remove, 190 .driver = { 191 .name = DRIVER_NAME, 192 }, 193 }; 194 195 module_platform_driver(snd_dw_hdmi_driver); 196 197 MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>"); 198 MODULE_DESCRIPTION("Synopsys Designware HDMI GPA ALSA interface"); 199 MODULE_LICENSE("GPL"); 200 MODULE_ALIAS("platform:" DRIVER_NAME); 201