Merge "drm/msm/dp: add support for dp aux" into msm-4.9
diff --git a/drivers/gpu/drm/msm/dp/dp_aux.c b/drivers/gpu/drm/msm/dp/dp_aux.c
new file mode 100644
index 0000000..30f477e
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_aux.c
@@ -0,0 +1,578 @@
+/*
+ * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__
+
+#include <linux/delay.h>
+
+#include "dp_aux.h"
+
+#define DP_AUX_ENUM_STR(x) #x
+
+struct aux_buf {
+ u8 *start; /* buffer start addr */
+ u8 *end; /* buffer end addr */
+ u8 *data; /* data pou32er */
+ u32 size; /* size of buffer */
+ u32 len; /* dara length */
+ u8 trans_num; /* transaction number */
+ enum aux_tx_mode tx_mode;
+};
+
+struct dp_aux_private {
+ struct device *dev;
+ struct dp_aux dp_aux;
+ struct dp_catalog_aux *catalog;
+
+ struct mutex mutex;
+ struct completion comp;
+
+ struct aux_cmd *cmds;
+ struct aux_buf txp;
+ struct aux_buf rxp;
+
+ u32 aux_error_num;
+
+ u8 txbuf[256];
+ u8 rxbuf[256];
+};
+
+static char *dp_aux_get_error(u32 aux_error)
+{
+ switch (aux_error) {
+ case DP_AUX_ERR_NONE:
+ return DP_AUX_ENUM_STR(DP_AUX_ERR_NONE);
+ case DP_AUX_ERR_ADDR:
+ return DP_AUX_ENUM_STR(DP_AUX_ERR_ADDR);
+ case DP_AUX_ERR_TOUT:
+ return DP_AUX_ENUM_STR(DP_AUX_ERR_TOUT);
+ case DP_AUX_ERR_NACK:
+ return DP_AUX_ENUM_STR(DP_AUX_ERR_NACK);
+ case DP_AUX_ERR_DEFER:
+ return DP_AUX_ENUM_STR(DP_AUX_ERR_DEFER);
+ case DP_AUX_ERR_NACK_DEFER:
+ return DP_AUX_ENUM_STR(DP_AUX_ERR_NACK_DEFER);
+ default:
+ return "unknown";
+ }
+}
+
+static void dp_aux_buf_init(struct aux_buf *buf, u8 *data, u32 size)
+{
+ buf->start = data;
+ buf->size = size;
+ buf->data = buf->start;
+ buf->end = buf->start + buf->size;
+ buf->len = 0;
+ buf->trans_num = 0;
+ buf->tx_mode = AUX_NATIVE;
+}
+
+static void dp_aux_buf_set(struct dp_aux_private *aux)
+{
+ init_completion(&aux->comp);
+ mutex_init(&aux->mutex);
+
+ dp_aux_buf_init(&aux->txp, aux->txbuf, sizeof(aux->txbuf));
+ dp_aux_buf_init(&aux->rxp, aux->rxbuf, sizeof(aux->rxbuf));
+}
+
+static void dp_aux_buf_reset(struct aux_buf *buf)
+{
+ buf->data = buf->start;
+ buf->len = 0;
+ buf->trans_num = 0;
+ buf->tx_mode = AUX_NATIVE;
+
+ memset(buf->start, 0x0, 256);
+}
+
+static void dp_aux_buf_push(struct aux_buf *buf, u32 len)
+{
+ buf->data += len;
+ buf->len += len;
+}
+
+static u32 dp_aux_buf_trailing(struct aux_buf *buf)
+{
+ return (u32)(buf->end - buf->data);
+}
+
+static u32 dp_aux_add_cmd(struct aux_buf *buf, struct aux_cmd *cmd)
+{
+ u8 data;
+ u8 *bp, *cp;
+ u32 i, len;
+
+ if (cmd->ex_mode == AUX_READ)
+ len = 4;
+ else
+ len = cmd->len + 4;
+
+ if (dp_aux_buf_trailing(buf) < len) {
+ pr_err("buf trailing error\n");
+ return 0;
+ }
+
+ /*
+ * cmd fifo only has depth of 144 bytes
+ * limit buf length to 128 bytes here
+ */
+ if ((buf->len + len) > 128) {
+ pr_err("buf len error\n");
+ return 0;
+ }
+
+ bp = buf->data;
+ data = cmd->addr >> 16;
+ data &= 0x0f; /* 4 addr bits */
+
+ if (cmd->ex_mode == AUX_READ)
+ data |= BIT(4);
+
+ *bp++ = data;
+ *bp++ = cmd->addr >> 8;
+ *bp++ = cmd->addr;
+ *bp++ = cmd->len - 1;
+
+ if (cmd->ex_mode == AUX_WRITE) {
+ cp = cmd->buf;
+
+ for (i = 0; i < cmd->len; i++)
+ *bp++ = *cp++;
+ }
+
+ dp_aux_buf_push(buf, len);
+
+ buf->tx_mode = cmd->tx_mode;
+
+ buf->trans_num++;
+
+ return cmd->len - 1;
+}
+
+static u32 dp_aux_cmd_fifo_tx(struct dp_aux_private *aux)
+{
+ u8 *dp;
+ u32 data, len, cnt;
+ struct aux_buf *tp = &aux->txp;
+
+ len = tp->len;
+ if (len == 0) {
+ pr_err("invalid len\n");
+ return 0;
+ }
+
+ cnt = 0;
+ dp = tp->start;
+
+ while (cnt < len) {
+ data = *dp;
+ data <<= 8;
+ data &= 0x00ff00;
+ if (cnt == 0)
+ data |= BIT(31);
+
+ aux->catalog->data = data;
+ aux->catalog->write_data(aux->catalog);
+
+ cnt++;
+ dp++;
+ }
+
+ data = (tp->trans_num - 1);
+ if (tp->tx_mode == AUX_I2C) {
+ data |= BIT(8); /* I2C */
+ data |= BIT(10); /* NO SEND ADDR */
+ data |= BIT(11); /* NO SEND STOP */
+ }
+
+ data |= BIT(9); /* GO */
+ aux->catalog->data = data;
+ aux->catalog->write_trans(aux->catalog);
+
+ return tp->len;
+}
+
+static u32 dp_cmd_fifo_rx(struct dp_aux_private *aux, u32 len)
+{
+ u32 data;
+ u8 *dp;
+ u32 i;
+ struct aux_buf *rp = &aux->rxp;
+
+ data = 0;
+ data |= BIT(31); /* INDEX_WRITE */
+ data |= BIT(0); /* read */
+
+ aux->catalog->data = data;
+ aux->catalog->write_data(aux->catalog);
+
+ dp = rp->data;
+
+ /* discard first byte */
+ data = aux->catalog->read_data(aux->catalog);
+
+ for (i = 0; i < len; i++) {
+ data = aux->catalog->read_data(aux->catalog);
+ *dp++ = (u8)((data >> 8) & 0xff);
+ }
+
+ rp->len = len;
+ return len;
+}
+
+static void dp_aux_native_handler(struct dp_aux_private *aux)
+{
+ u32 isr = aux->catalog->isr1;
+
+ if (isr & DP_INTR_AUX_I2C_DONE)
+ aux->aux_error_num = DP_AUX_ERR_NONE;
+ else if (isr & DP_INTR_WRONG_ADDR)
+ aux->aux_error_num = DP_AUX_ERR_ADDR;
+ else if (isr & DP_INTR_TIMEOUT)
+ aux->aux_error_num = DP_AUX_ERR_TOUT;
+ if (isr & DP_INTR_NACK_DEFER)
+ aux->aux_error_num = DP_AUX_ERR_NACK;
+
+ complete(&aux->comp);
+}
+
+static void dp_aux_i2c_handler(struct dp_aux_private *aux)
+{
+ u32 isr = aux->catalog->isr1;
+
+ if (isr & DP_INTR_AUX_I2C_DONE) {
+ if (isr & (DP_INTR_I2C_NACK | DP_INTR_I2C_DEFER))
+ aux->aux_error_num = DP_AUX_ERR_NACK;
+ else
+ aux->aux_error_num = DP_AUX_ERR_NONE;
+ } else {
+ if (isr & DP_INTR_WRONG_ADDR)
+ aux->aux_error_num = DP_AUX_ERR_ADDR;
+ else if (isr & DP_INTR_TIMEOUT)
+ aux->aux_error_num = DP_AUX_ERR_TOUT;
+ if (isr & DP_INTR_NACK_DEFER)
+ aux->aux_error_num = DP_AUX_ERR_NACK_DEFER;
+ if (isr & DP_INTR_I2C_NACK)
+ aux->aux_error_num = DP_AUX_ERR_NACK;
+ if (isr & DP_INTR_I2C_DEFER)
+ aux->aux_error_num = DP_AUX_ERR_DEFER;
+ }
+
+ complete(&aux->comp);
+}
+
+static void dp_aux_isr(struct dp_aux *dp_aux)
+{
+ struct dp_aux_private *aux;
+
+ if (!dp_aux) {
+ pr_err("invalid input\n");
+ return;
+ }
+
+ aux = container_of(dp_aux, struct dp_aux_private, dp_aux);
+
+ aux->catalog->get_irq(aux->catalog);
+
+ if (aux->cmds->tx_mode == AUX_NATIVE)
+ dp_aux_native_handler(aux);
+ else
+ dp_aux_i2c_handler(aux);
+}
+
+
+
+static int dp_aux_write(struct dp_aux_private *aux)
+{
+ struct aux_cmd *cm;
+ struct aux_buf *tp;
+ u32 len, ret, timeout;
+
+ mutex_lock(&aux->mutex);
+
+ tp = &aux->txp;
+ dp_aux_buf_reset(tp);
+
+ cm = aux->cmds;
+ while (cm) {
+ ret = dp_aux_add_cmd(tp, cm);
+ if (ret <= 0)
+ break;
+
+ if (!cm->next)
+ break;
+ cm++;
+ }
+
+ reinit_completion(&aux->comp);
+
+ len = dp_aux_cmd_fifo_tx(aux);
+
+ timeout = wait_for_completion_timeout(&aux->comp, HZ/4);
+ if (!timeout)
+ pr_err("aux write timeout\n");
+
+ pr_debug("aux status %s\n",
+ dp_aux_get_error(aux->aux_error_num));
+
+ if (aux->aux_error_num == DP_AUX_ERR_NONE)
+ ret = len;
+ else
+ ret = aux->aux_error_num;
+
+ mutex_unlock(&aux->mutex);
+ return ret;
+}
+
+static int dp_aux_read(struct dp_aux_private *aux)
+{
+ struct aux_cmd *cm;
+ struct aux_buf *tp, *rp;
+ u32 len, ret, timeout;
+
+ mutex_lock(&aux->mutex);
+
+ tp = &aux->txp;
+ rp = &aux->rxp;
+
+ dp_aux_buf_reset(tp);
+ dp_aux_buf_reset(rp);
+
+ cm = aux->cmds;
+ len = 0;
+
+ while (cm) {
+ ret = dp_aux_add_cmd(tp, cm);
+ len += cm->len;
+
+ if (ret <= 0)
+ break;
+
+ if (!cm->next)
+ break;
+ cm++;
+ }
+
+ reinit_completion(&aux->comp);
+
+ dp_aux_cmd_fifo_tx(aux);
+
+ timeout = wait_for_completion_timeout(&aux->comp, HZ/4);
+ if (!timeout)
+ pr_err("aux read timeout\n");
+
+ pr_debug("aux status %s\n",
+ dp_aux_get_error(aux->aux_error_num));
+
+ if (aux->aux_error_num == DP_AUX_ERR_NONE)
+ ret = dp_cmd_fifo_rx(aux, len);
+ else
+ ret = aux->aux_error_num;
+
+ aux->cmds->buf = rp->data;
+
+ mutex_unlock(&aux->mutex);
+
+ return ret;
+}
+
+static int dp_aux_write_ex(struct dp_aux *dp_aux, u32 addr, u32 len,
+ enum aux_tx_mode mode, u8 *buf)
+{
+ struct aux_cmd cmd = {0};
+ struct dp_aux_private *aux;
+
+ if (!dp_aux || !len) {
+ pr_err("invalid input\n");
+ return -EINVAL;
+ }
+
+ aux = container_of(dp_aux, struct dp_aux_private, dp_aux);
+
+ cmd.ex_mode = AUX_WRITE;
+ cmd.tx_mode = mode;
+ cmd.addr = addr;
+ cmd.len = len;
+ cmd.buf = buf;
+
+ aux->cmds = &cmd;
+
+ return dp_aux_write(aux);
+}
+
+static int dp_aux_read_ex(struct dp_aux *dp_aux, u32 addr, u32 len,
+ enum aux_tx_mode mode, u8 **buf)
+{
+ int rc = 0;
+ struct aux_cmd cmd = {0};
+ struct dp_aux_private *aux;
+
+ if (!dp_aux || !len) {
+ pr_err("invalid input\n");
+ rc = -EINVAL;
+ goto end;
+ }
+
+ aux = container_of(dp_aux, struct dp_aux_private, dp_aux);
+
+ cmd.ex_mode = AUX_READ;
+ cmd.tx_mode = mode;
+ cmd.addr = addr;
+ cmd.len = len;
+
+ aux->cmds = &cmd;
+
+ rc = dp_aux_read(aux);
+ if (rc <= 0) {
+ rc = -EINVAL;
+ goto end;
+ }
+
+ *buf = cmd.buf;
+end:
+ return rc;
+}
+
+static int dp_aux_process(struct dp_aux *dp_aux, struct aux_cmd *cmds)
+{
+ struct dp_aux_private *aux;
+
+ if (!dp_aux || !cmds) {
+ pr_err("invalid input\n");
+ return -EINVAL;
+ }
+
+ aux = container_of(dp_aux, struct dp_aux_private, dp_aux);
+
+ aux->cmds = cmds;
+
+ if (cmds->ex_mode == AUX_READ)
+ return dp_aux_read(aux);
+ else
+ return dp_aux_write(aux);
+}
+
+static bool dp_aux_ready(struct dp_aux *dp_aux)
+{
+ u8 data = 0;
+ int count, ret;
+ struct dp_aux_private *aux;
+
+ if (!dp_aux) {
+ pr_err("invalid input\n");
+ goto error;
+ }
+
+ aux = container_of(dp_aux, struct dp_aux_private, dp_aux);
+
+ for (count = 5; count; count--) {
+ ret = dp_aux_write_ex(dp_aux, 0x50, 1, AUX_I2C, &data);
+ if (ret >= 0)
+ break;
+
+ msleep(100);
+ }
+
+ if (count <= 0) {
+ pr_err("aux chan NOT ready\n");
+ goto error;
+ }
+
+ return true;
+error:
+ return false;
+}
+
+static void dp_aux_init(struct dp_aux *dp_aux, u32 *aux_cfg)
+{
+ struct dp_aux_private *aux;
+
+ if (!dp_aux) {
+ pr_err("invalid input\n");
+ return;
+ }
+
+ aux = container_of(dp_aux, struct dp_aux_private, dp_aux);
+
+ aux->catalog->reset(aux->catalog);
+ aux->catalog->enable(aux->catalog, true);
+ aux->catalog->setup(aux->catalog, aux_cfg);
+}
+
+static void dp_aux_deinit(struct dp_aux *dp_aux)
+{
+ struct dp_aux_private *aux;
+
+ if (!dp_aux) {
+ pr_err("invalid input\n");
+ return;
+ }
+
+ aux = container_of(dp_aux, struct dp_aux_private, dp_aux);
+
+ aux->catalog->enable(aux->catalog, false);
+}
+
+struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog)
+{
+ int rc = 0;
+ struct dp_aux_private *aux;
+ struct dp_aux *dp_aux;
+
+ if (!catalog) {
+ pr_err("invalid input\n");
+ rc = -ENODEV;
+ goto error;
+ }
+
+ aux = devm_kzalloc(dev, sizeof(*aux), GFP_KERNEL);
+ if (!aux) {
+ rc = -ENOMEM;
+ goto error;
+ }
+
+ aux->dev = dev;
+
+ dp_aux_buf_set(aux);
+
+ aux->catalog = catalog;
+
+ dp_aux = &aux->dp_aux;
+
+ dp_aux->process = dp_aux_process;
+ dp_aux->read = dp_aux_read_ex;
+ dp_aux->write = dp_aux_write_ex;
+ dp_aux->ready = dp_aux_ready;
+ dp_aux->isr = dp_aux_isr;
+ dp_aux->init = dp_aux_init;
+ dp_aux->deinit = dp_aux_deinit;
+
+ return dp_aux;
+error:
+ return ERR_PTR(rc);
+}
+
+void dp_aux_put(struct dp_aux *dp_aux)
+{
+ struct dp_aux_private *aux;
+
+ if (!dp_aux)
+ return;
+
+ aux = container_of(dp_aux, struct dp_aux_private, dp_aux);
+
+ devm_kfree(aux->dev, aux);
+}
diff --git a/drivers/gpu/drm/msm/dp/dp_aux.h b/drivers/gpu/drm/msm/dp/dp_aux.h
new file mode 100644
index 0000000..0603c15
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_aux.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef _DP_AUX_H_
+#define _DP_AUX_H_
+
+#include "dp_catalog.h"
+
+enum dp_aux_error {
+ DP_AUX_ERR_NONE = 0,
+ DP_AUX_ERR_ADDR = -1,
+ DP_AUX_ERR_TOUT = -2,
+ DP_AUX_ERR_NACK = -3,
+ DP_AUX_ERR_DEFER = -4,
+ DP_AUX_ERR_NACK_DEFER = -5,
+};
+
+enum aux_tx_mode {
+ AUX_NATIVE,
+ AUX_I2C,
+};
+
+enum aux_exe_mode {
+ AUX_WRITE,
+ AUX_READ,
+};
+
+struct aux_cmd {
+ enum aux_exe_mode ex_mode;
+ enum aux_tx_mode tx_mode;
+ u32 addr;
+ u32 len;
+ u8 *buf;
+ bool next;
+};
+
+struct dp_aux {
+ int (*process)(struct dp_aux *aux, struct aux_cmd *cmd);
+ int (*write)(struct dp_aux *aux, u32 addr, u32 len,
+ enum aux_tx_mode mode, u8 *buf);
+ int (*read)(struct dp_aux *aux, u32 addr, u32 len,
+ enum aux_tx_mode mode, u8 **buf);
+ bool (*ready)(struct dp_aux *aux);
+ void (*isr)(struct dp_aux *aux);
+ void (*init)(struct dp_aux *aux, u32 *aux_cfg);
+ void (*deinit)(struct dp_aux *aux);
+};
+
+struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog);
+void dp_aux_put(struct dp_aux *aux);
+
+#endif /*__DP_AUX_H_*/