/* * Copyright 2018 NXP * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include "imx-hdp.h" #include "imx-hdmi.h" #include "imx-dp.h" #include "../imx-drm.h" static u32 TMDS_rate_table[7] = { 25200, 27000, 54000, 74250, 148500, 297000, 594000, }; static u32 N_table_32k[8] = { /*25200, 27000, 54000, 74250, 148500, 297000, 594000,*/ 4096, 4096, 4096, 4096, 4096, 3072, 3072, 4096, }; static u32 N_table_44k[8] = { 6272, 6272, 6272, 6272, 6272, 4704, 9408, 6272, }; static u32 N_table_48k[8] = { 6144, 6144, 6144, 6144, 6144, 5120, 6144, 6144, }; static int select_N_index(u32 pclk) { int i = 0; for (i = 0; i < 7; i++) { if (pclk == TMDS_rate_table[i]) break; } if (i == 7) DRM_WARN("pclkc %d is not supported!\n", pclk); return i; } static void imx_hdmi_audio_avi_set(state_struct *state, u32 channels) { struct hdmi_audio_infoframe frame; u8 buf[32]; int ret; hdmi_audio_infoframe_init(&frame); frame.channels = channels; frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; if (channels == 2) frame.channel_allocation = 0; else if (channels == 4) frame.channel_allocation = 0x3; else if (channels == 8) frame.channel_allocation = 0x13; ret = hdmi_audio_infoframe_pack(&frame, buf + 1, sizeof(buf) - 1); if (ret < 0) { DRM_ERROR("failed to pack audio infoframe: %d\n", ret); return; } buf[0] = 0; CDN_API_InfoframeSet(state, 1, sizeof(buf), buf, HDMI_INFOFRAME_TYPE_AUDIO); } static u32 imx_hdp_audio(struct imx_hdp *hdmi, AUDIO_TYPE type, u32 sample_rate, u32 channels, u32 width) { AUDIO_FREQ freq; AUDIO_WIDTH bits; int ncts_n; state_struct *state = &hdmi->state; int idx_n = select_N_index(hdmi->video.cur_mode.clock); switch (sample_rate) { case 32000: freq = AUDIO_FREQ_32; ncts_n = N_table_32k[idx_n]; break; case 44100: freq = AUDIO_FREQ_44_1; ncts_n = N_table_44k[idx_n]; break; case 48000: freq = AUDIO_FREQ_48; ncts_n = N_table_48k[idx_n]; break; case 88200: freq = AUDIO_FREQ_88_2; ncts_n = N_table_44k[idx_n] * 2; break; case 96000: freq = AUDIO_FREQ_96; ncts_n = N_table_48k[idx_n] * 2; break; case 176400: freq = AUDIO_FREQ_176_4; ncts_n = N_table_44k[idx_n] * 4; break; case 192000: freq = AUDIO_FREQ_192; ncts_n = N_table_48k[idx_n] * 4; break; default: return -EINVAL; } switch (width) { case 16: bits = AUDIO_WIDTH_16; break; case 24: bits = AUDIO_WIDTH_24; break; case 32: bits = AUDIO_WIDTH_32; break; default: return -EINVAL; } CDN_API_AudioOff_blocking(state, type); CDN_API_AudioAutoConfig_blocking(state, type, channels, freq, 0, bits, hdmi->audio_type, ncts_n, AUDIO_MUTE_MODE_UNMUTE); if (hdmi->audio_type == CDN_HDMITX_TYPHOON || hdmi->audio_type == CDN_HDMITX_KIRAN) imx_hdmi_audio_avi_set(state, channels); return 0; } /* * HDMI audio codec callbacks */ static int imx_hdp_audio_hw_params(struct device *dev, void *data, struct hdmi_codec_daifmt *daifmt, struct hdmi_codec_params *params) { struct imx_hdp *hdmi = dev_get_drvdata(dev); unsigned int chan = params->cea.channels; dev_dbg(hdmi->dev, "%s: %u Hz, %d bit, %d channels\n", __func__, params->sample_rate, params->sample_width, chan); if (!hdmi->bridge.encoder) return -ENODEV; if (daifmt->fmt == HDMI_I2S) imx_hdp_audio(hdmi, AUDIO_TYPE_I2S, params->sample_rate, chan, params->sample_width); else if (daifmt->fmt == HDMI_SPDIF) imx_hdp_audio(hdmi, AUDIO_TYPE_SPIDIF_EXTERNAL, params->sample_rate, chan, params->sample_width); else return -EINVAL; return 0; } static void imx_hdp_audio_shutdown(struct device *dev, void *data) { struct imx_hdp *hdmi = dev_get_drvdata(dev); state_struct *state = &hdmi->state; CDN_API_InfoframeRemovePacket(state, 0x1, 0x84); } static int imx_hdp_audio_get_eld(struct device *dev, void *data, uint8_t *buf, size_t len) { struct imx_hdp *hdmi = dev_get_drvdata(dev); memcpy(buf, hdmi->connector.eld, min(sizeof(hdmi->connector.eld), len)); return 0; } static const struct hdmi_codec_ops imx_hdp_audio_codec_ops = { .hw_params = imx_hdp_audio_hw_params, .audio_shutdown = imx_hdp_audio_shutdown, .get_eld = imx_hdp_audio_get_eld, }; void imx_hdp_register_audio_driver(struct device *dev) { struct hdmi_codec_pdata codec_data = { .ops = &imx_hdp_audio_codec_ops, .max_i2s_channels = 8, .i2s = 1, }; struct platform_device *pdev; pdev = platform_device_register_data(dev, HDMI_CODEC_DRV_NAME, 1, &codec_data, sizeof(codec_data)); if (IS_ERR(pdev)) return; DRM_INFO("%s driver bound to HDMI\n", HDMI_CODEC_DRV_NAME); }