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 <jsarha@xxxxxx> --- drivers/gpu/drm/i2c/Kconfig | 1 + drivers/gpu/drm/i2c/tda998x_drv.c | 181 +++++++++++++++++++++++++++++++------- include/drm/i2c/tda998x.h | 1 + 3 files changed, 150 insertions(+), 33 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 2fc6399..1a9bbf2 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -20,6 +20,7 @@ #include <linux/module.h> #include <linux/irq.h> #include <sound/asoundef.h> +#include <sound/hdmi-codec.h> #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> @@ -30,9 +31,9 @@ #define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) -struct tda998x_audio { - u8 ports[2]; /* AP value */ - u8 port_types[2]; /* AFMT_xxx */ +struct tda998x_audio_port { + u8 format; /* AFMT_xxx */ + u8 config; /* AP value */ }; struct tda998x_priv { @@ -49,11 +50,13 @@ struct tda998x_priv { u8 vip_cntrl_2; struct tda998x_audio_params audio_params; + struct platform_device *audio_pdev; + wait_queue_head_t wq_edid; volatile int wq_edid_wait; struct drm_encoder *encoder; - struct tda998x_audio audio; + struct tda998x_audio_port audio_port[2]; }; #define to_tda998x_priv(x) ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv) @@ -701,7 +704,7 @@ tda998x_configure_audio(struct tda998x_priv *priv, break; default: - BUG(); + dev_err(&priv->hdmi->dev, "Unsupported I2S format\n"); return -EINVAL; } @@ -1028,7 +1031,7 @@ tda998x_encoder_mode_set(struct tda998x_priv *priv, tda998x_write_avi(priv, adjusted_mode); - if (priv->audio_params.config) { + if (priv->audio_params.format != AFMT_UNUSED) { tda998x_configure_audio(priv, &priv->audio_params, adjusted_mode->clock); @@ -1124,6 +1127,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; @@ -1160,6 +1165,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 */ @@ -1234,6 +1242,133 @@ 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 i, ret; + struct tda998x_audio_params audio = { + .sample_width = params->sample_width, + .sample_rate = params->sample_rate, + .cea = params->cea, + }; + + if (!priv->encoder->crtc) + return -ENODEV; + + memcpy(audio.status, params->iec.status, + min(sizeof(audio.status), sizeof(params->iec.status))); + + 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_port); i++) + if (priv->audio_port[i].format == AFMT_I2S) + audio.config = priv->audio_port[i].config; + audio.format = AFMT_I2S; + break; + case HDMI_SPDIF: + for (i = 0; i < ARRAY_SIZE(priv->audio_port); i++) + if (priv->audio_port[i].format == AFMT_SPDIF) + audio.config = priv->audio_port[i].config; + audio.format = AFMT_SPDIF; + break; + default: + dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt); + return -EINVAL; + } + + if (audio.config == 0) { + dev_err(dev, "%s: No audio configutation found\n", __func__); + return -EINVAL; + } + + ret = tda998x_configure_audio(priv, + &audio, + priv->encoder->crtc->hwmode.clock); + + 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 = { + .ops = &audio_codec_ops, + .max_i2s_channels = 2, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(priv->audio_port); i++) { + if (priv->audio_port[i].format == AFMT_I2S && + priv->audio_port[i].config != 0) + codec_data.i2s = 1; + if (priv->audio_port[i].format == AFMT_SPDIF && + priv->audio_port[i].config != 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, @@ -1274,12 +1409,12 @@ static int tda998x_parse_ports(struct tda998x_priv *priv, afmt = AFMT_SPDIF; else continue; - if (audio_index >= ARRAY_SIZE(priv->audio.ports)) { + if (audio_index >= ARRAY_SIZE(priv->audio_port)) { dev_err(&priv->hdmi->dev, "too many audio ports\n"); break; } - priv->audio.ports[audio_index] = reg; - priv->audio.port_types[audio_index] = afmt; + priv->audio_port[audio_index].format = afmt; + priv->audio_port[audio_index].config = reg; audio_index++; } return rgb_initialized; @@ -1412,30 +1547,8 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) priv->vip_cntrl_2 = video; } } - if (priv->audio.ports[0]) { - struct tda998x_audio_params params = { - .config = priv->audio.ports[0], - .format = priv->audio.port_types[0], - .sample_width = 24, - .sample_rate = 44100, - .cea = { - .channels = 2, - .coding_type = HDMI_AUDIO_CODING_TYPE_STREAM, - .sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM, - .sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM, - }, - .status = { - IEC958_AES0_CON_NOT_COPYRIGHT, - IEC958_AES1_CON_GENERAL, - IEC958_AES2_CON_SOURCE_UNSPEC | - IEC958_AES2_CON_CHANNEL_UNSPEC, - IEC958_AES3_CON_CLOCK_1000PPM | - IEC958_AES3_CON_FS_NOTID, - }, - }; - - priv->audio_params = params; - } + if (priv->audio_port[0].format != AFMT_UNUSED) + tda998x_audio_codec_init(priv, &client->dev); } return 0; @@ -1468,6 +1581,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; diff --git a/include/drm/i2c/tda998x.h b/include/drm/i2c/tda998x.h index dfe7829..5a0cabb 100644 --- a/include/drm/i2c/tda998x.h +++ b/include/drm/i2c/tda998x.h @@ -4,6 +4,7 @@ struct tda998x_audio_params { u8 config; enum { + AFMT_UNUSED = 0, AFMT_SPDIF, AFMT_I2S } format; -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html