| /* |
| * 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 <linux/clk.h> |
| #include <linux/kthread.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/component.h> |
| #include <linux/mfd/syscon.h> |
| #include <linux/of.h> |
| #include <linux/irq.h> |
| #include <linux/of_device.h> |
| #include <sound/hdmi-codec.h> |
| |
| #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 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); |
| 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) |
| { |
| } |
| |
| 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, |
| PLATFORM_DEVID_NONE, &codec_data, |
| sizeof(codec_data)); |
| if (IS_ERR(pdev)) |
| return; |
| |
| DRM_INFO("%s driver bound to HDMI\n", HDMI_CODEC_DRV_NAME); |
| } |
| |
| |