Merge pull request #4113 from jernejsk/a20_hdmi

Allwinner: Enable A20 HDMI audio
This commit is contained in:
Jonas Karlman 2020-01-12 22:38:21 +01:00 committed by GitHub
commit ba4460e16a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 639 additions and 0 deletions

View File

@ -0,0 +1,34 @@
#
# Configuration for HDMI
#
<confdir:pcm/hdmi.conf>
sun4i-hdmi.pcm.hdmi.0 {
@args [ CARD AES0 AES1 AES2 AES3 ]
@args.CARD { type string }
@args.AES0 { type integer }
@args.AES1 { type integer }
@args.AES2 { type integer }
@args.AES3 { type integer }
type hooks
slave.pcm {
type hw
card $CARD
device 0
}
hooks.0 {
type ctl_elems
hook_args [
{
interface MIXER
name "IEC958 Playback Default"
lock true
preserve true
optional true
value [ $AES0 $AES1 $AES2 $AES3 ]
}
]
}
hint.device 0
}

View File

@ -0,0 +1,605 @@
Subject: [1/2] dmaengine: sun4i: Add support for cyclic requests with dedicated DMA
From: Stefan Mavrodiev <stefan@olimex.com>
Date: Fri, 10 Jan 2020 16:11:39 +0200
Currently the cyclic transfers can be used only with normal DMAs. They
can be used by pcm_dmaengine module, which is required for implementing
sound with sun4i-hdmi encoder. This is so because the controller can
accept audio only from a dedicated DMA.
This patch enables them, following the existing style for the
scatter/gather type transfers.
Signed-off-by: Stefan Mavrodiev <stefan@olimex.com>
---
drivers/dma/sun4i-dma.c | 45 ++++++++++++++++++++++-------------------
1 file changed, 24 insertions(+), 21 deletions(-)
diff --git a/drivers/dma/sun4i-dma.c b/drivers/dma/sun4i-dma.c
index e397a50058c8..7b41815d86fb 100644
--- a/drivers/dma/sun4i-dma.c
+++ b/drivers/dma/sun4i-dma.c
@@ -669,43 +669,41 @@ sun4i_dma_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf, size_t len,
dma_addr_t src, dest;
u32 endpoints;
int nr_periods, offset, plength, i;
+ u8 ram_type, io_mode, linear_mode;
if (!is_slave_direction(dir)) {
dev_err(chan2dev(chan), "Invalid DMA direction\n");
return NULL;
}
- if (vchan->is_dedicated) {
- /*
- * As we are using this just for audio data, we need to use
- * normal DMA. There is nothing stopping us from supporting
- * dedicated DMA here as well, so if a client comes up and
- * requires it, it will be simple to implement it.
- */
- dev_err(chan2dev(chan),
- "Cyclic transfers are only supported on Normal DMA\n");
- return NULL;
- }
-
contract = generate_dma_contract();
if (!contract)
return NULL;
contract->is_cyclic = 1;
- /* Figure out the endpoints and the address we need */
+ if (vchan->is_dedicated) {
+ io_mode = SUN4I_DDMA_ADDR_MODE_IO;
+ linear_mode = SUN4I_DDMA_ADDR_MODE_LINEAR;
+ ram_type = SUN4I_DDMA_DRQ_TYPE_SDRAM;
+ } else {
+ io_mode = SUN4I_NDMA_ADDR_MODE_IO;
+ linear_mode = SUN4I_NDMA_ADDR_MODE_LINEAR;
+ ram_type = SUN4I_NDMA_DRQ_TYPE_SDRAM;
+ }
+
if (dir == DMA_MEM_TO_DEV) {
src = buf;
dest = sconfig->dst_addr;
- endpoints = SUN4I_DMA_CFG_SRC_DRQ_TYPE(SUN4I_NDMA_DRQ_TYPE_SDRAM) |
- SUN4I_DMA_CFG_DST_DRQ_TYPE(vchan->endpoint) |
- SUN4I_DMA_CFG_DST_ADDR_MODE(SUN4I_NDMA_ADDR_MODE_IO);
+ endpoints = SUN4I_DMA_CFG_DST_DRQ_TYPE(vchan->endpoint) |
+ SUN4I_DMA_CFG_DST_ADDR_MODE(io_mode) |
+ SUN4I_DMA_CFG_SRC_DRQ_TYPE(ram_type);
} else {
src = sconfig->src_addr;
dest = buf;
- endpoints = SUN4I_DMA_CFG_SRC_DRQ_TYPE(vchan->endpoint) |
- SUN4I_DMA_CFG_SRC_ADDR_MODE(SUN4I_NDMA_ADDR_MODE_IO) |
- SUN4I_DMA_CFG_DST_DRQ_TYPE(SUN4I_NDMA_DRQ_TYPE_SDRAM);
+ endpoints = SUN4I_DMA_CFG_DST_DRQ_TYPE(ram_type) |
+ SUN4I_DMA_CFG_SRC_DRQ_TYPE(vchan->endpoint) |
+ SUN4I_DMA_CFG_SRC_ADDR_MODE(io_mode);
}
/*
@@ -747,8 +745,13 @@ sun4i_dma_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf, size_t len,
dest = buf + offset;
/* Make the promise */
- promise = generate_ndma_promise(chan, src, dest,
- plength, sconfig, dir);
+ if (vchan->is_dedicated)
+ promise = generate_ddma_promise(chan, src, dest,
+ plength, sconfig);
+ else
+ promise = generate_ndma_promise(chan, src, dest,
+ plength, sconfig, dir);
+
if (!promise) {
/* TODO: should we free everything? */
return NULL;
Subject: [2/2] drm: sun4i: hdmi: Add support for sun4i HDMI encoder audio
From: Stefan Mavrodiev <stefan@olimex.com>
Date: Fri, 10 Jan 2020 16:11:40 +0200
Add HDMI audio support for the sun4i-hdmi encoder, used on
the older Allwinner chips - A10, A20, A31.
Most of the code is based on the BSP implementation. In it
dditional formats are supported (S20_3LE and S24_LE), however
there where some problems with them and only S16_LE is left.
Signed-off-by: Stefan Mavrodiev <stefan@olimex.com>
---
drivers/gpu/drm/sun4i/Kconfig | 1 +
drivers/gpu/drm/sun4i/Makefile | 1 +
drivers/gpu/drm/sun4i/sun4i_hdmi.h | 30 ++
drivers/gpu/drm/sun4i/sun4i_hdmi_audio.c | 375 +++++++++++++++++++++++
drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 4 +
5 files changed, 411 insertions(+)
create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_audio.c
diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
index 37e90e42943f..192b732b10cd 100644
--- a/drivers/gpu/drm/sun4i/Kconfig
+++ b/drivers/gpu/drm/sun4i/Kconfig
@@ -19,6 +19,7 @@ if DRM_SUN4I
config DRM_SUN4I_HDMI
tristate "Allwinner A10 HDMI Controller Support"
default DRM_SUN4I
+ select SND_PCM_ELD
help
Choose this option if you have an Allwinner SoC with an HDMI
controller.
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
index 0d04f2447b01..e2d82b451c36 100644
--- a/drivers/gpu/drm/sun4i/Makefile
+++ b/drivers/gpu/drm/sun4i/Makefile
@@ -5,6 +5,7 @@ sun4i-frontend-y += sun4i_frontend.o
sun4i-drm-y += sun4i_drv.o
sun4i-drm-y += sun4i_framebuffer.o
+sun4i-drm-hdmi-y += sun4i_hdmi_audio.o
sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
sun4i-drm-hdmi-y += sun4i_hdmi_i2c.o
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
index 7ad3f06c127e..456964e681b0 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
@@ -42,7 +42,32 @@
#define SUN4I_HDMI_VID_TIMING_POL_VSYNC BIT(1)
#define SUN4I_HDMI_VID_TIMING_POL_HSYNC BIT(0)
+#define SUN4I_HDMI_AUDIO_CTRL_REG 0x040
+#define SUN4I_HDMI_AUDIO_CTRL_ENABLE BIT(31)
+#define SUN4I_HDMI_AUDIO_CTRL_RESET BIT(30)
+
+#define SUN4I_HDMI_AUDIO_FMT_REG 0x048
+#define SUN4I_HDMI_AUDIO_FMT_SRC BIT(31)
+#define SUN4I_HDMI_AUDIO_FMT_LAYOUT BIT(3)
+#define SUN4I_HDMI_AUDIO_FMT_CH_CFG(n) (n - 1)
+#define SUN4I_HDMI_AUDIO_FMT_CH_CFG_MASK GENMASK(2, 0)
+
+#define SUN4I_HDMI_AUDIO_PCM_REG 0x4c
+#define SUN4I_HDMI_AUDIO_PCM_CH_MAP(n, m) ((m - 1) << (n * 4))
+#define SUN4I_HDMI_AUDIO_PCM_CH_MAP_MASK(n) (GENMASK(2, 0) << (n * 4))
+
+#define SUN4I_HDMI_AUDIO_CTS_REG 0x050
+#define SUN4I_HDMI_AUDIO_CTS(n) (n & GENMASK(19, 0))
+
+#define SUN4I_HDMI_AUDIO_N_REG 0x054
+#define SUN4I_HDMI_AUDIO_N(n) (n & GENMASK(19, 0))
+
+#define SUN4I_HDMI_AUDIO_STAT0_REG 0x58
+#define SUN4I_HDMI_AUDIO_STAT0_FREQ(n) (n << 24)
+#define SUN4I_HDMI_AUDIO_STAT0_FREQ_MASK GENMASK(27, 24)
+
#define SUN4I_HDMI_AVI_INFOFRAME_REG(n) (0x080 + (n))
+#define SUN4I_HDMI_AUDIO_INFOFRAME_REG(n) (0x0a0 + (n))
#define SUN4I_HDMI_PAD_CTRL0_REG 0x200
#define SUN4I_HDMI_PAD_CTRL0_BIASEN BIT(31)
@@ -283,9 +308,13 @@ struct sun4i_hdmi {
struct regmap_field *field_ddc_sda_en;
struct regmap_field *field_ddc_sck_en;
+ u8 hdmi_audio_channels;
+
struct sun4i_drv *drv;
bool hdmi_monitor;
+ bool hdmi_audio;
+
struct cec_adapter *cec_adap;
const struct sun4i_hdmi_variant *variant;
@@ -294,5 +323,6 @@ struct sun4i_hdmi {
int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
int sun4i_tmds_create(struct sun4i_hdmi *hdmi);
int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi);
+int sun4i_hdmi_audio_create(struct sun4i_hdmi *hdmi);
#endif /* _SUN4I_HDMI_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_audio.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_audio.c
new file mode 100644
index 000000000000..b6d4199d15ce
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_audio.c
@@ -0,0 +1,375 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2020 Olimex Ltd.
+ * Author: Stefan Mavrodiev <stefan@olimex.com>
+ */
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/module.h>
+#include <linux/of_dma.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_print.h>
+
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_drm_eld.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "sun4i_hdmi.h"
+
+static const struct snd_soc_dapm_widget sun4i_hdmi_audio_widgets[] = {
+ SND_SOC_DAPM_OUTPUT("TX"),
+};
+
+static const struct snd_soc_dapm_route sun4i_hdmi_audio_routes[] = {
+ { "TX", NULL, "Playback" },
+};
+
+static const struct snd_soc_component_driver sun4i_hdmi_audio_component = {
+ .dapm_widgets = sun4i_hdmi_audio_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sun4i_hdmi_audio_widgets),
+ .dapm_routes = sun4i_hdmi_audio_routes,
+ .num_dapm_routes = ARRAY_SIZE(sun4i_hdmi_audio_routes),
+};
+
+static int sun4i_hdmi_audio_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai);
+ struct sun4i_hdmi *hdmi = snd_soc_card_get_drvdata(card);
+ u32 reg;
+ int ret;
+
+ regmap_write(hdmi->regmap, SUN4I_HDMI_AUDIO_CTRL_REG, 0);
+ regmap_write(hdmi->regmap,
+ SUN4I_HDMI_AUDIO_CTRL_REG,
+ SUN4I_HDMI_AUDIO_CTRL_RESET);
+ ret = regmap_read_poll_timeout(hdmi->regmap,
+ SUN4I_HDMI_AUDIO_CTRL_REG,
+ reg, !reg, 100, 50000);
+ if (ret < 0) {
+ DRM_ERROR("Failed to reset HDMI Audio\n");
+ return ret;
+ }
+
+ regmap_write(hdmi->regmap,
+ SUN4I_HDMI_AUDIO_CTRL_REG,
+ SUN4I_HDMI_AUDIO_CTRL_ENABLE);
+
+ return snd_pcm_hw_constraint_eld(substream->runtime,
+ hdmi->connector.eld);
+}
+
+static void sun4i_hdmi_audio_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai);
+ struct sun4i_hdmi *hdmi = snd_soc_card_get_drvdata(card);
+
+ regmap_write(hdmi->regmap, SUN4I_HDMI_AUDIO_CTRL_REG, 0);
+}
+
+static int sun4i_hdmi_setup_audio_infoframes(struct sun4i_hdmi *hdmi)
+{
+ union hdmi_infoframe frame;
+ u8 buffer[14];
+ int i, ret;
+
+ ret = hdmi_audio_infoframe_init(&frame.audio);
+ if (ret < 0) {
+ DRM_ERROR("Failed to init HDMI audio infoframe\n");
+ return ret;
+ }
+
+ 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->hdmi_audio_channels;
+
+ ret = hdmi_infoframe_pack(&frame, buffer, sizeof(buffer));
+ if (ret < 0) {
+ DRM_ERROR("Failed to pack HDMI audio infoframe\n");
+ return ret;
+ }
+
+ for (i = 0; i < sizeof(buffer); i++)
+ writeb(buffer[i],
+ hdmi->base + SUN4I_HDMI_AUDIO_INFOFRAME_REG(i));
+
+ return 0;
+}
+
+static void sun4i_hdmi_audio_set_cts_n(struct sun4i_hdmi *hdmi,
+ struct snd_pcm_hw_params *params)
+{
+ struct drm_encoder *encoder = &hdmi->encoder;
+ struct drm_crtc *crtc = encoder->crtc;
+ const struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+ u32 rate = params_rate(params);
+ u32 n, cts;
+ u64 tmp;
+
+ /**
+ * Calculate Cycle Time Stamp (CTS) and Numerator (N):
+ *
+ * N = 128 * Samplerate / 1000
+ * CTS = (Ftdms * N) / (128 * Samplerate)
+ */
+
+ n = 128 * rate / 1000;
+ tmp = (u64)(mode->clock * 1000) * n;
+ do_div(tmp, 128 * rate);
+ cts = tmp;
+
+ regmap_write(hdmi->regmap,
+ SUN4I_HDMI_AUDIO_CTS_REG,
+ SUN4I_HDMI_AUDIO_CTS(cts));
+
+ regmap_write(hdmi->regmap,
+ SUN4I_HDMI_AUDIO_N_REG,
+ SUN4I_HDMI_AUDIO_N(n));
+}
+
+static int sun4i_hdmi_audio_set_hw_rate(struct sun4i_hdmi *hdmi,
+ struct snd_pcm_hw_params *params)
+{
+ u32 rate = params_rate(params);
+ u32 val;
+
+ switch (rate) {
+ case 44100:
+ val = 0x0;
+ break;
+ case 48000:
+ val = 0x2;
+ break;
+ case 32000:
+ val = 0x3;
+ break;
+ case 88200:
+ val = 0x8;
+ break;
+ case 96000:
+ val = 0x9;
+ break;
+ case 176400:
+ val = 0xc;
+ break;
+ case 192000:
+ val = 0xe;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(hdmi->regmap,
+ SUN4I_HDMI_AUDIO_STAT0_REG,
+ SUN4I_HDMI_AUDIO_STAT0_FREQ_MASK,
+ SUN4I_HDMI_AUDIO_STAT0_FREQ(val));
+
+ return 0;
+}
+
+static int sun4i_hdmi_audio_set_hw_channels(struct sun4i_hdmi *hdmi,
+ struct snd_pcm_hw_params *params)
+{
+ u32 channels = params_channels(params);
+
+ if (channels > 8)
+ return -EINVAL;
+
+ hdmi->hdmi_audio_channels = channels;
+
+ regmap_update_bits(hdmi->regmap,
+ SUN4I_HDMI_AUDIO_FMT_REG,
+ SUN4I_HDMI_AUDIO_FMT_LAYOUT,
+ (channels > 2) ? SUN4I_HDMI_AUDIO_FMT_LAYOUT : 0);
+
+ regmap_update_bits(hdmi->regmap,
+ SUN4I_HDMI_AUDIO_FMT_REG,
+ SUN4I_HDMI_AUDIO_FMT_CH_CFG_MASK,
+ SUN4I_HDMI_AUDIO_FMT_CH_CFG(channels));
+
+ regmap_write(hdmi->regmap, SUN4I_HDMI_AUDIO_PCM_REG, 0x76543210);
+
+ /**
+ * If only one channel is required, send the same sample
+ * to the sink device as a left and right channel.
+ */
+ if (channels == 1)
+ regmap_update_bits(hdmi->regmap,
+ SUN4I_HDMI_AUDIO_PCM_REG,
+ SUN4I_HDMI_AUDIO_PCM_CH_MAP_MASK(1),
+ SUN4I_HDMI_AUDIO_PCM_CH_MAP(1, 1));
+
+ return 0;
+}
+
+static int sun4i_hdmi_audio_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai);
+ struct sun4i_hdmi *hdmi = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ ret = sun4i_hdmi_audio_set_hw_rate(hdmi, params);
+ if (ret)
+ return ret;
+
+ ret = sun4i_hdmi_audio_set_hw_channels(hdmi, params);
+ if (ret)
+ return ret;
+
+ sun4i_hdmi_audio_set_cts_n(hdmi, params);
+
+ return 0;
+}
+
+static int sun4i_hdmi_audio_trigger(struct snd_pcm_substream *substream,
+ int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai);
+ struct sun4i_hdmi *hdmi = snd_soc_card_get_drvdata(card);
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ ret = sun4i_hdmi_setup_audio_infoframes(hdmi);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_dai_ops sun4i_hdmi_audio_dai_ops = {
+ .startup = sun4i_hdmi_audio_startup,
+ .shutdown = sun4i_hdmi_audio_shutdown,
+ .hw_params = sun4i_hdmi_audio_hw_params,
+ .trigger = sun4i_hdmi_audio_trigger,
+};
+
+static int sun4i_hdmi_audio_dai_probe(struct snd_soc_dai *dai)
+{
+ struct snd_dmaengine_dai_dma_data *dma_data;
+
+ dma_data = devm_kzalloc(dai->dev, sizeof(*dma_data), GFP_KERNEL);
+ if (!dma_data)
+ return -ENOMEM;
+
+ dma_data->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ dma_data->maxburst = 8;
+
+ snd_soc_dai_init_dma_data(dai, dma_data, NULL);
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver sun4i_hdmi_audio_dai = {
+ .name = "HDMI",
+ .ops = &sun4i_hdmi_audio_dai_ops,
+ .probe = sun4i_hdmi_audio_dai_probe,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 8,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ },
+};
+
+static const struct snd_pcm_hardware sun4i_hdmi_audio_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .channels_min = 1,
+ .channels_max = 8,
+ .buffer_bytes_max = 128 * 1024,
+ .period_bytes_min = 4 * 1024,
+ .period_bytes_max = 32 * 1024,
+ .periods_min = 2,
+ .periods_max = 8,
+ .fifo_size = 128,
+};
+
+static const struct snd_dmaengine_pcm_config sun4i_hdmi_audio_pcm_config = {
+ .chan_names[SNDRV_PCM_STREAM_PLAYBACK] = "audio-tx",
+ .pcm_hardware = &sun4i_hdmi_audio_pcm_hardware,
+ .prealloc_buffer_size = 128 * 1024,
+ .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
+};
+
+struct snd_soc_card sun4i_hdmi_audio_card = {
+ .name = "sun4i-hdmi",
+};
+
+int sun4i_hdmi_audio_create(struct sun4i_hdmi *hdmi)
+{
+ struct snd_soc_card *card = &sun4i_hdmi_audio_card;
+ struct snd_soc_dai_link_component *comp;
+ struct snd_soc_dai_link *link;
+ int ret;
+
+ ret = devm_snd_dmaengine_pcm_register(hdmi->dev,
+ &sun4i_hdmi_audio_pcm_config, 0);
+ if (ret) {
+ DRM_ERROR("Could not register PCM\n");
+ return ret;
+ }
+
+ ret = devm_snd_soc_register_component(hdmi->dev,
+ &sun4i_hdmi_audio_component,
+ &sun4i_hdmi_audio_dai, 1);
+ if (ret) {
+ DRM_ERROR("Could not register DAI\n");
+ return ret;
+ }
+
+ link = devm_kzalloc(hdmi->dev, sizeof(*link), GFP_KERNEL);
+ if (!link)
+ return -ENOMEM;
+
+ comp = devm_kzalloc(hdmi->dev, sizeof(*comp) * 3, GFP_KERNEL);
+ if (!comp)
+ return -ENOMEM;
+
+ link->cpus = &comp[0];
+ link->codecs = &comp[1];
+ link->platforms = &comp[2];
+
+ link->num_cpus = 1;
+ link->num_codecs = 1;
+ link->num_platforms = 1;
+
+ link->playback_only = 1;
+
+ link->name = "SUN4I-HDMI";
+ link->stream_name = "SUN4I-HDMI PCM";
+
+ link->codecs->name = dev_name(hdmi->dev);
+ link->codecs->dai_name = sun4i_hdmi_audio_dai.name;
+
+ link->cpus->dai_name = dev_name(hdmi->dev);
+
+ link->platforms->name = dev_name(hdmi->dev);
+
+ link->dai_fmt = SND_SOC_DAIFMT_I2S;
+
+ card->dai_link = link;
+ card->num_links = 1;
+ card->dev = hdmi->dev;
+
+ snd_soc_card_set_drvdata(card, hdmi);
+ return devm_snd_soc_register_card(hdmi->dev, card);
+}
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
index a7c4654445c7..79ecd89fb705 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
@@ -114,6 +114,9 @@ static void sun4i_hdmi_enable(struct drm_encoder *encoder)
val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE;
writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
+
+ if (hdmi->hdmi_audio && sun4i_hdmi_audio_create(hdmi))
+ DRM_ERROR("Couldn't create the HDMI audio adapter\n");
}
static void sun4i_hdmi_mode_set(struct drm_encoder *encoder,
@@ -218,6 +221,7 @@ static int sun4i_hdmi_get_modes(struct drm_connector *connector)
if (!edid)
return 0;
+ hdmi->hdmi_audio = drm_detect_monitor_audio(edid);
hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid);
DRM_DEBUG_DRIVER("Monitor is %s monitor\n",
hdmi->hdmi_monitor ? "an HDMI" : "a DVI");