From patchwork Fri Jan 10 14:11:39 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Mavrodiev X-Patchwork-Id: 11327475 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 09332930 for ; Fri, 10 Jan 2020 14:12:44 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id DB4FA2080D for ; Fri, 10 Jan 2020 14:12:43 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="BxYZNNoM" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org DB4FA2080D Authentication-Results: mail.kernel.org; dmarc=none (p=none dis=none) header.from=olimex.com Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:Cc:List-Subscribe: List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:References: In-Reply-To:Message-Id:Date:Subject:To:From:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Owner; bh=tucQxrpy2hDrcbb5GRBUq7o+smWSqwUvc/o4yphZCgs=; b=BxYZNNoMeNhEXAvlMWWkaOqEYr JYZRSRCvs2LSGkKxSIOzan5cJhnxwALJXUCePKOg+doELiE9DkckbRPJGG9uu9Mi6+AfU1+L0XGdZ XIxcxjWNu9QrtdhtC2tlDOgWBMvjJEHZBqawK9AoG+5KtTxZep00ZFBvBwoaMkoqQP38J0U+gjzTl Z2mpFiJlJ7V7oTwdG2ASzU5YehhBxuui6vn7ABCYJOugYfty5XO4HoqylOAc7b124jbqWeLJWFCNG XRO6pR3VnVmO9PM3my998K49041XOSaXkbG5o7jDXBTCZnVfsHydQxCa2vjVUjjVNinvTH/YGlX+t kFhPnX7g==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.92.3 #3 (Red Hat Linux)) id 1ipv1k-0000Rr-M2; Fri, 10 Jan 2020 14:12:40 +0000 Received: from olimex.com ([184.105.72.32]) by bombadil.infradead.org with esmtps (Exim 4.92.3 #3 (Red Hat Linux)) id 1ipv1Q-00008H-Qa for linux-arm-kernel@lists.infradead.org; Fri, 10 Jan 2020 14:12:23 +0000 Received: from localhost.localdomain ([94.155.250.134]) by olimex.com with ESMTPSA (ECDHE-RSA-AES128-GCM-SHA256:TLSv1.2:Kx=ECDH:Au=RSA:Enc=AESGCM(128):Mac=AEAD) (SMTP-AUTH username stefan@olimex.com, mechanism PLAIN) for ; Fri, 10 Jan 2020 06:12:01 -0800 From: Stefan Mavrodiev To: Dan Williams , Vinod Koul , Maxime Ripard , Chen-Yu Tsai , David Airlie , Daniel Vetter , linux-kernel@vger.kernel.org (open list), dmaengine@vger.kernel.org (open list:DMA GENERIC OFFLOAD ENGINE SUBSYSTEM), linux-arm-kernel@lists.infradead.org (moderated list:ARM/Allwinner sunXi SoC support), dri-devel@lists.freedesktop.org (open list:DRM DRIVERS FOR ALLWINNER A10) Subject: [PATCH 1/2] dmaengine: sun4i: Add support for cyclic requests with dedicated DMA Date: Fri, 10 Jan 2020 16:11:39 +0200 Message-Id: <20200110141140.28527-2-stefan@olimex.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200110141140.28527-1-stefan@olimex.com> References: <20200110141140.28527-1-stefan@olimex.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20200110_061220_892964_AA72A085 X-CRM114-Status: GOOD ( 14.20 ) X-Spam-Score: -0.0 (/) X-Spam-Report: SpamAssassin version 3.4.2 on bombadil.infradead.org summary: Content analysis details: (-0.0 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 SPF_HELO_PASS SPF: HELO matches SPF record -0.0 SPF_PASS SPF: sender matches SPF record X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: linux-sunxi@googlegroups.com, Stefan Mavrodiev MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org 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 Acked-by: Maxime Ripard Acked-by: Vinod Koul --- 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; From patchwork Fri Jan 10 14:11:40 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Mavrodiev X-Patchwork-Id: 11327473 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 1D711930 for ; Fri, 10 Jan 2020 14:12:23 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id ED50F2080D for ; Fri, 10 Jan 2020 14:12:22 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="YW9ZPhr2" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org ED50F2080D Authentication-Results: mail.kernel.org; dmarc=none (p=none dis=none) header.from=olimex.com Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:Cc:List-Subscribe: List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:References: In-Reply-To:Message-Id:Date:Subject:To:From:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Owner; bh=V9X9xLHJp9FoDD3iptcU3Yijjj53Qa/WucbHUl2Il6Q=; b=YW9ZPhr2dh1eD7ue/57SzD27gs CcPSdmD3zRnfHgwpFNF904mznV0EHMMlBIS6k0RsHjQz85HSO5DNeOBOruwGS8qkbgxX/C185wAy/ MgPlFC+uCnrFPHe7s/0PvhvGSRjNyIdnkqbBk3IiiB9hzsL2jWHjyn+YS99unsp3N57kppFH3ft92 rXpLxCGJR0ORW3fqt2w4T3e4EW+H4QleOTGYDnGaGkugELtTOQ1jIj9F/smnv3JPTbmQYw22qbbQZ rP2Jb1BVPsxWKOvHayuZ6IugzSG1ssGhal80crkXAJIkePEc9TBc5W589bfur+ma46XqZDEgO3CEe 4wlwYlOw==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.92.3 #3 (Red Hat Linux)) id 1ipv1N-00007Z-P9; Fri, 10 Jan 2020 14:12:17 +0000 Received: from olimex.com ([2001:470:0:1f2::b869:4820]) by bombadil.infradead.org with esmtps (Exim 4.92.3 #3 (Red Hat Linux)) id 1ipv1I-0008Tg-Vx for linux-arm-kernel@lists.infradead.org; Fri, 10 Jan 2020 14:12:15 +0000 Received: from localhost.localdomain ([94.155.250.134]) by olimex.com with ESMTPSA (ECDHE-RSA-AES128-GCM-SHA256:TLSv1.2:Kx=ECDH:Au=RSA:Enc=AESGCM(128):Mac=AEAD) (SMTP-AUTH username stefan@olimex.com, mechanism PLAIN) for ; Fri, 10 Jan 2020 06:12:04 -0800 From: Stefan Mavrodiev To: Dan Williams , Vinod Koul , Maxime Ripard , Chen-Yu Tsai , David Airlie , Daniel Vetter , linux-kernel@vger.kernel.org (open list), dmaengine@vger.kernel.org (open list:DMA GENERIC OFFLOAD ENGINE SUBSYSTEM), linux-arm-kernel@lists.infradead.org (moderated list:ARM/Allwinner sunXi SoC support), dri-devel@lists.freedesktop.org (open list:DRM DRIVERS FOR ALLWINNER A10) Subject: [PATCH 2/2] drm: sun4i: hdmi: Add support for sun4i HDMI encoder audio Date: Fri, 10 Jan 2020 16:11:40 +0200 Message-Id: <20200110141140.28527-3-stefan@olimex.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200110141140.28527-1-stefan@olimex.com> References: <20200110141140.28527-1-stefan@olimex.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20200110_061213_071933_3D9CA179 X-CRM114-Status: GOOD ( 20.71 ) X-Spam-Score: -0.0 (/) X-Spam-Report: SpamAssassin version 3.4.2 on bombadil.infradead.org summary: Content analysis details: (-0.0 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 SPF_HELO_PASS SPF: HELO matches SPF record -0.0 SPF_PASS SPF: sender matches SPF record X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: linux-sunxi@googlegroups.com, Stefan Mavrodiev MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org 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");