diff options
author | Russell King <rmk+kernel@arm.linux.org.uk> | 2015-06-05 07:22:46 -0400 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2015-08-18 06:33:58 -0400 |
commit | b872a8e16b3fb17c3fe1e97f4ed0803f4a740aae (patch) | |
tree | ce8d2a4178dc5fc48aa51f24827cd218b5468125 | |
parent | 2fada109cfb7dbfd52f472140d6477a27a1f0d6d (diff) |
drm: bridge/dw_hdmi: fix phy enable/disable handling
The dw_hdmi enable/disable handling is particularly weak in several
regards:
* The hotplug interrupt could call hdmi_poweron() or hdmi_poweroff()
while DRM is setting a mode, which could race with a mode being set.
* Hotplug will always re-enable the phy whenever it detects an active
hotplug signal, even if DRM has disabled the output.
Resolve all of these by introducing a mutex to prevent races, and a
state-tracking bool so we know whether DRM wishes the output to be
enabled. We choose to use our own mutex rather than ->struct_mutex
so that we can still process interrupts in a timely fashion.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
-rw-r--r-- | drivers/gpu/drm/bridge/dw_hdmi.c | 29 |
1 files changed, 22 insertions, 7 deletions
diff --git a/drivers/gpu/drm/bridge/dw_hdmi.c b/drivers/gpu/drm/bridge/dw_hdmi.c index cef31d5cacb3..c5c4553258b6 100644 --- a/drivers/gpu/drm/bridge/dw_hdmi.c +++ b/drivers/gpu/drm/bridge/dw_hdmi.c | |||
@@ -125,6 +125,9 @@ struct dw_hdmi { | |||
125 | bool sink_is_hdmi; | 125 | bool sink_is_hdmi; |
126 | bool sink_has_audio; | 126 | bool sink_has_audio; |
127 | 127 | ||
128 | struct mutex mutex; /* for state below and previous_mode */ | ||
129 | bool disabled; /* DRM has disabled our bridge */ | ||
130 | |||
128 | spinlock_t audio_lock; | 131 | spinlock_t audio_lock; |
129 | struct mutex audio_mutex; | 132 | struct mutex audio_mutex; |
130 | unsigned int sample_rate; | 133 | unsigned int sample_rate; |
@@ -1375,8 +1378,12 @@ static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge, | |||
1375 | { | 1378 | { |
1376 | struct dw_hdmi *hdmi = bridge->driver_private; | 1379 | struct dw_hdmi *hdmi = bridge->driver_private; |
1377 | 1380 | ||
1381 | mutex_lock(&hdmi->mutex); | ||
1382 | |||
1378 | /* Store the display mode for plugin/DKMS poweron events */ | 1383 | /* Store the display mode for plugin/DKMS poweron events */ |
1379 | memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode)); | 1384 | memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode)); |
1385 | |||
1386 | mutex_unlock(&hdmi->mutex); | ||
1380 | } | 1387 | } |
1381 | 1388 | ||
1382 | static bool dw_hdmi_bridge_mode_fixup(struct drm_bridge *bridge, | 1389 | static bool dw_hdmi_bridge_mode_fixup(struct drm_bridge *bridge, |
@@ -1390,14 +1397,20 @@ static void dw_hdmi_bridge_disable(struct drm_bridge *bridge) | |||
1390 | { | 1397 | { |
1391 | struct dw_hdmi *hdmi = bridge->driver_private; | 1398 | struct dw_hdmi *hdmi = bridge->driver_private; |
1392 | 1399 | ||
1400 | mutex_lock(&hdmi->mutex); | ||
1401 | hdmi->disabled = true; | ||
1393 | dw_hdmi_poweroff(hdmi); | 1402 | dw_hdmi_poweroff(hdmi); |
1403 | mutex_unlock(&hdmi->mutex); | ||
1394 | } | 1404 | } |
1395 | 1405 | ||
1396 | static void dw_hdmi_bridge_enable(struct drm_bridge *bridge) | 1406 | static void dw_hdmi_bridge_enable(struct drm_bridge *bridge) |
1397 | { | 1407 | { |
1398 | struct dw_hdmi *hdmi = bridge->driver_private; | 1408 | struct dw_hdmi *hdmi = bridge->driver_private; |
1399 | 1409 | ||
1410 | mutex_lock(&hdmi->mutex); | ||
1400 | dw_hdmi_poweron(hdmi); | 1411 | dw_hdmi_poweron(hdmi); |
1412 | hdmi->disabled = false; | ||
1413 | mutex_unlock(&hdmi->mutex); | ||
1401 | } | 1414 | } |
1402 | 1415 | ||
1403 | static void dw_hdmi_bridge_nop(struct drm_bridge *bridge) | 1416 | static void dw_hdmi_bridge_nop(struct drm_bridge *bridge) |
@@ -1520,20 +1533,20 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) | |||
1520 | phy_int_pol = hdmi_readb(hdmi, HDMI_PHY_POL0); | 1533 | phy_int_pol = hdmi_readb(hdmi, HDMI_PHY_POL0); |
1521 | 1534 | ||
1522 | if (intr_stat & HDMI_IH_PHY_STAT0_HPD) { | 1535 | if (intr_stat & HDMI_IH_PHY_STAT0_HPD) { |
1536 | hdmi_modb(hdmi, ~phy_int_pol, HDMI_PHY_HPD, HDMI_PHY_POL0); | ||
1537 | mutex_lock(&hdmi->mutex); | ||
1523 | if (phy_int_pol & HDMI_PHY_HPD) { | 1538 | if (phy_int_pol & HDMI_PHY_HPD) { |
1524 | dev_dbg(hdmi->dev, "EVENT=plugin\n"); | 1539 | dev_dbg(hdmi->dev, "EVENT=plugin\n"); |
1525 | 1540 | ||
1526 | hdmi_modb(hdmi, 0, HDMI_PHY_HPD, HDMI_PHY_POL0); | 1541 | if (!hdmi->disabled) |
1527 | 1542 | dw_hdmi_poweron(hdmi); | |
1528 | dw_hdmi_poweron(hdmi); | ||
1529 | } else { | 1543 | } else { |
1530 | dev_dbg(hdmi->dev, "EVENT=plugout\n"); | 1544 | dev_dbg(hdmi->dev, "EVENT=plugout\n"); |
1531 | 1545 | ||
1532 | hdmi_modb(hdmi, HDMI_PHY_HPD, HDMI_PHY_HPD, | 1546 | if (!hdmi->disabled) |
1533 | HDMI_PHY_POL0); | 1547 | dw_hdmi_poweroff(hdmi); |
1534 | |||
1535 | dw_hdmi_poweroff(hdmi); | ||
1536 | } | 1548 | } |
1549 | mutex_unlock(&hdmi->mutex); | ||
1537 | drm_helper_hpd_irq_event(hdmi->bridge->dev); | 1550 | drm_helper_hpd_irq_event(hdmi->bridge->dev); |
1538 | } | 1551 | } |
1539 | 1552 | ||
@@ -1601,7 +1614,9 @@ int dw_hdmi_bind(struct device *dev, struct device *master, | |||
1601 | hdmi->sample_rate = 48000; | 1614 | hdmi->sample_rate = 48000; |
1602 | hdmi->ratio = 100; | 1615 | hdmi->ratio = 100; |
1603 | hdmi->encoder = encoder; | 1616 | hdmi->encoder = encoder; |
1617 | hdmi->disabled = true; | ||
1604 | 1618 | ||
1619 | mutex_init(&hdmi->mutex); | ||
1605 | mutex_init(&hdmi->audio_mutex); | 1620 | mutex_init(&hdmi->audio_mutex); |
1606 | spin_lock_init(&hdmi->audio_lock); | 1621 | spin_lock_init(&hdmi->audio_lock); |
1607 | 1622 | ||