aboutsummaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/bridge/synopsys
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/bridge/synopsys')
-rw-r--r--drivers/gpu/drm/bridge/synopsys/Kconfig1
-rw-r--r--drivers/gpu/drm/bridge/synopsys/Makefile1
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c25
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h1
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c18
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c81
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi.c539
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi.h58
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c74
9 files changed, 646 insertions, 152 deletions
diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig
index 3cc53b44186e..21a1be3ced0f 100644
--- a/drivers/gpu/drm/bridge/synopsys/Kconfig
+++ b/drivers/gpu/drm/bridge/synopsys/Kconfig
@@ -1,3 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
config DRM_DW_HDMI
tristate
select DRM_KMS_HELPER
diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile
index 3e1b1e3d9533..91d746ad5de1 100644
--- a/drivers/gpu/drm/bridge/synopsys/Makefile
+++ b/drivers/gpu/drm/bridge/synopsys/Makefile
@@ -1,3 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c
index ed7af7518b52..2b7539701b42 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c
@@ -1,10 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* DesignWare HDMI audio driver
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
* Written and tested against the Designware HDMI Tx found in iMX6.
*/
#include <linux/io.h>
@@ -66,10 +63,6 @@ enum {
HDMI_REVISION_ID = 0x0001,
HDMI_IH_AHBDMAAUD_STAT0 = 0x0109,
HDMI_IH_MUTE_AHBDMAAUD_STAT0 = 0x0189,
- HDMI_FC_AUDICONF2 = 0x1027,
- HDMI_FC_AUDSCONF = 0x1063,
- HDMI_FC_AUDSCONF_LAYOUT1 = 1 << 0,
- HDMI_FC_AUDSCONF_LAYOUT0 = 0 << 0,
HDMI_AHB_DMA_CONF0 = 0x3600,
HDMI_AHB_DMA_START = 0x3601,
HDMI_AHB_DMA_STOP = 0x3602,
@@ -406,7 +399,7 @@ static int dw_hdmi_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_dw_hdmi *dw = substream->private_data;
- u8 threshold, conf0, conf1, layout, ca;
+ u8 threshold, conf0, conf1, ca;
/* Setup as per 3.0.5 FSL 4.1.0 BSP */
switch (dw->revision) {
@@ -437,20 +430,12 @@ static int dw_hdmi_prepare(struct snd_pcm_substream *substream)
conf1 = default_hdmi_channel_config[runtime->channels - 2].conf1;
ca = default_hdmi_channel_config[runtime->channels - 2].ca;
- /*
- * For >2 channel PCM audio, we need to select layout 1
- * and set an appropriate channel map.
- */
- if (runtime->channels > 2)
- layout = HDMI_FC_AUDSCONF_LAYOUT1;
- else
- layout = HDMI_FC_AUDSCONF_LAYOUT0;
-
writeb_relaxed(threshold, dw->data.base + HDMI_AHB_DMA_THRSLD);
writeb_relaxed(conf0, dw->data.base + HDMI_AHB_DMA_CONF0);
writeb_relaxed(conf1, dw->data.base + HDMI_AHB_DMA_CONF1);
- writeb_relaxed(layout, dw->data.base + HDMI_FC_AUDSCONF);
- writeb_relaxed(ca, dw->data.base + HDMI_FC_AUDICONF2);
+
+ dw_hdmi_set_channel_count(dw->data.hdmi, runtime->channels);
+ dw_hdmi_set_channel_allocation(dw->data.hdmi, ca);
switch (runtime->format) {
case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h
index 63b5756f463b..cb07dc0da5a7 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h
@@ -14,6 +14,7 @@ struct dw_hdmi_audio_data {
struct dw_hdmi_i2s_audio_data {
struct dw_hdmi *hdmi;
+ u8 *eld;
void (*write)(struct dw_hdmi *hdmi, u8 val, int offset);
u8 (*read)(struct dw_hdmi *hdmi, int offset);
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c
index 6c323510f128..70ab4fbdc23e 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c
@@ -1,11 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Designware HDMI CEC driver
*
* Copyright (C) 2015-2017 Russell King.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
#include <linux/interrupt.h>
#include <linux/io.h>
@@ -259,8 +256,8 @@ static int dw_hdmi_cec_probe(struct platform_device *pdev)
dw_hdmi_write(cec, 0, HDMI_CEC_POLARITY);
cec->adap = cec_allocate_adapter(&dw_hdmi_cec_ops, cec, "dw_hdmi",
- CEC_CAP_LOG_ADDRS | CEC_CAP_TRANSMIT |
- CEC_CAP_RC | CEC_CAP_PASSTHROUGH,
+ CEC_CAP_DEFAULTS |
+ CEC_CAP_CONNECTOR_INFO,
CEC_MAX_LOG_ADDRS);
if (IS_ERR(cec->adap))
return PTR_ERR(cec->adap);
@@ -281,13 +278,14 @@ static int dw_hdmi_cec_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
- cec->notify = cec_notifier_get(pdev->dev.parent);
+ cec->notify = cec_notifier_cec_adap_register(pdev->dev.parent,
+ NULL, cec->adap);
if (!cec->notify)
return -ENOMEM;
ret = cec_register_adapter(cec->adap, pdev->dev.parent);
if (ret < 0) {
- cec_notifier_put(cec->notify);
+ cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
return ret;
}
@@ -297,8 +295,6 @@ static int dw_hdmi_cec_probe(struct platform_device *pdev)
*/
devm_remove_action(&pdev->dev, dw_hdmi_cec_del, cec);
- cec_register_cec_notifier(cec->adap, cec->notify);
-
return 0;
}
@@ -306,8 +302,8 @@ static int dw_hdmi_cec_remove(struct platform_device *pdev)
{
struct dw_hdmi_cec *cec = platform_get_drvdata(pdev);
+ cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
cec_unregister_adapter(cec->adap);
- cec_notifier_put(cec->notify);
return 0;
}
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c
index 5cbb71a866d5..d7e65c869415 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c
@@ -10,6 +10,7 @@
#include <linux/module.h>
#include <drm/bridge/dw_hdmi.h>
+#include <drm/drm_crtc.h>
#include <sound/hdmi-codec.h>
@@ -44,14 +45,30 @@ static int dw_hdmi_i2s_hw_params(struct device *dev, void *data,
u8 inputclkfs = 0;
/* it cares I2S only */
- if ((fmt->fmt != HDMI_I2S) ||
- (fmt->bit_clk_master | fmt->frame_clk_master)) {
- dev_err(dev, "unsupported format/settings\n");
+ if (fmt->bit_clk_master | fmt->frame_clk_master) {
+ dev_err(dev, "unsupported clock settings\n");
return -EINVAL;
}
+ /* Reset the FIFOs before applying new params */
+ hdmi_write(audio, HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0);
+ hdmi_write(audio, (u8)~HDMI_MC_SWRSTZ_I2SSWRST_REQ, HDMI_MC_SWRSTZ);
+
inputclkfs = HDMI_AUD_INPUTCLKFS_64FS;
- conf0 = HDMI_AUD_CONF0_I2S_ALL_ENABLE;
+ conf0 = (HDMI_AUD_CONF0_I2S_SELECT | HDMI_AUD_CONF0_I2S_EN0);
+
+ /* Enable the required i2s lanes */
+ switch (hparms->channels) {
+ case 7 ... 8:
+ conf0 |= HDMI_AUD_CONF0_I2S_EN3;
+ /* Fall-thru */
+ case 5 ... 6:
+ conf0 |= HDMI_AUD_CONF0_I2S_EN2;
+ /* Fall-thru */
+ case 3 ... 4:
+ conf0 |= HDMI_AUD_CONF0_I2S_EN1;
+ /* Fall-thru */
+ }
switch (hparms->sample_width) {
case 16:
@@ -63,12 +80,44 @@ static int dw_hdmi_i2s_hw_params(struct device *dev, void *data,
break;
}
+ switch (fmt->fmt) {
+ case HDMI_I2S:
+ conf1 |= HDMI_AUD_CONF1_MODE_I2S;
+ break;
+ case HDMI_RIGHT_J:
+ conf1 |= HDMI_AUD_CONF1_MODE_RIGHT_J;
+ break;
+ case HDMI_LEFT_J:
+ conf1 |= HDMI_AUD_CONF1_MODE_LEFT_J;
+ break;
+ case HDMI_DSP_A:
+ conf1 |= HDMI_AUD_CONF1_MODE_BURST_1;
+ break;
+ case HDMI_DSP_B:
+ conf1 |= HDMI_AUD_CONF1_MODE_BURST_2;
+ break;
+ default:
+ dev_err(dev, "unsupported format\n");
+ return -EINVAL;
+ }
+
dw_hdmi_set_sample_rate(hdmi, hparms->sample_rate);
+ dw_hdmi_set_channel_status(hdmi, hparms->iec.status);
+ dw_hdmi_set_channel_count(hdmi, hparms->channels);
+ dw_hdmi_set_channel_allocation(hdmi, hparms->cea.channel_allocation);
hdmi_write(audio, inputclkfs, HDMI_AUD_INPUTCLKFS);
hdmi_write(audio, conf0, HDMI_AUD_CONF0);
hdmi_write(audio, conf1, HDMI_AUD_CONF1);
+ return 0;
+}
+
+static int dw_hdmi_i2s_audio_startup(struct device *dev, void *data)
+{
+ struct dw_hdmi_i2s_audio_data *audio = data;
+ struct dw_hdmi *hdmi = audio->hdmi;
+
dw_hdmi_audio_enable(hdmi);
return 0;
@@ -80,8 +129,15 @@ static void dw_hdmi_i2s_audio_shutdown(struct device *dev, void *data)
struct dw_hdmi *hdmi = audio->hdmi;
dw_hdmi_audio_disable(hdmi);
+}
- hdmi_write(audio, HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0);
+static int dw_hdmi_i2s_get_eld(struct device *dev, void *data, uint8_t *buf,
+ size_t len)
+{
+ struct dw_hdmi_i2s_audio_data *audio = data;
+
+ memcpy(buf, audio->eld, min_t(size_t, MAX_ELD_BYTES, len));
+ return 0;
}
static int dw_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
@@ -104,10 +160,23 @@ static int dw_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
return -EINVAL;
}
+static int dw_hdmi_i2s_hook_plugged_cb(struct device *dev, void *data,
+ hdmi_codec_plugged_cb fn,
+ struct device *codec_dev)
+{
+ struct dw_hdmi_i2s_audio_data *audio = data;
+ struct dw_hdmi *hdmi = audio->hdmi;
+
+ return dw_hdmi_set_plugged_cb(hdmi, fn, codec_dev);
+}
+
static struct hdmi_codec_ops dw_hdmi_i2s_ops = {
.hw_params = dw_hdmi_i2s_hw_params,
+ .audio_startup = dw_hdmi_i2s_audio_startup,
.audio_shutdown = dw_hdmi_i2s_audio_shutdown,
+ .get_eld = dw_hdmi_i2s_get_eld,
.get_dai_id = dw_hdmi_i2s_get_dai_id,
+ .hook_plugged_cb = dw_hdmi_i2s_hook_plugged_cb,
};
static int snd_dw_hdmi_probe(struct platform_device *pdev)
@@ -119,7 +188,7 @@ static int snd_dw_hdmi_probe(struct platform_device *pdev)
pdata.ops = &dw_hdmi_i2s_ops;
pdata.i2s = 1;
- pdata.max_i2s_channels = 6;
+ pdata.max_i2s_channels = 8;
pdata.data = audio;
memset(&pdevinfo, 0, sizeof(pdevinfo));
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index a63e5f0dae56..67fca439bbfb 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -1,45 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* DesignWare High-Definition Multimedia Interface (HDMI) driver
*
* Copyright (C) 2013-2015 Mentor Graphics Inc.
* Copyright (C) 2011-2013 Freescale Semiconductor, Inc.
* Copyright (C) 2010, Guennadi Liakhovetski <[email protected]>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
*/
-#include <linux/module.h>
-#include <linux/irq.h>
+#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
-#include <linux/clk.h>
#include <linux/hdmi.h>
+#include <linux/irq.h>
+#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_device.h>
+#include <linux/pinctrl/consumer.h>
#include <linux/regmap.h>
+#include <linux/dma-mapping.h>
#include <linux/spinlock.h>
-#include <drm/drm_of.h>
-#include <drm/drmP.h>
-#include <drm/drm_atomic_helper.h>
-#include <drm/drm_edid.h>
-#include <drm/drm_encoder_slave.h>
-#include <drm/drm_scdc_helper.h>
-#include <drm/drm_probe_helper.h>
-#include <drm/bridge/dw_hdmi.h>
+#include <media/cec-notifier.h>
#include <uapi/linux/media-bus-format.h>
#include <uapi/linux/videodev2.h>
-#include "dw-hdmi.h"
+#include <drm/bridge/dw_hdmi.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_scdc_helper.h>
+
#include "dw-hdmi-audio.h"
#include "dw-hdmi-cec.h"
+#include "dw-hdmi.h"
-#include <media/cec-notifier.h>
-
+#define DDC_CI_ADDR 0x37
#define DDC_SEGMENT_ADDR 0x30
#define HDMI_EDID_LEN 512
@@ -169,6 +168,10 @@ struct dw_hdmi {
bool sink_is_hdmi;
bool sink_has_audio;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *default_state;
+ struct pinctrl_state *unwedge_state;
+
struct mutex mutex; /* for state below and previous_mode */
enum drm_connector_force force; /* mutex-protected force state */
bool disabled; /* DRM has disabled our bridge */
@@ -189,7 +192,12 @@ struct dw_hdmi {
void (*enable_audio)(struct dw_hdmi *hdmi);
void (*disable_audio)(struct dw_hdmi *hdmi);
+ struct mutex cec_notifier_mutex;
struct cec_notifier *cec_notifier;
+
+ hdmi_codec_plugged_cb plugged_cb;
+ struct device *codec_dev;
+ enum drm_connector_status last_connector_result;
};
#define HDMI_IH_PHY_STAT0_RX_SENSE \
@@ -214,6 +222,28 @@ static inline u8 hdmi_readb(struct dw_hdmi *hdmi, int offset)
return val;
}
+static void handle_plugged_change(struct dw_hdmi *hdmi, bool plugged)
+{
+ if (hdmi->plugged_cb && hdmi->codec_dev)
+ hdmi->plugged_cb(hdmi->codec_dev, plugged);
+}
+
+int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn,
+ struct device *codec_dev)
+{
+ bool plugged;
+
+ mutex_lock(&hdmi->mutex);
+ hdmi->plugged_cb = fn;
+ hdmi->codec_dev = codec_dev;
+ plugged = hdmi->last_connector_result == connector_status_connected;
+ handle_plugged_change(hdmi, plugged);
+ mutex_unlock(&hdmi->mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_set_plugged_cb);
+
static void hdmi_modb(struct dw_hdmi *hdmi, u8 data, u8 mask, unsigned reg)
{
regmap_update_bits(hdmi->regm, reg << hdmi->reg_shift, mask, data);
@@ -227,6 +257,13 @@ static void hdmi_mask_writeb(struct dw_hdmi *hdmi, u8 data, unsigned int reg,
static void dw_hdmi_i2c_init(struct dw_hdmi *hdmi)
{
+ hdmi_writeb(hdmi, HDMI_PHY_I2CM_INT_ADDR_DONE_POL,
+ HDMI_PHY_I2CM_INT_ADDR);
+
+ hdmi_writeb(hdmi, HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL |
+ HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL,
+ HDMI_PHY_I2CM_CTLINT_ADDR);
+
/* Software reset */
hdmi_writeb(hdmi, 0x00, HDMI_I2CM_SOFTRSTZ);
@@ -247,11 +284,82 @@ static void dw_hdmi_i2c_init(struct dw_hdmi *hdmi)
HDMI_IH_MUTE_I2CM_STAT0);
}
+static bool dw_hdmi_i2c_unwedge(struct dw_hdmi *hdmi)
+{
+ /* If no unwedge state then give up */
+ if (!hdmi->unwedge_state)
+ return false;
+
+ dev_info(hdmi->dev, "Attempting to unwedge stuck i2c bus\n");
+
+ /*
+ * This is a huge hack to workaround a problem where the dw_hdmi i2c
+ * bus could sometimes get wedged. Once wedged there doesn't appear
+ * to be any way to unwedge it (including the HDMI_I2CM_SOFTRSTZ)
+ * other than pulsing the SDA line.
+ *
+ * We appear to be able to pulse the SDA line (in the eyes of dw_hdmi)
+ * by:
+ * 1. Remux the pin as a GPIO output, driven low.
+ * 2. Wait a little while. 1 ms seems to work, but we'll do 10.
+ * 3. Immediately jump to remux the pin as dw_hdmi i2c again.
+ *
+ * At the moment of remuxing, the line will still be low due to its
+ * recent stint as an output, but then it will be pulled high by the
+ * (presumed) external pullup. dw_hdmi seems to see this as a rising
+ * edge and that seems to get it out of its jam.
+ *
+ * This wedging was only ever seen on one TV, and only on one of
+ * its HDMI ports. It happened when the TV was powered on while the
+ * device was plugged in. A scope trace shows the TV bringing both SDA
+ * and SCL low, then bringing them both back up at roughly the same
+ * time. Presumably this confuses dw_hdmi because it saw activity but
+ * no real STOP (maybe it thinks there's another master on the bus?).
+ * Giving it a clean rising edge of SDA while SCL is already high
+ * presumably makes dw_hdmi see a STOP which seems to bring dw_hdmi out
+ * of its stupor.
+ *
+ * Note that after coming back alive, transfers seem to immediately
+ * resume, so if we unwedge due to a timeout we should wait a little
+ * longer for our transfer to finish, since it might have just started
+ * now.
+ */
+ pinctrl_select_state(hdmi->pinctrl, hdmi->unwedge_state);
+ msleep(10);
+ pinctrl_select_state(hdmi->pinctrl, hdmi->default_state);
+
+ return true;
+}
+
+static int dw_hdmi_i2c_wait(struct dw_hdmi *hdmi)
+{
+ struct dw_hdmi_i2c *i2c = hdmi->i2c;
+ int stat;
+
+ stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
+ if (!stat) {
+ /* If we can't unwedge, return timeout */
+ if (!dw_hdmi_i2c_unwedge(hdmi))
+ return -EAGAIN;
+
+ /* We tried to unwedge; give it another chance */
+ stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
+ if (!stat)
+ return -EAGAIN;
+ }
+
+ /* Check for error condition on the bus */
+ if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR)
+ return -EIO;
+
+ return 0;
+}
+
static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi,
unsigned char *buf, unsigned int length)
{
struct dw_hdmi_i2c *i2c = hdmi->i2c;
- int stat;
+ int ret;
if (!i2c->is_regaddr) {
dev_dbg(hdmi->dev, "set read register address to 0\n");
@@ -270,13 +378,9 @@ static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi,
hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_READ,
HDMI_I2CM_OPERATION);
- stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
- if (!stat)
- return -EAGAIN;
-
- /* Check for error condition on the bus */
- if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR)
- return -EIO;
+ ret = dw_hdmi_i2c_wait(hdmi);
+ if (ret)
+ return ret;
*buf++ = hdmi_readb(hdmi, HDMI_I2CM_DATAI);
}
@@ -289,7 +393,7 @@ static int dw_hdmi_i2c_write(struct dw_hdmi *hdmi,
unsigned char *buf, unsigned int length)
{
struct dw_hdmi_i2c *i2c = hdmi->i2c;
- int stat;
+ int ret;
if (!i2c->is_regaddr) {
/* Use the first write byte as register address */
@@ -307,13 +411,9 @@ static int dw_hdmi_i2c_write(struct dw_hdmi *hdmi,
hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_WRITE,
HDMI_I2CM_OPERATION);
- stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
- if (!stat)
- return -EAGAIN;
-
- /* Check for error condition on the bus */
- if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR)
- return -EIO;
+ ret = dw_hdmi_i2c_wait(hdmi);
+ if (ret)
+ return ret;
}
return 0;
@@ -327,6 +427,15 @@ static int dw_hdmi_i2c_xfer(struct i2c_adapter *adap,
u8 addr = msgs[0].addr;
int i, ret = 0;
+ if (addr == DDC_CI_ADDR)
+ /*
+ * The internal I2C controller does not support the multi-byte
+ * read and write operations needed for DDC/CI.
+ * TOFIX: Blacklist the DDC/CI address until we filter out
+ * unsupported I2C operations.
+ */
+ return -EOPNOTSUPP;
+
dev_dbg(hdmi->dev, "xfer: num: %d, addr: %#x\n", num, addr);
for (i = 0; i < num; i++) {
@@ -437,8 +546,14 @@ static void hdmi_set_cts_n(struct dw_hdmi *hdmi, unsigned int cts,
/* nshift factor = 0 */
hdmi_modb(hdmi, 0, HDMI_AUD_CTS3_N_SHIFT_MASK, HDMI_AUD_CTS3);
- hdmi_writeb(hdmi, ((cts >> 16) & HDMI_AUD_CTS3_AUDCTS19_16_MASK) |
- HDMI_AUD_CTS3_CTS_MANUAL, HDMI_AUD_CTS3);
+ /* Use automatic CTS generation mode when CTS is not set */
+ if (cts)
+ hdmi_writeb(hdmi, ((cts >> 16) &
+ HDMI_AUD_CTS3_AUDCTS19_16_MASK) |
+ HDMI_AUD_CTS3_CTS_MANUAL,
+ HDMI_AUD_CTS3);
+ else
+ hdmi_writeb(hdmi, 0, HDMI_AUD_CTS3);
hdmi_writeb(hdmi, (cts >> 8) & 0xff, HDMI_AUD_CTS2);
hdmi_writeb(hdmi, cts & 0xff, HDMI_AUD_CTS1);
@@ -503,29 +618,58 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk)
return n;
}
+/*
+ * When transmitting IEC60958 linear PCM audio, these registers allow to
+ * configure the channel status information of all the channel status
+ * bits in the IEC60958 frame. For the moment this configuration is only
+ * used when the I2S audio interface, General Purpose Audio (GPA),
+ * or AHB audio DMA (AHBAUDDMA) interface is active
+ * (for S/PDIF interface this information comes from the stream).
+ */
+void dw_hdmi_set_channel_status(struct dw_hdmi *hdmi,
+ u8 *channel_status)
+{
+ /*
+ * Set channel status register for frequency and word length.
+ * Use default values for other registers.
+ */
+ hdmi_writeb(hdmi, channel_status[3], HDMI_FC_AUDSCHNLS7);
+ hdmi_writeb(hdmi, channel_status[4], HDMI_FC_AUDSCHNLS8);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_set_channel_status);
+
static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi,
unsigned long pixel_clk, unsigned int sample_rate)
{
unsigned long ftdms = pixel_clk;
unsigned int n, cts;
+ u8 config3;
u64 tmp;
n = hdmi_compute_n(sample_rate, pixel_clk);
- /*
- * Compute the CTS value from the N value. Note that CTS and N
- * can be up to 20 bits in total, so we need 64-bit math. Also
- * note that our TDMS clock is not fully accurate; it is accurate
- * to kHz. This can introduce an unnecessary remainder in the
- * calculation below, so we don't try to warn about that.
- */
- tmp = (u64)ftdms * n;
- do_div(tmp, 128 * sample_rate);
- cts = tmp;
+ config3 = hdmi_readb(hdmi, HDMI_CONFIG3_ID);
- dev_dbg(hdmi->dev, "%s: fs=%uHz ftdms=%lu.%03luMHz N=%d cts=%d\n",
- __func__, sample_rate, ftdms / 1000000, (ftdms / 1000) % 1000,
- n, cts);
+ /* Only compute CTS when using internal AHB audio */
+ if (config3 & HDMI_CONFIG3_AHBAUDDMA) {
+ /*
+ * Compute the CTS value from the N value. Note that CTS and N
+ * can be up to 20 bits in total, so we need 64-bit math. Also
+ * note that our TDMS clock is not fully accurate; it is
+ * accurate to kHz. This can introduce an unnecessary remainder
+ * in the calculation below, so we don't try to warn about that.
+ */
+ tmp = (u64)ftdms * n;
+ do_div(tmp, 128 * sample_rate);
+ cts = tmp;
+
+ dev_dbg(hdmi->dev, "%s: fs=%uHz ftdms=%lu.%03luMHz N=%d cts=%d\n",
+ __func__, sample_rate,
+ ftdms / 1000000, (ftdms / 1000) % 1000,
+ n, cts);
+ } else {
+ cts = 0;
+ }
spin_lock_irq(&hdmi->audio_lock);
hdmi->audio_n = n;
@@ -559,6 +703,42 @@ void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate)
}
EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_rate);
+void dw_hdmi_set_channel_count(struct dw_hdmi *hdmi, unsigned int cnt)
+{
+ u8 layout;
+
+ mutex_lock(&hdmi->audio_mutex);
+
+ /*
+ * For >2 channel PCM audio, we need to select layout 1
+ * and set an appropriate channel map.
+ */
+ if (cnt > 2)
+ layout = HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT1;
+ else
+ layout = HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT0;
+
+ hdmi_modb(hdmi, layout, HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_MASK,
+ HDMI_FC_AUDSCONF);
+
+ /* Set the audio infoframes channel count */
+ hdmi_modb(hdmi, (cnt - 1) << HDMI_FC_AUDICONF0_CC_OFFSET,
+ HDMI_FC_AUDICONF0_CC_MASK, HDMI_FC_AUDICONF0);
+
+ mutex_unlock(&hdmi->audio_mutex);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_set_channel_count);
+
+void dw_hdmi_set_channel_allocation(struct dw_hdmi *hdmi, unsigned int ca)
+{
+ mutex_lock(&hdmi->audio_mutex);
+
+ hdmi_writeb(hdmi, ca, HDMI_FC_AUDICONF2);
+
+ mutex_unlock(&hdmi->audio_mutex);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_set_channel_allocation);
+
static void hdmi_enable_audio_clk(struct dw_hdmi *hdmi, bool enable)
{
if (enable)
@@ -1037,6 +1217,35 @@ void dw_hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data,
}
EXPORT_SYMBOL_GPL(dw_hdmi_phy_i2c_write);
+/* Filter out invalid setups to avoid configuring SCDC and scrambling */
+static bool dw_hdmi_support_scdc(struct dw_hdmi *hdmi)
+{
+ struct drm_display_info *display = &hdmi->connector.display_info;
+
+ /* Completely disable SCDC support for older controllers */
+ if (hdmi->version < 0x200a)
+ return false;
+
+ /* Disable if no DDC bus */
+ if (!hdmi->ddc)
+ return false;
+
+ /* Disable if SCDC is not supported, or if an HF-VSDB block is absent */
+ if (!display->hdmi.scdc.supported ||
+ !display->hdmi.scdc.scrambling.supported)
+ return false;
+
+ /*
+ * Disable if display only support low TMDS rates and scrambling
+ * for low rates is not supported either
+ */
+ if (!display->hdmi.scdc.scrambling.low_rates &&
+ display->max_tmds_clock <= 340000)
+ return false;
+
+ return true;
+}
+
/*
* HDMI2.0 Specifies the following procedure for High TMDS Bit Rates:
* - The Source shall suspend transmission of the TMDS clock and data
@@ -1055,7 +1264,7 @@ void dw_hdmi_set_high_tmds_clock_ratio(struct dw_hdmi *hdmi)
unsigned long mtmdsclock = hdmi->hdmi_data.video_mode.mtmdsclock;
/* Control for TMDS Bit Period/TMDS Clock-Period Ratio */
- if (hdmi->connector.display_info.hdmi.scdc.supported) {
+ if (dw_hdmi_support_scdc(hdmi)) {
if (mtmdsclock > HDMI14_MAX_TMDSCLK)
drm_scdc_set_high_tmds_clock_ratio(hdmi->ddc, 1);
else
@@ -1561,6 +1770,41 @@ static void hdmi_config_vendor_specific_infoframe(struct dw_hdmi *hdmi,
HDMI_FC_DATAUTO0_VSD_MASK);
}
+static void hdmi_config_drm_infoframe(struct dw_hdmi *hdmi)
+{
+ const struct drm_connector_state *conn_state = hdmi->connector.state;
+ struct hdmi_drm_infoframe frame;
+ u8 buffer[30];
+ ssize_t err;
+ int i;
+
+ if (!hdmi->plat_data->use_drm_infoframe)
+ return;
+
+ hdmi_modb(hdmi, HDMI_FC_PACKET_TX_EN_DRM_DISABLE,
+ HDMI_FC_PACKET_TX_EN_DRM_MASK, HDMI_FC_PACKET_TX_EN);
+
+ err = drm_hdmi_infoframe_set_hdr_metadata(&frame, conn_state);
+ if (err < 0)
+ return;
+
+ err = hdmi_drm_infoframe_pack(&frame, buffer, sizeof(buffer));
+ if (err < 0) {
+ dev_err(hdmi->dev, "Failed to pack drm infoframe: %zd\n", err);
+ return;
+ }
+
+ hdmi_writeb(hdmi, frame.version, HDMI_FC_DRM_HB0);
+ hdmi_writeb(hdmi, frame.length, HDMI_FC_DRM_HB1);
+
+ for (i = 0; i < frame.length; i++)
+ hdmi_writeb(hdmi, buffer[4 + i], HDMI_FC_DRM_PB0 + i);
+
+ hdmi_writeb(hdmi, 1, HDMI_FC_DRM_UP);
+ hdmi_modb(hdmi, HDMI_FC_PACKET_TX_EN_DRM_ENABLE,
+ HDMI_FC_PACKET_TX_EN_DRM_MASK, HDMI_FC_PACKET_TX_EN);
+}
+
static void hdmi_av_composer(struct dw_hdmi *hdmi,
const struct drm_display_mode *mode)
{
@@ -1579,8 +1823,9 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
/* Set up HDMI_FC_INVIDCONF */
inv_val = (hdmi->hdmi_data.hdcp_enable ||
- vmode->mtmdsclock > HDMI14_MAX_TMDSCLK ||
- hdmi_info->scdc.scrambling.low_rates ?
+ (dw_hdmi_support_scdc(hdmi) &&
+ (vmode->mtmdsclock > HDMI14_MAX_TMDSCLK ||
+ hdmi_info->scdc.scrambling.low_rates)) ?
HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE :
HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE);
@@ -1646,7 +1891,7 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
}
/* Scrambling Control */
- if (hdmi_info->scdc.supported) {
+ if (dw_hdmi_support_scdc(hdmi)) {
if (vmode->mtmdsclock > HDMI14_MAX_TMDSCLK ||
hdmi_info->scdc.scrambling.low_rates) {
/*
@@ -1658,13 +1903,13 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
* Source Devices compliant shall set the
* Source Version = 1.
*/
- drm_scdc_readb(&hdmi->i2c->adap, SCDC_SINK_VERSION,
+ drm_scdc_readb(hdmi->ddc, SCDC_SINK_VERSION,
&bytes);
- drm_scdc_writeb(&hdmi->i2c->adap, SCDC_SOURCE_VERSION,
+ drm_scdc_writeb(hdmi->ddc, SCDC_SOURCE_VERSION,
min_t(u8, bytes, SCDC_MIN_SOURCE_VERSION));
/* Enabled Scrambling in the Sink */
- drm_scdc_set_scrambling(&hdmi->i2c->adap, 1);
+ drm_scdc_set_scrambling(hdmi->ddc, 1);
/*
* To activate the scrambler feature, you must ensure
@@ -1680,7 +1925,7 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
hdmi_writeb(hdmi, 0, HDMI_FC_SCRAMBLER_CTRL);
hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ,
HDMI_MC_SWRSTZ);
- drm_scdc_set_scrambling(&hdmi->i2c->adap, 0);
+ drm_scdc_set_scrambling(hdmi->ddc, 0);
}
}
@@ -1774,6 +2019,8 @@ static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi)
* iteration for others.
* The Amlogic Meson GX SoCs (v2.01a) have been identified as needing
* the workaround with a single iteration.
+ * The Rockchip RK3288 SoC (v2.00a) and RK3328/RK3399 SoCs (v2.11a) have
+ * been identified as needing the workaround with a single iteration.
*/
switch (hdmi->version) {
@@ -1782,7 +2029,9 @@ static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi)
break;
case 0x131a:
case 0x132a:
+ case 0x200a:
case 0x201a:
+ case 0x211a:
case 0x212a:
count = 1;
break;
@@ -1867,7 +2116,7 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
/* HDMI Initialization Step E - Configure audio */
hdmi_clk_regenerator_update_pixel_clock(hdmi);
- hdmi_enable_audio_clk(hdmi, true);
+ hdmi_enable_audio_clk(hdmi, hdmi->audio_enable);
}
/* not for DVI mode */
@@ -1877,6 +2126,7 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
/* HDMI Initialization Step F - Configure AVI InfoFrame */
hdmi_config_AVI(hdmi, mode);
hdmi_config_vendor_specific_infoframe(hdmi, mode);
+ hdmi_config_drm_infoframe(hdmi);
} else {
dev_dbg(hdmi->dev, "%s DVI mode\n", __func__);
}
@@ -1891,16 +2141,6 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
return 0;
}
-static void dw_hdmi_setup_i2c(struct dw_hdmi *hdmi)
-{
- hdmi_writeb(hdmi, HDMI_PHY_I2CM_INT_ADDR_DONE_POL,
- HDMI_PHY_I2CM_INT_ADDR);
-
- hdmi_writeb(hdmi, HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL |
- HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL,
- HDMI_PHY_I2CM_CTLINT_ADDR);
-}
-
static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi)
{
u8 ih_mute;
@@ -2015,6 +2255,7 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
connector);
+ enum drm_connector_status result;
mutex_lock(&hdmi->mutex);
hdmi->force = DRM_FORCE_UNSPECIFIED;
@@ -2022,7 +2263,18 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
dw_hdmi_update_phy_mask(hdmi);
mutex_unlock(&hdmi->mutex);
- return hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data);
+ result = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data);
+
+ mutex_lock(&hdmi->mutex);
+ if (result != hdmi->last_connector_result) {
+ dev_dbg(hdmi->dev, "read_hpd result: %d", result);
+ handle_plugged_change(hdmi,
+ result == connector_status_connected);
+ hdmi->last_connector_result = result;
+ }
+ mutex_unlock(&hdmi->mutex);
+
+ return result;
}
static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
@@ -2053,6 +2305,45 @@ static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
return ret;
}
+static bool hdr_metadata_equal(const struct drm_connector_state *old_state,
+ const struct drm_connector_state *new_state)
+{
+ struct drm_property_blob *old_blob = old_state->hdr_output_metadata;
+ struct drm_property_blob *new_blob = new_state->hdr_output_metadata;
+
+ if (!old_blob || !new_blob)
+ return old_blob == new_blob;
+
+ if (old_blob->length != new_blob->length)
+ return false;
+
+ return !memcmp(old_blob->data, new_blob->data, old_blob->length);
+}
+
+static int dw_hdmi_connector_atomic_check(struct drm_connector *connector,
+ struct drm_atomic_state *state)
+{
+ struct drm_connector_state *old_state =
+ drm_atomic_get_old_connector_state(state, connector);
+ struct drm_connector_state *new_state =
+ drm_atomic_get_new_connector_state(state, connector);
+ struct drm_crtc *crtc = new_state->crtc;
+ struct drm_crtc_state *crtc_state;
+
+ if (!crtc)
+ return 0;
+
+ if (!hdr_metadata_equal(old_state, new_state)) {
+ crtc_state = drm_atomic_get_crtc_state(state, crtc);
+ if (IS_ERR(crtc_state))
+ return PTR_ERR(crtc_state);
+
+ crtc_state->mode_changed = true;
+ }
+
+ return 0;
+}
+
static void dw_hdmi_connector_force(struct drm_connector *connector)
{
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
@@ -2077,6 +2368,7 @@ static const struct drm_connector_funcs dw_hdmi_connector_funcs = {
static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = {
.get_modes = dw_hdmi_connector_get_modes,
+ .atomic_check = dw_hdmi_connector_atomic_check,
};
static int dw_hdmi_bridge_attach(struct drm_bridge *bridge)
@@ -2084,20 +2376,48 @@ static int dw_hdmi_bridge_attach(struct drm_bridge *bridge)
struct dw_hdmi *hdmi = bridge->driver_private;
struct drm_encoder *encoder = bridge->encoder;
struct drm_connector *connector = &hdmi->connector;
+ struct cec_connector_info conn_info;
+ struct cec_notifier *notifier;
connector->interlace_allowed = 1;
connector->polled = DRM_CONNECTOR_POLL_HPD;
drm_connector_helper_add(connector, &dw_hdmi_connector_helper_funcs);
- drm_connector_init(bridge->dev, connector, &dw_hdmi_connector_funcs,
- DRM_MODE_CONNECTOR_HDMIA);
+ drm_connector_init_with_ddc(bridge->dev, connector,
+ &dw_hdmi_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA,
+ hdmi->ddc);
+
+ if (hdmi->version >= 0x200a && hdmi->plat_data->use_drm_infoframe)
+ drm_object_attach_property(&connector->base,
+ connector->dev->mode_config.hdr_output_metadata_property, 0);
drm_connector_attach_encoder(connector, encoder);
+ cec_fill_conn_info_from_drm(&conn_info, connector);
+
+ notifier = cec_notifier_conn_register(hdmi->dev, NULL, &conn_info);
+ if (!notifier)
+ return -ENOMEM;
+
+ mutex_lock(&hdmi->cec_notifier_mutex);
+ hdmi->cec_notifier = notifier;
+ mutex_unlock(&hdmi->cec_notifier_mutex);
+
return 0;
}
+static void dw_hdmi_bridge_detach(struct drm_bridge *bridge)
+{
+ struct dw_hdmi *hdmi = bridge->driver_private;
+
+ mutex_lock(&hdmi->cec_notifier_mutex);
+ cec_notifier_conn_unregister(hdmi->cec_notifier);
+ hdmi->cec_notifier = NULL;
+ mutex_unlock(&hdmi->cec_notifier_mutex);
+}
+
static enum drm_mode_status
dw_hdmi_bridge_mode_valid(struct drm_bridge *bridge,
const struct drm_display_mode *mode)
@@ -2154,6 +2474,7 @@ static void dw_hdmi_bridge_enable(struct drm_bridge *bridge)
static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
.attach = dw_hdmi_bridge_attach,
+ .detach = dw_hdmi_bridge_detach,
.enable = dw_hdmi_bridge_enable,
.disable = dw_hdmi_bridge_disable,
.mode_set = dw_hdmi_bridge_mode_set,
@@ -2261,9 +2582,11 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
phy_stat & HDMI_PHY_HPD,
phy_stat & HDMI_PHY_RX_SENSE);
- if ((phy_stat & (HDMI_PHY_RX_SENSE | HDMI_PHY_HPD)) == 0)
- cec_notifier_set_phys_addr(hdmi->cec_notifier,
- CEC_PHYS_ADDR_INVALID);
+ if ((phy_stat & (HDMI_PHY_RX_SENSE | HDMI_PHY_HPD)) == 0) {
+ mutex_lock(&hdmi->cec_notifier_mutex);
+ cec_notifier_phys_addr_invalidate(hdmi->cec_notifier);
+ mutex_unlock(&hdmi->cec_notifier_mutex);
+ }
}
if (intr_stat & HDMI_IH_PHY_STAT0_HPD) {
@@ -2401,6 +2724,21 @@ static const struct regmap_config hdmi_regmap_32bit_config = {
.max_register = HDMI_I2CM_FS_SCL_LCNT_0_ADDR << 2,
};
+static void dw_hdmi_init_hw(struct dw_hdmi *hdmi)
+{
+ initialize_hdmi_ih_mutes(hdmi);
+
+ /*
+ * Reset HDMI DDC I2C master controller and mute I2CM interrupts.
+ * Even if we are using a separate i2c adapter doing this doesn't
+ * hurt.
+ */
+ dw_hdmi_i2c_init(hdmi);
+
+ if (hdmi->phy.ops->setup_hpd)
+ hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data);
+}
+
static struct dw_hdmi *
__dw_hdmi_probe(struct platform_device *pdev,
const struct dw_hdmi_plat_data *plat_data)
@@ -2431,9 +2769,11 @@ __dw_hdmi_probe(struct platform_device *pdev,
hdmi->rxsense = true;
hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE);
hdmi->mc_clkdis = 0x7f;
+ hdmi->last_connector_result = connector_status_disconnected;
mutex_init(&hdmi->mutex);
mutex_init(&hdmi->audio_mutex);
+ mutex_init(&hdmi->cec_notifier_mutex);
spin_lock_init(&hdmi->audio_lock);
ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0);
@@ -2552,7 +2892,7 @@ __dw_hdmi_probe(struct platform_device *pdev,
prod_id1 & HDMI_PRODUCT_ID1_HDCP ? "with" : "without",
hdmi->phy.name);
- initialize_hdmi_ih_mutes(hdmi);
+ dw_hdmi_init_hw(hdmi);
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
@@ -2566,12 +2906,6 @@ __dw_hdmi_probe(struct platform_device *pdev,
if (ret)
goto err_iahb;
- hdmi->cec_notifier = cec_notifier_get(dev);
- if (!hdmi->cec_notifier) {
- ret = -ENOMEM;
- goto err_iahb;
- }
-
/*
* To prevent overflows in HDMI_IH_FC_STAT2, set the clk regenerator
* N and cts values before enabling phy
@@ -2580,6 +2914,24 @@ __dw_hdmi_probe(struct platform_device *pdev,
/* If DDC bus is not specified, try to register HDMI I2C bus */
if (!hdmi->ddc) {
+ /* Look for (optional) stuff related to unwedging */
+ hdmi->pinctrl = devm_pinctrl_get(dev);
+ if (!IS_ERR(hdmi->pinctrl)) {
+ hdmi->unwedge_state =
+ pinctrl_lookup_state(hdmi->pinctrl, "unwedge");
+ hdmi->default_state =
+ pinctrl_lookup_state(hdmi->pinctrl, "default");
+
+ if (IS_ERR(hdmi->default_state) ||
+ IS_ERR(hdmi->unwedge_state)) {
+ if (!IS_ERR(hdmi->unwedge_state))
+ dev_warn(dev,
+ "Unwedge requires default pinctrl\n");
+ hdmi->default_state = NULL;
+ hdmi->unwedge_state = NULL;
+ }
+ }
+
hdmi->ddc = dw_hdmi_i2c_adapter(hdmi);
if (IS_ERR(hdmi->ddc))
hdmi->ddc = NULL;
@@ -2591,10 +2943,6 @@ __dw_hdmi_probe(struct platform_device *pdev,
hdmi->bridge.of_node = pdev->dev.of_node;
#endif
- dw_hdmi_setup_i2c(hdmi);
- if (hdmi->phy.ops->setup_hpd)
- hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data);
-
memset(&pdevinfo, 0, sizeof(pdevinfo));
pdevinfo.parent = dev;
pdevinfo.id = PLATFORM_DEVID_AUTO;
@@ -2622,6 +2970,7 @@ __dw_hdmi_probe(struct platform_device *pdev,
struct dw_hdmi_i2s_audio_data audio;
audio.hdmi = hdmi;
+ audio.eld = hdmi->connector.eld;
audio.write = hdmi_writeb;
audio.read = hdmi_readb;
hdmi->enable_audio = dw_hdmi_i2s_audio_enable;
@@ -2647,10 +2996,6 @@ __dw_hdmi_probe(struct platform_device *pdev,
hdmi->cec = platform_device_register_full(&pdevinfo);
}
- /* Reset HDMI DDC I2C master controller and mute I2CM interrupts */
- if (hdmi->i2c)
- dw_hdmi_i2c_init(hdmi);
-
return hdmi;
err_iahb:
@@ -2659,9 +3004,6 @@ err_iahb:
hdmi->ddc = NULL;
}
- if (hdmi->cec_notifier)
- cec_notifier_put(hdmi->cec_notifier);
-
clk_disable_unprepare(hdmi->iahb_clk);
if (hdmi->cec_clk)
clk_disable_unprepare(hdmi->cec_clk);
@@ -2683,9 +3025,6 @@ static void __dw_hdmi_remove(struct dw_hdmi *hdmi)
/* Disable all interrupts */
hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
- if (hdmi->cec_notifier)
- cec_notifier_put(hdmi->cec_notifier);
-
clk_disable_unprepare(hdmi->iahb_clk);
clk_disable_unprepare(hdmi->isfr_clk);
if (hdmi->cec_clk)
@@ -2754,6 +3093,12 @@ void dw_hdmi_unbind(struct dw_hdmi *hdmi)
}
EXPORT_SYMBOL_GPL(dw_hdmi_unbind);
+void dw_hdmi_resume(struct dw_hdmi *hdmi)
+{
+ dw_hdmi_init_hw(hdmi);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_resume);
+
MODULE_AUTHOR("Sascha Hauer <[email protected]>");
MODULE_AUTHOR("Andy Yan <[email protected]>");
MODULE_AUTHOR("Yakir Yang <[email protected]>");
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h
index 3f3c616eba97..1999db05bc3b 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h
@@ -1,10 +1,6 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2011 Freescale Semiconductor, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
*/
#ifndef __DW_HDMI_H__
@@ -162,6 +158,8 @@
#define HDMI_FC_SPDDEVICEINF 0x1062
#define HDMI_FC_AUDSCONF 0x1063
#define HDMI_FC_AUDSSTAT 0x1064
+#define HDMI_FC_AUDSCHNLS7 0x106e
+#define HDMI_FC_AUDSCHNLS8 0x106f
#define HDMI_FC_DATACH0FILL 0x1070
#define HDMI_FC_DATACH1FILL 0x1071
#define HDMI_FC_DATACH2FILL 0x1072
@@ -256,6 +254,7 @@
#define HDMI_FC_POL2 0x10DB
#define HDMI_FC_PRCONF 0x10E0
#define HDMI_FC_SCRAMBLER_CTRL 0x10E1
+#define HDMI_FC_PACKET_TX_EN 0x10E3
#define HDMI_FC_GMD_STAT 0x1100
#define HDMI_FC_GMD_EN 0x1101
@@ -291,6 +290,37 @@
#define HDMI_FC_GMD_PB26 0x111F
#define HDMI_FC_GMD_PB27 0x1120
+#define HDMI_FC_DRM_UP 0x1167
+#define HDMI_FC_DRM_HB0 0x1168
+#define HDMI_FC_DRM_HB1 0x1169
+#define HDMI_FC_DRM_PB0 0x116A
+#define HDMI_FC_DRM_PB1 0x116B
+#define HDMI_FC_DRM_PB2 0x116C
+#define HDMI_FC_DRM_PB3 0x116D
+#define HDMI_FC_DRM_PB4 0x116E
+#define HDMI_FC_DRM_PB5 0x116F
+#define HDMI_FC_DRM_PB6 0x1170
+#define HDMI_FC_DRM_PB7 0x1171
+#define HDMI_FC_DRM_PB8 0x1172
+#define HDMI_FC_DRM_PB9 0x1173
+#define HDMI_FC_DRM_PB10 0x1174
+#define HDMI_FC_DRM_PB11 0x1175
+#define HDMI_FC_DRM_PB12 0x1176
+#define HDMI_FC_DRM_PB13 0x1177
+#define HDMI_FC_DRM_PB14 0x1178
+#define HDMI_FC_DRM_PB15 0x1179
+#define HDMI_FC_DRM_PB16 0x117A
+#define HDMI_FC_DRM_PB17 0x117B
+#define HDMI_FC_DRM_PB18 0x117C
+#define HDMI_FC_DRM_PB19 0x117D
+#define HDMI_FC_DRM_PB20 0x117E
+#define HDMI_FC_DRM_PB21 0x117F
+#define HDMI_FC_DRM_PB22 0x1180
+#define HDMI_FC_DRM_PB23 0x1181
+#define HDMI_FC_DRM_PB24 0x1182
+#define HDMI_FC_DRM_PB25 0x1183
+#define HDMI_FC_DRM_PB26 0x1184
+
#define HDMI_FC_DBGFORCE 0x1200
#define HDMI_FC_DBGAUD0CH0 0x1201
#define HDMI_FC_DBGAUD1CH0 0x1202
@@ -746,6 +776,11 @@ enum {
HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_MASK = 0x0F,
HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_OFFSET = 0,
+/* FC_PACKET_TX_EN field values */
+ HDMI_FC_PACKET_TX_EN_DRM_MASK = 0x80,
+ HDMI_FC_PACKET_TX_EN_DRM_ENABLE = 0x80,
+ HDMI_FC_PACKET_TX_EN_DRM_DISABLE = 0x00,
+
/* FC_AVICONF0-FC_AVICONF3 field values */
HDMI_FC_AVICONF0_PIX_FMT_MASK = 0x03,
HDMI_FC_AVICONF0_PIX_FMT_RGB = 0x00,
@@ -869,12 +904,18 @@ enum {
/* AUD_CONF0 field values */
HDMI_AUD_CONF0_SW_RESET = 0x80,
- HDMI_AUD_CONF0_I2S_ALL_ENABLE = 0x2F,
+ HDMI_AUD_CONF0_I2S_SELECT = 0x20,
+ HDMI_AUD_CONF0_I2S_EN3 = 0x08,
+ HDMI_AUD_CONF0_I2S_EN2 = 0x04,
+ HDMI_AUD_CONF0_I2S_EN1 = 0x02,
+ HDMI_AUD_CONF0_I2S_EN0 = 0x01,
/* AUD_CONF1 field values */
HDMI_AUD_CONF1_MODE_I2S = 0x00,
- HDMI_AUD_CONF1_MODE_RIGHT_J = 0x02,
- HDMI_AUD_CONF1_MODE_LEFT_J = 0x04,
+ HDMI_AUD_CONF1_MODE_RIGHT_J = 0x20,
+ HDMI_AUD_CONF1_MODE_LEFT_J = 0x40,
+ HDMI_AUD_CONF1_MODE_BURST_1 = 0x60,
+ HDMI_AUD_CONF1_MODE_BURST_2 = 0x80,
HDMI_AUD_CONF1_WIDTH_16 = 0x10,
HDMI_AUD_CONF1_WIDTH_24 = 0x18,
@@ -942,6 +983,7 @@ enum {
HDMI_MC_CLKDIS_PIXELCLK_DISABLE = 0x1,
/* MC_SWRSTZ field values */
+ HDMI_MC_SWRSTZ_I2SSWRST_REQ = 0x08,
HDMI_MC_SWRSTZ_TMDSSWRST_REQ = 0x02,
/* MC_FLOWCTRL field values */
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
index e915ae8c9a92..b6e793bb653c 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
@@ -10,20 +10,24 @@
#include <linux/clk.h>
#include <linux/component.h>
+#include <linux/debugfs.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
-#include <drm/drmP.h>
+
+#include <video/mipi_display.h>
+
+#include <drm/bridge/dw_mipi_dsi.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_crtc.h>
#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
#include <drm/drm_of.h>
+#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
-#include <drm/bridge/dw_mipi_dsi.h>
-#include <video/mipi_display.h>
#define HWVER_131 0x31333100 /* IP version 1.31 */
@@ -86,6 +90,8 @@
#define VID_MODE_TYPE_NON_BURST_SYNC_EVENTS 0x1
#define VID_MODE_TYPE_BURST 0x2
#define VID_MODE_TYPE_MASK 0x3
+#define VID_MODE_VPG_ENABLE BIT(16)
+#define VID_MODE_VPG_HORIZONTAL BIT(24)
#define DSI_VID_PKT_SIZE 0x3c
#define VID_PKT_SIZE(p) ((p) & 0x3fff)
@@ -230,6 +236,13 @@ struct dw_mipi_dsi {
u32 format;
unsigned long mode_flags;
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfs;
+
+ bool vpg;
+ bool vpg_horizontal;
+#endif /* CONFIG_DEBUG_FS */
+
struct dw_mipi_dsi *master; /* dual-dsi master ptr */
struct dw_mipi_dsi *slave; /* dual-dsi slave ptr */
@@ -303,7 +316,8 @@ static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host,
return ret;
if (panel) {
- bridge = drm_panel_bridge_add(panel, DRM_MODE_CONNECTOR_DSI);
+ bridge = drm_panel_bridge_add_typed(panel,
+ DRM_MODE_CONNECTOR_DSI);
if (IS_ERR(bridge))
return PTR_ERR(bridge);
}
@@ -515,6 +529,13 @@ static void dw_mipi_dsi_video_mode_config(struct dw_mipi_dsi *dsi)
else
val |= VID_MODE_TYPE_NON_BURST_SYNC_EVENTS;
+#ifdef CONFIG_DEBUG_FS
+ if (dsi->vpg) {
+ val |= VID_MODE_VPG_ENABLE;
+ val |= dsi->vpg_horizontal ? VID_MODE_VPG_HORIZONTAL : 0;
+ }
+#endif /* CONFIG_DEBUG_FS */
+
dsi_write(dsi, DSI_VID_MODE_CFG, val);
}
@@ -775,6 +796,10 @@ static void dw_mipi_dsi_clear_err(struct dw_mipi_dsi *dsi)
static void dw_mipi_dsi_bridge_post_disable(struct drm_bridge *bridge)
{
struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge);
+ const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops;
+
+ if (phy_ops->power_off)
+ phy_ops->power_off(dsi->plat_data->priv_data);
/*
* Switch to command mode before panel-bridge post_disable &
@@ -874,11 +899,15 @@ static void dw_mipi_dsi_bridge_mode_set(struct drm_bridge *bridge,
static void dw_mipi_dsi_bridge_enable(struct drm_bridge *bridge)
{
struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge);
+ const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops;
/* Switch to video mode for panel-bridge enable & panel enable */
dw_mipi_dsi_set_mode(dsi, MIPI_DSI_MODE_VIDEO);
if (dsi->slave)
dw_mipi_dsi_set_mode(dsi->slave, MIPI_DSI_MODE_VIDEO);
+
+ if (phy_ops->power_on)
+ phy_ops->power_on(dsi->plat_data->priv_data);
}
static enum drm_mode_status
@@ -919,6 +948,33 @@ static const struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = {
.attach = dw_mipi_dsi_bridge_attach,
};
+#ifdef CONFIG_DEBUG_FS
+
+static void dw_mipi_dsi_debugfs_init(struct dw_mipi_dsi *dsi)
+{
+ dsi->debugfs = debugfs_create_dir(dev_name(dsi->dev), NULL);
+ if (IS_ERR(dsi->debugfs)) {
+ dev_err(dsi->dev, "failed to create debugfs root\n");
+ return;
+ }
+
+ debugfs_create_bool("vpg", 0660, dsi->debugfs, &dsi->vpg);
+ debugfs_create_bool("vpg_horizontal", 0660, dsi->debugfs,
+ &dsi->vpg_horizontal);
+}
+
+static void dw_mipi_dsi_debugfs_remove(struct dw_mipi_dsi *dsi)
+{
+ debugfs_remove_recursive(dsi->debugfs);
+}
+
+#else
+
+static void dw_mipi_dsi_debugfs_init(struct dw_mipi_dsi *dsi) { }
+static void dw_mipi_dsi_debugfs_remove(struct dw_mipi_dsi *dsi) { }
+
+#endif /* CONFIG_DEBUG_FS */
+
static struct dw_mipi_dsi *
__dw_mipi_dsi_probe(struct platform_device *pdev,
const struct dw_mipi_dsi_plat_data *plat_data)
@@ -926,7 +982,6 @@ __dw_mipi_dsi_probe(struct platform_device *pdev,
struct device *dev = &pdev->dev;
struct reset_control *apb_rst;
struct dw_mipi_dsi *dsi;
- struct resource *res;
int ret;
dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
@@ -942,11 +997,7 @@ __dw_mipi_dsi_probe(struct platform_device *pdev,
}
if (!plat_data->base) {
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res)
- return ERR_PTR(-ENODEV);
-
- dsi->base = devm_ioremap_resource(dev, res);
+ dsi->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(dsi->base))
return ERR_PTR(-ENODEV);
@@ -989,6 +1040,7 @@ __dw_mipi_dsi_probe(struct platform_device *pdev,
clk_disable_unprepare(dsi->pclk);
}
+ dw_mipi_dsi_debugfs_init(dsi);
pm_runtime_enable(dev);
dsi->dsi_host.ops = &dw_mipi_dsi_host_ops;
@@ -996,6 +1048,7 @@ __dw_mipi_dsi_probe(struct platform_device *pdev,
ret = mipi_dsi_host_register(&dsi->dsi_host);
if (ret) {
dev_err(dev, "Failed to register MIPI host: %d\n", ret);
+ dw_mipi_dsi_debugfs_remove(dsi);
return ERR_PTR(ret);
}
@@ -1013,6 +1066,7 @@ static void __dw_mipi_dsi_remove(struct dw_mipi_dsi *dsi)
mipi_dsi_host_unregister(&dsi->dsi_host);
pm_runtime_disable(dsi->dev);
+ dw_mipi_dsi_debugfs_remove(dsi);
}
void dw_mipi_dsi_set_slave(struct dw_mipi_dsi *dsi, struct dw_mipi_dsi *slave)