From 7e567624dc5a44276d9df253f5ca829d911b4e93 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 9 Aug 2016 22:00:05 +0300 Subject: [PATCH] drm/i2c: tda998x: Register ASoC hdmi-codec and add audio DT binding Register ASoC HDMI codec for audio functionality and adds device tree binding for audio configuration. With the registered HDMI codec the tda998x node can be used like a regular codec node in ASoC card configurations. HDMI audio info-frame and audio stream header is generated by the ASoC HDMI codec. The codec also applies constraints for available sample-rates based on Edid Like Data from the display. The device tree binding document has been updated [1]. Part of this patch has been inspired by Jean Francoise's "drm/i2c: tda998x: Add support of a DT graph of ports"-patch [2]. There may still be some identical lines left from the original patch and some of the ideas have come from there. [1] Documentation/devicetree/bindings/display/bridge/tda998x.txt [2] http://mailman.alsa-project.org/pipermail/alsa-devel/2015-July/095255.html Signed-off-by: Jyri Sarha Signed-off-by: Russell King --- .../bindings/display/bridge/tda998x.txt | 18 ++ drivers/gpu/drm/i2c/Kconfig | 1 + drivers/gpu/drm/i2c/tda998x_drv.c | 213 +++++++++++++++++- include/drm/i2c/tda998x.h | 5 +- include/dt-bindings/display/tda998x.h | 7 + 5 files changed, 239 insertions(+), 5 deletions(-) create mode 100644 include/dt-bindings/display/tda998x.h diff --git a/Documentation/devicetree/bindings/display/bridge/tda998x.txt b/Documentation/devicetree/bindings/display/bridge/tda998x.txt index e178e6b9f9ee..24cc2466185a 100644 --- a/Documentation/devicetree/bindings/display/bridge/tda998x.txt +++ b/Documentation/devicetree/bindings/display/bridge/tda998x.txt @@ -21,8 +21,19 @@ Optional properties: - video-ports: 24 bits value which defines how the video controller output is wired to the TDA998x input - default: <0x230145> + - audio-ports: array of 8-bit values, 2 values per one DAI[1]. + The first value defines the DAI type: TDA998x_SPDIF or TDA998x_I2S[2]. + The second value defines the tda998x AP_ENA reg content when the DAI + in question is used. The implementation allows one or two DAIs. If two + DAIs are defined, they must be of different type. + +[1] Documentation/sound/alsa/soc/DAI.txt +[2] include/dt-bindings/display/tda998x.h + Example: +#include + tda998x: hdmi-encoder { compatible = "nxp,tda998x"; reg = <0x70>; @@ -30,4 +41,11 @@ Example: interrupts = <27 2>; /* falling edge */ pinctrl-0 = <&pmx_camera>; pinctrl-names = "default"; + video-ports = <0x230145>; + + #sound-dai-cells = <2>; + /* DAI-format AP_ENA reg value */ + audio-ports = < TDA998x_SPDIF 0x04 + TDA998x_I2S 0x03>; + }; diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 22c7ed63a001..088f2781d303 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 f7c414a76538..9798d400d817 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 @@ -30,6 +31,11 @@ #define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) +struct tda998x_audio_port { + u8 format; /* AFMT_xxx */ + u8 config; /* AP value */ +}; + struct tda998x_priv { struct i2c_client *cec; struct i2c_client *hdmi; @@ -43,6 +49,9 @@ struct tda998x_priv { u8 vip_cntrl_2; struct tda998x_audio_params audio_params; + struct platform_device *audio_pdev; + struct mutex audio_mutex; + wait_queue_head_t wq_edid; volatile int wq_edid_wait; @@ -53,6 +62,8 @@ struct tda998x_priv { struct drm_encoder encoder; struct drm_connector connector; + + struct tda998x_audio_port audio_port[2]; }; #define conn_to_tda998x_priv(x) \ @@ -743,7 +754,7 @@ tda998x_configure_audio(struct tda998x_priv *priv, break; default: - BUG(); + dev_err(&priv->hdmi->dev, "Unsupported I2S format\n"); return -EINVAL; } @@ -1064,9 +1075,11 @@ tda998x_encoder_mode_set(struct drm_encoder *encoder, tda998x_write_avi(priv, adjusted_mode); if (priv->audio_params.format != AFMT_UNUSED) { + mutex_lock(&priv->audio_mutex); tda998x_configure_audio(priv, &priv->audio_params, adjusted_mode->clock); + mutex_unlock(&priv->audio_mutex); } } } @@ -1167,6 +1180,8 @@ static int tda998x_connector_get_modes(struct drm_connector *connector) 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; @@ -1188,6 +1203,9 @@ static void tda998x_destroy(struct tda998x_priv *priv) cec_write(priv, REG_CEC_RXSHPDINTENA, 0); reg_clear(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); + if (priv->audio_pdev) + platform_device_unregister(priv->audio_pdev); + if (priv->hdmi->irq) free_irq(priv->hdmi->irq, priv); @@ -1197,8 +1215,189 @@ static void tda998x_destroy(struct tda998x_priv *priv) i2c_unregister_device(priv->cec); } +static int tda998x_audio_hw_params(struct device *dev, void *data, + 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; + } + + mutex_lock(&priv->audio_mutex); + ret = tda998x_configure_audio(priv, + &audio, + priv->encoder.crtc->hwmode.clock); + + if (ret == 0) + priv->audio_params = audio; + mutex_unlock(&priv->audio_mutex); + + return ret; +} + +static void tda998x_audio_shutdown(struct device *dev, void *data) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + + mutex_lock(&priv->audio_mutex); + + reg_write(priv, REG_ENA_AP, 0); + + priv->audio_params.format = AFMT_UNUSED; + + mutex_unlock(&priv->audio_mutex); +} + +int tda998x_audio_digital_mute(struct device *dev, void *data, bool enable) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + + mutex_lock(&priv->audio_mutex); + + tda998x_audio_mute(priv, enable); + + mutex_unlock(&priv->audio_mutex); + return 0; +} + +static int tda998x_audio_get_eld(struct device *dev, void *data, + 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)); + + return PTR_ERR_OR_ZERO(priv->audio_pdev); +} + /* I2C driver functions */ +static int tda998x_get_audio_ports(struct tda998x_priv *priv, + struct device_node *np) +{ + const u32 *port_data; + u32 size; + int i; + + port_data = of_get_property(np, "audio-ports", &size); + if (!port_data) + return 0; + + size /= sizeof(u32); + if (size > 2 * ARRAY_SIZE(priv->audio_port) || size % 2 != 0) { + dev_err(&priv->hdmi->dev, + "Bad number of elements in audio-ports dt-property\n"); + return -EINVAL; + } + + size /= 2; + + for (i = 0; i < size; i++) { + u8 afmt = be32_to_cpup(&port_data[2*i]); + u8 ena_ap = be32_to_cpup(&port_data[2*i+1]); + + if (afmt != AFMT_SPDIF && afmt != AFMT_I2S) { + dev_err(&priv->hdmi->dev, + "Bad audio format %u\n", afmt); + return -EINVAL; + } + + priv->audio_port[i].format = afmt; + priv->audio_port[i].config = ena_ap; + } + + if (priv->audio_port[0].format == priv->audio_port[1].format) { + dev_err(&priv->hdmi->dev, + "There can only be on I2S port and one SPDIF port\n"); + return -EINVAL; + } + return 0; +} + static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) { struct device_node *np = client->dev.of_node; @@ -1312,7 +1511,7 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) if (!np) return 0; /* non-DT */ - /* get the optional video properties */ + /* get the device tree parameters */ ret = of_property_read_u32(np, "video-ports", &video); if (ret == 0) { priv->vip_cntrl_0 = video >> 16; @@ -1320,8 +1519,16 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) priv->vip_cntrl_2 = video; } - return 0; + mutex_init(&priv->audio_mutex); /* Protect access from audio thread */ + ret = tda998x_get_audio_ports(priv, np); + if (ret) + goto fail; + + if (priv->audio_port[0].format != AFMT_UNUSED) + tda998x_audio_codec_init(priv, &client->dev); + + return 0; fail: /* if encoder_init fails, the encoder slave is never registered, * so cleanup here: diff --git a/include/drm/i2c/tda998x.h b/include/drm/i2c/tda998x.h index a8dc06e67b03..a25483090cd5 100644 --- a/include/drm/i2c/tda998x.h +++ b/include/drm/i2c/tda998x.h @@ -2,11 +2,12 @@ #define __DRM_I2C_TDA998X_H__ #include +#include enum { AFMT_UNUSED = 0, - AFMT_SPDIF = 1, - AFMT_I2S = 2, + AFMT_SPDIF = TDA998x_SPDIF, + AFMT_I2S = TDA998x_I2S, }; struct tda998x_audio_params { diff --git a/include/dt-bindings/display/tda998x.h b/include/dt-bindings/display/tda998x.h new file mode 100644 index 000000000000..34757a3847ef --- /dev/null +++ b/include/dt-bindings/display/tda998x.h @@ -0,0 +1,7 @@ +#ifndef _DT_BINDINGS_TDA998X_H +#define _DT_BINDINGS_TDA998X_H + +#define TDA998x_SPDIF 1 +#define TDA998x_I2S 2 + +#endif /*_DT_BINDINGS_TDA998X_H */ -- 2.20.1