// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * dw-hdmi-gp-audio.c * * Copyright 2020-2022 NXP */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dw-hdmi-audio.h" #define DRIVER_NAME "dw-hdmi-gp-audio" #define DRV_NAME "hdmi-gp-audio" struct snd_dw_hdmi { struct dw_hdmi_audio_data data; struct platform_device *audio_pdev; unsigned int pos; }; struct dw_hdmi_channel_conf { u8 conf1; u8 ca; }; /* * The default mapping of ALSA channels to HDMI channels and speaker * allocation bits. Note that we can't do channel remapping here - * channels must be in the same order. * * Mappings for alsa-lib pcm/surround*.conf files: * * Front Sur4.0 Sur4.1 Sur5.0 Sur5.1 Sur7.1 * Channels 2 4 6 6 6 8 * * Our mapping from ALSA channel to CEA686D speaker name and HDMI channel: * * Number of ALSA channels * ALSA Channel 2 3 4 5 6 7 8 * 0 FL:0 = = = = = = * 1 FR:1 = = = = = = * 2 FC:3 RL:4 LFE:2 = = = * 3 RR:5 RL:4 FC:3 = = * 4 RR:5 RL:4 = = * 5 RR:5 = = * 6 RC:6 = * 7 RLC/FRC RLC/FRC */ static struct dw_hdmi_channel_conf default_hdmi_channel_config[7] = { { 0x03, 0x00 }, /* FL,FR */ { 0x0b, 0x02 }, /* FL,FR,FC */ { 0x33, 0x08 }, /* FL,FR,RL,RR */ { 0x37, 0x09 }, /* FL,FR,LFE,RL,RR */ { 0x3f, 0x0b }, /* FL,FR,LFE,FC,RL,RR */ { 0x7f, 0x0f }, /* FL,FR,LFE,FC,RL,RR,RC */ { 0xff, 0x13 }, /* FL,FR,LFE,FC,RL,RR,[FR]RC,[FR]LC */ }; static int audio_hw_params(struct device *dev, void *data, struct hdmi_codec_daifmt *daifmt, struct hdmi_codec_params *params) { struct snd_dw_hdmi *dw = dev_get_drvdata(dev); u8 ca; dw_hdmi_set_sample_rate(dw->data.hdmi, params->sample_rate); ca = default_hdmi_channel_config[params->channels - 2].ca; dw_hdmi_set_channel_count(dw->data.hdmi, params->channels); dw_hdmi_set_channel_allocation(dw->data.hdmi, ca); dw_hdmi_set_sample_non_pcm(dw->data.hdmi, params->iec.status[0] & IEC958_AES0_NONAUDIO); dw_hdmi_set_sample_width(dw->data.hdmi, params->sample_width); return 0; } static void audio_shutdown(struct device *dev, void *data) { } static int audio_mute_stream(struct device *dev, void *data, bool enable, int direction) { struct snd_dw_hdmi *dw = dev_get_drvdata(dev); if (!enable) dw_hdmi_audio_enable(dw->data.hdmi); else dw_hdmi_audio_disable(dw->data.hdmi); return 0; } static int audio_get_eld(struct device *dev, void *data, u8 *buf, size_t len) { struct dw_hdmi_audio_data *audio = data; u8 *eld; eld = audio->get_eld(audio->hdmi); if (eld) memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len)); else /* Pass en empty ELD if connector not available */ memset(buf, 0, len); return 0; } static int audio_hook_plugged_cb(struct device *dev, void *data, hdmi_codec_plugged_cb fn, struct device *codec_dev) { struct snd_dw_hdmi *dw = dev_get_drvdata(dev); return dw_hdmi_set_plugged_cb(dw->data.hdmi, fn, codec_dev); } static const struct hdmi_codec_ops audio_codec_ops = { .hw_params = audio_hw_params, .audio_shutdown = audio_shutdown, .mute_stream = audio_mute_stream, .get_eld = audio_get_eld, .hook_plugged_cb = audio_hook_plugged_cb, }; static int snd_dw_hdmi_probe(struct platform_device *pdev) { struct dw_hdmi_audio_data *data = pdev->dev.platform_data; struct snd_dw_hdmi *dw; const struct hdmi_codec_pdata codec_data = { .i2s = 1, .spdif = 0, .ops = &audio_codec_ops, .max_i2s_channels = 8, .data = data, }; dw = devm_kzalloc(&pdev->dev, sizeof(*dw), GFP_KERNEL); if (!dw) return -ENOMEM; dw->data = *data; platform_set_drvdata(pdev, dw); dw->audio_pdev = platform_device_register_data(&pdev->dev, HDMI_CODEC_DRV_NAME, 1, &codec_data, sizeof(codec_data)); return PTR_ERR_OR_ZERO(dw->audio_pdev); } static void snd_dw_hdmi_remove(struct platform_device *pdev) { struct snd_dw_hdmi *dw = platform_get_drvdata(pdev); platform_device_unregister(dw->audio_pdev); } static struct platform_driver snd_dw_hdmi_driver = { .probe = snd_dw_hdmi_probe, .remove = snd_dw_hdmi_remove, .driver = { .name = DRIVER_NAME, }, }; module_platform_driver(snd_dw_hdmi_driver); MODULE_AUTHOR("Shengjiu Wang "); MODULE_DESCRIPTION("Synopsys Designware HDMI GPA ALSA interface"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:" DRIVER_NAME);