summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Robinson <pbrobinson@gmail.com>2017-03-21 16:35:26 +0000
committerPeter Robinson <pbrobinson@gmail.com>2017-03-21 16:35:26 +0000
commit580d60f07aa0583f95da6bcf742c90e397027534 (patch)
tree2d2d3247eece48c1de7f56bbaf1912e2079c2f31
parent88fc0e2f65816ea00498a0302a7fd5f54695ea9c (diff)
downloadkernel-580d60f07aa0583f95da6bcf742c90e397027534.tar.gz
kernel-580d60f07aa0583f95da6bcf742c90e397027534.tar.xz
kernel-580d60f07aa0583f95da6bcf742c90e397027534.zip
Add initial support for vc4 HDMI Audio
-rw-r--r--bcm283x-hdmi-audio.patch836
-rw-r--r--filter-aarch64.sh2
-rw-r--r--filter-armv7hl.sh2
-rw-r--r--kernel.spec5
4 files changed, 843 insertions, 2 deletions
diff --git a/bcm283x-hdmi-audio.patch b/bcm283x-hdmi-audio.patch
new file mode 100644
index 000000000..3ed3d2d34
--- /dev/null
+++ b/bcm283x-hdmi-audio.patch
@@ -0,0 +1,836 @@
+From bbcb8aacb871edf0360e808180162591b11c6a35 Mon Sep 17 00:00:00 2001
+From: Boris Brezillon <boris.brezillon@free-electrons.com>
+Date: Mon, 27 Feb 2017 12:28:01 -0800
+Subject: [PATCH 1/3] dt-bindings: Document the dmas and dma-names properties
+ for VC4 HDMI
+
+These are optional, but necessary for HDMI audio support.
+
+Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
+Signed-off-by: Eric Anholt <eric@anholt.net>
+Acked-by: Rob Herring <robh@kernel.org>
+Link: http://patchwork.freedesktop.org/patch/msgid/20170227202803.12855-1-eric@anholt.net
+---
+ Documentation/devicetree/bindings/display/brcm,bcm-vc4.txt | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/Documentation/devicetree/bindings/display/brcm,bcm-vc4.txt b/Documentation/devicetree/bindings/display/brcm,bcm-vc4.txt
+index 34c7fddcea39..ca02d3e4db91 100644
+--- a/Documentation/devicetree/bindings/display/brcm,bcm-vc4.txt
++++ b/Documentation/devicetree/bindings/display/brcm,bcm-vc4.txt
+@@ -34,6 +34,9 @@ Optional properties for HDMI:
+ - hpd-gpios: The GPIO pin for HDMI hotplug detect (if it doesn't appear
+ as an interrupt/status bit in the HDMI controller
+ itself). See bindings/pinctrl/brcm,bcm2835-gpio.txt
++- dmas: Should contain one entry pointing to the DMA channel used to
++ transfer audio data
++- dma-names: Should contain "audio-rx"
+
+ Required properties for DPI:
+ - compatible: Should be "brcm,bcm2835-dpi"
+--
+2.12.0
+
+From 8e13e0d8ecf2202c707225a612d10c9534d849f7 Mon Sep 17 00:00:00 2001
+From: Eric Anholt <eric@anholt.net>
+Date: Mon, 27 Feb 2017 12:28:02 -0800
+Subject: [PATCH 2/3] 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: Eric Anholt <eric@anholt.net>
+Acked-by: Boris Brezillon <boris.brezillon@free-electrons.com>
+Link: http://patchwork.freedesktop.org/patch/msgid/20170227202803.12855-2-eric@anholt.net
+---
+ drivers/gpu/drm/vc4/Kconfig | 4 +
+ drivers/gpu/drm/vc4/vc4_hdmi.c | 494 ++++++++++++++++++++++++++++++++++++++++-
+ drivers/gpu/drm/vc4/vc4_regs.h | 107 ++++++++-
+ 3 files changed, 603 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/gpu/drm/vc4/Kconfig b/drivers/gpu/drm/vc4/Kconfig
+index e1517d07cb7d..973b4203c0b2 100644
+--- a/drivers/gpu/drm/vc4/Kconfig
++++ b/drivers/gpu/drm/vc4/Kconfig
+@@ -2,11 +2,15 @@ config DRM_VC4
+ tristate "Broadcom VC4 Graphics"
+ depends on ARCH_BCM2835 || COMPILE_TEST
+ depends on DRM
++ depends on SND && SND_SOC
+ depends on COMMON_CLK
+ select DRM_KMS_HELPER
+ select DRM_KMS_CMA_HELPER
+ select DRM_GEM_CMA_HELPER
+ select DRM_PANEL
++ select SND_PCM
++ select SND_PCM_ELD
++ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select DRM_MIPI_DSI
+ help
+ Choose this option if you have a system that has a Broadcom
+diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c
+index 93d5994f3a04..e4abf4bfc464 100644
+--- a/drivers/gpu/drm/vc4/vc4_hdmi.c
++++ b/drivers/gpu/drm/vc4/vc4_hdmi.c
+@@ -31,11 +31,27 @@
+ #include "linux/clk.h"
+ #include "linux/component.h"
+ #include "linux/i2c.h"
++#include "linux/of_address.h"
+ #include "linux/of_gpio.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_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. */
+ struct vc4_hdmi {
+ struct platform_device *pdev;
+@@ -43,6 +59,8 @@ struct vc4_hdmi {
+ struct drm_encoder *encoder;
+ struct drm_connector *connector;
+
++ struct vc4_hdmi_audio audio;
++
+ struct i2c_adapter *ddc;
+ void __iomem *hdmicore_regs;
+ void __iomem *hd_regs;
+@@ -98,6 +116,10 @@ static const struct {
+ HDMI_REG(VC4_HDMI_SW_RESET_CONTROL),
+ HDMI_REG(VC4_HDMI_HOTPLUG_INT),
+ 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_HORZA),
+ HDMI_REG(VC4_HDMI_HORZB),
+@@ -108,6 +130,7 @@ static const struct {
+ HDMI_REG(VC4_HDMI_VERTB0),
+ HDMI_REG(VC4_HDMI_VERTB1),
+ HDMI_REG(VC4_HDMI_TX_PHY_RESET_CTL),
++ HDMI_REG(VC4_HDMI_TX_PHY_CTL0),
+ };
+
+ static const struct {
+@@ -116,6 +139,9 @@ static const struct {
+ } hd_regs[] = {
+ HDMI_REG(VC4_HD_M_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_CSC_CTL),
+ HDMI_REG(VC4_HD_FRAME_COUNT),
+@@ -215,6 +241,7 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector)
+
+ drm_mode_connector_update_edid_property(connector, edid);
+ ret = drm_add_edid_modes(connector, edid);
++ drm_edid_to_eld(connector, edid);
+
+ return ret;
+ }
+@@ -300,7 +327,7 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder,
+ struct drm_device *dev = encoder->dev;
+ struct vc4_dev *vc4 = to_vc4_dev(dev);
+ 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];
+ ssize_t len, i;
+ int ret;
+@@ -381,6 +408,24 @@ static void vc4_hdmi_set_spd_infoframe(struct drm_encoder *encoder)
+ 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)
+ {
+ vc4_hdmi_set_avi_infoframe(encoder);
+@@ -589,6 +634,447 @@ static const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = {
+ .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)
+ {
+ struct platform_device *pdev = to_platform_device(dev);
+@@ -720,6 +1206,10 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
+ goto err_destroy_encoder;
+ }
+
++ ret = vc4_hdmi_audio_init(hdmi);
++ if (ret)
++ goto err_destroy_encoder;
++
+ return 0;
+
+ err_destroy_encoder:
+@@ -741,6 +1231,8 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master,
+ struct vc4_dev *vc4 = drm->dev_private;
+ struct vc4_hdmi *hdmi = vc4->hdmi;
+
++ vc4_hdmi_audio_cleanup(hdmi);
++
+ vc4_hdmi_connector_destroy(hdmi->connector);
+ vc4_hdmi_encoder_destroy(hdmi->encoder);
+
+diff --git a/drivers/gpu/drm/vc4/vc4_regs.h b/drivers/gpu/drm/vc4/vc4_regs.h
+index 385405a2df05..932093936178 100644
+--- a/drivers/gpu/drm/vc4/vc4_regs.h
++++ b/drivers/gpu/drm/vc4/vc4_regs.h
+@@ -446,11 +446,62 @@
+ #define VC4_HDMI_HOTPLUG 0x00c
+ # 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_ENABLE BIT(16)
+
+ #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_VPOS BIT(14)
+ # define VC4_HDMI_HORZA_HPOS BIT(13)
+@@ -512,7 +563,11 @@
+
+ #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_HD_M_CTL 0x00c
+@@ -522,6 +577,56 @@
+ # define VC4_HD_M_ENABLE BIT(0)
+
+ #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_ENABLE BIT(31)
+--
+2.12.0
+
+From 25ea82d7f7c869ff81ff8e64d24c5c4a896239fe Mon Sep 17 00:00:00 2001
+From: Boris Brezillon <boris.brezillon@free-electrons.com>
+Date: Mon, 27 Feb 2017 12:28:03 -0800
+Subject: [PATCH 3/3] ARM: dts: bcm283x: Add HDMI audio related properties
+
+Add the dmas and dma-names properties to support HDMI audio.
+
+Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
+Signed-off-by: Eric Anholt <eric@anholt.net>
+---
+ arch/arm/boot/dts/bcm283x.dtsi | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/arch/arm/boot/dts/bcm283x.dtsi b/arch/arm/boot/dts/bcm283x.dtsi
+index a3106aa446c6..a31b0b303ddc 100644
+--- a/arch/arm/boot/dts/bcm283x.dtsi
++++ b/arch/arm/boot/dts/bcm283x.dtsi
+@@ -499,6 +499,8 @@
+ clocks = <&clocks BCM2835_PLLH_PIX>,
+ <&clocks BCM2835_CLOCK_HSM>;
+ clock-names = "pixel", "hdmi";
++ dmas = <&dma 17>;
++ dma-names = "audio-rx";
+ status = "disabled";
+ };
+
+--
+2.12.0
+
diff --git a/filter-aarch64.sh b/filter-aarch64.sh
index de039f17f..70d6338b0 100644
--- a/filter-aarch64.sh
+++ b/filter-aarch64.sh
@@ -13,4 +13,4 @@ driverdirs="atm auxdisplay bcma bluetooth firewire fmc infiniband isdn leds medi
ethdrvs="3com adaptec arc alteon atheros broadcom cadence calxeda chelsio cisco dec dlink emulex icplus marvell micrel myricom neterion nvidia oki-semi packetengines qlogic rdc renesas sfc silan sis smsc stmicro sun tehuti ti via wiznet xircom"
-singlemods="ntb_netdev iscsi_ibft iscsi_boot_sysfs megaraid pmcraid qedi qla1280 9pnet_rdma rpcrdma nvmet-rdma nvme-rdma hid-picolcd hid-prodikeys hwa-hc hwpoison-inject target_core_user sbp_target cxgbit iw_cxgb3 iw_cxgb4 cxgb3i cxgb3i cxgb3i_ddp cxgb4i chcr"
+singlemods="ntb_netdev iscsi_ibft iscsi_boot_sysfs megaraid pmcraid qedi qla1280 9pnet_rdma rpcrdma nvmet-rdma nvme-rdma hid-picolcd hid-prodikeys hwa-hc hwpoison-inject target_core_user sbp_target cxgbit iw_cxgb3 iw_cxgb4 cxgb3i cxgb3i cxgb3i_ddp cxgb4i chcr vc4"
diff --git a/filter-armv7hl.sh b/filter-armv7hl.sh
index 863e1aeac..2141dfef4 100644
--- a/filter-armv7hl.sh
+++ b/filter-armv7hl.sh
@@ -15,4 +15,4 @@ ethdrvs="3com adaptec alteon altera amd atheros broadcom cadence chelsio cisco d
drmdrvs="amd armada bridge ast exynos i2c imx mgag200 msm omapdrm panel nouveau radeon rockchip tegra tilcdc via"
-singlemods="ntb_netdev iscsi_ibft iscsi_boot_sysfs megaraid pmcraid qedi qla1280 9pnet_rdma rpcrdma nvmet-rdma nvme-rdma hid-picolcd hid-prodikeys hwa-hc hwpoison-inject target_core_user sbp_target cxgbit iw_cxgb3 iw_cxgb4 cxgb3i cxgb3i cxgb3i_ddp cxgb4i chcr"
+singlemods="ntb_netdev iscsi_ibft iscsi_boot_sysfs megaraid pmcraid qedi qla1280 9pnet_rdma rpcrdma nvmet-rdma nvme-rdma hid-picolcd hid-prodikeys hwa-hc hwpoison-inject target_core_user sbp_target cxgbit iw_cxgb3 iw_cxgb4 cxgb3i cxgb3i cxgb3i_ddp cxgb4i chcr vc4"
diff --git a/kernel.spec b/kernel.spec
index 19aa29c50..94fa8eff9 100644
--- a/kernel.spec
+++ b/kernel.spec
@@ -543,6 +543,8 @@ Patch435: bcm283x-fixes.patch
# https://lists.freedesktop.org/archives/dri-devel/2017-February/133823.html
Patch436: vc4-fix-vblank-cursor-update-issue.patch
+Patch437: bcm283x-hdmi-audio.patch
+
# http://www.spinics.net/lists/arm-kernel/msg552554.html
Patch438: arm-imx6-hummingboard2.patch
@@ -2167,6 +2169,9 @@ fi
#
#
%changelog
+* Tue Mar 21 2017 Peter Robinson <pbrobinson@fedoraproject.org>
+- Add initial support for vc4 HDMI Audio
+
* Mon Mar 20 2017 Laura Abbott <labbott@fedoraproject.org> - 4.11.0-0.rc3.git0.1
- Linux v4.11-rc3
- Fix for debuginfo conflicts (rhbz 1431296)