@@ -17,6 +17,8 @@
#include <drm/drm_crtc_helper.h>
#include <drm/drm_edid.h>
+#include <sound/hdmi_drm.h>
+
#include "sti_hdmi.h"
#include "sti_hdmi_tx3g4c28phy.h"
#include "sti_hdmi_tx3g0c55phy.h"
@@ -34,6 +36,8 @@
#define HDMI_DFLT_CHL0_DAT 0x0110
#define HDMI_DFLT_CHL1_DAT 0x0114
#define HDMI_DFLT_CHL2_DAT 0x0118
+#define HDMI_AUDIO_CFG 0x0200
+#define HDMI_SPDIF_FIFO_STATUS 0x0204
#define HDMI_SW_DI_1_HEAD_WORD 0x0210
#define HDMI_SW_DI_1_PKT_WORD0 0x0214
#define HDMI_SW_DI_1_PKT_WORD1 0x0218
@@ -43,6 +47,9 @@
#define HDMI_SW_DI_1_PKT_WORD5 0x0228
#define HDMI_SW_DI_1_PKT_WORD6 0x022C
#define HDMI_SW_DI_CFG 0x0230
+#define HDMI_SAMPLE_FLAT_MASK 0x0244
+#define HDMI_AUDN 0x0400
+#define HDMI_AUD_CTS 0x0404
#define HDMI_SW_DI_2_HEAD_WORD 0x0600
#define HDMI_SW_DI_2_PKT_WORD0 0x0604
#define HDMI_SW_DI_2_PKT_WORD1 0x0608
@@ -109,6 +116,29 @@
#define HDMI_STA_SW_RST BIT(1)
+#define HDMI_AUD_CFG_8CH BIT(0)
+#define HDMI_AUD_CFG_SPDIF_DIV_2 BIT(1)
+#define HDMI_AUD_CFG_SPDIF_DIV_3 BIT(2)
+#define HDMI_AUD_CFG_SPDIF_CLK_DIV_4 (BIT(1) | BIT(2))
+#define HDMI_AUD_CFG_CTS_CLK_256FS BIT(12)
+#define HDMI_AUD_CFG_DTS_INVALID BIT(16)
+#define HDMI_AUD_CFG_ONE_BIT_INVALID (BIT(18) | BIT(19) | BIT(20) | BIT(21))
+#define HDMI_AUD_CFG_CH12_VALID BIT(28)
+#define HDMI_AUD_CFG_CH34_VALID BIT(29)
+#define HDMI_AUD_CFG_CH56_VALID BIT(30)
+#define HDMI_AUD_CFG_CH78_VALID BIT(31)
+
+/* sample flat mask */
+#define HDMI_SAMPLE_FLAT_NO 0
+#define HDMI_SAMPLE_FLAT_SP0 BIT(0)
+#define HDMI_SAMPLE_FLAT_SP1 BIT(1)
+#define HDMI_SAMPLE_FLAT_SP2 BIT(2)
+#define HDMI_SAMPLE_FLAT_SP3 BIT(3)
+#define HDMI_SAMPLE_FLAT_ALL (HDMI_SAMPLE_FLAT_SP0 \
+ | HDMI_SAMPLE_FLAT_SP1 \
+ | HDMI_SAMPLE_FLAT_SP2 \
+ | HDMI_SAMPLE_FLAT_SP3)
+
#define HDMI_INFOFRAME_HEADER_TYPE(x) (((x) & 0xff) << 0)
#define HDMI_INFOFRAME_HEADER_VERSION(x) (((x) & 0xff) << 8)
#define HDMI_INFOFRAME_HEADER_LEN(x) (((x) & 0x0f) << 16)
@@ -380,19 +410,15 @@ static int hdmi_avi_infoframe_config(struct sti_hdmi *hdmi)
*/
static int hdmi_audio_infoframe_config(struct sti_hdmi *hdmi)
{
- struct hdmi_audio_infoframe infofame;
+ struct hdmi_audio_infoframe *infoframe;
u8 buffer[HDMI_INFOFRAME_SIZE(AUDIO)];
int ret;
- ret = hdmi_audio_infoframe_init(&infofame);
- if (ret < 0) {
- DRM_ERROR("failed to setup audio infoframe: %d\n", ret);
- return ret;
- }
+ DRM_DEBUG_DRIVER("enter %s\n", __func__);
- infofame.channels = 2;
+ infoframe = &hdmi->audio.infoframe;
- ret = hdmi_audio_infoframe_pack(&infofame, buffer, sizeof(buffer));
+ ret = hdmi_audio_infoframe_pack(infoframe, buffer, sizeof(buffer));
if (ret < 0) {
DRM_ERROR("failed to pack audio infoframe: %d\n", ret);
return ret;
@@ -404,6 +430,60 @@ static int hdmi_audio_infoframe_config(struct sti_hdmi *hdmi)
}
/**
+ * set audio frame rate
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ *
+ */
+static int hdmi_audio_set_infoframe(struct sti_hdmi *hdmi,
+ struct hdmi_audio_infoframe *info)
+{
+ struct hdmi_audio_n_cts n_cts;
+ int ret, audio_cfg;
+
+ DRM_DEBUG_DRIVER("enter %s\n", __func__);
+
+ hdmi->audio.infoframe = *info;
+
+ if (!hdmi->enabled)
+ return 0;
+
+ /* update HDMI registers according to configuration */
+ audio_cfg = HDMI_AUD_CFG_SPDIF_DIV_2 | HDMI_AUD_CFG_DTS_INVALID |
+ HDMI_AUD_CFG_ONE_BIT_INVALID;
+
+ switch (info->channels) {
+ case 8:
+ audio_cfg |= HDMI_AUD_CFG_CH78_VALID;
+ case 6:
+ audio_cfg |= HDMI_AUD_CFG_CH56_VALID;
+ case 4:
+ audio_cfg |= HDMI_AUD_CFG_CH34_VALID | HDMI_AUD_CFG_8CH;
+ case 2:
+ audio_cfg |= HDMI_AUD_CFG_CH12_VALID;
+ break;
+ default:
+ DRM_ERROR("ERROR: Unsupported number of channels (%d)!\n",
+ info->channels);
+ return -EINVAL;
+ }
+
+ hdmi_write(hdmi, audio_cfg, HDMI_AUDIO_CFG);
+
+ /* update N parameter */
+ ret = hdmi_audio_compute_n_cts(info->sample_frequency,
+ hdmi->mode.clock, &n_cts);
+
+ DRM_DEBUG_DRIVER("sample_frequency= %d, pix clock = %d\n",
+ info->sample_frequency, hdmi->mode.clock);
+ DRM_DEBUG_DRIVER("n= %d, cts = %d\n", n_cts.n, n_cts.cts);
+
+ hdmi_write(hdmi, n_cts.n, HDMI_AUDN);
+
+ return hdmi_audio_infoframe_config(hdmi);
+}
+
+/**
* Software reset of the hdmi subsystem
*
* @hdmi: pointer on the hdmi internal structure
@@ -462,7 +542,6 @@ static void sti_hdmi_disable(struct drm_bridge *bridge)
/* Disable HDMI */
val &= ~HDMI_CFG_DEVICE_EN;
hdmi_write(hdmi, val, HDMI_CFG);
-
hdmi_write(hdmi, 0xffffffff, HDMI_INT_CLR);
/* Stop the phy */
@@ -567,6 +646,53 @@ static const struct drm_bridge_funcs sti_hdmi_bridge_funcs = {
.mode_set = sti_hdmi_set_mode,
};
+static int sti_hdmi_audio_disable(struct drm_bridge *bridge)
+{
+ struct sti_hdmi *hdmi = bridge->driver_private;
+
+ DRM_DEBUG_DRIVER("enter %s\n", __func__);
+ /* mute */
+ hdmi_write(hdmi, HDMI_SAMPLE_FLAT_ALL, HDMI_SAMPLE_FLAT_MASK);
+
+ return 0;
+}
+
+static int sti_hdmi_audio_set_mode(struct drm_bridge *bridge,
+ struct hdmi_audio_mode *mode)
+{
+ struct sti_hdmi *hdmi = bridge->driver_private;
+
+ DRM_DEBUG_DRIVER("enter %s\n", __func__);
+ hdmi_audio_set_infoframe(hdmi, &mode->infoframe);
+
+ return 0;
+}
+
+static int sti_hdmi_audio_bridge_enable(struct drm_bridge *bridge)
+{
+ struct sti_hdmi *hdmi = bridge->driver_private;
+
+ DRM_DEBUG_DRIVER("enter %s\n", __func__);
+ /* unmute */
+ hdmi_write(hdmi, HDMI_SAMPLE_FLAT_NO, HDMI_SAMPLE_FLAT_MASK);
+
+ return 0;
+}
+
+static uint8_t *sti_hdmi_audio_get_mode(struct drm_bridge *bridge)
+{
+ struct sti_hdmi *hdmi = bridge->driver_private;
+
+ return hdmi->drm_connector->eld;
+}
+
+static const struct drm_audio_bridge_funcs sti_hdmi_audio_bridge_funcs = {
+ .enable = sti_hdmi_audio_bridge_enable,
+ .disable = sti_hdmi_audio_disable,
+ .mode_set = sti_hdmi_audio_set_mode,
+ .mode_get = sti_hdmi_audio_get_mode,
+};
+
static int sti_hdmi_connector_get_modes(struct drm_connector *connector)
{
struct sti_hdmi_connector *hdmi_connector
@@ -583,6 +709,7 @@ static int sti_hdmi_connector_get_modes(struct drm_connector *connector)
count = drm_add_edid_modes(connector, edid);
drm_mode_connector_update_edid_property(connector, edid);
+ drm_edid_to_eld(connector, edid);
kfree(edid);
return count;
@@ -657,10 +784,13 @@ static void sti_hdmi_connector_destroy(struct drm_connector *connector)
{
struct sti_hdmi_connector *hdmi_connector
= to_sti_hdmi_connector(connector);
+ struct sti_hdmi *hdmi = hdmi_connector->hdmi;
+ drm_bridge_remove(connector->encoder->bridge);
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
kfree(hdmi_connector);
+ hdmi->drm_connector = NULL;
}
static struct drm_connector_funcs sti_hdmi_connector_funcs = {
@@ -698,6 +828,9 @@ static int sti_hdmi_bind(struct device *dev, struct device *master, void *data)
/* Set the drm device handle */
hdmi->drm_dev = drm_dev;
+ /* initialise audio infoframe */
+ hdmi_audio_infoframe_init(&hdmi->audio.infoframe);
+
encoder = sti_hdmi_find_encoder(drm_dev);
if (!encoder)
goto err_adapt;
@@ -705,8 +838,6 @@ static int sti_hdmi_bind(struct device *dev, struct device *master, void *data)
connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL);
if (!connector)
goto err_adapt;
-
-
connector->hdmi = hdmi;
bridge = devm_kzalloc(dev, sizeof(*bridge), GFP_KERNEL);
@@ -715,8 +846,14 @@ static int sti_hdmi_bind(struct device *dev, struct device *master, void *data)
bridge->driver_private = hdmi;
bridge->funcs = &sti_hdmi_bridge_funcs;
+ bridge->audio_funcs = &sti_hdmi_audio_bridge_funcs;
drm_bridge_attach(drm_dev, bridge);
+ bridge->of_node = dev->of_node;
+ err = drm_bridge_add(bridge);
+ if (err)
+ goto err_adapt;
+
encoder->bridge = bridge;
connector->encoder = encoder;
@@ -733,6 +870,8 @@ static int sti_hdmi_bind(struct device *dev, struct device *master, void *data)
if (err)
goto err_connector;
+ hdmi->drm_connector = drm_connector;
+
err = drm_mode_connector_attach_encoder(drm_connector, encoder);
if (err) {
DRM_ERROR("Failed to attach a connector to a encoder\n");
@@ -748,6 +887,8 @@ err_sysfs:
drm_connector_unregister(drm_connector);
err_connector:
drm_connector_cleanup(drm_connector);
+ drm_bridge_remove(bridge);
+ hdmi->drm_connector = NULL;
err_adapt:
put_device(&hdmi->ddc_adapt->dev);
return -EINVAL;
@@ -777,6 +918,25 @@ static const struct of_device_id hdmi_of_match[] = {
};
MODULE_DEVICE_TABLE(of, hdmi_of_match);
+static void sti_hdmi_register_audio_driver(struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct device_node *np_child;
+ struct platform_device *pdev;
+
+ np_child = of_get_child_by_name(np, "sound-dai");
+ if (!np_child)
+ return;
+
+ pdev = platform_device_register_data(dev,
+ HDMI_DRM_CODEC_DRV_NAME,
+ PLATFORM_DEVID_AUTO, NULL, 0);
+ if (IS_ERR(pdev))
+ return;
+
+ DRM_INFO("%s driver bound to HDMI\n", HDMI_DRM_CODEC_DRV_NAME);
+}
+
static int sti_hdmi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -877,6 +1037,8 @@ static int sti_hdmi_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, hdmi);
+ sti_hdmi_register_audio_driver(dev);
+
return component_add(&pdev->dev, &sti_hdmi_ops);
}
@@ -36,6 +36,7 @@ struct hdmi_phy_ops {
* @clk_tmds: hdmi tmds clock
* @clk_phy: hdmi phy clock
* @clk_audio: hdmi audio clock
+ * @audio: hdmi audio state
* @irq: hdmi interrupt number
* @irq_status: interrupt status register
* @phy_ops: phy start/stop operations
@@ -55,6 +56,7 @@ struct sti_hdmi {
struct clk *clk_tmds;
struct clk *clk_phy;
struct clk *clk_audio;
+ struct hdmi_audio_mode audio;
int irq;
u32 irq_status;
struct hdmi_phy_ops *phy_ops;
@@ -64,6 +66,7 @@ struct sti_hdmi {
bool event_received;
struct reset_control *reset;
struct i2c_adapter *ddc_adapt;
+ struct drm_connector *drm_connector;
};
u32 hdmi_read(struct sti_hdmi *hdmi, int offset);
Registeur Asoc codec and implement audio bridge ops. Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com> --- drivers/gpu/drm/sti/sti_hdmi.c | 184 ++++++++++++++++++++++++++++++++++++++--- drivers/gpu/drm/sti/sti_hdmi.h | 3 + 2 files changed, 176 insertions(+), 11 deletions(-)