diff --git a/projects/Allwinner/devices/A20/filesystem/usr/share/alsa/cards/sun4i-hdmi.conf b/projects/Allwinner/devices/A20/filesystem/usr/share/alsa/cards/sun4i-hdmi.conf new file mode 100644 index 0000000000..e6873ff061 --- /dev/null +++ b/projects/Allwinner/devices/A20/filesystem/usr/share/alsa/cards/sun4i-hdmi.conf @@ -0,0 +1,34 @@ +# +# Configuration for HDMI +# + + + +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 +} diff --git a/projects/Allwinner/devices/A20/patches/linux/disp-fix.patch b/projects/Allwinner/devices/A20/patches/linux/0001-disp-fix.patch similarity index 100% rename from projects/Allwinner/devices/A20/patches/linux/disp-fix.patch rename to projects/Allwinner/devices/A20/patches/linux/0001-disp-fix.patch diff --git a/projects/Allwinner/devices/A20/patches/linux/0002-Add-support-for-sun4i-HDMI-audio.patch b/projects/Allwinner/devices/A20/patches/linux/0002-Add-support-for-sun4i-HDMI-audio.patch new file mode 100644 index 0000000000..5564bbb8ba --- /dev/null +++ b/projects/Allwinner/devices/A20/patches/linux/0002-Add-support-for-sun4i-HDMI-audio.patch @@ -0,0 +1,605 @@ +Subject: [1/2] dmaengine: sun4i: Add support for cyclic requests with dedicated DMA +From: Stefan Mavrodiev +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 +--- + 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 +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 +--- + 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 ++ */ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++#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");