diff mbox

[1/2] drm: bridge/dw_hdmi: Cache edid data

Message ID 1438955961-27232-2-git-send-email-s.hauer@pengutronix.de (mailing list archive)
State New, archived
Headers show

Commit Message

Sascha Hauer Aug. 7, 2015, 1:59 p.m. UTC
Instead of rereading the edid data each time userspace asks for them
read them once and cache them in the previously unused edid field in
struct dw_hdmi. This makes the code a little bit more efficient.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 drivers/gpu/drm/bridge/dw_hdmi.c | 41 +++++++++++++++++++++++++---------------
 1 file changed, 26 insertions(+), 15 deletions(-)

Comments

Russell King - ARM Linux Aug. 7, 2015, 2:13 p.m. UTC | #1
On Fri, Aug 07, 2015 at 03:59:20PM +0200, Sascha Hauer wrote:
> Instead of rereading the edid data each time userspace asks for them
> read them once and cache them in the previously unused edid field in
> struct dw_hdmi. This makes the code a little bit more efficient.

How has this been tested?

Has it been tested with an AV amplifier in the path to the display device?
If not, it really needs to be tested, because such devices modify the EDID
data, or subsitute their own, and alter the EDID data depending on their
needs.  When they do, they lower and re-assert the HPD signal, possibly
for as little as 100ms - defined by the HDMI spec.

For example, an AV amplifier in standby can provide the displays EDID in
the first page of EDID, along with the displays extended EDID in the
second page.  When the AV amplifier is powered up, it can provide a
different second page of EDID information detailing the audio formats
that it can accept, and it will lower and re-assert the HPD signal to
cause the connected devices to read the updated EDID information.  Same
thing can happen when switching to standby mode too.
Lucas Stach Aug. 7, 2015, 2:20 p.m. UTC | #2
Am Freitag, den 07.08.2015, 15:13 +0100 schrieb Russell King - ARM
Linux:
> On Fri, Aug 07, 2015 at 03:59:20PM +0200, Sascha Hauer wrote:
> > Instead of rereading the edid data each time userspace asks for them
> > read them once and cache them in the previously unused edid field in
> > struct dw_hdmi. This makes the code a little bit more efficient.
> 
> How has this been tested?
> 
> Has it been tested with an AV amplifier in the path to the display device?
> If not, it really needs to be tested, because such devices modify the EDID
> data, or subsitute their own, and alter the EDID data depending on their
> needs.  When they do, they lower and re-assert the HPD signal, possibly
> for as little as 100ms - defined by the HDMI spec.
> 
This has not been tested with an AV amp, but if the amp behaves the way
you describe it this should be fine.

If we get an HPD event we inform the DRM core, which will then call our
connector detect function, triggering a re-read of the EDID data.

Regards,
Lucas

> For example, an AV amplifier in standby can provide the displays EDID in
> the first page of EDID, along with the displays extended EDID in the
> second page.  When the AV amplifier is powered up, it can provide a
> different second page of EDID information detailing the audio formats
> that it can accept, and it will lower and re-assert the HPD signal to
> cause the connected devices to read the updated EDID information.  Same
> thing can happen when switching to standby mode too.
>
Russell King - ARM Linux Aug. 7, 2015, 10:40 p.m. UTC | #3
On Fri, Aug 07, 2015 at 04:20:47PM +0200, Lucas Stach wrote:
> Am Freitag, den 07.08.2015, 15:13 +0100 schrieb Russell King - ARM
> Linux:
> > On Fri, Aug 07, 2015 at 03:59:20PM +0200, Sascha Hauer wrote:
> > > Instead of rereading the edid data each time userspace asks for them
> > > read them once and cache them in the previously unused edid field in
> > > struct dw_hdmi. This makes the code a little bit more efficient.
> > 
> > How has this been tested?
> > 
> > Has it been tested with an AV amplifier in the path to the display device?
> > If not, it really needs to be tested, because such devices modify the EDID
> > data, or subsitute their own, and alter the EDID data depending on their
> > needs.  When they do, they lower and re-assert the HPD signal, possibly
> > for as little as 100ms - defined by the HDMI spec.
> > 
> This has not been tested with an AV amp, but if the amp behaves the way
> you describe it this should be fine.
> 
> If we get an HPD event we inform the DRM core, which will then call our
> connector detect function, triggering a re-read of the EDID data.

I'm still nervous about this.

Consider:
- HPD raised
- We start to read the EDID
- HPD dropped, EDID updates, HPD raised
- EDID checksum fails

We're now in the situation where we don't retry reading the EDID, because
we've effectively cached the failure.

I don't think we should be caching the failure - I think what we want is:

dw_hdmi_connector_detect(struct drm_connector *connector)
{
	if (!(hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD))
		return connector_status_disconnected;

	if (hdmi->ddc)
		read_edid();

	return connector_status_connected;
}

dw_hdmi_connector_get_modes(struct drm_connector *connector)
{
	if (!hdmi->edid)
		read_edid();

	if (hdmi->edid) {
		ret = drm_add_edid_modes(connector, hdmi->edid);
		...
	} else {
		...
	}

	return ret;
}

so we at least have the ability to retry a failed EDID without having to
pull the connector and try plugging the sink back in.

However, if we do this, then we might as well just modify
dw_hdmi_connector_get_modes(), and arrange for a HPD de-assertion to
NULL and free hdmi->edid so the next get_modes() call triggers a re-read.

Alternatively, if you look at the complexity in
drm_helper_probe_single_connector_modes_merge_bits(), even fixing this
would still leave a significant amount of work being done on every
first call to get_connector, especially so if we're loading override
EDID from the firmware files.  Maybe rather than fixing this in every
driver, maybe it ought to be handled further up the stack, possibly
with an opt-in flag?  We'd probably need to get smarter at reporting
a disconnect to close a race there though (where we don't get around
to calling ->detect fast enough after we notice HPD has been deasserted,
but if it's taking longer than 100ms to get there, we're probably fscked
anyway.)
Russell King - ARM Linux Aug. 8, 2015, 1:35 p.m. UTC | #4
On Fri, Aug 07, 2015 at 11:40:28PM +0100, Russell King - ARM Linux wrote:
> However, if we do this, then we might as well just modify
> dw_hdmi_connector_get_modes(), and arrange for a HPD de-assertion to
> NULL and free hdmi->edid so the next get_modes() call triggers a re-read.

Testing with the AV amp seems to be working okay.

For reference, he's the RXSENSE / HPD activity, with the AV amp
configured for HDMI passthrough in standby mode.  This output is from
the IRQ thread rather than the hardware IRQ function, so timings may not
be accurate.

unplug:
[ 9128.933266] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX3210,---)
[ 9128.935883] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX----,---)

plugin:
[ 9167.072748] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX3210,HPD)
[ 9167.121773] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX3210,---)
[ 9167.122323] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX----,---)
[ 9168.320609] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX3210,HPD)

av poweron:
[ 9185.696008] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX3210,---)
[ 9185.696376] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX----,---)
[ 9187.286580] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX3210,HPD)

tv poweron:
[ 9207.701588] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX3210,---)
[ 9207.701953] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX----,---)
[ 9210.638573] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX3210,HPD)

av standby:
[ 9230.588697] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX3210,---)
[ 9230.589062] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX----,---)
[ 9231.788225] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX3210,HPD)

tv standby:
(nothing)

... and a bit later, when the AV amp switches into a lower standby mode:
[ 9290.700523] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX----,HPD)
[ 9290.944270] dwhdmi-imx 120000.hdmi: dw_hdmi_irq(RX----,---)

The interesting one is the plug-in event, where we see HPD dropped after
about 50ms.

I've previously had the opportunity to test the older code with another
manufacturer's AV amp, which I no longer have access to.  My notes for
that setup are as below - I didn't note down the kernel logs in this
case...

TV (no AV amp):
imxboot: initial(+rxsense,+hpd) ... -rxsense
standby->on: -hpd, +rxsense, +hpd, -hpd, -rxsense, (+rxsense +hpd)
on->unplug: -hpd, -rxsense
unplug->plug: (+rxsense +hpd)
plug->standby: no effect
standby->unplug: -hpd, -rxsense
unplug->plug: +rxsense, +hpd

AV (AV + TV in standby mode initially, AV connected to TV):
unplugged: -rxsense,-hpd
unplugged->plugged: +rxsense, +hpd
        EDID reads from AV
AV standby,TV standby->TV on: -hpd, 2s, +hpd
        EDID reads from TV
TV on,AV standby->AV on: -hpd, 1.2s, +hpd
	EDID reads from TV with AV capabilities merged
TV on,AV on->AV standby: -hpd, 1.2s, +hpd

I have a whole bunch of further patches for dw-hdmi, so if you don't
mind, I'll take your two, merge them on the front of my series, and
re-post them.  I think we're getting close to the time where we can
see initial audio support merged for dw-hdmi.
Russell King - ARM Linux Aug. 8, 2015, 3:13 p.m. UTC | #5
On Sat, Aug 08, 2015 at 02:35:40PM +0100, Russell King - ARM Linux wrote:
> I have a whole bunch of further patches for dw-hdmi, so if you don't
> mind, I'll take your two, merge them on the front of my series, and
> re-post them.  I think we're getting close to the time where we can
> see initial audio support merged for dw-hdmi.

Having tried to apply the patches on top of my previous pull request to
David (sent on 15th July), it would be much better if we waited.  David
is away at the moment, apparently due back in a little over a week's time.
Hence, there's no processing of any development pull requests at the
moment, which is why it hasn't shown up in linux-next.

My series is based on 9203dd016a5d ("ALSA: pcm: add IEC958 channel status
helper") which pre-dates the fixes to get_modes() to return non-zero,
which are already in Linus' tree.  I _could_ rebase it, but that's not
good practice once it's been asked to be pulled.

At the moment, my tree does not introduce any conflicts.

Trying to merge your patch will create conflicts: if I merge the fix into
my tree, we end up with duplicate commits.  If I tweak your patch to
apply to my tree as the code used to stand, git will end up with two
conflicting changes.  If I merge mainline into my tree, Linus doesn't
like cross-merges.
diff mbox

Patch

diff --git a/drivers/gpu/drm/bridge/dw_hdmi.c b/drivers/gpu/drm/bridge/dw_hdmi.c
index 816d104..8d9b53c 100644
--- a/drivers/gpu/drm/bridge/dw_hdmi.c
+++ b/drivers/gpu/drm/bridge/dw_hdmi.c
@@ -117,7 +117,7 @@  struct dw_hdmi {
 
 	int vic;
 
-	u8 edid[HDMI_EDID_LEN];
+	struct edid *edid;
 	bool cable_plugin;
 
 	bool phy_enabled;
@@ -1386,28 +1386,39 @@  dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
 	struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
 					     connector);
 
-	return hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD ?
-		connector_status_connected : connector_status_disconnected;
+	if (!(hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD))
+		return connector_status_disconnected;
+
+	if (hdmi->ddc) {
+		if (hdmi->edid) {
+			drm_mode_connector_update_edid_property(connector,
+					NULL);
+			kfree(hdmi->edid);
+		}
+
+		hdmi->edid = drm_get_edid(connector, hdmi->ddc);
+		if (hdmi->edid) {
+			drm_mode_connector_update_edid_property(connector,
+					hdmi->edid);
+
+			dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n",
+					hdmi->edid->width_cm,
+					hdmi->edid->height_cm);
+		}
+	}
+
+	return connector_status_connected;
 }
 
 static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
 {
 	struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
 					     connector);
-	struct edid *edid;
 	int ret = 0;
 
-	if (!hdmi->ddc)
-		return 0;
-
-	edid = drm_get_edid(connector, hdmi->ddc);
-	if (edid) {
-		dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n",
-			edid->width_cm, edid->height_cm);
-
-		drm_mode_connector_update_edid_property(connector, edid);
-		ret = drm_add_edid_modes(connector, edid);
-		kfree(edid);
+	if (hdmi->edid) {
+		ret = drm_add_edid_modes(connector, hdmi->edid);
+		drm_edid_to_eld(connector, hdmi->edid);
 	} else {
 		dev_dbg(hdmi->dev, "failed to get edid\n");
 	}