drm/vc4: Add HDMI audio support
authorEric Anholt <eric@anholt.net>
Mon, 27 Feb 2017 20:28:02 +0000 (12:28 -0800)
committerEric Anholt <eric@anholt.net>
Thu, 16 Mar 2017 17:33:30 +0000 (10:33 -0700)
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
drivers/gpu/drm/vc4/vc4_hdmi.c
drivers/gpu/drm/vc4/vc4_regs.h

index e1517d07cb7d22776ca164a5d2d9b87e55a5563a..973b4203c0b264115b7cd9d4a433b449bd0ec3b3 100644 (file)
@@ -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
index 1be1e83047207b637b158947f24c2942146eeed0..e9cbe269710bc194d7caab8031e0d71ec3d61b9e 100644 (file)
 #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;
@@ -60,6 +76,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;
@@ -115,6 +133,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),
@@ -125,6 +147,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 {
@@ -133,6 +156,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),
@@ -232,6 +258,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;
 }
@@ -317,7 +344,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;
@@ -398,6 +425,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);
@@ -606,6 +651,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);
@@ -737,6 +1223,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:
@@ -758,6 +1248,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);
 
index 385405a2df05eb3dd86d4f687aa8205331bec3cc..932093936178674173a84002b33e07e9a37fdfe9 100644 (file)
 #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)
 
 #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
 # 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)