drm/bridge: synopsys: Add audio support for dw-hdmi-qp

Signed-off-by: Sugar Zhang <sugar.zhang@rock-chips.com>
Change-Id: I41ceead79d46e08d5022bc1cc536af89437003a3
This commit is contained in:
Sugar Zhang
2021-11-01 14:28:52 +08:00
committed by Tao Huang
parent 17517804c1
commit b1662f1291
7 changed files with 629 additions and 7 deletions

View File

@@ -2,7 +2,7 @@
obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o dw-hdmi-hdcp.o \
dw-hdmi-qp.o
obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o
obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o dw-hdmi-qp-i2s-audio.o
obj-$(CONFIG_DRM_DW_HDMI_CEC) += dw-hdmi-cec.o
obj-$(CONFIG_DRM_DW_MIPI_DSI) += dw-mipi-dsi.o

View File

@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2021 Rockchip Electronics Co. Ltd.
* Author: Sugar Zhang <sugar.zhang@rock-chips.com>
*/
#ifndef DW_HDMI_QP_AUDIO_H
#define DW_HDMI_QP_AUDIO_H
struct dw_hdmi_qp;
struct dw_hdmi_qp_audio_data {
phys_addr_t phys;
void __iomem *base;
int irq;
struct dw_hdmi_qp *hdmi;
u8 *eld;
};
struct dw_hdmi_qp_i2s_audio_data {
struct dw_hdmi_qp *hdmi;
u8 *eld;
void (*write)(struct dw_hdmi_qp *hdmi, u32 val, int offset);
u32 (*read)(struct dw_hdmi_qp *hdmi, int offset);
void (*mod)(struct dw_hdmi_qp *hdmi, u32 val, u32 mask, u32 reg);
};
#endif

View File

@@ -0,0 +1,224 @@
// SPDX-License-Identifier: GPL-2.0
/*
* dw-hdmi-qp-i2s-audio.c
*
* Copyright (c) 2021 Rockchip Electronics Co. Ltd.
* Author: Sugar Zhang <sugar.zhang@rock-chips.com>
*/
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <drm/bridge/dw_hdmi.h>
#include <drm/drm_crtc.h>
#include <sound/hdmi-codec.h>
#include "dw-hdmi-qp.h"
#include "dw-hdmi-qp-audio.h"
#define DRIVER_NAME "dw-hdmi-qp-i2s-audio"
static inline void hdmi_write(struct dw_hdmi_qp_i2s_audio_data *audio,
u32 val, int offset)
{
struct dw_hdmi_qp *hdmi = audio->hdmi;
audio->write(hdmi, val, offset);
}
static inline u32 hdmi_read(struct dw_hdmi_qp_i2s_audio_data *audio, int offset)
{
struct dw_hdmi_qp *hdmi = audio->hdmi;
return audio->read(hdmi, offset);
}
static inline void hdmi_mod(struct dw_hdmi_qp_i2s_audio_data *audio,
u32 data, u32 mask, u32 reg)
{
struct dw_hdmi_qp *hdmi = audio->hdmi;
return audio->mod(hdmi, data, mask, reg);
}
static int dw_hdmi_qp_i2s_hw_params(struct device *dev, void *data,
struct hdmi_codec_daifmt *fmt,
struct hdmi_codec_params *hparms)
{
struct dw_hdmi_qp_i2s_audio_data *audio = data;
struct dw_hdmi_qp *hdmi = audio->hdmi;
u32 conf0 = 0;
if (fmt->bit_clk_master | fmt->frame_clk_master) {
dev_err(dev, "unsupported clock settings\n");
return -EINVAL;
}
/* Reset the audio data path of the AVP */
hdmi_write(audio, AVP_DATAPATH_PACKET_AUDIO_SWINIT_P, GLOBAL_SWRESET_REQUEST);
/* Clear the audio FIFO */
hdmi_write(audio, AUDIO_FIFO_CLR_P, AUDIO_INTERFACE_CONTROL0);
/* Disable AUDS, ACR, AUDI, AMD */
hdmi_mod(audio, 0,
PKTSCHED_ACR_TX_EN | PKTSCHED_AUDS_TX_EN |
PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN,
PKTSCHED_PKT_EN);
/* Select I2S interface as the audio source */
hdmi_mod(audio, AUD_IF_I2S, AUD_IF_SEL_MSK, AUDIO_INTERFACE_CONFIG0);
/* Enable the active i2s lanes */
switch (hparms->channels) {
case 7 ... 8:
conf0 |= I2S_LINES_EN(3);
fallthrough;
case 5 ... 6:
conf0 |= I2S_LINES_EN(2);
fallthrough;
case 3 ... 4:
conf0 |= I2S_LINES_EN(1);
fallthrough;
default:
conf0 |= I2S_LINES_EN(0);
break;
}
hdmi_mod(audio, conf0, I2S_LINES_EN_MSK, AUDIO_INTERFACE_CONFIG0);
/* Enable bpcuv generated internally */
hdmi_mod(audio, I2S_BPCUV_RCV_DIS, I2S_BPCUV_RCV_MSK, AUDIO_INTERFACE_CONFIG0);
/* Configure the audio format */
hdmi_mod(audio, AUD_ASP, AUD_FORMAT_MSK, AUDIO_INTERFACE_CONFIG0);
/* Enable audio FIFO auto clear when overflow */
hdmi_mod(audio, AUD_FIFO_INIT_ON_OVF_EN, AUD_FIFO_INIT_ON_OVF_MSK,
AUDIO_INTERFACE_CONFIG0);
dw_hdmi_qp_set_sample_rate(hdmi, hparms->sample_rate);
dw_hdmi_qp_set_channel_status(hdmi, hparms->iec.status);
dw_hdmi_qp_set_channel_count(hdmi, hparms->channels);
dw_hdmi_qp_set_channel_allocation(hdmi, hparms->cea.channel_allocation);
/* Enable ACR, AUDI, AMD */
hdmi_mod(audio,
PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN,
PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN,
PKTSCHED_PKT_EN);
/* Enable AUDS */
hdmi_mod(audio, PKTSCHED_AUDS_TX_EN, PKTSCHED_AUDS_TX_EN, PKTSCHED_PKT_EN);
return 0;
}
static int dw_hdmi_qp_i2s_audio_startup(struct device *dev, void *data)
{
struct dw_hdmi_qp_i2s_audio_data *audio = data;
struct dw_hdmi_qp *hdmi = audio->hdmi;
dw_hdmi_qp_audio_enable(hdmi);
return 0;
}
static void dw_hdmi_qp_i2s_audio_shutdown(struct device *dev, void *data)
{
struct dw_hdmi_qp_i2s_audio_data *audio = data;
struct dw_hdmi_qp *hdmi = audio->hdmi;
dw_hdmi_qp_audio_disable(hdmi);
}
static int dw_hdmi_qp_i2s_get_eld(struct device *dev, void *data, uint8_t *buf,
size_t len)
{
struct dw_hdmi_qp_i2s_audio_data *audio = data;
memcpy(buf, audio->eld, min_t(size_t, MAX_ELD_BYTES, len));
return 0;
}
static int dw_hdmi_qp_i2s_get_dai_id(struct snd_soc_component *component,
struct device_node *endpoint)
{
struct of_endpoint of_ep;
int ret;
ret = of_graph_parse_endpoint(endpoint, &of_ep);
if (ret < 0)
return ret;
/*
* HDMI sound should be located as reg = <2>
* Then, it is sound port 0
*/
if (of_ep.port == 2)
return 0;
return -EINVAL;
}
static struct hdmi_codec_ops dw_hdmi_qp_i2s_ops = {
.hw_params = dw_hdmi_qp_i2s_hw_params,
.audio_startup = dw_hdmi_qp_i2s_audio_startup,
.audio_shutdown = dw_hdmi_qp_i2s_audio_shutdown,
.get_eld = dw_hdmi_qp_i2s_get_eld,
.get_dai_id = dw_hdmi_qp_i2s_get_dai_id,
};
static int snd_dw_hdmi_qp_probe(struct platform_device *pdev)
{
struct dw_hdmi_qp_i2s_audio_data *audio = pdev->dev.platform_data;
struct platform_device_info pdevinfo;
struct hdmi_codec_pdata pdata;
struct platform_device *platform;
pdata.ops = &dw_hdmi_qp_i2s_ops;
pdata.i2s = 1;
pdata.max_i2s_channels = 8;
pdata.data = audio;
memset(&pdevinfo, 0, sizeof(pdevinfo));
pdevinfo.parent = pdev->dev.parent;
pdevinfo.id = PLATFORM_DEVID_AUTO;
pdevinfo.name = HDMI_CODEC_DRV_NAME;
pdevinfo.data = &pdata;
pdevinfo.size_data = sizeof(pdata);
pdevinfo.dma_mask = DMA_BIT_MASK(32);
platform = platform_device_register_full(&pdevinfo);
if (IS_ERR(platform))
return PTR_ERR(platform);
dev_set_drvdata(&pdev->dev, platform);
return 0;
}
static int snd_dw_hdmi_qp_remove(struct platform_device *pdev)
{
struct platform_device *platform = dev_get_drvdata(&pdev->dev);
platform_device_unregister(platform);
return 0;
}
static struct platform_driver snd_dw_hdmi_qp_driver = {
.probe = snd_dw_hdmi_qp_probe,
.remove = snd_dw_hdmi_qp_remove,
.driver = {
.name = DRIVER_NAME,
},
};
module_platform_driver(snd_dw_hdmi_qp_driver);
MODULE_AUTHOR("Sugar Zhang <sugar.zhang@rock-chips.com>");
MODULE_DESCRIPTION("Synopsis Designware HDMI QP I2S ALSA SoC interface");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRIVER_NAME);

View File

@@ -34,6 +34,7 @@
#include <uapi/linux/media-bus-format.h>
#include <uapi/linux/videodev2.h>
#include "dw-hdmi-qp-audio.h"
#include "dw-hdmi-qp.h"
#include <media/cec-notifier.h>
@@ -53,6 +54,61 @@ static const unsigned int dw_hdmi_cable[] = {
EXTCON_NONE,
};
/*
* Unless otherwise noted, entries in this table are 100% optimization.
* Values can be obtained from hdmi_compute_n() but that function is
* slow so we pre-compute values we expect to see.
*
* All 32k and 48k values are expected to be the same (due to the way
* the math works) for any rate that's an exact kHz.
*/
static const struct dw_hdmi_audio_tmds_n common_tmds_n_table[] = {
{ .tmds = 25175000, .n_32k = 4096, .n_44k1 = 12854, .n_48k = 6144, },
{ .tmds = 25200000, .n_32k = 4096, .n_44k1 = 5656, .n_48k = 6144, },
{ .tmds = 27000000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
{ .tmds = 28320000, .n_32k = 4096, .n_44k1 = 5586, .n_48k = 6144, },
{ .tmds = 30240000, .n_32k = 4096, .n_44k1 = 5642, .n_48k = 6144, },
{ .tmds = 31500000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, },
{ .tmds = 32000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, },
{ .tmds = 33750000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, },
{ .tmds = 36000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
{ .tmds = 40000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, },
{ .tmds = 49500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
{ .tmds = 50000000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, },
{ .tmds = 54000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
{ .tmds = 65000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
{ .tmds = 68250000, .n_32k = 4096, .n_44k1 = 5376, .n_48k = 6144, },
{ .tmds = 71000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
{ .tmds = 72000000, .n_32k = 4096, .n_44k1 = 5635, .n_48k = 6144, },
{ .tmds = 73250000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, },
{ .tmds = 74250000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, },
{ .tmds = 75000000, .n_32k = 4096, .n_44k1 = 5880, .n_48k = 6144, },
{ .tmds = 78750000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, },
{ .tmds = 78800000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, },
{ .tmds = 79500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, },
{ .tmds = 83500000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
{ .tmds = 85500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
{ .tmds = 88750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, },
{ .tmds = 97750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, },
{ .tmds = 101000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
{ .tmds = 106500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, },
{ .tmds = 108000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
{ .tmds = 115500000, .n_32k = 4096, .n_44k1 = 5712, .n_48k = 6144, },
{ .tmds = 119000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, },
{ .tmds = 135000000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
{ .tmds = 146250000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, },
{ .tmds = 148500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
{ .tmds = 154000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, },
{ .tmds = 162000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
/* For 297 MHz+ HDMI spec have some other rule for setting N */
{ .tmds = 297000000, .n_32k = 3073, .n_44k1 = 4704, .n_48k = 5120, },
{ .tmds = 594000000, .n_32k = 3073, .n_44k1 = 9408, .n_48k = 10240, },
/* End of table */
{ .tmds = 0, .n_32k = 0, .n_44k1 = 0, .n_48k = 0, },
};
static const struct drm_display_mode dw_hdmi_default_modes[] = {
/* 2 - 720x480@60Hz 4:3 */
{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736,
@@ -161,6 +217,7 @@ struct dw_hdmi_qp {
struct drm_connector connector;
struct drm_bridge bridge;
struct platform_device *hdcp_dev;
struct platform_device *audio;
struct device *dev;
struct dw_hdmi_qp_i2c *i2c;
@@ -210,6 +267,15 @@ struct dw_hdmi_qp {
u32 flt_intr;
u32 earc_intr;
spinlock_t audio_lock;
struct mutex audio_mutex;
unsigned int sample_rate;
unsigned int audio_cts;
unsigned int audio_n;
bool audio_enable;
void (*enable_audio)(struct dw_hdmi_qp *hdmi);
void (*disable_audio)(struct dw_hdmi_qp *hdmi);
struct dentry *debugfs_dir;
bool scramble_low_rates;
@@ -246,6 +312,245 @@ static void hdmi_modb(struct dw_hdmi_qp *hdmi, u32 data, u32 mask, u32 reg)
regmap_update_bits(hdmi->regm, reg, mask, data);
}
static void hdmi_set_cts_n(struct dw_hdmi_qp *hdmi, unsigned int cts,
unsigned int n)
{
/* Set N */
hdmi_modb(hdmi, n, AUDPKT_ACR_N_VALUE, AUDPKT_ACR_CONTROL0);
/* Set CTS */
if (cts)
hdmi_modb(hdmi, AUDPKT_ACR_CTS_OVR_EN, AUDPKT_ACR_CTS_OVR_EN_MSK,
AUDPKT_ACR_CONTROL1);
else
hdmi_modb(hdmi, 0, AUDPKT_ACR_CTS_OVR_EN_MSK,
AUDPKT_ACR_CONTROL1);
hdmi_modb(hdmi, AUDPKT_ACR_CTS_OVR_VAL(cts), AUDPKT_ACR_CTS_OVR_VAL_MSK,
AUDPKT_ACR_CONTROL1);
}
static int hdmi_match_tmds_n_table(struct dw_hdmi_qp *hdmi,
unsigned long pixel_clk,
unsigned long freq)
{
const struct dw_hdmi_plat_data *plat_data = hdmi->plat_data;
const struct dw_hdmi_audio_tmds_n *tmds_n = NULL;
int i;
if (plat_data->tmds_n_table) {
for (i = 0; plat_data->tmds_n_table[i].tmds != 0; i++) {
if (pixel_clk == plat_data->tmds_n_table[i].tmds) {
tmds_n = &plat_data->tmds_n_table[i];
break;
}
}
}
if (tmds_n == NULL) {
for (i = 0; common_tmds_n_table[i].tmds != 0; i++) {
if (pixel_clk == common_tmds_n_table[i].tmds) {
tmds_n = &common_tmds_n_table[i];
break;
}
}
}
if (tmds_n == NULL)
return -ENOENT;
switch (freq) {
case 32000:
return tmds_n->n_32k;
case 44100:
case 88200:
case 176400:
return (freq / 44100) * tmds_n->n_44k1;
case 48000:
case 96000:
case 192000:
return (freq / 48000) * tmds_n->n_48k;
default:
return -ENOENT;
}
}
static u64 hdmi_audio_math_diff(unsigned int freq, unsigned int n,
unsigned int pixel_clk)
{
u64 final, diff;
u64 cts;
final = (u64)pixel_clk * n;
cts = final;
do_div(cts, 128 * freq);
diff = final - (u64)cts * (128 * freq);
return diff;
}
static unsigned int hdmi_compute_n(struct dw_hdmi_qp *hdmi,
unsigned long pixel_clk,
unsigned long freq)
{
unsigned int min_n = DIV_ROUND_UP((128 * freq), 1500);
unsigned int max_n = (128 * freq) / 300;
unsigned int ideal_n = (128 * freq) / 1000;
unsigned int best_n_distance = ideal_n;
unsigned int best_n = 0;
u64 best_diff = U64_MAX;
int n;
/* If the ideal N could satisfy the audio math, then just take it */
if (hdmi_audio_math_diff(freq, ideal_n, pixel_clk) == 0)
return ideal_n;
for (n = min_n; n <= max_n; n++) {
u64 diff = hdmi_audio_math_diff(freq, n, pixel_clk);
if (diff < best_diff || (diff == best_diff &&
abs(n - ideal_n) < best_n_distance)) {
best_n = n;
best_diff = diff;
best_n_distance = abs(best_n - ideal_n);
}
/*
* The best N already satisfy the audio math, and also be
* the closest value to ideal N, so just cut the loop.
*/
if ((best_diff == 0) && (abs(n - ideal_n) > best_n_distance))
break;
}
return best_n;
}
static unsigned int hdmi_find_n(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk,
unsigned long sample_rate)
{
int n;
n = hdmi_match_tmds_n_table(hdmi, pixel_clk, sample_rate);
if (n > 0)
return n;
dev_warn(hdmi->dev, "Rate %lu missing; compute N dynamically\n",
pixel_clk);
return hdmi_compute_n(hdmi, pixel_clk, sample_rate);
}
/*
* When transmitting IEC60958 linear PCM audio, these registers allow to
* configure the channel status information of all the channel status
* bits in the IEC60958 frame. For the moment this configuration is only
* used when the I2S audio interface, General Purpose Audio (GPA),
* or AHB audio DMA (AHBAUDDMA) interface is active
* (for S/PDIF interface this information comes from the stream).
*/
void dw_hdmi_qp_set_channel_status(struct dw_hdmi_qp *hdmi,
u8 *channel_status)
{
}
EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_channel_status);
static void hdmi_set_clk_regenerator(struct dw_hdmi_qp *hdmi,
unsigned long pixel_clk, unsigned int sample_rate)
{
unsigned int n = 0, cts = 0;
n = hdmi_find_n(hdmi, pixel_clk, sample_rate);
spin_lock_irq(&hdmi->audio_lock);
hdmi->audio_n = n;
hdmi->audio_cts = cts;
hdmi_set_cts_n(hdmi, cts, hdmi->audio_enable ? n : 0);
spin_unlock_irq(&hdmi->audio_lock);
}
static void hdmi_init_clk_regenerator(struct dw_hdmi_qp *hdmi)
{
mutex_lock(&hdmi->audio_mutex);
hdmi_set_clk_regenerator(hdmi, 74250000, hdmi->sample_rate);
mutex_unlock(&hdmi->audio_mutex);
}
static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi_qp *hdmi)
{
mutex_lock(&hdmi->audio_mutex);
hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock,
hdmi->sample_rate);
mutex_unlock(&hdmi->audio_mutex);
}
void dw_hdmi_qp_set_sample_rate(struct dw_hdmi_qp *hdmi, unsigned int rate)
{
mutex_lock(&hdmi->audio_mutex);
hdmi->sample_rate = rate;
hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock,
hdmi->sample_rate);
mutex_unlock(&hdmi->audio_mutex);
}
EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_sample_rate);
void dw_hdmi_qp_set_channel_count(struct dw_hdmi_qp *hdmi, unsigned int cnt)
{
}
EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_channel_count);
void dw_hdmi_qp_set_channel_allocation(struct dw_hdmi_qp *hdmi, unsigned int ca)
{
}
EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_channel_allocation);
static void hdmi_enable_audio_clk(struct dw_hdmi_qp *hdmi, bool enable)
{
if (enable)
hdmi_modb(hdmi, 0,
AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE);
else
hdmi_modb(hdmi, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE,
AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE);
}
static void dw_hdmi_i2s_audio_enable(struct dw_hdmi_qp *hdmi)
{
hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n);
hdmi_enable_audio_clk(hdmi, true);
}
static void dw_hdmi_i2s_audio_disable(struct dw_hdmi_qp *hdmi)
{
hdmi_enable_audio_clk(hdmi, false);
}
void dw_hdmi_qp_audio_enable(struct dw_hdmi_qp *hdmi)
{
unsigned long flags;
spin_lock_irqsave(&hdmi->audio_lock, flags);
hdmi->audio_enable = true;
if (hdmi->enable_audio)
hdmi->enable_audio(hdmi);
spin_unlock_irqrestore(&hdmi->audio_lock, flags);
}
EXPORT_SYMBOL_GPL(dw_hdmi_qp_audio_enable);
void dw_hdmi_qp_audio_disable(struct dw_hdmi_qp *hdmi)
{
unsigned long flags;
spin_lock_irqsave(&hdmi->audio_lock, flags);
hdmi->audio_enable = false;
if (hdmi->disable_audio)
hdmi->disable_audio(hdmi);
spin_unlock_irqrestore(&hdmi->audio_lock, flags);
}
EXPORT_SYMBOL_GPL(dw_hdmi_qp_audio_disable);
static bool hdmi_bus_fmt_is_rgb(unsigned int bus_format)
{
switch (bus_format) {
@@ -1113,6 +1418,14 @@ static int dw_hdmi_qp_setup(struct dw_hdmi_qp *hdmi,
if (ret)
return ret;
if (hdmi->sink_has_audio) {
dev_dbg(hdmi->dev, "sink has audio support\n");
/* HDMI Initialization Step E - Configure audio */
hdmi_clk_regenerator_update_pixel_clock(hdmi);
hdmi_enable_audio_clk(hdmi, hdmi->audio_enable);
}
/* not for DVI mode */
if (hdmi->sink_is_hdmi) {
dev_info(hdmi->dev, "%s HDMI mode\n", __func__);
@@ -1693,6 +2006,8 @@ __dw_hdmi_probe(struct platform_device *pdev,
struct device_node *np = dev->of_node;
struct device_node *ddc_node;
struct dw_hdmi_qp *hdmi;
struct dw_hdmi_qp_i2s_audio_data audio;
struct platform_device_info pdevinfo;
struct resource *iores = NULL;
int irq;
int ret;
@@ -1704,10 +2019,13 @@ __dw_hdmi_probe(struct platform_device *pdev,
hdmi->connector.stereo_allowed = 1;
hdmi->plat_data = plat_data;
hdmi->dev = dev;
hdmi->sample_rate = 48000;
hdmi->disabled = true;
mutex_init(&hdmi->mutex);
mutex_init(&hdmi->audio_mutex);
mutex_init(&hdmi->cec_notifier_mutex);
spin_lock_init(&hdmi->audio_lock);
ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0);
if (ddc_node) {
@@ -1832,6 +2150,8 @@ __dw_hdmi_probe(struct platform_device *pdev,
if (ret)
goto err_ref;
hdmi_init_clk_regenerator(hdmi);
/* If DDC bus is not specified, try to register HDMI I2C bus */
if (!hdmi->ddc) {
hdmi->ddc = dw_hdmi_i2c_adapter(hdmi);
@@ -1860,6 +2180,23 @@ __dw_hdmi_probe(struct platform_device *pdev,
hdmi->connector.ycbcr_420_allowed = hdmi->plat_data->ycbcr_420_allowed;
audio.hdmi = hdmi;
audio.eld = hdmi->connector.eld;
audio.write = hdmi_writel;
audio.read = hdmi_readl;
audio.mod = hdmi_modb;
hdmi->enable_audio = dw_hdmi_i2s_audio_enable;
hdmi->disable_audio = dw_hdmi_i2s_audio_disable;
memset(&pdevinfo, 0, sizeof(pdevinfo));
pdevinfo.parent = dev;
pdevinfo.id = PLATFORM_DEVID_AUTO;
pdevinfo.name = "dw-hdmi-qp-i2s-audio";
pdevinfo.data = &audio;
pdevinfo.size_data = sizeof(audio);
pdevinfo.dma_mask = DMA_BIT_MASK(32);
hdmi->audio = platform_device_register_full(&pdevinfo);
hdmi->extcon = devm_extcon_dev_allocate(hdmi->dev, dw_hdmi_cable);
if (IS_ERR(hdmi->extcon)) {
dev_err(hdmi->dev, "allocate extcon failed\n");
@@ -1928,6 +2265,9 @@ static void __dw_hdmi_remove(struct dw_hdmi_qp *hdmi)
hdmi->connector.funcs->destroy(&hdmi->connector);
if (hdmi->audio && !IS_ERR(hdmi->audio))
platform_device_unregister(hdmi->audio);
if (hdmi->bridge.encoder)
hdmi->bridge.encoder->funcs->destroy(hdmi->bridge.encoder);

View File

@@ -95,12 +95,30 @@
#define VIDEO_PACKING_CONFIG0 0x81c
/* Audio Interface Registers */
#define AUDIO_INTERFACE_CONFIG0 0x820
#define AUD_IF_SEL 0x3
#define I2S_LINES_EN 0xf0
#define SPDIF_LINES_EN (0xf << 16)
#define AUD_FORMAT (0x7 << 24)
#define AUD_IF_SEL_MSK 0x3
#define AUD_IF_SPDIF 0x2
#define AUD_IF_I2S 0x1
#define AUD_IF_PAI 0x0
#define AUD_FIFO_INIT_ON_OVF_MSK BIT(2)
#define AUD_FIFO_INIT_ON_OVF_EN BIT(2)
#define I2S_LINES_EN_MSK GENMASK(7, 4)
#define I2S_LINES_EN(x) BIT(x + 4)
#define I2S_BPCUV_RCV_MSK BIT(12)
#define I2S_BPCUV_RCV_EN BIT(12)
#define I2S_BPCUV_RCV_DIS 0
#define SPDIF_LINES_EN GENMASK(19, 16)
#define AUD_FORMAT_MSK GENMASK(26, 24)
#define AUD_3DOBA (0x7 << 24)
#define AUD_3DASP (0x6 << 24)
#define AUD_MSOBA (0x5 << 24)
#define AUD_MSASP (0x4 << 24)
#define AUD_HBR (0x3 << 24)
#define AUD_DST (0x2 << 24)
#define AUD_OBA (0x1 << 24)
#define AUD_ASP (0x0 << 24)
#define AUDIO_INTERFACE_CONFIG1 0x824
#define AUDIO_INTERFACE_CONTROL0 0x82c
#define AUDIO_FIFO_CLR_P BIT(0)
#define AUDIO_INTERFACE_STATUS0 0x834
/* Frame Composer Registers */
#define FRAME_COMPOSER_CONFIG0 0x840
@@ -368,6 +386,9 @@
#define AUDPKT_ACR_CONTROL0 0xe40
#define AUDPKT_ACR_N_VALUE 0xfffff
#define AUDPKT_ACR_CONTROL1 0xe44
#define AUDPKT_ACR_CTS_OVR_VAL_MSK GENMASK(23, 4)
#define AUDPKT_ACR_CTS_OVR_VAL(x) ((x) << 4)
#define AUDPKT_ACR_CTS_OVR_EN_MSK BIT(1)
#define AUDPKT_ACR_CTS_OVR_EN BIT(1)
#define AUDPKT_ACR_STATUS0 0xe4c
#define AUDPKT_CHSTATUS_OVR0 0xe60

View File

@@ -2921,7 +2921,8 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
if (!hdmi->id) {
val = HIWORD_UPDATE(RK3588_SCLIN_MASK, RK3588_SCLIN_MASK) |
HIWORD_UPDATE(RK3588_SDAIN_MASK, RK3588_SDAIN_MASK) |
HIWORD_UPDATE(RK3588_MODE_MASK, RK3588_MODE_MASK);
HIWORD_UPDATE(RK3588_MODE_MASK, RK3588_MODE_MASK) |
HIWORD_UPDATE(RK3588_I2S_SEL_MASK, RK3588_I2S_SEL_MASK);
regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON3, val);
val = HIWORD_UPDATE(RK3588_SET_HPD_PATH_MASK,
@@ -2934,7 +2935,8 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
} else {
val = HIWORD_UPDATE(RK3588_SCLIN_MASK, RK3588_SCLIN_MASK) |
HIWORD_UPDATE(RK3588_SDAIN_MASK, RK3588_SDAIN_MASK) |
HIWORD_UPDATE(RK3588_MODE_MASK, RK3588_MODE_MASK);
HIWORD_UPDATE(RK3588_MODE_MASK, RK3588_MODE_MASK) |
HIWORD_UPDATE(RK3588_I2S_SEL_MASK, RK3588_I2S_SEL_MASK);
regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON6, val);
val = HIWORD_UPDATE(RK3588_SET_HPD_PATH_MASK,

View File

@@ -286,5 +286,11 @@ void dw_hdmi_qp_resume(struct device *dev, struct dw_hdmi_qp *hdmi);
void dw_hdmi_qp_cec_set_hpd(struct dw_hdmi_qp *hdmi, bool plug_in, bool change);
void dw_hdmi_qp_set_cec_adap(struct dw_hdmi_qp *hdmi, struct cec_adapter *adap);
int dw_hdmi_qp_set_earc(struct dw_hdmi_qp *hdmi);
void dw_hdmi_qp_set_sample_rate(struct dw_hdmi_qp *hdmi, unsigned int rate);
void dw_hdmi_qp_set_channel_count(struct dw_hdmi_qp *hdmi, unsigned int cnt);
void dw_hdmi_qp_set_channel_status(struct dw_hdmi_qp *hdmi, u8 *channel_status);
void dw_hdmi_qp_set_channel_allocation(struct dw_hdmi_qp *hdmi, unsigned int ca);
void dw_hdmi_qp_audio_enable(struct dw_hdmi_qp *hdmi);
void dw_hdmi_qp_audio_disable(struct dw_hdmi_qp *hdmi);
#endif /* __IMX_HDMI_H__ */