Commit bb7d7856 authored by Eric Anholt's avatar Eric Anholt

drm/vc4: Add HDMI audio support

The HDMI encoder IP embeds all needed blocks to output audio, with a
custom DAI called MAI moving audio between the two parts of the HDMI
core.  This driver now exposes a sound card to let users stream audio
to their display.

Using the hdmi-codec driver has been considered here, but MAI meant
having to significantly rework hdmi-codec, and it would have left
little shared code with the I2S mode anyway.

The encoder requires that the audio be SPDIF-formatted frames only,
which alsalib will format-convert for us.

This patch is the combined work of Eric Anholt (initial register setup
with a separate dmaengine driver and using simple-audio-card) and
Boris Brezillon (moving it all into HDMI, massive debug to get it
actually working), and which Eric has the permission to release.

v2: Drop "-audio" from sound card name, since that's already implied
    (suggestion by Boris)
Signed-off-by: default avatarEric Anholt <eric@anholt.net>
Acked-by: default avatarBoris Brezillon <boris.brezillon@free-electrons.com>
Link: http://patchwork.freedesktop.org/patch/msgid/20170227202803.12855-2-eric@anholt.net
parent 199a7694
...@@ -2,11 +2,15 @@ config DRM_VC4 ...@@ -2,11 +2,15 @@ config DRM_VC4
tristate "Broadcom VC4 Graphics" tristate "Broadcom VC4 Graphics"
depends on ARCH_BCM2835 || COMPILE_TEST depends on ARCH_BCM2835 || COMPILE_TEST
depends on DRM depends on DRM
depends on SND && SND_SOC
depends on COMMON_CLK depends on COMMON_CLK
select DRM_KMS_HELPER select DRM_KMS_HELPER
select DRM_KMS_CMA_HELPER select DRM_KMS_CMA_HELPER
select DRM_GEM_CMA_HELPER select DRM_GEM_CMA_HELPER
select DRM_PANEL select DRM_PANEL
select SND_PCM
select SND_PCM_ELD
select SND_SOC_GENERIC_DMAENGINE_PCM
select DRM_MIPI_DSI select DRM_MIPI_DSI
help help
Choose this option if you have a system that has a Broadcom Choose this option if you have a system that has a Broadcom
......
...@@ -48,11 +48,27 @@ ...@@ -48,11 +48,27 @@
#include "linux/clk.h" #include "linux/clk.h"
#include "linux/component.h" #include "linux/component.h"
#include "linux/i2c.h" #include "linux/i2c.h"
#include "linux/of_address.h"
#include "linux/of_gpio.h" #include "linux/of_gpio.h"
#include "linux/of_platform.h" #include "linux/of_platform.h"
#include "linux/rational.h"
#include "sound/dmaengine_pcm.h"
#include "sound/pcm_drm_eld.h"
#include "sound/pcm_params.h"
#include "sound/soc.h"
#include "vc4_drv.h" #include "vc4_drv.h"
#include "vc4_regs.h" #include "vc4_regs.h"
/* HDMI audio information */
struct vc4_hdmi_audio {
struct snd_soc_card card;
struct snd_soc_dai_link link;
int samplerate;
int channels;
struct snd_dmaengine_dai_dma_data dma_data;
struct snd_pcm_substream *substream;
};
/* General HDMI hardware state. */ /* General HDMI hardware state. */
struct vc4_hdmi { struct vc4_hdmi {
struct platform_device *pdev; struct platform_device *pdev;
...@@ -60,6 +76,8 @@ struct vc4_hdmi { ...@@ -60,6 +76,8 @@ struct vc4_hdmi {
struct drm_encoder *encoder; struct drm_encoder *encoder;
struct drm_connector *connector; struct drm_connector *connector;
struct vc4_hdmi_audio audio;
struct i2c_adapter *ddc; struct i2c_adapter *ddc;
void __iomem *hdmicore_regs; void __iomem *hdmicore_regs;
void __iomem *hd_regs; void __iomem *hd_regs;
...@@ -115,6 +133,10 @@ static const struct { ...@@ -115,6 +133,10 @@ static const struct {
HDMI_REG(VC4_HDMI_SW_RESET_CONTROL), HDMI_REG(VC4_HDMI_SW_RESET_CONTROL),
HDMI_REG(VC4_HDMI_HOTPLUG_INT), HDMI_REG(VC4_HDMI_HOTPLUG_INT),
HDMI_REG(VC4_HDMI_HOTPLUG), HDMI_REG(VC4_HDMI_HOTPLUG),
HDMI_REG(VC4_HDMI_MAI_CHANNEL_MAP),
HDMI_REG(VC4_HDMI_MAI_CONFIG),
HDMI_REG(VC4_HDMI_MAI_FORMAT),
HDMI_REG(VC4_HDMI_AUDIO_PACKET_CONFIG),
HDMI_REG(VC4_HDMI_RAM_PACKET_CONFIG), HDMI_REG(VC4_HDMI_RAM_PACKET_CONFIG),
HDMI_REG(VC4_HDMI_HORZA), HDMI_REG(VC4_HDMI_HORZA),
HDMI_REG(VC4_HDMI_HORZB), HDMI_REG(VC4_HDMI_HORZB),
...@@ -125,6 +147,7 @@ static const struct { ...@@ -125,6 +147,7 @@ static const struct {
HDMI_REG(VC4_HDMI_VERTB0), HDMI_REG(VC4_HDMI_VERTB0),
HDMI_REG(VC4_HDMI_VERTB1), HDMI_REG(VC4_HDMI_VERTB1),
HDMI_REG(VC4_HDMI_TX_PHY_RESET_CTL), HDMI_REG(VC4_HDMI_TX_PHY_RESET_CTL),
HDMI_REG(VC4_HDMI_TX_PHY_CTL0),
}; };
static const struct { static const struct {
...@@ -133,6 +156,9 @@ static const struct { ...@@ -133,6 +156,9 @@ static const struct {
} hd_regs[] = { } hd_regs[] = {
HDMI_REG(VC4_HD_M_CTL), HDMI_REG(VC4_HD_M_CTL),
HDMI_REG(VC4_HD_MAI_CTL), HDMI_REG(VC4_HD_MAI_CTL),
HDMI_REG(VC4_HD_MAI_THR),
HDMI_REG(VC4_HD_MAI_FMT),
HDMI_REG(VC4_HD_MAI_SMP),
HDMI_REG(VC4_HD_VID_CTL), HDMI_REG(VC4_HD_VID_CTL),
HDMI_REG(VC4_HD_CSC_CTL), HDMI_REG(VC4_HD_CSC_CTL),
HDMI_REG(VC4_HD_FRAME_COUNT), HDMI_REG(VC4_HD_FRAME_COUNT),
...@@ -232,6 +258,7 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector) ...@@ -232,6 +258,7 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector)
drm_mode_connector_update_edid_property(connector, edid); drm_mode_connector_update_edid_property(connector, edid);
ret = drm_add_edid_modes(connector, edid); ret = drm_add_edid_modes(connector, edid);
drm_edid_to_eld(connector, edid);
return ret; return ret;
} }
...@@ -317,7 +344,7 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, ...@@ -317,7 +344,7 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder,
struct drm_device *dev = encoder->dev; struct drm_device *dev = encoder->dev;
struct vc4_dev *vc4 = to_vc4_dev(dev); struct vc4_dev *vc4 = to_vc4_dev(dev);
u32 packet_id = frame->any.type - 0x80; u32 packet_id = frame->any.type - 0x80;
u32 packet_reg = VC4_HDMI_GCP_0 + VC4_HDMI_PACKET_STRIDE * packet_id; u32 packet_reg = VC4_HDMI_RAM_PACKET(packet_id);
uint8_t buffer[VC4_HDMI_PACKET_STRIDE]; uint8_t buffer[VC4_HDMI_PACKET_STRIDE];
ssize_t len, i; ssize_t len, i;
int ret; int ret;
...@@ -398,6 +425,24 @@ static void vc4_hdmi_set_spd_infoframe(struct drm_encoder *encoder) ...@@ -398,6 +425,24 @@ static void vc4_hdmi_set_spd_infoframe(struct drm_encoder *encoder)
vc4_hdmi_write_infoframe(encoder, &frame); vc4_hdmi_write_infoframe(encoder, &frame);
} }
static void vc4_hdmi_set_audio_infoframe(struct drm_encoder *encoder)
{
struct drm_device *drm = encoder->dev;
struct vc4_dev *vc4 = drm->dev_private;
struct vc4_hdmi *hdmi = vc4->hdmi;
union hdmi_infoframe frame;
int ret;
ret = hdmi_audio_infoframe_init(&frame.audio);
frame.audio.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
frame.audio.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM;
frame.audio.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM;
frame.audio.channels = hdmi->audio.channels;
vc4_hdmi_write_infoframe(encoder, &frame);
}
static void vc4_hdmi_set_infoframes(struct drm_encoder *encoder) static void vc4_hdmi_set_infoframes(struct drm_encoder *encoder)
{ {
vc4_hdmi_set_avi_infoframe(encoder); vc4_hdmi_set_avi_infoframe(encoder);
...@@ -606,6 +651,447 @@ static const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = { ...@@ -606,6 +651,447 @@ static const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = {
.enable = vc4_hdmi_encoder_enable, .enable = vc4_hdmi_encoder_enable,
}; };
/* HDMI audio codec callbacks */
static void vc4_hdmi_audio_set_mai_clock(struct vc4_hdmi *hdmi)
{
struct drm_device *drm = hdmi->encoder->dev;
struct vc4_dev *vc4 = to_vc4_dev(drm);
u32 hsm_clock = clk_get_rate(hdmi->hsm_clock);
unsigned long n, m;
rational_best_approximation(hsm_clock, hdmi->audio.samplerate,
VC4_HD_MAI_SMP_N_MASK >>
VC4_HD_MAI_SMP_N_SHIFT,
(VC4_HD_MAI_SMP_M_MASK >>
VC4_HD_MAI_SMP_M_SHIFT) + 1,
&n, &m);
HD_WRITE(VC4_HD_MAI_SMP,
VC4_SET_FIELD(n, VC4_HD_MAI_SMP_N) |
VC4_SET_FIELD(m - 1, VC4_HD_MAI_SMP_M));
}
static void vc4_hdmi_set_n_cts(struct vc4_hdmi *hdmi)
{
struct drm_encoder *encoder = hdmi->encoder;
struct drm_crtc *crtc = encoder->crtc;
struct drm_device *drm = encoder->dev;
struct vc4_dev *vc4 = to_vc4_dev(drm);
const struct drm_display_mode *mode = &crtc->state->adjusted_mode;
u32 samplerate = hdmi->audio.samplerate;
u32 n, cts;
u64 tmp;
n = 128 * samplerate / 1000;
tmp = (u64)(mode->clock * 1000) * n;
do_div(tmp, 128 * samplerate);
cts = tmp;
HDMI_WRITE(VC4_HDMI_CRP_CFG,
VC4_HDMI_CRP_CFG_EXTERNAL_CTS_EN |
VC4_SET_FIELD(n, VC4_HDMI_CRP_CFG_N));
/*
* We could get slightly more accurate clocks in some cases by
* providing a CTS_1 value. The two CTS values are alternated
* between based on the period fields
*/
HDMI_WRITE(VC4_HDMI_CTS_0, cts);
HDMI_WRITE(VC4_HDMI_CTS_1, cts);
}
static inline struct vc4_hdmi *dai_to_hdmi(struct snd_soc_dai *dai)
{
struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai);
return snd_soc_card_get_drvdata(card);
}
static int vc4_hdmi_audio_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct vc4_hdmi *hdmi = dai_to_hdmi(dai);
struct drm_encoder *encoder = hdmi->encoder;
struct vc4_dev *vc4 = to_vc4_dev(encoder->dev);
int ret;
if (hdmi->audio.substream && hdmi->audio.substream != substream)
return -EINVAL;
hdmi->audio.substream = substream;
/*
* If the HDMI encoder hasn't probed, or the encoder is
* currently in DVI mode, treat the codec dai as missing.
*/
if (!encoder->crtc || !(HDMI_READ(VC4_HDMI_RAM_PACKET_CONFIG) &
VC4_HDMI_RAM_PACKET_ENABLE))
return -ENODEV;
ret = snd_pcm_hw_constraint_eld(substream->runtime,
hdmi->connector->eld);
if (ret)
return ret;
return 0;
}
static int vc4_hdmi_audio_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
return 0;
}
static void vc4_hdmi_audio_reset(struct vc4_hdmi *hdmi)
{
struct drm_encoder *encoder = hdmi->encoder;
struct drm_device *drm = encoder->dev;
struct device *dev = &hdmi->pdev->dev;
struct vc4_dev *vc4 = to_vc4_dev(drm);
int ret;
ret = vc4_hdmi_stop_packet(encoder, HDMI_INFOFRAME_TYPE_AUDIO);
if (ret)
dev_err(dev, "Failed to stop audio infoframe: %d\n", ret);
HD_WRITE(VC4_HD_MAI_CTL, VC4_HD_MAI_CTL_RESET);
HD_WRITE(VC4_HD_MAI_CTL, VC4_HD_MAI_CTL_ERRORF);
HD_WRITE(VC4_HD_MAI_CTL, VC4_HD_MAI_CTL_FLUSH);
}
static void vc4_hdmi_audio_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct vc4_hdmi *hdmi = dai_to_hdmi(dai);
if (substream != hdmi->audio.substream)
return;
vc4_hdmi_audio_reset(hdmi);
hdmi->audio.substream = NULL;
}
/* HDMI audio codec callbacks */
static int vc4_hdmi_audio_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct vc4_hdmi *hdmi = dai_to_hdmi(dai);
struct drm_encoder *encoder = hdmi->encoder;
struct drm_device *drm = encoder->dev;
struct device *dev = &hdmi->pdev->dev;
struct vc4_dev *vc4 = to_vc4_dev(drm);
u32 audio_packet_config, channel_mask;
u32 channel_map, i;
if (substream != hdmi->audio.substream)
return -EINVAL;
dev_dbg(dev, "%s: %u Hz, %d bit, %d channels\n", __func__,
params_rate(params), params_width(params),
params_channels(params));
hdmi->audio.channels = params_channels(params);
hdmi->audio.samplerate = params_rate(params);
HD_WRITE(VC4_HD_MAI_CTL,
VC4_HD_MAI_CTL_RESET |
VC4_HD_MAI_CTL_FLUSH |
VC4_HD_MAI_CTL_DLATE |
VC4_HD_MAI_CTL_ERRORE |
VC4_HD_MAI_CTL_ERRORF);
vc4_hdmi_audio_set_mai_clock(hdmi);
audio_packet_config =
VC4_HDMI_AUDIO_PACKET_ZERO_DATA_ON_SAMPLE_FLAT |
VC4_HDMI_AUDIO_PACKET_ZERO_DATA_ON_INACTIVE_CHANNELS |
VC4_SET_FIELD(0xf, VC4_HDMI_AUDIO_PACKET_B_FRAME_IDENTIFIER);
channel_mask = GENMASK(hdmi->audio.channels - 1, 0);
audio_packet_config |= VC4_SET_FIELD(channel_mask,
VC4_HDMI_AUDIO_PACKET_CEA_MASK);
/* Set the MAI threshold. This logic mimics the firmware's. */
if (hdmi->audio.samplerate > 96000) {
HD_WRITE(VC4_HD_MAI_THR,
VC4_SET_FIELD(0x12, VC4_HD_MAI_THR_DREQHIGH) |
VC4_SET_FIELD(0x12, VC4_HD_MAI_THR_DREQLOW));
} else if (hdmi->audio.samplerate > 48000) {
HD_WRITE(VC4_HD_MAI_THR,
VC4_SET_FIELD(0x14, VC4_HD_MAI_THR_DREQHIGH) |
VC4_SET_FIELD(0x12, VC4_HD_MAI_THR_DREQLOW));
} else {
HD_WRITE(VC4_HD_MAI_THR,
VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_PANICHIGH) |
VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_PANICLOW) |
VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_DREQHIGH) |
VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_DREQLOW));
}
HDMI_WRITE(VC4_HDMI_MAI_CONFIG,
VC4_HDMI_MAI_CONFIG_BIT_REVERSE |
VC4_SET_FIELD(channel_mask, VC4_HDMI_MAI_CHANNEL_MASK));
channel_map = 0;
for (i = 0; i < 8; i++) {
if (channel_mask & BIT(i))
channel_map |= i << (3 * i);
}
HDMI_WRITE(VC4_HDMI_MAI_CHANNEL_MAP, channel_map);
HDMI_WRITE(VC4_HDMI_AUDIO_PACKET_CONFIG, audio_packet_config);
vc4_hdmi_set_n_cts(hdmi);
return 0;
}
static int vc4_hdmi_audio_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct vc4_hdmi *hdmi = dai_to_hdmi(dai);
struct drm_encoder *encoder = hdmi->encoder;
struct drm_device *drm = encoder->dev;
struct vc4_dev *vc4 = to_vc4_dev(drm);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
vc4_hdmi_set_audio_infoframe(encoder);
HDMI_WRITE(VC4_HDMI_TX_PHY_CTL0,
HDMI_READ(VC4_HDMI_TX_PHY_CTL0) &
~VC4_HDMI_TX_PHY_RNG_PWRDN);
HD_WRITE(VC4_HD_MAI_CTL,
VC4_SET_FIELD(hdmi->audio.channels,
VC4_HD_MAI_CTL_CHNUM) |
VC4_HD_MAI_CTL_ENABLE);
break;
case SNDRV_PCM_TRIGGER_STOP:
HD_WRITE(VC4_HD_MAI_CTL,
VC4_HD_MAI_CTL_DLATE |
VC4_HD_MAI_CTL_ERRORE |
VC4_HD_MAI_CTL_ERRORF);
HDMI_WRITE(VC4_HDMI_TX_PHY_CTL0,
HDMI_READ(VC4_HDMI_TX_PHY_CTL0) |
VC4_HDMI_TX_PHY_RNG_PWRDN);
break;
default:
break;
}
return 0;
}
static inline struct vc4_hdmi *
snd_component_to_hdmi(struct snd_soc_component *component)
{
struct snd_soc_card *card = snd_soc_component_get_drvdata(component);
return snd_soc_card_get_drvdata(card);
}
static int vc4_hdmi_audio_eld_ctl_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct vc4_hdmi *hdmi = snd_component_to_hdmi(component);
uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
uinfo->count = sizeof(hdmi->connector->eld);
return 0;
}
static int vc4_hdmi_audio_eld_ctl_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct vc4_hdmi *hdmi = snd_component_to_hdmi(component);
memcpy(ucontrol->value.bytes.data, hdmi->connector->eld,
sizeof(hdmi->connector->eld));
return 0;
}
static const struct snd_kcontrol_new vc4_hdmi_audio_controls[] = {
{
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "ELD",
.info = vc4_hdmi_audio_eld_ctl_info,
.get = vc4_hdmi_audio_eld_ctl_get,
},
};
static const struct snd_soc_dapm_widget vc4_hdmi_audio_widgets[] = {
SND_SOC_DAPM_OUTPUT("TX"),
};
static const struct snd_soc_dapm_route vc4_hdmi_audio_routes[] = {
{ "TX", NULL, "Playback" },
};
static const struct snd_soc_codec_driver vc4_hdmi_audio_codec_drv = {
.component_driver = {
.controls = vc4_hdmi_audio_controls,
.num_controls = ARRAY_SIZE(vc4_hdmi_audio_controls),
.dapm_widgets = vc4_hdmi_audio_widgets,
.num_dapm_widgets = ARRAY_SIZE(vc4_hdmi_audio_widgets),
.dapm_routes = vc4_hdmi_audio_routes,
.num_dapm_routes = ARRAY_SIZE(vc4_hdmi_audio_routes),
},
};
static const struct snd_soc_dai_ops vc4_hdmi_audio_dai_ops = {
.startup = vc4_hdmi_audio_startup,
.shutdown = vc4_hdmi_audio_shutdown,
.hw_params = vc4_hdmi_audio_hw_params,
.set_fmt = vc4_hdmi_audio_set_fmt,
.trigger = vc4_hdmi_audio_trigger,
};
static struct snd_soc_dai_driver vc4_hdmi_audio_codec_dai_drv = {
.name = "vc4-hdmi-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |
SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |
SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,
},
};
static const struct snd_soc_component_driver vc4_hdmi_audio_cpu_dai_comp = {
.name = "vc4-hdmi-cpu-dai-component",
};
static int vc4_hdmi_audio_cpu_dai_probe(struct snd_soc_dai *dai)
{
struct vc4_hdmi *hdmi = dai_to_hdmi(dai);
snd_soc_dai_init_dma_data(dai, &hdmi->audio.dma_data, NULL);
return 0;
}
static struct snd_soc_dai_driver vc4_hdmi_audio_cpu_dai_drv = {
.name = "vc4-hdmi-cpu-dai",
.probe = vc4_hdmi_audio_cpu_dai_probe,
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |
SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |
SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,
},
.ops = &vc4_hdmi_audio_dai_ops,
};
static const struct snd_dmaengine_pcm_config pcm_conf = {
.chan_names[SNDRV_PCM_STREAM_PLAYBACK] = "audio-rx",
.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
};
static int vc4_hdmi_audio_init(struct vc4_hdmi *hdmi)
{
struct snd_soc_dai_link *dai_link = &hdmi->audio.link;
struct snd_soc_card *card = &hdmi->audio.card;
struct device *dev = &hdmi->pdev->dev;
const __be32 *addr;
int ret;
if (!of_find_property(dev->of_node, "dmas", NULL)) {
dev_warn(dev,
"'dmas' DT property is missing, no HDMI audio\n");
return 0;
}
/*
* Get the physical address of VC4_HD_MAI_DATA. We need to retrieve
* the bus address specified in the DT, because the physical address
* (the one returned by platform_get_resource()) is not appropriate
* for DMA transfers.
* This VC/MMU should probably be exposed to avoid this kind of hacks.
*/
addr = of_get_address(dev->of_node, 1, NULL, NULL);
hdmi->audio.dma_data.addr = be32_to_cpup(addr) + VC4_HD_MAI_DATA;
hdmi->audio.dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
hdmi->audio.dma_data.maxburst = 2;
ret = devm_snd_dmaengine_pcm_register(dev, &pcm_conf, 0);
if (ret) {
dev_err(dev, "Could not register PCM component: %d\n", ret);
return ret;
}
ret = devm_snd_soc_register_component(dev, &vc4_hdmi_audio_cpu_dai_comp,
&vc4_hdmi_audio_cpu_dai_drv, 1);
if (ret) {
dev_err(dev, "Could not register CPU DAI: %d\n", ret);
return ret;
}
/* register codec and codec dai */
ret = snd_soc_register_codec(dev, &vc4_hdmi_audio_codec_drv,
&vc4_hdmi_audio_codec_dai_drv, 1);
if (ret) {
dev_err(dev, "Could not register codec: %d\n", ret);
return ret;
}
dai_link->name = "MAI";
dai_link->stream_name = "MAI PCM";
dai_link->codec_dai_name = vc4_hdmi_audio_codec_dai_drv.name;
dai_link->cpu_dai_name = dev_name(dev);
dai_link->codec_name = dev_name(dev);
dai_link->platform_name = dev_name(dev);
card->dai_link = dai_link;
card->num_links = 1;
card->name = "vc4-hdmi";
card->dev = dev;
/*
* Be careful, snd_soc_register_card() calls dev_set_drvdata() and
* stores a pointer to the snd card object in dev->driver_data. This
* means we cannot use it for something else. The hdmi back-pointer is
* now stored in card->drvdata and should be retrieved with
* snd_soc_card_get_drvdata() if needed.
*/
snd_soc_card_set_drvdata(card, hdmi);
ret = devm_snd_soc_register_card(dev, card);
if (ret) {
dev_err(dev, "Could not register sound card: %d\n", ret);
goto unregister_codec;
}
return 0;
unregister_codec:
snd_soc_unregister_codec(dev);
return ret;
}
static void vc4_hdmi_audio_cleanup(struct vc4_hdmi *hdmi)
{
struct device *dev = &hdmi->pdev->dev;
/*
* If drvdata is not set this means the audio card was not
* registered, just skip codec unregistration in this case.
*/
if (dev_get_drvdata(dev))
snd_soc_unregister_codec(dev);
}
static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
{ {
struct platform_device *pdev = to_platform_device(dev); struct platform_device *pdev = to_platform_device(dev);
...@@ -737,6 +1223,10 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) ...@@ -737,6 +1223,10 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
goto err_destroy_encoder; goto err_destroy_encoder;
} }
ret = vc4_hdmi_audio_init(hdmi);
if (ret)
goto err_destroy_encoder;
return 0; return 0;
err_destroy_encoder: err_destroy_encoder:
...@@ -758,6 +1248,8 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master, ...@@ -758,6 +1248,8 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master,
struct vc4_dev *vc4 = drm->dev_private; struct vc4_dev *vc4 = drm->dev_private;
struct vc4_hdmi *hdmi = vc4->hdmi; struct vc4_hdmi *hdmi = vc4->hdmi;
vc4_hdmi_audio_cleanup(hdmi);
vc4_hdmi_connector_destroy(hdmi->connector); vc4_hdmi_connector_destroy(hdmi->connector);
vc4_hdmi_encoder_destroy(hdmi->encoder); vc4_hdmi_encoder_destroy(hdmi->encoder);
......
...@@ -446,11 +446,62 @@ ...@@ -446,11 +446,62 @@
#define VC4_HDMI_HOTPLUG 0x00c #define VC4_HDMI_HOTPLUG 0x00c
# define VC4_HDMI_HOTPLUG_CONNECTED BIT(0) # define VC4_HDMI_HOTPLUG_CONNECTED BIT(0)
/* 3 bits per field, where each field maps from that corresponding MAI
* bus channel to the given HDMI channel.
*/
#define VC4_HDMI_MAI_CHANNEL_MAP 0x090
#define VC4_HDMI_MAI_CONFIG 0x094
# define VC4_HDMI_MAI_CONFIG_FORMAT_REVERSE BIT(27)
# define VC4_HDMI_MAI_CONFIG_BIT_REVERSE BIT(26)
# define VC4_HDMI_MAI_CHANNEL_MASK_MASK VC4_MASK(15, 0)
# define VC4_HDMI_MAI_CHANNEL_MASK_SHIFT 0
/* Last received format word on the MAI bus. */
#define VC4_HDMI_MAI_FORMAT 0x098
#define VC4_HDMI_AUDIO_PACKET_CONFIG 0x09c
# define VC4_HDMI_AUDIO_PACKET_ZERO_DATA_ON_SAMPLE_FLAT BIT(29)
# define VC4_HDMI_AUDIO_PACKET_ZERO_DATA_ON_INACTIVE_CHANNELS BIT(24)
# define VC4_HDMI_AUDIO_PACKET_FORCE_SAMPLE_PRESENT BIT(19)
# define VC4_HDMI_AUDIO_PACKET_FORCE_B_FRAME BIT(18)
# define VC4_HDMI_AUDIO_PACKET_B_FRAME_IDENTIFIER_MASK VC4_MASK(13, 10)
# define VC4_HDMI_AUDIO_PACKET_B_FRAME_IDENTIFIER_SHIFT 10
/* If set, then multichannel, otherwise 2 channel. */
# define VC4_HDMI_AUDIO_PACKET_AUDIO_LAYOUT BIT(9)
/* If set, then AUDIO_LAYOUT overrides audio_cea_mask */
# define VC4_HDMI_AUDIO_PACKET_FORCE_AUDIO_LAYOUT BIT(8)
# define VC4_HDMI_AUDIO_PACKET_CEA_MASK_MASK VC4_MASK(7, 0)
# define VC4_HDMI_AUDIO_PACKET_CEA_MASK_SHIFT 0
#define VC4_HDMI_RAM_PACKET_CONFIG 0x0a0 #define VC4_HDMI_RAM_PACKET_CONFIG 0x0a0
# define VC4_HDMI_RAM_PACKET_ENABLE BIT(16) # define VC4_HDMI_RAM_PACKET_ENABLE BIT(16)
#define VC4_HDMI_RAM_PACKET_STATUS 0x0a4 #define VC4_HDMI_RAM_PACKET_STATUS 0x0a4
#define VC4_HDMI_CRP_CFG 0x0a8
/* When set, the CTS_PERIOD counts based on MAI bus sync pulse instead
* of pixel clock.
*/
# define VC4_HDMI_CRP_USE_MAI_BUS_SYNC_FOR_CTS BIT(26)
/* When set, no CRP packets will be sent. */
# define VC4_HDMI_CRP_CFG_DISABLE BIT(25)
/* If set, generates CTS values based on N, audio clock, and video
* clock. N must be divisible by 128.
*/
# define VC4_HDMI_CRP_CFG_EXTERNAL_CTS_EN BIT(24)
# define VC4_HDMI_CRP_CFG_N_MASK VC4_MASK(19, 0)
# define VC4_HDMI_CRP_CFG_N_SHIFT 0
/* 20-bit fields containing CTS values to be transmitted if !EXTERNAL_CTS_EN */
#define VC4_HDMI_CTS_0 0x0ac
#define VC4_HDMI_CTS_1 0x0b0
/* 20-bit fields containing number of clocks to send CTS0/1 before
* switching to the other one.
*/
#define VC4_HDMI_CTS_PERIOD_0 0x0b4
#define VC4_HDMI_CTS_PERIOD_1 0x0b8
#define VC4_HDMI_HORZA 0x0c4 #define VC4_HDMI_HORZA 0x0c4
# define VC4_HDMI_HORZA_VPOS BIT(14) # define VC4_HDMI_HORZA_VPOS BIT(14)
# define VC4_HDMI_HORZA_HPOS BIT(13) # define VC4_HDMI_HORZA_HPOS BIT(13)
...@@ -512,7 +563,11 @@ ...@@ -512,7 +563,11 @@
#define VC4_HDMI_TX_PHY_RESET_CTL 0x2c0 #define VC4_HDMI_TX_PHY_RESET_CTL 0x2c0
#define VC4_HDMI_GCP_0 0x400 #define VC4_HDMI_TX_PHY_CTL0 0x2c4
# define VC4_HDMI_TX_PHY_RNG_PWRDN BIT(25)
#define VC4_HDMI_GCP(x) (0x400 + ((x) * 0x4))
#define VC4_HDMI_RAM_PACKET(x) (0x400 + ((x) * 0x24))
#define VC4_HDMI_PACKET_STRIDE 0x24 #define VC4_HDMI_PACKET_STRIDE 0x24
#define VC4_HD_M_CTL 0x00c #define VC4_HD_M_CTL 0x00c
...@@ -522,6 +577,56 @@ ...@@ -522,6 +577,56 @@
# define VC4_HD_M_ENABLE BIT(0) # define VC4_HD_M_ENABLE BIT(0)
#define VC4_HD_MAI_CTL 0x014 #define VC4_HD_MAI_CTL 0x014
/* Set when audio stream is received at a slower rate than the
* sampling period, so MAI fifo goes empty. Write 1 to clear.
*/
# define VC4_HD_MAI_CTL_DLATE BIT(15)
# define VC4_HD_MAI_CTL_BUSY BIT(14)
# define VC4_HD_MAI_CTL_CHALIGN BIT(13)
# define VC4_HD_MAI_CTL_WHOLSMP BIT(12)
# define VC4_HD_MAI_CTL_FULL BIT(11)
# define VC4_HD_MAI_CTL_EMPTY BIT(10)
# define VC4_HD_MAI_CTL_FLUSH BIT(9)
/* If set, MAI bus generates SPDIF (bit 31) parity instead of passing
* through.
*/
# define VC4_HD_MAI_CTL_PAREN BIT(8)
# define VC4_HD_MAI_CTL_CHNUM_MASK VC4_MASK(7, 4)
# define VC4_HD_MAI_CTL_CHNUM_SHIFT 4
# define VC4_HD_MAI_CTL_ENABLE BIT(3)
/* Underflow error status bit, write 1 to clear. */
# define VC4_HD_MAI_CTL_ERRORE BIT(2)
/* Overflow error status bit, write 1 to clear. */
# define VC4_HD_MAI_CTL_ERRORF BIT(1)
/* Single-shot reset bit. Read value is undefined. */
# define VC4_HD_MAI_CTL_RESET BIT(0)
#define VC4_HD_MAI_THR 0x018
# define VC4_HD_MAI_THR_PANICHIGH_MASK VC4_MASK(29, 24)
# define VC4_HD_MAI_THR_PANICHIGH_SHIFT 24
# define VC4_HD_MAI_THR_PANICLOW_MASK VC4_MASK(21, 16)
# define VC4_HD_MAI_THR_PANICLOW_SHIFT 16
# define VC4_HD_MAI_THR_DREQHIGH_MASK VC4_MASK(13, 8)
# define VC4_HD_MAI_THR_DREQHIGH_SHIFT 8
# define VC4_HD_MAI_THR_DREQLOW_MASK VC4_MASK(5, 0)
# define VC4_HD_MAI_THR_DREQLOW_SHIFT 0
/* Format header to be placed on the MAI data. Unused. */
#define VC4_HD_MAI_FMT 0x01c
/* Register for DMAing in audio data to be transported over the MAI
* bus to the Falcon core.
*/
#define VC4_HD_MAI_DATA 0x020
/* Divider from HDMI HSM clock to MAI serial clock. Sampling period
* converges to N / (M + 1) cycles.
*/
#define VC4_HD_MAI_SMP 0x02c
# define VC4_HD_MAI_SMP_N_MASK VC4_MASK(31, 8)
# define VC4_HD_MAI_SMP_N_SHIFT 8
# define VC4_HD_MAI_SMP_M_MASK VC4_MASK(7, 0)
# define VC4_HD_MAI_SMP_M_SHIFT 0
#define VC4_HD_VID_CTL 0x038 #define VC4_HD_VID_CTL 0x038
# define VC4_HD_VID_CTL_ENABLE BIT(31) # define VC4_HD_VID_CTL_ENABLE BIT(31)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment