From patchwork Fri Jun 28 09:23:39 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Johan Jonker X-Patchwork-Id: 13715864 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id F274AC2BBCA for ; Fri, 28 Jun 2024 09:23:45 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 440B810EBF5; Fri, 28 Jun 2024 09:23:45 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="Qv2cYLOe"; dkim-atps=neutral Received: from mail-ed1-f43.google.com (mail-ed1-f43.google.com [209.85.208.43]) by gabe.freedesktop.org (Postfix) with ESMTPS id 8A7B310EBF5 for ; Fri, 28 Jun 2024 09:23:43 +0000 (UTC) Received: by mail-ed1-f43.google.com with SMTP id 4fb4d7f45d1cf-57d20d89748so445004a12.0 for ; Fri, 28 Jun 2024 02:23:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1719566622; x=1720171422; darn=lists.freedesktop.org; h=content-transfer-encoding:content-language:cc:to:subject:from :user-agent:mime-version:date:message-id:from:to:cc:subject:date :message-id:reply-to; bh=baYtxBYZ7pjEtPz9CdjikSrYOvBcpn6u3FQ2N+5TP40=; b=Qv2cYLOeSJLgPLIKkOBiewTSpRuvzALvkfP7/cAeoohgWSEVfJW9RD37QKj3RJ1PoR JlrnwctdXqewG3ZbXCyGHbjJDJg5wqDy4XcgiP6EEkGd6Dhcpkqmru7Z4yfHBvTOVUoQ S/tWnLlr0emkeGC+x1tSOmbMOeoEdKjX6mFxj/h33Tg9g87Zh9uwVcFUashlSuIjuzfX SspP4bK8ITiFlxGq+MvJHLY3R5/W/u2ytbICY2bcnUSL8nGGNErrEjlCn4ZaedB4CDtS SoV2VJA3TSmWMgCBtU+X9pY7zx5dQGMJhZGQbbyGZ95VtbJoWj71kzqjt/60tnFEBVYI eHMw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1719566622; x=1720171422; h=content-transfer-encoding:content-language:cc:to:subject:from :user-agent:mime-version:date:message-id:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=baYtxBYZ7pjEtPz9CdjikSrYOvBcpn6u3FQ2N+5TP40=; b=v07aWa8MWH/tu/NWS461SvkFfegwuoeKow1knwob7L7OGoMHh/USdZLlc/tiwdBMr0 D/otec+fN22V2UB35Ycovi5OD3k8rTNuuff5teTSoI5e65DSpV+OtSwQiFMDoX1/h2Vh 0tP8LodYoLycdGLRUlO9Xg9jp9WKskzTa9AnTjIiJaCE0N5zfeZAiOO8eP7jfQNXxBDb BJ68thCLwmokdwEIM9E89e2pRB12XsZM9gqQr7sIoUY+UiDMVM0sijSWeTU/GWncgrKV d5KwwjzPNBjrTlJ2wZdWGeCcJLSZC2gtR02tEbr03y4Nm5hzHbebMUyCdOvMxrkxIo/m Kkqg== X-Forwarded-Encrypted: i=1; AJvYcCXJufMB38oN7fOrrajyKA2/5VlchgSYLYASP2Nz7euP9eoLtmZsnPAMJdVf/4e+Jyw0YiKxPKvcrRZvBAPnApKRgD9ab/dAnKUb4fQqmspX X-Gm-Message-State: AOJu0YzXKCp0ivG74tfsI48hz8WWo8AYDke4qsgkZTAB1VEUEGRzieLR urAQ/3vbC65jWv88q0KRNISWYyi562pM0f+yklIXj5uaA8mu+Pk5 X-Google-Smtp-Source: AGHT+IE/3Afs9vHxmzvsl3H9DsS1V7bwU/VSy6NAflbuOWuaWkgoVTgn8QbmHhY5fbqaugsJqQRbZA== X-Received: by 2002:a50:d7cf:0:b0:57c:84fe:1acf with SMTP id 4fb4d7f45d1cf-57d49cb4600mr10197228a12.17.1719566621565; Fri, 28 Jun 2024 02:23:41 -0700 (PDT) Received: from ?IPV6:2a02:a449:4071:1:32d0:42ff:fe10:6983? (2a02-a449-4071-1-32d0-42ff-fe10-6983.fixed6.kpn.net. [2a02:a449:4071:1:32d0:42ff:fe10:6983]) by smtp.gmail.com with ESMTPSA id 4fb4d7f45d1cf-58612c835f7sm747284a12.8.2024.06.28.02.23.40 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Fri, 28 Jun 2024 02:23:40 -0700 (PDT) Message-ID: <5c651b3f-fe30-4874-98ed-044f7c62dd97@gmail.com> Date: Fri, 28 Jun 2024 11:23:39 +0200 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird From: Johan Jonker Subject: [PATCH v7] drm/rockchip: rk3066_hdmi: add sound support To: heiko@sntech.de Cc: hjc@rock-chips.com, andy.yan@rock-chips.com, maarten.lankhorst@linux.intel.com, mripard@kernel.org, tzimmermann@suse.de, airlied@gmail.com, daniel@ffwll.ch, lgirdwood@gmail.com, broonie@kernel.org, linux-sound@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-arm-kernel@lists.infradead.org, linux-rockchip@lists.infradead.org, linux-kernel@vger.kernel.org Content-Language: en-US X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Add sound support to the RK3066 HDMI driver. The HDMI TX audio source is connected to I2S_8CH. Signed-off-by: Zheng Yang Signed-off-by: Johan Jonker --- Changed V7: rebase --- drivers/gpu/drm/rockchip/Kconfig | 2 + drivers/gpu/drm/rockchip/rk3066_hdmi.c | 274 ++++++++++++++++++++++++- 2 files changed, 275 insertions(+), 1 deletion(-) -- 2.39.2 diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index 1bf3e2829cd0..a32ee558408c 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -102,6 +102,8 @@ config ROCKCHIP_RGB config ROCKCHIP_RK3066_HDMI bool "Rockchip specific extensions for RK3066 HDMI" depends on DRM_ROCKCHIP + select SND_SOC_HDMI_CODEC if SND_SOC + select SND_SOC_ROCKCHIP_I2S if SND_SOC help This selects support for Rockchip SoC specific extensions for the RK3066 HDMI driver. If you want to enable diff --git a/drivers/gpu/drm/rockchip/rk3066_hdmi.c b/drivers/gpu/drm/rockchip/rk3066_hdmi.c index 784de990da1b..d3128b787629 100644 --- a/drivers/gpu/drm/rockchip/rk3066_hdmi.c +++ b/drivers/gpu/drm/rockchip/rk3066_hdmi.c @@ -15,12 +15,20 @@ #include #include +#include + #include "rk3066_hdmi.h" #include "rockchip_drm_drv.h" #define DEFAULT_PLLA_RATE 30000000 +struct audio_info { + int channels; + int sample_rate; + int sample_width; +}; + struct hdmi_data_info { int vic; /* The CEA Video ID (VIC) of the current drm display mode. */ unsigned int enc_out_format; @@ -54,9 +62,16 @@ struct rk3066_hdmi { unsigned int tmdsclk; + struct platform_device *audio_pdev; + struct audio_info audio; + bool audio_enable; + struct hdmi_data_info hdmi_data; }; +static int +rk3066_hdmi_config_audio(struct rk3066_hdmi *hdmi, struct audio_info *audio); + static struct rk3066_hdmi *encoder_to_rk3066_hdmi(struct drm_encoder *encoder) { struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); @@ -214,6 +229,23 @@ static int rk3066_hdmi_config_avi(struct rk3066_hdmi *hdmi, HDMI_INFOFRAME_AVI, 0, 0, 0); } +static int rk3066_hdmi_config_aai(struct rk3066_hdmi *hdmi, + struct audio_info *audio) +{ + union hdmi_infoframe frame; + int rc; + + rc = 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; + + return rk3066_hdmi_upload_frame(hdmi, rc, &frame, + HDMI_INFOFRAME_AAI, 0, 0, 0); +} + static int rk3066_hdmi_config_video_timing(struct rk3066_hdmi *hdmi, struct drm_display_mode *mode) { @@ -364,6 +396,7 @@ static int rk3066_hdmi_setup(struct rk3066_hdmi *hdmi, hdmi_modb(hdmi, HDMI_HDCP_CTRL, HDMI_VIDEO_MODE_MASK, HDMI_VIDEO_MODE_HDMI); rk3066_hdmi_config_avi(hdmi, mode); + rk3066_hdmi_config_audio(hdmi, &hdmi->audio); } else { hdmi_modb(hdmi, HDMI_HDCP_CTRL, HDMI_VIDEO_MODE_MASK, 0); } @@ -380,9 +413,20 @@ static int rk3066_hdmi_setup(struct rk3066_hdmi *hdmi, */ rk3066_hdmi_i2c_init(hdmi); - /* Unmute video output. */ + /* Unmute video and audio output. */ hdmi_modb(hdmi, HDMI_VIDEO_CTRL2, HDMI_VIDEO_AUDIO_DISABLE_MASK, HDMI_AUDIO_DISABLE); + if (hdmi->audio_enable) { + hdmi_modb(hdmi, HDMI_VIDEO_CTRL2, HDMI_AUDIO_DISABLE, 0); + /* Reset audio capture logic. */ + hdmi_modb(hdmi, HDMI_VIDEO_CTRL2, + HDMI_AUDIO_CP_LOGIC_RESET_MASK, + HDMI_AUDIO_CP_LOGIC_RESET); + usleep_range(900, 1000); + hdmi_modb(hdmi, HDMI_VIDEO_CTRL2, + HDMI_AUDIO_CP_LOGIC_RESET_MASK, 0); + } + return 0; } @@ -534,6 +578,230 @@ struct drm_connector_helper_funcs rk3066_hdmi_connector_helper_funcs = { .best_encoder = rk3066_hdmi_connector_best_encoder, }; +static int +rk3066_hdmi_config_audio(struct rk3066_hdmi *hdmi, struct audio_info *audio) +{ + u32 rate, channel, word_length, N, CTS; + u64 tmp; + + if (audio->channels < 3) + channel = HDMI_AUDIO_I2S_CHANNEL_1_2; + else if (audio->channels < 5) + channel = HDMI_AUDIO_I2S_CHANNEL_3_4; + else if (audio->channels < 7) + channel = HDMI_AUDIO_I2S_CHANNEL_5_6; + else + channel = HDMI_AUDIO_I2S_CHANNEL_7_8; + + switch (audio->sample_rate) { + case 32000: + rate = HDMI_AUDIO_SAMPLE_FRE_32000; + N = N_32K; + break; + case 44100: + rate = HDMI_AUDIO_SAMPLE_FRE_44100; + N = N_441K; + break; + case 48000: + rate = HDMI_AUDIO_SAMPLE_FRE_48000; + N = N_48K; + break; + case 88200: + rate = HDMI_AUDIO_SAMPLE_FRE_88200; + N = N_882K; + break; + case 96000: + rate = HDMI_AUDIO_SAMPLE_FRE_96000; + N = N_96K; + break; + case 176400: + rate = HDMI_AUDIO_SAMPLE_FRE_176400; + N = N_1764K; + break; + case 192000: + rate = HDMI_AUDIO_SAMPLE_FRE_192000; + N = N_192K; + break; + default: + DRM_DEV_ERROR(hdmi->dev, "no support for sample rate %d\n", + audio->sample_rate); + return -ENOENT; + } + + switch (audio->sample_width) { + case 16: + word_length = 0x02; + break; + case 20: + word_length = 0x0a; + break; + case 24: + word_length = 0x0b; + break; + default: + DRM_DEV_ERROR(hdmi->dev, "no support for word length %d\n", + audio->sample_width); + return -ENOENT; + } + + tmp = (u64)hdmi->tmdsclk * N; + do_div(tmp, 128 * audio->sample_rate); + CTS = tmp; + + /* Set_audio source I2S. */ + hdmi_writeb(hdmi, HDMI_AUDIO_CTRL1, 0x00); + hdmi_writeb(hdmi, HDMI_AUDIO_CTRL2, 0x40); + hdmi_writeb(hdmi, HDMI_I2S_AUDIO_CTRL, + HDMI_AUDIO_I2S_FORMAT_STANDARD | channel); + hdmi_writeb(hdmi, HDMI_I2S_SWAP, 0x00); + hdmi_modb(hdmi, HDMI_AV_CTRL1, HDMI_AUDIO_SAMPLE_FRE_MASK, rate); + hdmi_writeb(hdmi, HDMI_AUDIO_SRC_NUM_AND_LENGTH, word_length); + + /* Set N value. */ + hdmi_modb(hdmi, HDMI_LR_SWAP_N3, + HDMI_AUDIO_N_19_16_MASK, (N >> 16) & 0x0F); + hdmi_writeb(hdmi, HDMI_N2, (N >> 8) & 0xFF); + hdmi_writeb(hdmi, HDMI_N1, N & 0xFF); + + /* Set CTS value. */ + hdmi_writeb(hdmi, HDMI_CTS_EXT1, CTS & 0xff); + hdmi_writeb(hdmi, HDMI_CTS_EXT2, (CTS >> 8) & 0xff); + hdmi_writeb(hdmi, HDMI_CTS_EXT3, (CTS >> 16) & 0xff); + + if (audio->channels > 2) + hdmi_modb(hdmi, HDMI_LR_SWAP_N3, + HDMI_AUDIO_LR_SWAP_MASK, + HDMI_AUDIO_LR_SWAP_SUBPACKET1); + rate = (~(rate >> 4)) & 0x0f; + hdmi_writeb(hdmi, HDMI_AUDIO_STA_BIT_CTRL1, rate); + hdmi_writeb(hdmi, HDMI_AUDIO_STA_BIT_CTRL2, 0); + + return rk3066_hdmi_config_aai(hdmi, audio); +} + +static int rk3066_hdmi_audio_hw_params(struct device *dev, void *d, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct rk3066_hdmi *hdmi = dev_get_drvdata(dev); + struct drm_display_info *display = &hdmi->connector.display_info; + + if (!display->has_audio) { + DRM_DEV_ERROR(hdmi->dev, "no audio support\n"); + return -ENODEV; + } + + if (!hdmi->encoder.encoder.crtc) + return -ENODEV; + + switch (daifmt->fmt) { + case HDMI_I2S: + break; + default: + DRM_DEV_ERROR(dev, "invalid format %d\n", daifmt->fmt); + return -EINVAL; + } + + hdmi->audio.channels = params->channels; + hdmi->audio.sample_rate = params->sample_rate; + hdmi->audio.sample_width = params->sample_width; + + return rk3066_hdmi_config_audio(hdmi, &hdmi->audio); +} + +static void rk3066_hdmi_audio_shutdown(struct device *dev, void *d) +{ + /* do nothing */ +} + +static int +rk3066_hdmi_audio_mute_stream(struct device *dev, void *d, + bool mute, int direction) +{ + struct rk3066_hdmi *hdmi = dev_get_drvdata(dev); + struct drm_display_info *display = &hdmi->connector.display_info; + + if (!display->has_audio) { + DRM_DEV_ERROR(hdmi->dev, "no audio support\n"); + return -ENODEV; + } + + hdmi->audio_enable = !mute; + + if (mute) + hdmi_modb(hdmi, HDMI_VIDEO_CTRL2, + HDMI_AUDIO_DISABLE, HDMI_AUDIO_DISABLE); + else + hdmi_modb(hdmi, HDMI_VIDEO_CTRL2, HDMI_AUDIO_DISABLE, 0); + + /* + * Under power mode E we need to reset the audio capture logic to + * make the audio setting update. + */ + if (rk3066_hdmi_get_power_mode(hdmi) == HDMI_SYS_POWER_MODE_E) { + hdmi_modb(hdmi, HDMI_VIDEO_CTRL2, + HDMI_AUDIO_CP_LOGIC_RESET_MASK, + HDMI_AUDIO_CP_LOGIC_RESET); + usleep_range(900, 1000); + hdmi_modb(hdmi, HDMI_VIDEO_CTRL2, + HDMI_AUDIO_CP_LOGIC_RESET_MASK, 0); + } + + return 0; +} + +static int rk3066_hdmi_audio_get_eld(struct device *dev, void *d, + u8 *buf, size_t len) +{ + struct rk3066_hdmi *hdmi = dev_get_drvdata(dev); + struct drm_mode_config *config = &hdmi->encoder.encoder.dev->mode_config; + struct drm_connector *connector; + int ret = -ENODEV; + + mutex_lock(&config->mutex); + list_for_each_entry(connector, &config->connector_list, head) { + if (&hdmi->encoder.encoder == connector->encoder) { + memcpy(buf, connector->eld, + min(sizeof(connector->eld), len)); + ret = 0; + } + } + mutex_unlock(&config->mutex); + + return ret; +} + +static const struct hdmi_codec_ops audio_codec_ops = { + .hw_params = rk3066_hdmi_audio_hw_params, + .audio_shutdown = rk3066_hdmi_audio_shutdown, + .mute_stream = rk3066_hdmi_audio_mute_stream, + .get_eld = rk3066_hdmi_audio_get_eld, + .no_capture_mute = 1, +}; + +static int rk3066_hdmi_audio_codec_init(struct rk3066_hdmi *hdmi, + struct device *dev) +{ + struct hdmi_codec_pdata codec_data = { + .i2s = 1, + .ops = &audio_codec_ops, + .max_i2s_channels = 8, + }; + + hdmi->audio.channels = 2; + hdmi->audio.sample_rate = 48000; + hdmi->audio.sample_width = 16; + hdmi->audio_enable = false; + hdmi->audio_pdev = + platform_device_register_data(dev, + HDMI_CODEC_DRV_NAME, + PLATFORM_DEVID_NONE, + &codec_data, + sizeof(codec_data)); + + return PTR_ERR_OR_ZERO(hdmi->audio_pdev); +} + static int rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi) { @@ -566,6 +834,8 @@ rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi) drm_connector_attach_encoder(&hdmi->connector, encoder); + rk3066_hdmi_audio_codec_init(hdmi, dev); + return 0; } @@ -813,6 +1083,7 @@ static int rk3066_hdmi_bind(struct device *dev, struct device *master, return 0; err_cleanup_hdmi: + platform_device_unregister(hdmi->audio_pdev); hdmi->connector.funcs->destroy(&hdmi->connector); hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder); err_disable_i2c: @@ -828,6 +1099,7 @@ static void rk3066_hdmi_unbind(struct device *dev, struct device *master, { struct rk3066_hdmi *hdmi = dev_get_drvdata(dev); + platform_device_unregister(hdmi->audio_pdev); hdmi->connector.funcs->destroy(&hdmi->connector); hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder);