Commit 81fb55e5 authored by Maxime Ripard's avatar Maxime Ripard

drm/vc4: hdmi: Add a spinlock to protect register access

The vc4 HDMI driver has multiple path shared between the CEC, ALSA and
KMS frameworks, plus two interrupt handlers (CEC and hotplug) that will
read and modify a number of registers.

Even though not bug has been reported so far, it's definitely unsafe, so
let's just add a spinlock to protect the register access of the HDMI
controller.

Link: https://lore.kernel.org/r/20211025141113.702757-5-maxime@cerno.tech
Fixes: c8b75bca ("drm/vc4: Add KMS support for Raspberry Pi.")
Acked-by: default avatarDaniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: default avatarMaxime Ripard <maxime@cerno.tech>
parent eeb6ab46
This diff is collapsed.
...@@ -178,6 +178,11 @@ struct vc4_hdmi { ...@@ -178,6 +178,11 @@ struct vc4_hdmi {
struct debugfs_regset32 hdmi_regset; struct debugfs_regset32 hdmi_regset;
struct debugfs_regset32 hd_regset; struct debugfs_regset32 hd_regset;
/**
* @hw_lock: Spinlock protecting device register access.
*/
spinlock_t hw_lock;
}; };
static inline struct vc4_hdmi * static inline struct vc4_hdmi *
......
...@@ -130,31 +130,49 @@ ...@@ -130,31 +130,49 @@
void vc4_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi, void vc4_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi,
struct vc4_hdmi_connector_state *conn_state) struct vc4_hdmi_connector_state *conn_state)
{ {
unsigned long flags;
/* PHY should be in reset, like /* PHY should be in reset, like
* vc4_hdmi_encoder_disable() does. * vc4_hdmi_encoder_disable() does.
*/ */
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0xf << 16); HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0xf << 16);
HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0); HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
} }
void vc4_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi) void vc4_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi)
{ {
unsigned long flags;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0xf << 16); HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0xf << 16);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
} }
void vc4_hdmi_phy_rng_enable(struct vc4_hdmi *vc4_hdmi) void vc4_hdmi_phy_rng_enable(struct vc4_hdmi *vc4_hdmi)
{ {
unsigned long flags;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
HDMI_WRITE(HDMI_TX_PHY_CTL_0, HDMI_WRITE(HDMI_TX_PHY_CTL_0,
HDMI_READ(HDMI_TX_PHY_CTL_0) & HDMI_READ(HDMI_TX_PHY_CTL_0) &
~VC4_HDMI_TX_PHY_RNG_PWRDN); ~VC4_HDMI_TX_PHY_RNG_PWRDN);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
} }
void vc4_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi) void vc4_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi)
{ {
unsigned long flags;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
HDMI_WRITE(HDMI_TX_PHY_CTL_0, HDMI_WRITE(HDMI_TX_PHY_CTL_0,
HDMI_READ(HDMI_TX_PHY_CTL_0) | HDMI_READ(HDMI_TX_PHY_CTL_0) |
VC4_HDMI_TX_PHY_RNG_PWRDN); VC4_HDMI_TX_PHY_RNG_PWRDN);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
} }
static unsigned long long static unsigned long long
...@@ -336,6 +354,8 @@ phy_get_channel_settings(enum vc4_hdmi_phy_channel chan, ...@@ -336,6 +354,8 @@ phy_get_channel_settings(enum vc4_hdmi_phy_channel chan,
static void vc5_hdmi_reset_phy(struct vc4_hdmi *vc4_hdmi) static void vc5_hdmi_reset_phy(struct vc4_hdmi *vc4_hdmi)
{ {
lockdep_assert_held(&vc4_hdmi->hw_lock);
HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0x0f); HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0x0f);
HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL, BIT(10)); HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL, BIT(10));
} }
...@@ -348,10 +368,13 @@ void vc5_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi, ...@@ -348,10 +368,13 @@ void vc5_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi,
unsigned long long pixel_freq = conn_state->pixel_rate; unsigned long long pixel_freq = conn_state->pixel_rate;
unsigned long long vco_freq; unsigned long long vco_freq;
unsigned char word_sel; unsigned char word_sel;
unsigned long flags;
u8 vco_sel, vco_div; u8 vco_sel, vco_div;
vco_freq = phy_get_vco_freq(pixel_freq, &vco_sel, &vco_div); vco_freq = phy_get_vco_freq(pixel_freq, &vco_sel, &vco_div);
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
vc5_hdmi_reset_phy(vc4_hdmi); vc5_hdmi_reset_phy(vc4_hdmi);
HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL, HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL,
...@@ -501,23 +524,37 @@ void vc5_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi, ...@@ -501,23 +524,37 @@ void vc5_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi,
HDMI_READ(HDMI_TX_PHY_RESET_CTL) | HDMI_READ(HDMI_TX_PHY_RESET_CTL) |
VC4_HDMI_TX_PHY_RESET_CTL_PLL_RESETB | VC4_HDMI_TX_PHY_RESET_CTL_PLL_RESETB |
VC4_HDMI_TX_PHY_RESET_CTL_PLLDIV_RESETB); VC4_HDMI_TX_PHY_RESET_CTL_PLLDIV_RESETB);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
} }
void vc5_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi) void vc5_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi)
{ {
unsigned long flags;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
vc5_hdmi_reset_phy(vc4_hdmi); vc5_hdmi_reset_phy(vc4_hdmi);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
} }
void vc5_hdmi_phy_rng_enable(struct vc4_hdmi *vc4_hdmi) void vc5_hdmi_phy_rng_enable(struct vc4_hdmi *vc4_hdmi)
{ {
unsigned long flags;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL, HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL,
HDMI_READ(HDMI_TX_PHY_POWERDOWN_CTL) & HDMI_READ(HDMI_TX_PHY_POWERDOWN_CTL) &
~VC4_HDMI_TX_PHY_POWERDOWN_CTL_RNDGEN_PWRDN); ~VC4_HDMI_TX_PHY_POWERDOWN_CTL_RNDGEN_PWRDN);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
} }
void vc5_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi) void vc5_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi)
{ {
unsigned long flags;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL, HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL,
HDMI_READ(HDMI_TX_PHY_POWERDOWN_CTL) | HDMI_READ(HDMI_TX_PHY_POWERDOWN_CTL) |
VC4_HDMI_TX_PHY_POWERDOWN_CTL_RNDGEN_PWRDN); VC4_HDMI_TX_PHY_POWERDOWN_CTL_RNDGEN_PWRDN);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
} }
...@@ -442,6 +442,8 @@ static inline void vc4_hdmi_write(struct vc4_hdmi *hdmi, ...@@ -442,6 +442,8 @@ static inline void vc4_hdmi_write(struct vc4_hdmi *hdmi,
const struct vc4_hdmi_variant *variant = hdmi->variant; const struct vc4_hdmi_variant *variant = hdmi->variant;
void __iomem *base; void __iomem *base;
lockdep_assert_held(&hdmi->hw_lock);
WARN_ON(!pm_runtime_active(&hdmi->pdev->dev)); WARN_ON(!pm_runtime_active(&hdmi->pdev->dev));
if (reg >= variant->num_registers) { if (reg >= variant->num_registers) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment