From patchwork Wed May 13 09:23:47 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jyri Sarha X-Patchwork-Id: 6395771 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 D939A9F32B for ; Wed, 13 May 2015 09:24:31 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id A446920437 for ; Wed, 13 May 2015 09:24:30 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 0D94A20443 for ; Wed, 13 May 2015 09:24:29 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S933501AbbEMJY2 (ORCPT ); Wed, 13 May 2015 05:24:28 -0400 Received: from comal.ext.ti.com ([198.47.26.152]:38577 "EHLO comal.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S933335AbbEMJYY (ORCPT ); Wed, 13 May 2015 05:24:24 -0400 Received: from dlelxv90.itg.ti.com ([172.17.2.17]) by comal.ext.ti.com (8.13.7/8.13.7) with ESMTP id t4D9Nx75029463; Wed, 13 May 2015 04:23:59 -0500 Received: from DFLE73.ent.ti.com (dfle73.ent.ti.com [128.247.5.110]) by dlelxv90.itg.ti.com (8.14.3/8.13.8) with ESMTP id t4D9NxkD012148; Wed, 13 May 2015 04:23:59 -0500 Received: from dlep32.itg.ti.com (157.170.170.100) by DFLE73.ent.ti.com (128.247.5.110) with Microsoft SMTP Server id 14.3.224.2; Wed, 13 May 2015 04:23:59 -0500 Received: from imryr.ti.com (ileax41-snat.itg.ti.com [10.172.224.153]) by dlep32.itg.ti.com (8.14.3/8.13.8) with ESMTP id t4D9NpF5019320; Wed, 13 May 2015 04:23:57 -0500 From: Jyri Sarha To: , , , CC: , , , , , , Jyri Sarha Subject: [PATCH early RFC 2/2] drm/i2c: tda998x: HACK Implement primitive HDMI audio with ASoC hdmi-code-lib Date: Wed, 13 May 2015 12:23:47 +0300 Message-ID: <92bebd31e0e5bed63af8d310c48f01409be88e56.1431508524.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=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, T_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 This patch is to demonstrate how to use the ASoC hdmi-codec-lib to implement ASoC codec API in tda998x driver. I do not have proper documentation for tda998x family chips so I lack the necessary information for making a proper binding for audio part of the chip. The configuration is hard coded to work on Beaglebone-Black. Signed-off-by: Jyri Sarha --- drivers/gpu/drm/i2c/Kconfig | 1 + drivers/gpu/drm/i2c/tda998x_drv.c | 238 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+) diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 22c7ed6..92302e9 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_LIB 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 5febffd..797b4d5 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 @@ -31,6 +32,7 @@ #define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) struct tda998x_priv { + struct hdmi_codec_drvdata audio_data; struct i2c_client *cec; struct i2c_client *hdmi; struct mutex mutex; @@ -44,6 +46,10 @@ struct tda998x_priv { u8 vip_cntrl_2; struct tda998x_encoder_params params; + struct mutex sads_mutex; + struct cea_sad *sads; + int sads_count; + wait_queue_head_t wq_edid; volatile int wq_edid_wait; struct drm_encoder *encoder; @@ -1115,6 +1121,13 @@ 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); + + mutex_lock(&priv->sads_mutex); + kfree(priv->sads); + priv->sads = NULL; + priv->sads_count = drm_edid_to_sad(edid, &priv->sads); + mutex_unlock(&priv->sads_mutex); + kfree(edid); return n; @@ -1151,6 +1164,14 @@ static void tda998x_destroy(struct tda998x_priv *priv) } i2c_unregister_device(priv->cec); + + asoc_hdmi_codec_unregister(&priv->hdmi->dev); + + mutex_lock(&priv->sads_mutex); + kfree(priv->sads); + priv->sads = NULL; + priv->sads_count = -ENODEV; + mutex_unlock(&priv->sads_mutex); } /* Slave encoder support */ @@ -1225,6 +1246,217 @@ static struct drm_encoder_slave_funcs tda998x_encoder_slave_funcs = { .set_property = tda998x_encoder_set_property, }; +static int +tda998x_configure_audio2(struct tda998x_priv *priv, + int mode_clock, + int audio_ena, + struct hdmi_codec_params *params, + struct hdmi_codec_daifmt *daifmt) +{ + uint8_t buf[6], clksel_aip, clksel_fs, cts_n, adiv; + uint8_t infoframe_buf[HDMI_INFOFRAME_SIZE(AUDIO)]; + int infoframe_len; + uint32_t n; + + infoframe_len = hdmi_audio_infoframe_pack(¶ms->cea, infoframe_buf, + sizeof(infoframe_buf)); + if (infoframe_len < 0) { + dev_err(&priv->hdmi->dev, + "Failed to pack audio infoframe: %d\n", + infoframe_len); + return infoframe_len; + } + + /* Enable audio ports */ + reg_write(priv, REG_ENA_AP, audio_ena); + reg_write(priv, REG_ENA_ACLK, daifmt->fmt == HDMI_SPDIF ? 0 : 1); + + /* Set audio input source */ + switch (daifmt->fmt) { + case HDMI_SPDIF: + reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_SPDIF); + clksel_aip = AIP_CLKSEL_AIP_SPDIF; + clksel_fs = AIP_CLKSEL_FS_FS64SPDIF; + cts_n = CTS_N_M(3) | CTS_N_K(3); + break; + + case HDMI_I2S: + reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_I2S); + clksel_aip = AIP_CLKSEL_AIP_I2S; + clksel_fs = AIP_CLKSEL_FS_ACLK; + switch (params->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: + dev_err(&priv->hdmi->dev, "Unsupported I2S format\n"); + return -EINVAL; + } + + reg_write(priv, REG_AIP_CLKSEL, clksel_aip); + reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_LAYOUT | + AIP_CNTRL_0_ACR_MAN); /* auto CTS */ + reg_write(priv, REG_CTS_N, cts_n); + + /* + * Audio input somehow depends on HDMI line rate which is + * related to pixclk. Testing showed that modes with pixclk + * >100MHz need a larger divider while <40MHz need the default. + * There is no detailed info in the datasheet, so we just + * assume 100MHz requires larger divider. + */ + adiv = AUDIO_DIV_SERCLK_8; + if (mode_clock > 100000) + adiv++; /* AUDIO_DIV_SERCLK_16 */ + + /* S/PDIF asks for a larger divider */ + if (daifmt->fmt == HDMI_SPDIF) + adiv++; /* AUDIO_DIV_SERCLK_16 or _32 */ + + reg_write(priv, REG_AUDIO_DIV, adiv); + + /* + * This is the approximate value of N, which happens to be + * the recommended values for non-coherent clocks. + */ + n = 128 * params->sample_rate / 1000; + + /* Write the CTS and N values */ + buf[0] = 0x44; + buf[1] = 0x42; + buf[2] = 0x01; + buf[3] = n; + buf[4] = n >> 8; + buf[5] = n >> 16; + reg_write_range(priv, REG_ACR_CTS_0, buf, 6); + + /* Set CTS clock reference */ + reg_write(priv, REG_AIP_CLKSEL, clksel_aip | clksel_fs); + + /* Reset CTS generator */ + reg_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS); + reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS); + + /* Write the channel status */ + reg_write_range(priv, REG_CH_STAT_B(0), params->iec.status, 4); + + tda998x_audio_mute(priv, true); + msleep(20); + tda998x_audio_mute(priv, false); + + /* Write the audio information packet */ + tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, + infoframe_buf, + infoframe_len); + return 0; +} + +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); + + 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; + } + break; + case HDMI_SPDIF: + break; + default: + dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt); + return -EINVAL; + } + + return tda998x_configure_audio2(priv, + priv->encoder->crtc->hwmode.clock, + priv->params.audio_cfg, + params, + daifmt); +} + +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_sads(struct device *dev, struct cea_sad **sads) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + int ret = priv->sads_count; + + mutex_lock(&priv->sads_mutex); + if (priv->sads_count > 0) { + *sads = kcalloc(priv->sads_count, sizeof(**sads), GFP_KERNEL); + if (*sads == NULL) { + ret = -ENOMEM; + goto out; + } + memcpy(*sads, priv->sads, priv->sads_count * sizeof(**sads)); + } + mutex_unlock(&priv->sads_mutex); + +out: + 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_sads = tda998x_audio_get_sads, +}; + +static int tda998x_audio_codec_init(struct tda998x_priv *priv, + struct device *dev) +{ + struct hdmi_codec_data codec_data = { + .dev = dev, + .ops = &audio_codec_ops, + .i2s = 1, + .spdif = 0, + .max_i2s_channels = 2, + }; + + /* This should be taken from dt or pdata */ + priv->params.audio_cfg = 0x3; + + return asoc_hdmi_codec_register(&codec_data); +} + /* I2C driver functions */ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) @@ -1345,6 +1577,10 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) priv->vip_cntrl_2 = video; } + mutex_init(&priv->sads_mutex); + + tda998x_audio_codec_init(priv, &client->dev); + return 0; fail: @@ -1375,6 +1611,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;