From patchwork Fri Aug 14 09:30:44 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jyri Sarha X-Patchwork-Id: 7013291 Return-Path: X-Original-To: patchwork-linux-omap@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 5F7479F358 for ; Fri, 14 Aug 2015 09:31:50 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 13A15206AB for ; Fri, 14 Aug 2015 09:31:49 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 71DC220665 for ; Fri, 14 Aug 2015 09:31:47 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754779AbbHNJbr (ORCPT ); Fri, 14 Aug 2015 05:31:47 -0400 Received: from arroyo.ext.ti.com ([192.94.94.40]:56953 "EHLO arroyo.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754705AbbHNJbp (ORCPT ); Fri, 14 Aug 2015 05:31:45 -0400 Received: from dflxv15.itg.ti.com ([128.247.5.124]) by arroyo.ext.ti.com (8.13.7/8.13.7) with ESMTP id t7E9VNGM001276; Fri, 14 Aug 2015 04:31:23 -0500 Received: from DLEE71.ent.ti.com (dlee71.ent.ti.com [157.170.170.114]) by dflxv15.itg.ti.com (8.14.3/8.13.8) with ESMTP id t7E9VNMQ011528; Fri, 14 Aug 2015 04:31:23 -0500 Received: from dlep33.itg.ti.com (157.170.170.75) by DLEE71.ent.ti.com (157.170.170.114) with Microsoft SMTP Server id 14.3.224.2; Fri, 14 Aug 2015 04:31:23 -0500 Received: from imryr.ti.com (ileax41-snat.itg.ti.com [10.172.224.153]) by dlep33.itg.ti.com (8.14.3/8.13.8) with ESMTP id t7E9V1Pk028704; Fri, 14 Aug 2015 04:31:20 -0500 From: Jyri Sarha To: , , , , , CC: , , , , , , , Jyri Sarha Subject: [PATCH RFC v3 6/7] drm/i2c: tda998x: Register ASoC HDMI codec for audio functionality Date: Fri, 14 Aug 2015 12:30:44 +0300 Message-ID: <1e8e3215caa932dab68de8576aee81f5e1008163.1439542916.git.jsarha@ti.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: References: MIME-Version: 1.0 Sender: linux-omap-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-omap@vger.kernel.org X-Spam-Status: No, score=-7.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Register ASoC HDMI codec for audio functionality. This is an initial ASoC audio implementation for tda998x driver and it does not use all the features provided by hdmi-codec. HDMI audio info-frame and audio stream header is generated by the ASoC HDMI codec. The codec also applies constraints for available sample-rates. Implementation of audio_startup for hdmi_codec_ops would enable tda998x driver to abort ongoing playback if the HDMI cable is unplugged or re-plugged to a device without audio capability. Signed-off-by: Jyri Sarha --- drivers/gpu/drm/i2c/Kconfig | 1 + drivers/gpu/drm/i2c/tda998x_drv.c | 241 +++++++++++++++++++++++++++++++++----- 2 files changed, 214 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 22c7ed6..088f278 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -28,6 +28,7 @@ config DRM_I2C_SIL164 config DRM_I2C_NXP_TDA998X tristate "NXP Semiconductors TDA998X HDMI encoder" default m if DRM_TILCDC + select SND_SOC_HDMI_CODEC if SND_SOC help Support for NXP Semiconductors TDA998X HDMI encoders. diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index 4dc2dc0..8444e18 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -49,6 +50,8 @@ struct tda998x_priv { u8 vip_cntrl_2; struct tda998x_encoder_params params; + struct platform_device *audio_pdev; + wait_queue_head_t wq_edid; volatile int wq_edid_wait; struct drm_encoder *encoder; @@ -435,7 +438,7 @@ out: } static void -reg_write_range(struct tda998x_priv *priv, uint16_t reg, uint8_t *p, int cnt) +reg_write_range(struct tda998x_priv *priv, uint16_t reg, const u8 *p, int cnt) { struct i2c_client *client = priv->hdmi; uint8_t buf[cnt+1]; @@ -619,7 +622,7 @@ tda998x_write_if(struct tda998x_priv *priv, uint8_t bit, uint16_t addr, } static void -tda998x_write_aif(struct tda998x_priv *priv, struct tda998x_encoder_params *p) +tda998x_write_raw_aif(struct tda998x_priv *priv, u8 *audio_frame) { u8 buf[PB(HDMI_AUDIO_INFOFRAME_SIZE) + 1]; @@ -627,10 +630,10 @@ tda998x_write_aif(struct tda998x_priv *priv, struct tda998x_encoder_params *p) buf[HB(0)] = HDMI_INFOFRAME_TYPE_AUDIO; buf[HB(1)] = 0x01; buf[HB(2)] = HDMI_AUDIO_INFOFRAME_SIZE; - buf[PB(1)] = p->audio_frame[1] & 0x07; /* CC */ - buf[PB(2)] = p->audio_frame[2] & 0x1c; /* SF */ - buf[PB(4)] = p->audio_frame[4]; - buf[PB(5)] = p->audio_frame[5] & 0xf8; /* DM_INH + LSV */ + buf[PB(1)] = audio_frame[1] & 0x07; /* CC */ + buf[PB(2)] = audio_frame[2] & 0x1c; /* SF */ + buf[PB(4)] = audio_frame[4]; + buf[PB(5)] = audio_frame[5] & 0xf8; /* DM_INH + LSV */ buf[PB(0)] = tda998x_cksum(buf, sizeof(buf)); @@ -638,6 +641,24 @@ tda998x_write_aif(struct tda998x_priv *priv, struct tda998x_encoder_params *p) sizeof(buf)); } +static int tda998x_write_aif(struct tda998x_priv *priv, + struct hdmi_audio_infoframe *cea) +{ + uint8_t buf[HDMI_INFOFRAME_SIZE(AUDIO)]; + int len; + + len = hdmi_audio_infoframe_pack(cea, buf, sizeof(buf)); + if (len < 0) { + dev_err(&priv->hdmi->dev, + "Failed to pack audio infoframe: %d\n", len); + return len; + } + + /* Write the audio information packet */ + tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, buf, len); + return 0; +} + static void tda998x_write_avi(struct tda998x_priv *priv, struct drm_display_mode *mode) { @@ -670,19 +691,24 @@ static void tda998x_audio_mute(struct tda998x_priv *priv, bool on) } } -static void +static int tda998x_configure_audio(struct tda998x_priv *priv, - struct drm_display_mode *mode, struct tda998x_encoder_params *p) + int mode_clock, + int ena_ap, + int dai_format, + int sample_width, + int sample_rate, + const u8 *status) { uint8_t buf[6], clksel_aip, clksel_fs, cts_n, adiv; uint32_t n; /* Enable audio ports */ - reg_write(priv, REG_ENA_AP, p->audio_cfg); - reg_write(priv, REG_ENA_ACLK, p->audio_clk_cfg); + reg_write(priv, REG_ENA_AP, ena_ap); + reg_write(priv, REG_ENA_ACLK, dai_format == AFMT_SPDIF ? 0 : 1); /* Set audio input source */ - switch (p->audio_format) { + switch (dai_format) { case AFMT_SPDIF: reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_SPDIF); clksel_aip = AIP_CLKSEL_AIP_SPDIF; @@ -694,12 +720,25 @@ tda998x_configure_audio(struct tda998x_priv *priv, reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_I2S); clksel_aip = AIP_CLKSEL_AIP_I2S; clksel_fs = AIP_CLKSEL_FS_ACLK; - cts_n = CTS_N_M(3) | CTS_N_K(3); + switch (sample_width) { + case 16: + cts_n = CTS_N_M(3) | CTS_N_K(1); + break; + case 18: + case 20: + case 24: + cts_n = CTS_N_M(3) | CTS_N_K(2); + break; + default: + case 32: + cts_n = CTS_N_M(3) | CTS_N_K(3); + break; + } break; default: - BUG(); - return; + dev_err(&priv->hdmi->dev, "Unsupported I2S format\n"); + return -EINVAL; } reg_write(priv, REG_AIP_CLKSEL, clksel_aip); @@ -715,11 +754,11 @@ tda998x_configure_audio(struct tda998x_priv *priv, * assume 100MHz requires larger divider. */ adiv = AUDIO_DIV_SERCLK_8; - if (mode->clock > 100000) + if (mode_clock > 100000) adiv++; /* AUDIO_DIV_SERCLK_16 */ /* S/PDIF asks for a larger divider */ - if (p->audio_format == AFMT_SPDIF) + if (dai_format == AFMT_SPDIF) adiv++; /* AUDIO_DIV_SERCLK_16 or _32 */ reg_write(priv, REG_AUDIO_DIV, adiv); @@ -728,7 +767,7 @@ tda998x_configure_audio(struct tda998x_priv *priv, * This is the approximate value of N, which happens to be * the recommended values for non-coherent clocks. */ - n = 128 * p->audio_sample_rate / 1000; + n = 128 * sample_rate / 1000; /* Write the CTS and N values */ buf[0] = 0x44; @@ -747,19 +786,13 @@ tda998x_configure_audio(struct tda998x_priv *priv, reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS); /* Write the channel status */ - buf[0] = IEC958_AES0_CON_NOT_COPYRIGHT; - buf[1] = 0x00; - buf[2] = IEC958_AES3_CON_FS_NOTID; - buf[3] = IEC958_AES4_CON_ORIGFS_NOTID | - IEC958_AES4_CON_MAX_WORDLEN_24; - reg_write_range(priv, REG_CH_STAT_B(0), buf, 4); + reg_write_range(priv, REG_CH_STAT_B(0), status, 4); tda998x_audio_mute(priv, true); msleep(20); tda998x_audio_mute(priv, false); - /* Write the audio information packet */ - tda998x_write_aif(priv, p); + return 0; } /* DRM encoder functions */ @@ -1033,9 +1066,24 @@ tda998x_encoder_mode_set(struct tda998x_priv *priv, tda998x_write_avi(priv, adjusted_mode); - if (priv->params.audio_cfg) - tda998x_configure_audio(priv, adjusted_mode, - &priv->params); + if (priv->params.audio_cfg) { + static const u8 status[] = { + IEC958_AES0_CON_NOT_COPYRIGHT, + 0x00, + IEC958_AES3_CON_FS_NOTID, + IEC958_AES4_CON_ORIGFS_NOTID, + IEC958_AES4_CON_MAX_WORDLEN_24 + }; + + tda998x_configure_audio(priv, adjusted_mode->clock, + priv->params.audio_cfg, + priv->params.audio_clk_cfg, + 24, + priv->params.audio_sample_rate, + status); + + tda998x_write_raw_aif(priv, priv->params.audio_frame); + } } } @@ -1127,6 +1175,8 @@ tda998x_encoder_get_modes(struct tda998x_priv *priv, drm_mode_connector_update_edid_property(connector, edid); n = drm_add_edid_modes(connector, edid); priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid); + drm_edid_to_eld(connector, edid); + kfree(edid); return n; @@ -1163,6 +1213,9 @@ static void tda998x_destroy(struct tda998x_priv *priv) } i2c_unregister_device(priv->cec); + + if (priv->audio_pdev) + platform_device_unregister(priv->audio_pdev); } /* Slave encoder support */ @@ -1237,6 +1290,134 @@ static struct drm_encoder_slave_funcs tda998x_encoder_slave_funcs = { .set_property = tda998x_encoder_set_property, }; +static int tda998x_audio_hw_params(struct device *dev, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + int ena_ap = -1; + int dai_format, i, ret; + + if (!priv->encoder->crtc) + return -ENODEV; + + switch (daifmt->fmt) { + case HDMI_I2S: + if (daifmt->bit_clk_inv || daifmt->frame_clk_inv || + daifmt->bit_clk_master || daifmt->frame_clk_master) { + dev_err(dev, "%s: Bad flags %d %d %d %d\n", __func__, + daifmt->bit_clk_inv, daifmt->frame_clk_inv, + daifmt->bit_clk_master, + daifmt->frame_clk_master); + return -EINVAL; + } + for (i = 0; i < ARRAY_SIZE(priv->audio.ports); i++) + if (priv->audio.port_types[i] == AFMT_I2S) + ena_ap = priv->audio.ports[i]; + dai_format = AFMT_I2S; + break; + case HDMI_SPDIF: + for (i = 0; i < ARRAY_SIZE(priv->audio.ports); i++) + if (priv->audio.port_types == AFMT_SPDIF) + ena_ap = priv->audio.ports[i]; + dai_format = AFMT_SPDIF; + break; + default: + dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt); + return -EINVAL; + } + + if (ena_ap < 0) { + dev_err(dev, "%s: No audio configutation found\n", __func__); + return -EINVAL; + } + + ret = tda998x_configure_audio(priv, + priv->encoder->crtc->hwmode.clock, + ena_ap, + dai_format, + params->sample_width, + params->sample_rate, + params->iec.status); + + if (!ret) + ret = tda998x_write_aif(priv, ¶ms->cea); + + return ret; +} + +static void tda998x_audio_shutdown(struct device *dev) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + + reg_write(priv, REG_ENA_AP, 0); +} + +int tda998x_audio_digital_mute(struct device *dev, bool enable) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + + tda998x_audio_mute(priv, enable); + + return 0; +} + +static int tda998x_audio_get_eld(struct device *dev, uint8_t *buf, size_t len) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + struct drm_mode_config *config = &priv->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 (priv->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 = tda998x_audio_hw_params, + .audio_shutdown = tda998x_audio_shutdown, + .digital_mute = tda998x_audio_digital_mute, + .get_eld = tda998x_audio_get_eld, +}; + +static int tda998x_audio_codec_init(struct tda998x_priv *priv, + struct device *dev) +{ + struct hdmi_codec_pdata codec_data = { + .dev = dev, + .ops = &audio_codec_ops, + .max_i2s_channels = 2, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(priv->audio.ports); i++) { + if (priv->audio.port_types[i] == AFMT_I2S && + priv->audio.ports[i] != 0) + codec_data.i2s = 1; + if (priv->audio.port_types[i] == AFMT_SPDIF && + priv->audio.ports[i] != 0) + codec_data.spdif = 1; + } + + priv->audio_pdev = platform_device_register_data( + dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO, + &codec_data, sizeof(codec_data)); + + if (IS_ERR(priv->audio_pdev)) + return PTR_ERR(priv->audio_pdev); + + return 0; +} + /* I2C driver functions */ static int tda998x_parse_ports(struct tda998x_priv *priv, @@ -1425,6 +1606,8 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) } } + tda998x_audio_codec_init(priv, &client->dev); + return 0; fail: @@ -1455,6 +1638,8 @@ static int tda998x_encoder_init(struct i2c_client *client, return ret; } + dev_set_drvdata(&client->dev, priv); + encoder_slave->slave_priv = priv; encoder_slave->slave_funcs = &tda998x_encoder_slave_funcs;