From patchwork Mon Dec 3 15:36:42 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Thierry Reding X-Patchwork-Id: 10709847 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id E0AD51057 for ; Mon, 3 Dec 2018 15:36:50 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id CEE192B25F for ; Mon, 3 Dec 2018 15:36:50 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id C2C852B2A1; Mon, 3 Dec 2018 15:36:50 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,DKIM_ADSP_CUSTOM_MED, FREEMAIL_FROM,MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 057DC2B25F for ; Mon, 3 Dec 2018 15:36:50 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 0AF696E078; Mon, 3 Dec 2018 15:36:49 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from mail-ed1-x542.google.com (mail-ed1-x542.google.com [IPv6:2a00:1450:4864:20::542]) by gabe.freedesktop.org (Postfix) with ESMTPS id 22BFD6E078 for ; Mon, 3 Dec 2018 15:36:48 +0000 (UTC) Received: by mail-ed1-x542.google.com with SMTP id o10so11095924edt.13 for ; Mon, 03 Dec 2018 07:36:48 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=oGVYQZu5oFU55t7T2nD2tBnEW80ZNSFj2YPGScdofDA=; b=uPS9N9Iv3K3pT8VES6Zz42GmMlY2rQuMa1uSxNwlkCIMQcljOY6gKHHO0FJgRXTd5k +uYbG3rHHMZd+lbWl4/ZsALNvG0yYDrCbCkdD7v904NcNLyy3mdXF6Jvho7/wnE+yVRW TaD+jx9tqlkVWnaDSUxxLayqcpl5zNXGIv2FHFnLb0Oai1UJ8gzxXohRYdZBgUSE+ZXs a30nK8MFSuF8uv+MIlSrrtoU88maBIB1pXvpgYaNupcKSH+Sa+1Gno41P10qHc10DeXr dU5VymxfPe0yQ42Xa6BouhKY8P0ACvMJJlgaSw66aq8pMlJCDlhVtmWstsjQHxoLmmzm 2ayw== X-Gm-Message-State: AA+aEWYSJPnXdPHhaW6j6oEAm/Ro1jjU8vlXvPk833NKLe0TFVgMmuQO jBnIHkwiy/OXfSlNeyTpH7w= X-Google-Smtp-Source: AFSGD/W0VZGok27jpFwiD26UcPzducwougPVFRMAV8l8Md6otE88fKzNdTl/bzYrO4sxMvRRsMDDZQ== X-Received: by 2002:aa7:c6d1:: with SMTP id b17mr15192708eds.249.1543851406526; Mon, 03 Dec 2018 07:36:46 -0800 (PST) Received: from localhost (pD9E51040.dip0.t-ipconnect.de. [217.229.16.64]) by smtp.gmail.com with ESMTPSA id k12sm4012724eda.12.2018.12.03.07.36.43 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Mon, 03 Dec 2018 07:36:43 -0800 (PST) From: Thierry Reding To: Thierry Reding Subject: [PATCH] drm/tegra: sor: Support for audio over HDMI Date: Mon, 3 Dec 2018 16:36:42 +0100 Message-Id: <20181203153642.13562-1-thierry.reding@gmail.com> X-Mailer: git-send-email 2.19.1 MIME-Version: 1.0 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: linux-tegra@vger.kernel.org, Sameer Pujar , dri-devel@lists.freedesktop.org, Jon Hunter Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" X-Virus-Scanned: ClamAV using ClamSMTP From: Thierry Reding This code is very similar to the audio over HDMI support on older chips. Interoperation with the audio codec is done via a pair of codec scratch registers and an interrupt that is raised at the SOR when the codec has written those registers. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 229 ++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/tegra/sor.h | 68 +++++++++++ 2 files changed, 297 insertions(+) diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index b129da2e5afd..22a54434a757 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -19,6 +19,8 @@ #include +#include + #include #include #include @@ -407,6 +409,7 @@ struct tegra_sor { const struct tegra_sor_soc *soc; void __iomem *regs; unsigned int index; + unsigned int irq; struct reset_control *rst; struct clk *clk_parent; @@ -433,6 +436,11 @@ struct tegra_sor { struct delayed_work scdc; bool scdc_enabled; + + struct { + unsigned int sample_rate; + unsigned int channels; + } audio; }; struct tegra_sor_state { @@ -2139,6 +2147,144 @@ tegra_sor_hdmi_setup_avi_infoframe(struct tegra_sor *sor, return 0; } +static void tegra_sor_write_eld(struct tegra_sor *sor) +{ + size_t length = drm_eld_size(sor->output.connector.eld), i; + + for (i = 0; i < length; i++) + tegra_sor_writel(sor, i << 8 | sor->output.connector.eld[i], + SOR_AUDIO_HDA_ELD_BUFWR); + + /* + * The HDA codec will always report an ELD buffer size of 96 bytes and + * the HDA codec driver will check that each byte read from the buffer + * is valid. Therefore every byte must be written, even if no 96 bytes + * were parsed from EDID. + */ + for (i = length; i < 96; i++) + tegra_sor_writel(sor, i << 8 | 0, SOR_AUDIO_HDA_ELD_BUFWR); +} + +static void tegra_sor_audio_prepare(struct tegra_sor *sor) +{ + u32 value; + + tegra_sor_write_eld(sor); + + value = SOR_AUDIO_HDA_PRESENSE_ELDV | SOR_AUDIO_HDA_PRESENSE_PD; + tegra_sor_writel(sor, value, SOR_AUDIO_HDA_PRESENSE); +} + +static void tegra_sor_audio_unprepare(struct tegra_sor *sor) +{ + tegra_sor_writel(sor, 0, SOR_AUDIO_HDA_PRESENSE); +} + +static int tegra_sor_hdmi_enable_audio_infoframe(struct tegra_sor *sor) +{ + u8 buffer[HDMI_INFOFRAME_SIZE(AUDIO)]; + struct hdmi_audio_infoframe frame; + u32 value; + int err; + + err = hdmi_audio_infoframe_init(&frame); + if (err < 0) { + dev_err(sor->dev, "failed to setup audio infoframe: %d\n", err); + return err; + } + + frame.channels = sor->audio.channels; + + err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + dev_err(sor->dev, "failed to pack audio infoframe: %d\n", err); + return err; + } + + tegra_sor_hdmi_write_infopack(sor, buffer, err); + + value = tegra_sor_readl(sor, SOR_HDMI_AUDIO_INFOFRAME_CTRL); + value |= INFOFRAME_CTRL_CHECKSUM_ENABLE; + value |= INFOFRAME_CTRL_ENABLE; + tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_INFOFRAME_CTRL); + + return 0; +} + +static void tegra_sor_hdmi_audio_enable(struct tegra_sor *sor) +{ + u32 value; + + value = tegra_sor_readl(sor, SOR_AUDIO_CNTRL); + + /* select HDA audio input */ + value &= ~SOR_AUDIO_CNTRL_SOURCE_SELECT(SOURCE_SELECT_MASK); + value |= SOR_AUDIO_CNTRL_SOURCE_SELECT(SOURCE_SELECT_HDA); + + /* inject null samples */ + if (sor->audio.channels != 2) + value &= ~SOR_AUDIO_CNTRL_INJECT_NULLSMPL; + else + value |= SOR_AUDIO_CNTRL_INJECT_NULLSMPL; + + value |= SOR_AUDIO_CNTRL_AFIFO_FLUSH; + + tegra_sor_writel(sor, value, SOR_AUDIO_CNTRL); + + /* enable advertising HBR capability */ + tegra_sor_writel(sor, SOR_AUDIO_SPARE_HBR_ENABLE, SOR_AUDIO_SPARE); + + tegra_sor_writel(sor, 0, SOR_HDMI_ACR_CTRL); + + value = SOR_HDMI_SPARE_ACR_PRIORITY_HIGH | + SOR_HDMI_SPARE_CTS_RESET(1) | + SOR_HDMI_SPARE_HW_CTS_ENABLE; + tegra_sor_writel(sor, value, SOR_HDMI_SPARE); + + /* enable HW CTS */ + value = SOR_HDMI_ACR_SUBPACK_LOW_SB1(0); + tegra_sor_writel(sor, value, SOR_HDMI_ACR_0441_SUBPACK_LOW); + + /* allow packet to be sent */ + value = SOR_HDMI_ACR_SUBPACK_HIGH_ENABLE; + tegra_sor_writel(sor, value, SOR_HDMI_ACR_0441_SUBPACK_HIGH); + + /* reset N counter and enable lookup */ + value = SOR_HDMI_AUDIO_N_RESET | SOR_HDMI_AUDIO_N_LOOKUP; + tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_N); + + value = (24000 * 4096) / (128 * sor->audio.sample_rate / 1000); + tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_0320); + tegra_sor_writel(sor, 4096, SOR_AUDIO_NVAL_0320); + + tegra_sor_writel(sor, 20000, SOR_AUDIO_AVAL_0441); + tegra_sor_writel(sor, 4704, SOR_AUDIO_NVAL_0441); + + tegra_sor_writel(sor, 20000, SOR_AUDIO_AVAL_0882); + tegra_sor_writel(sor, 9408, SOR_AUDIO_NVAL_0882); + + tegra_sor_writel(sor, 20000, SOR_AUDIO_AVAL_1764); + tegra_sor_writel(sor, 18816, SOR_AUDIO_NVAL_1764); + + value = (24000 * 6144) / (128 * sor->audio.sample_rate / 1000); + tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_0480); + tegra_sor_writel(sor, 6144, SOR_AUDIO_NVAL_0480); + + value = (24000 * 12288) / (128 * sor->audio.sample_rate / 1000); + tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_0960); + tegra_sor_writel(sor, 12288, SOR_AUDIO_NVAL_0960); + + value = (24000 * 24576) / (128 * sor->audio.sample_rate / 1000); + tegra_sor_writel(sor, value, SOR_AUDIO_AVAL_1920); + tegra_sor_writel(sor, 24576, SOR_AUDIO_NVAL_1920); + + value = tegra_sor_readl(sor, SOR_HDMI_AUDIO_N); + value &= ~SOR_HDMI_AUDIO_N_RESET; + tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_N); + + tegra_sor_hdmi_enable_audio_infoframe(sor); +} + static void tegra_sor_hdmi_disable_audio_infoframe(struct tegra_sor *sor) { u32 value; @@ -2148,6 +2294,11 @@ static void tegra_sor_hdmi_disable_audio_infoframe(struct tegra_sor *sor) tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_INFOFRAME_CTRL); } +static void tegra_sor_hdmi_audio_disable(struct tegra_sor *sor) +{ + tegra_sor_hdmi_disable_audio_infoframe(sor); +} + static struct tegra_sor_hdmi_settings * tegra_sor_hdmi_find_settings(struct tegra_sor *sor, unsigned long frequency) { @@ -2243,6 +2394,7 @@ static void tegra_sor_hdmi_disable(struct drm_encoder *encoder) u32 value; int err; + tegra_sor_audio_unprepare(sor); tegra_sor_hdmi_scdc_stop(sor); err = tegra_sor_detach(sor); @@ -2651,6 +2803,7 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) dev_err(sor->dev, "failed to wakeup SOR: %d\n", err); tegra_sor_hdmi_scdc_start(sor); + tegra_sor_audio_prepare(sor); } static const struct drm_encoder_helper_funcs tegra_sor_hdmi_helpers = { @@ -2666,6 +2819,7 @@ static int tegra_sor_init(struct host1x_client *client) struct tegra_sor *sor = host1x_client_to_sor(client); int connector = DRM_MODE_CONNECTOR_Unknown; int encoder = DRM_MODE_ENCODER_NONE; + u32 value; int err; if (!sor->aux) { @@ -2759,6 +2913,15 @@ static int tegra_sor_init(struct host1x_client *client) if (err < 0) return err; + /* + * Enable and unmask the HDA codec SCRATCH0 register interrupt. This + * is used for interoperability between the HDA codec driver and the + * HDMI/DP driver. + */ + value = SOR_INT_CODEC_SCRATCH1 | SOR_INT_CODEC_SCRATCH0; + tegra_sor_writel(sor, value, SOR_INT_ENABLE); + tegra_sor_writel(sor, value, SOR_INT_MASK); + return 0; } @@ -2767,6 +2930,9 @@ static int tegra_sor_exit(struct host1x_client *client) struct tegra_sor *sor = host1x_client_to_sor(client); int err; + tegra_sor_writel(sor, 0, SOR_INT_MASK); + tegra_sor_writel(sor, 0, SOR_INT_ENABLE); + tegra_output_exit(&sor->output); if (sor->aux) { @@ -3037,6 +3203,54 @@ static int tegra_sor_parse_dt(struct tegra_sor *sor) return 0; } +static void tegra_hda_parse_format(unsigned int format, unsigned int *rate, + unsigned int *channels) +{ + unsigned int mul, div; + + if (format & AC_FMT_BASE_44K) + *rate = 44100; + else + *rate = 48000; + + mul = (format & AC_FMT_MULT_MASK) >> AC_FMT_MULT_SHIFT; + div = (format & AC_FMT_DIV_MASK) >> AC_FMT_DIV_SHIFT; + + *rate = *rate * (mul + 1) / (div + 1); + + *channels = (format & AC_FMT_CHAN_MASK) >> AC_FMT_CHAN_SHIFT; +} + +static irqreturn_t tegra_sor_irq(int irq, void *data) +{ + struct tegra_sor *sor = data; + u32 value; + + value = tegra_sor_readl(sor, SOR_INT_STATUS); + tegra_sor_writel(sor, value, SOR_INT_STATUS); + + if (value & SOR_INT_CODEC_SCRATCH0) { + value = tegra_sor_readl(sor, SOR_AUDIO_HDA_CODEC_SCRATCH0); + + if (value & SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID) { + unsigned int format, sample_rate, channels; + + format = value & SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK; + + tegra_hda_parse_format(format, &sample_rate, &channels); + + sor->audio.sample_rate = sample_rate; + sor->audio.channels = channels; + + tegra_sor_hdmi_audio_enable(sor); + } else { + tegra_sor_hdmi_audio_disable(sor); + } + } + + return IRQ_HANDLED; +} + static int tegra_sor_probe(struct platform_device *pdev) { struct device_node *np; @@ -3119,6 +3333,21 @@ static int tegra_sor_probe(struct platform_device *pdev) goto remove; } + err = platform_get_irq(pdev, 0); + if (err < 0) { + dev_err(&pdev->dev, "failed to get IRQ: %d\n", err); + goto remove; + } + + sor->irq = err; + + err = devm_request_irq(sor->dev, sor->irq, tegra_sor_irq, 0, + dev_name(sor->dev), sor); + if (err < 0) { + dev_err(&pdev->dev, "failed to request IRQ: %d\n", err); + goto remove; + } + if (!pdev->dev.pm_domain) { sor->rst = devm_reset_control_get(&pdev->dev, "sor"); if (IS_ERR(sor->rst)) { diff --git a/drivers/gpu/drm/tegra/sor.h b/drivers/gpu/drm/tegra/sor.h index fb0854d92a27..13f7e68bec42 100644 --- a/drivers/gpu/drm/tegra/sor.h +++ b/drivers/gpu/drm/tegra/sor.h @@ -364,12 +364,28 @@ #define INFOFRAME_HEADER_VERSION(x) (((x) & 0xff) << 8) #define INFOFRAME_HEADER_TYPE(x) (((x) & 0xff) << 0) +#define SOR_HDMI_ACR_CTRL 0xb1 + +#define SOR_HDMI_ACR_0320_SUBPACK_LOW 0xb2 +#define SOR_HDMI_ACR_SUBPACK_LOW_SB1(x) (((x) & 0xff) << 24) + +#define SOR_HDMI_ACR_0320_SUBPACK_HIGH 0xb3 +#define SOR_HDMI_ACR_SUBPACK_HIGH_ENABLE (1 << 31) + +#define SOR_HDMI_ACR_0441_SUBPACK_LOW 0xb4 +#define SOR_HDMI_ACR_0441_SUBPACK_HIGH 0xb5 + #define SOR_HDMI_CTRL 0xc0 #define SOR_HDMI_CTRL_ENABLE (1 << 30) #define SOR_HDMI_CTRL_MAX_AC_PACKET(x) (((x) & 0x1f) << 16) #define SOR_HDMI_CTRL_AUDIO_LAYOUT (1 << 10) #define SOR_HDMI_CTRL_REKEY(x) (((x) & 0x7f) << 0) +#define SOR_HDMI_SPARE 0xcb +#define SOR_HDMI_SPARE_ACR_PRIORITY_HIGH (1 << 31) +#define SOR_HDMI_SPARE_CTS_RESET(x) (((x) & 0x7) << 16) +#define SOR_HDMI_SPARE_HW_CTS_ENABLE (1 << 0) + #define SOR_REFCLK 0xe6 #define SOR_REFCLK_DIV_INT(x) ((((x) >> 2) & 0xff) << 8) #define SOR_REFCLK_DIV_FRAC(x) (((x) & 0x3) << 6) @@ -378,10 +394,62 @@ #define SOR_INPUT_CONTROL_ARM_VIDEO_RANGE_LIMITED (1 << 1) #define SOR_INPUT_CONTROL_HDMI_SRC_SELECT(x) (((x) & 0x1) << 0) +#define SOR_AUDIO_CNTRL 0xfc +#define SOR_AUDIO_CNTRL_INJECT_NULLSMPL (1 << 29) +#define SOR_AUDIO_CNTRL_SOURCE_SELECT(x) (((x) & 0x3) << 20) +#define SOURCE_SELECT_MASK 0x3 +#define SOURCE_SELECT_HDA 0x2 +#define SOURCE_SELECT_SPDIF 0x1 +#define SOURCE_SELECT_AUTO 0x0 +#define SOR_AUDIO_CNTRL_AFIFO_FLUSH (1 << 12) + +#define SOR_AUDIO_SPARE 0xfe +#define SOR_AUDIO_SPARE_HBR_ENABLE (1 << 27) + +#define SOR_AUDIO_NVAL_0320 0xff +#define SOR_AUDIO_NVAL_0441 0x100 +#define SOR_AUDIO_NVAL_0882 0x101 +#define SOR_AUDIO_NVAL_1764 0x102 +#define SOR_AUDIO_NVAL_0480 0x103 +#define SOR_AUDIO_NVAL_0960 0x104 +#define SOR_AUDIO_NVAL_1920 0x105 + +#define SOR_AUDIO_HDA_CODEC_SCRATCH0 0x10a +#define SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID (1 << 30) +#define SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK 0xffff + +#define SOR_AUDIO_HDA_ELD_BUFWR 0x10c +#define SOR_AUDIO_HDA_ELD_BUFWR_INDEX(x) (((x) & 0xff) << 8) +#define SOR_AUDIO_HDA_ELD_BUFWR_DATA(x) (((x) & 0xff) << 0) + +#define SOR_AUDIO_HDA_PRESENSE 0x10d +#define SOR_AUDIO_HDA_PRESENSE_ELDV (1 << 1) +#define SOR_AUDIO_HDA_PRESENSE_PD (1 << 0) + +#define SOR_AUDIO_AVAL_0320 0x10f +#define SOR_AUDIO_AVAL_0441 0x110 +#define SOR_AUDIO_AVAL_0882 0x111 +#define SOR_AUDIO_AVAL_1764 0x112 +#define SOR_AUDIO_AVAL_0480 0x113 +#define SOR_AUDIO_AVAL_0960 0x114 +#define SOR_AUDIO_AVAL_1920 0x115 + +#define SOR_INT_STATUS 0x11c +#define SOR_INT_CODEC_CP_REQUEST (1 << 2) +#define SOR_INT_CODEC_SCRATCH1 (1 << 1) +#define SOR_INT_CODEC_SCRATCH0 (1 << 0) + +#define SOR_INT_MASK 0x11d +#define SOR_INT_ENABLE 0x11e + #define SOR_HDMI_VSI_INFOFRAME_CTRL 0x123 #define SOR_HDMI_VSI_INFOFRAME_STATUS 0x124 #define SOR_HDMI_VSI_INFOFRAME_HEADER 0x125 +#define SOR_HDMI_AUDIO_N 0x13c +#define SOR_HDMI_AUDIO_N_LOOKUP (1 << 28) +#define SOR_HDMI_AUDIO_N_RESET (1 << 20) + #define SOR_HDMI2_CTRL 0x13e #define SOR_HDMI2_CTRL_CLOCK_MODE_DIV_BY_4 (1 << 1) #define SOR_HDMI2_CTRL_SCRAMBLE (1 << 0)