drm/msm/dp: add support for dp aux

Add AUX communication related functionalities that a client
can use. Provide a get API that a client can call to get
access to AUX module's functionalities. The AUX functionalities
includes read and write data from and to a sink device. This
module can be used to read EDID, DPCD, link training data and
HDCP related data.

Change-Id: I97fed02216733e6cccdd00b79e35d7b9976241af
Signed-off-by: Ajay Singh Parmar <aparmar@codeaurora.org>
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_*/