Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

+22 -7
+22 -7
drivers/gpu/drm/bridge/dw_hdmi.c
··· 125 125 bool sink_is_hdmi; 126 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 131 spinlock_t audio_lock; 129 132 struct mutex audio_mutex; 130 133 unsigned int sample_rate; ··· 1378 1375 { 1379 1376 struct dw_hdmi *hdmi = bridge->driver_private; 1380 1377 1378 + mutex_lock(&hdmi->mutex); 1379 + 1381 1380 /* Store the display mode for plugin/DKMS poweron events */ 1382 1381 memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode)); 1382 + 1383 + mutex_unlock(&hdmi->mutex); 1383 1384 } 1384 1385 1385 1386 static bool dw_hdmi_bridge_mode_fixup(struct drm_bridge *bridge, ··· 1397 1390 { 1398 1391 struct dw_hdmi *hdmi = bridge->driver_private; 1399 1392 1393 + mutex_lock(&hdmi->mutex); 1394 + hdmi->disabled = true; 1400 1395 dw_hdmi_poweroff(hdmi); 1396 + mutex_unlock(&hdmi->mutex); 1401 1397 } 1402 1398 1403 1399 static void dw_hdmi_bridge_enable(struct drm_bridge *bridge) 1404 1400 { 1405 1401 struct dw_hdmi *hdmi = bridge->driver_private; 1406 1402 1403 + mutex_lock(&hdmi->mutex); 1407 1404 dw_hdmi_poweron(hdmi); 1405 + hdmi->disabled = false; 1406 + mutex_unlock(&hdmi->mutex); 1408 1407 } 1409 1408 1410 1409 static void dw_hdmi_bridge_nop(struct drm_bridge *bridge) ··· 1533 1520 phy_int_pol = hdmi_readb(hdmi, HDMI_PHY_POL0); 1534 1521 1535 1522 if (intr_stat & HDMI_IH_PHY_STAT0_HPD) { 1523 + hdmi_modb(hdmi, ~phy_int_pol, HDMI_PHY_HPD, HDMI_PHY_POL0); 1524 + mutex_lock(&hdmi->mutex); 1536 1525 if (phy_int_pol & HDMI_PHY_HPD) { 1537 1526 dev_dbg(hdmi->dev, "EVENT=plugin\n"); 1538 1527 1539 - hdmi_modb(hdmi, 0, HDMI_PHY_HPD, HDMI_PHY_POL0); 1540 - 1541 - dw_hdmi_poweron(hdmi); 1528 + if (!hdmi->disabled) 1529 + dw_hdmi_poweron(hdmi); 1542 1530 } else { 1543 1531 dev_dbg(hdmi->dev, "EVENT=plugout\n"); 1544 1532 1545 - hdmi_modb(hdmi, HDMI_PHY_HPD, HDMI_PHY_HPD, 1546 - HDMI_PHY_POL0); 1547 - 1548 - dw_hdmi_poweroff(hdmi); 1533 + if (!hdmi->disabled) 1534 + dw_hdmi_poweroff(hdmi); 1549 1535 } 1536 + mutex_unlock(&hdmi->mutex); 1550 1537 drm_helper_hpd_irq_event(hdmi->bridge->dev); 1551 1538 } 1552 1539 ··· 1614 1601 hdmi->sample_rate = 48000; 1615 1602 hdmi->ratio = 100; 1616 1603 hdmi->encoder = encoder; 1604 + hdmi->disabled = true; 1617 1605 1606 + mutex_init(&hdmi->mutex); 1618 1607 mutex_init(&hdmi->audio_mutex); 1619 1608 spin_lock_init(&hdmi->audio_lock); 1620 1609