242 lines
5.3 KiB
C
242 lines
5.3 KiB
C
/*
|
|
* 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 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);
|
|
}
|
|
|
|
|