summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/tegra/dpaux.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/tegra/dpaux.c')
-rw-r--r--drivers/gpu/drm/tegra/dpaux.c79
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);
OpenPOWER on IntegriCloud