diff options
Diffstat (limited to 'drivers/gpu/drm/msm/dp/dp_aux.c')
-rw-r--r-- | drivers/gpu/drm/msm/dp/dp_aux.c | 216 |
1 files changed, 184 insertions, 32 deletions
diff --git a/drivers/gpu/drm/msm/dp/dp_aux.c b/drivers/gpu/drm/msm/dp/dp_aux.c index bc8d46abfc61..3825a2fb48e2 100644 --- a/drivers/gpu/drm/msm/dp/dp_aux.c +++ b/drivers/gpu/drm/msm/dp/dp_aux.c @@ -4,6 +4,7 @@ */ #include <linux/delay.h> +#include <linux/iopoll.h> #include <linux/phy/phy.h> #include <drm/drm_print.h> @@ -22,7 +23,7 @@ enum msm_dp_aux_err { struct msm_dp_aux_private { struct device *dev; - struct msm_dp_catalog *catalog; + void __iomem *aux_base; struct phy *phy; @@ -45,6 +46,80 @@ struct msm_dp_aux_private { struct drm_dp_aux msm_dp_aux; }; +static inline u32 msm_dp_read_aux(struct msm_dp_aux_private *aux, u32 offset) +{ + return readl_relaxed(aux->aux_base + offset); +} + +static inline void msm_dp_write_aux(struct msm_dp_aux_private *aux, + u32 offset, u32 data) +{ + /* + * To make sure aux reg writes happens before any other operation, + * this function uses writel() instread of writel_relaxed() + */ + writel(data, aux->aux_base + offset); +} + +static void msm_dp_aux_clear_hw_interrupts(struct msm_dp_aux_private *aux) +{ + msm_dp_read_aux(aux, REG_DP_PHY_AUX_INTERRUPT_STATUS); + msm_dp_write_aux(aux, REG_DP_PHY_AUX_INTERRUPT_CLEAR, 0x1f); + msm_dp_write_aux(aux, REG_DP_PHY_AUX_INTERRUPT_CLEAR, 0x9f); + msm_dp_write_aux(aux, REG_DP_PHY_AUX_INTERRUPT_CLEAR, 0); +} + +/* + * NOTE: resetting AUX controller will also clear any pending HPD related interrupts + */ +static void msm_dp_aux_reset(struct msm_dp_aux_private *aux) +{ + u32 aux_ctrl; + + aux_ctrl = msm_dp_read_aux(aux, REG_DP_AUX_CTRL); + + aux_ctrl |= DP_AUX_CTRL_RESET; + msm_dp_write_aux(aux, REG_DP_AUX_CTRL, aux_ctrl); + usleep_range(1000, 1100); /* h/w recommended delay */ + + aux_ctrl &= ~DP_AUX_CTRL_RESET; + msm_dp_write_aux(aux, REG_DP_AUX_CTRL, aux_ctrl); +} + +static void msm_dp_aux_enable(struct msm_dp_aux_private *aux) +{ + u32 aux_ctrl; + + aux_ctrl = msm_dp_read_aux(aux, REG_DP_AUX_CTRL); + + msm_dp_write_aux(aux, REG_DP_TIMEOUT_COUNT, 0xffff); + msm_dp_write_aux(aux, REG_DP_AUX_LIMITS, 0xffff); + + aux_ctrl |= DP_AUX_CTRL_ENABLE; + msm_dp_write_aux(aux, REG_DP_AUX_CTRL, aux_ctrl); +} + +static void msm_dp_aux_disable(struct msm_dp_aux_private *aux) +{ + u32 aux_ctrl; + + aux_ctrl = msm_dp_read_aux(aux, REG_DP_AUX_CTRL); + aux_ctrl &= ~DP_AUX_CTRL_ENABLE; + msm_dp_write_aux(aux, REG_DP_AUX_CTRL, aux_ctrl); +} + +static int msm_dp_aux_wait_for_hpd_connect_state(struct msm_dp_aux_private *aux, + unsigned long wait_us) +{ + u32 state; + + /* poll for hpd connected status every 2ms and timeout after wait_us */ + return readl_poll_timeout(aux->aux_base + + REG_DP_DP_HPD_INT_STATUS, + state, state & DP_DP_HPD_STATE_STATUS_CONNECTED, + min(wait_us, 2000), wait_us); +} + #define MAX_AUX_RETRIES 5 static ssize_t msm_dp_aux_write(struct msm_dp_aux_private *aux, @@ -88,11 +163,11 @@ static ssize_t msm_dp_aux_write(struct msm_dp_aux_private *aux, /* index = 0, write */ if (i == 0) reg |= DP_AUX_DATA_INDEX_WRITE; - msm_dp_catalog_aux_write_data(aux->catalog, reg); + msm_dp_write_aux(aux, REG_DP_AUX_DATA, reg); } - msm_dp_catalog_aux_clear_trans(aux->catalog, false); - msm_dp_catalog_aux_clear_hw_interrupts(aux->catalog); + msm_dp_write_aux(aux, REG_DP_AUX_TRANS_CTRL, 0); + msm_dp_aux_clear_hw_interrupts(aux); reg = 0; /* Transaction number == 1 */ if (!aux->native) { /* i2c */ @@ -106,7 +181,7 @@ static ssize_t msm_dp_aux_write(struct msm_dp_aux_private *aux, } reg |= DP_AUX_TRANS_CTRL_GO; - msm_dp_catalog_aux_write_trans(aux->catalog, reg); + msm_dp_write_aux(aux, REG_DP_AUX_TRANS_CTRL, reg); return len; } @@ -139,20 +214,22 @@ static ssize_t msm_dp_aux_cmd_fifo_rx(struct msm_dp_aux_private *aux, u32 i, actual_i; u32 len = msg->size; - msm_dp_catalog_aux_clear_trans(aux->catalog, true); + data = msm_dp_read_aux(aux, REG_DP_AUX_TRANS_CTRL); + data &= ~DP_AUX_TRANS_CTRL_GO; + msm_dp_write_aux(aux, REG_DP_AUX_TRANS_CTRL, data); data = DP_AUX_DATA_INDEX_WRITE; /* INDEX_WRITE */ data |= DP_AUX_DATA_READ; /* read */ - msm_dp_catalog_aux_write_data(aux->catalog, data); + msm_dp_write_aux(aux, REG_DP_AUX_DATA, data); dp = msg->buffer; /* discard first byte */ - data = msm_dp_catalog_aux_read_data(aux->catalog); + data = msm_dp_read_aux(aux, REG_DP_AUX_DATA); for (i = 0; i < len; i++) { - data = msm_dp_catalog_aux_read_data(aux->catalog); + data = msm_dp_read_aux(aux, REG_DP_AUX_DATA); *dp++ = (u8)((data >> DP_AUX_DATA_OFFSET) & 0xff); actual_i = (data >> DP_AUX_DATA_INDEX_OFFSET) & 0xFF; @@ -335,8 +412,8 @@ static ssize_t msm_dp_aux_transfer(struct drm_dp_aux *msm_dp_aux, phy_calibrate(aux->phy); } /* reset aux if link is in connected state */ - if (msm_dp_catalog_link_is_connected(aux->catalog)) - msm_dp_catalog_aux_reset(aux->catalog); + if (msm_dp_aux_is_link_connected(msm_dp_aux)) + msm_dp_aux_reset(aux); } else { aux->retry_cnt = 0; switch (aux->aux_error_num) { @@ -369,9 +446,8 @@ exit: return ret; } -irqreturn_t msm_dp_aux_isr(struct drm_dp_aux *msm_dp_aux) +irqreturn_t msm_dp_aux_isr(struct drm_dp_aux *msm_dp_aux, u32 isr) { - u32 isr; struct msm_dp_aux_private *aux; if (!msm_dp_aux) { @@ -381,12 +457,6 @@ irqreturn_t msm_dp_aux_isr(struct drm_dp_aux *msm_dp_aux) aux = container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux); - isr = msm_dp_catalog_aux_get_irq(aux->catalog); - - /* no interrupts pending, return immediately */ - if (!isr) - return IRQ_NONE; - if (!aux->cmd_busy) { DRM_ERROR("Unexpected DP AUX IRQ %#010x when not busy\n", isr); return IRQ_NONE; @@ -403,7 +473,7 @@ irqreturn_t msm_dp_aux_isr(struct drm_dp_aux *msm_dp_aux) if (isr & DP_INTR_AUX_ERROR) { aux->aux_error_num = DP_AUX_ERR_PHY; - msm_dp_catalog_aux_clear_hw_interrupts(aux->catalog); + msm_dp_aux_clear_hw_interrupts(aux); } else if (isr & DP_INTR_NACK_DEFER) { aux->aux_error_num = DP_AUX_ERR_NACK_DEFER; } else if (isr & DP_INTR_WRONG_ADDR) { @@ -444,7 +514,7 @@ void msm_dp_aux_reconfig(struct drm_dp_aux *msm_dp_aux) aux = container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux); phy_calibrate(aux->phy); - msm_dp_catalog_aux_reset(aux->catalog); + msm_dp_aux_reset(aux); } void msm_dp_aux_init(struct drm_dp_aux *msm_dp_aux) @@ -460,7 +530,7 @@ void msm_dp_aux_init(struct drm_dp_aux *msm_dp_aux) mutex_lock(&aux->mutex); - msm_dp_catalog_aux_enable(aux->catalog, true); + msm_dp_aux_enable(aux); aux->retry_cnt = 0; aux->initted = true; @@ -476,7 +546,7 @@ void msm_dp_aux_deinit(struct drm_dp_aux *msm_dp_aux) mutex_lock(&aux->mutex); aux->initted = false; - msm_dp_catalog_aux_enable(aux->catalog, false); + msm_dp_aux_disable(aux); mutex_unlock(&aux->mutex); } @@ -517,23 +587,105 @@ static int msm_dp_wait_hpd_asserted(struct drm_dp_aux *msm_dp_aux, if (ret) return ret; - ret = msm_dp_catalog_aux_wait_for_hpd_connect_state(aux->catalog, wait_us); + ret = msm_dp_aux_wait_for_hpd_connect_state(aux, wait_us); pm_runtime_put_sync(aux->dev); return ret; } -struct drm_dp_aux *msm_dp_aux_get(struct device *dev, struct msm_dp_catalog *catalog, +void msm_dp_aux_hpd_enable(struct drm_dp_aux *msm_dp_aux) +{ + struct msm_dp_aux_private *aux = + container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux); + u32 reg; + + /* Configure REFTIMER and enable it */ + reg = msm_dp_read_aux(aux, REG_DP_DP_HPD_REFTIMER); + reg |= DP_DP_HPD_REFTIMER_ENABLE; + msm_dp_write_aux(aux, REG_DP_DP_HPD_REFTIMER, reg); + + /* Enable HPD */ + msm_dp_write_aux(aux, REG_DP_DP_HPD_CTRL, DP_DP_HPD_CTRL_HPD_EN); +} + +void msm_dp_aux_hpd_disable(struct drm_dp_aux *msm_dp_aux) +{ + struct msm_dp_aux_private *aux = + container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux); + u32 reg; + + reg = msm_dp_read_aux(aux, REG_DP_DP_HPD_REFTIMER); + reg &= ~DP_DP_HPD_REFTIMER_ENABLE; + msm_dp_write_aux(aux, REG_DP_DP_HPD_REFTIMER, reg); + + msm_dp_write_aux(aux, REG_DP_DP_HPD_CTRL, 0); +} + +void msm_dp_aux_hpd_intr_enable(struct drm_dp_aux *msm_dp_aux) +{ + struct msm_dp_aux_private *aux = + container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux); + u32 reg; + + reg = msm_dp_read_aux(aux, REG_DP_DP_HPD_INT_MASK); + reg |= DP_DP_HPD_INT_MASK; + msm_dp_write_aux(aux, REG_DP_DP_HPD_INT_MASK, + reg & DP_DP_HPD_INT_MASK); +} + +void msm_dp_aux_hpd_intr_disable(struct drm_dp_aux *msm_dp_aux) +{ + struct msm_dp_aux_private *aux = + container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux); + u32 reg; + + reg = msm_dp_read_aux(aux, REG_DP_DP_HPD_INT_MASK); + reg &= ~DP_DP_HPD_INT_MASK; + msm_dp_write_aux(aux, REG_DP_DP_HPD_INT_MASK, + reg & DP_DP_HPD_INT_MASK); +} + +u32 msm_dp_aux_get_hpd_intr_status(struct drm_dp_aux *msm_dp_aux) +{ + struct msm_dp_aux_private *aux = + container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux); + int isr, mask; + + isr = msm_dp_read_aux(aux, REG_DP_DP_HPD_INT_STATUS); + msm_dp_write_aux(aux, REG_DP_DP_HPD_INT_ACK, + (isr & DP_DP_HPD_INT_MASK)); + mask = msm_dp_read_aux(aux, REG_DP_DP_HPD_INT_MASK); + + /* + * We only want to return interrupts that are unmasked to the caller. + * However, the interrupt status field also contains other + * informational bits about the HPD state status, so we only mask + * out the part of the register that tells us about which interrupts + * are pending. + */ + return isr & (mask | ~DP_DP_HPD_INT_MASK); +} + +u32 msm_dp_aux_is_link_connected(struct drm_dp_aux *msm_dp_aux) +{ + struct msm_dp_aux_private *aux = + container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux); + u32 status; + + status = msm_dp_read_aux(aux, REG_DP_DP_HPD_INT_STATUS); + status >>= DP_DP_HPD_STATE_STATUS_BITS_SHIFT; + status &= DP_DP_HPD_STATE_STATUS_BITS_MASK; + + return status; +} + +struct drm_dp_aux *msm_dp_aux_get(struct device *dev, struct phy *phy, - bool is_edp) + bool is_edp, + void __iomem *aux_base) { struct msm_dp_aux_private *aux; - if (!catalog) { - DRM_ERROR("invalid input\n"); - return ERR_PTR(-ENODEV); - } - aux = devm_kzalloc(dev, sizeof(*aux), GFP_KERNEL); if (!aux) return ERR_PTR(-ENOMEM); @@ -544,9 +696,9 @@ struct drm_dp_aux *msm_dp_aux_get(struct device *dev, struct msm_dp_catalog *cat mutex_init(&aux->mutex); aux->dev = dev; - aux->catalog = catalog; aux->phy = phy; aux->retry_cnt = 0; + aux->aux_base = aux_base; /* * Use the drm_dp_aux_init() to use the aux adapter |