diff options
Diffstat (limited to 'drivers/gpu/drm/tegra/dpaux.c')
-rw-r--r-- | drivers/gpu/drm/tegra/dpaux.c | 79 |
1 files changed, 54 insertions, 25 deletions
diff --git a/drivers/gpu/drm/tegra/dpaux.c b/drivers/gpu/drm/tegra/dpaux.c index d536ed381fbd..3f132e356e9c 100644 --- a/drivers/gpu/drm/tegra/dpaux.c +++ b/drivers/gpu/drm/tegra/dpaux.c @@ -15,6 +15,7 @@ #include <linux/platform_device.h> #include <linux/reset.h> #include <linux/regulator/consumer.h> +#include <linux/workqueue.h> #include <drm/drm_dp_helper.h> #include <drm/drm_panel.h> @@ -41,6 +42,7 @@ struct tegra_dpaux { struct regulator *vdd; struct completion complete; + struct work_struct work; struct list_head list; }; @@ -49,6 +51,11 @@ static inline struct tegra_dpaux *to_dpaux(struct drm_dp_aux *aux) return container_of(aux, struct tegra_dpaux, aux); } +static inline struct tegra_dpaux *work_to_dpaux(struct work_struct *work) +{ + return container_of(work, struct tegra_dpaux, work); +} + static inline unsigned long tegra_dpaux_readl(struct tegra_dpaux *dpaux, unsigned long offset) { @@ -99,55 +106,73 @@ static void tegra_dpaux_read_fifo(struct tegra_dpaux *dpaux, u8 *buffer, static ssize_t tegra_dpaux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) { - unsigned long value = DPAUX_DP_AUXCTL_TRANSACTREQ; unsigned long timeout = msecs_to_jiffies(250); struct tegra_dpaux *dpaux = to_dpaux(aux); unsigned long status; ssize_t ret = 0; + u32 value; - if (msg->size < 1 || msg->size > 16) + /* Tegra has 4x4 byte DP AUX transmit and receive FIFOs. */ + if (msg->size > 16) return -EINVAL; - tegra_dpaux_writel(dpaux, msg->address, DPAUX_DP_AUXADDR); + /* + * Allow zero-sized messages only for I2C, in which case they specify + * address-only transactions. + */ + if (msg->size < 1) { + switch (msg->request & ~DP_AUX_I2C_MOT) { + case DP_AUX_I2C_WRITE: + case DP_AUX_I2C_READ: + value = DPAUX_DP_AUXCTL_CMD_ADDRESS_ONLY; + break; + + default: + return -EINVAL; + } + } else { + /* For non-zero-sized messages, set the CMDLEN field. */ + value = DPAUX_DP_AUXCTL_CMDLEN(msg->size - 1); + } switch (msg->request & ~DP_AUX_I2C_MOT) { case DP_AUX_I2C_WRITE: if (msg->request & DP_AUX_I2C_MOT) - value = DPAUX_DP_AUXCTL_CMD_MOT_WR; + value |= DPAUX_DP_AUXCTL_CMD_MOT_WR; else - value = DPAUX_DP_AUXCTL_CMD_I2C_WR; + value |= DPAUX_DP_AUXCTL_CMD_I2C_WR; break; case DP_AUX_I2C_READ: if (msg->request & DP_AUX_I2C_MOT) - value = DPAUX_DP_AUXCTL_CMD_MOT_RD; + value |= DPAUX_DP_AUXCTL_CMD_MOT_RD; else - value = DPAUX_DP_AUXCTL_CMD_I2C_RD; + value |= DPAUX_DP_AUXCTL_CMD_I2C_RD; break; case DP_AUX_I2C_STATUS: if (msg->request & DP_AUX_I2C_MOT) - value = DPAUX_DP_AUXCTL_CMD_MOT_RQ; + value |= DPAUX_DP_AUXCTL_CMD_MOT_RQ; else - value = DPAUX_DP_AUXCTL_CMD_I2C_RQ; + value |= DPAUX_DP_AUXCTL_CMD_I2C_RQ; break; case DP_AUX_NATIVE_WRITE: - value = DPAUX_DP_AUXCTL_CMD_AUX_WR; + value |= DPAUX_DP_AUXCTL_CMD_AUX_WR; break; case DP_AUX_NATIVE_READ: - value = DPAUX_DP_AUXCTL_CMD_AUX_RD; + value |= DPAUX_DP_AUXCTL_CMD_AUX_RD; break; default: return -EINVAL; } - value |= DPAUX_DP_AUXCTL_CMDLEN(msg->size - 1); + tegra_dpaux_writel(dpaux, msg->address, DPAUX_DP_AUXADDR); tegra_dpaux_writel(dpaux, value, DPAUX_DP_AUXCTL); if ((msg->request & DP_AUX_I2C_READ) == 0) { @@ -198,7 +223,7 @@ static ssize_t tegra_dpaux_transfer(struct drm_dp_aux *aux, break; } - if (msg->reply == DP_AUX_NATIVE_REPLY_ACK) { + if ((msg->size > 0) && (msg->reply == DP_AUX_NATIVE_REPLY_ACK)) { if (msg->request & DP_AUX_I2C_READ) { size_t count = value & DPAUX_DP_AUXSTAT_REPLY_MASK; @@ -213,6 +238,14 @@ static ssize_t tegra_dpaux_transfer(struct drm_dp_aux *aux, return ret; } +static void tegra_dpaux_hotplug(struct work_struct *work) +{ + struct tegra_dpaux *dpaux = work_to_dpaux(work); + + if (dpaux->output) + drm_helper_hpd_irq_event(dpaux->output->connector.dev); +} + static irqreturn_t tegra_dpaux_irq(int irq, void *data) { struct tegra_dpaux *dpaux = data; @@ -223,16 +256,8 @@ static irqreturn_t tegra_dpaux_irq(int irq, void *data) value = tegra_dpaux_readl(dpaux, DPAUX_INTR_AUX); tegra_dpaux_writel(dpaux, value, DPAUX_INTR_AUX); - if (value & DPAUX_INTR_PLUG_EVENT) { - if (dpaux->output) { - drm_helper_hpd_irq_event(dpaux->output->connector.dev); - } - } - - if (value & DPAUX_INTR_UNPLUG_EVENT) { - if (dpaux->output) - drm_helper_hpd_irq_event(dpaux->output->connector.dev); - } + if (value & (DPAUX_INTR_PLUG_EVENT | DPAUX_INTR_UNPLUG_EVENT)) + schedule_work(&dpaux->work); if (value & DPAUX_INTR_IRQ_EVENT) { /* TODO: handle this */ @@ -255,6 +280,7 @@ static int tegra_dpaux_probe(struct platform_device *pdev) if (!dpaux) return -ENOMEM; + INIT_WORK(&dpaux->work, tegra_dpaux_hotplug); init_completion(&dpaux->complete); INIT_LIST_HEAD(&dpaux->list); dpaux->dev = &pdev->dev; @@ -314,7 +340,7 @@ static int tegra_dpaux_probe(struct platform_device *pdev) dpaux->aux.transfer = tegra_dpaux_transfer; dpaux->aux.dev = &pdev->dev; - err = drm_dp_aux_register_i2c_bus(&dpaux->aux); + err = drm_dp_aux_register(&dpaux->aux); if (err < 0) return err; @@ -337,12 +363,14 @@ static int tegra_dpaux_remove(struct platform_device *pdev) { struct tegra_dpaux *dpaux = platform_get_drvdata(pdev); - drm_dp_aux_unregister_i2c_bus(&dpaux->aux); + drm_dp_aux_unregister(&dpaux->aux); mutex_lock(&dpaux_lock); list_del(&dpaux->list); mutex_unlock(&dpaux_lock); + cancel_work_sync(&dpaux->work); + clk_disable_unprepare(dpaux->clk_parent); reset_control_assert(dpaux->rst); clk_disable_unprepare(dpaux->clk); @@ -386,6 +414,7 @@ int tegra_dpaux_attach(struct tegra_dpaux *dpaux, struct tegra_output *output) unsigned long timeout; int err; + output->connector.polled = DRM_CONNECTOR_POLL_HPD; dpaux->output = output; err = regulator_enable(dpaux->vdd); |