mmc: cmdq_hci: ice: Handle ICE HCI errors in cmdq mode

With ICE HCI addition in SDHC, couple of errors conditions
got introduced. These errors would be detected by ICE HCI and
triggers interrupt.

The two errors are:
1. Generic Crypto Error (GCE): This error is detected during the
transfer. Need to reset SDHC to recover.
2. Invalid Crypto Configuration Error (ICCE): The HW detects it
before the beginning of the transfer and terminates the transfer
before actual execution started.

Driver has to handle these error for recovering ICE-HCI from
these error conditions. This patch is for handling there errors
in cmdq mode.

Change-Id: Ia10075aca429cbc49ccccdae5c0499c7a176b8a2
Signed-off-by: Veerabhadrarao Badiganti <vbadigan@codeaurora.org>
diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c
index fc7f95d..82ff1a5 100644
--- a/drivers/mmc/host/cmdq_hci.c
+++ b/drivers/mmc/host/cmdq_hci.c
@@ -145,6 +145,29 @@
 	mb();
 }
 
+static int cmdq_clear_task_poll(struct cmdq_host *cq_host, unsigned int tag)
+{
+	int retries = 100;
+
+	cmdq_clear_set_irqs(cq_host, CQIS_TCL, 0);
+	cmdq_writel(cq_host, 1<<tag, CQTCLR);
+	while (retries) {
+		/*
+		 * Task Clear register and doorbell,
+		 * both should indicate that task is cleared
+		 */
+		if ((cmdq_readl(cq_host, CQTCLR) & 1<<tag) ||
+			(cmdq_readl(cq_host, CQTDBR) & 1<<tag)) {
+			udelay(5);
+			retries--;
+			continue;
+		} else
+			break;
+	}
+
+	cmdq_clear_set_irqs(cq_host, 0, CQIS_TCL);
+	return retries ? 0 : -ETIMEDOUT;
+}
 
 #define DRV_NAME "cmdq-host"
 
@@ -843,6 +866,8 @@
 	struct mmc_request *mrq;
 	int ret;
 	u32 dbr_set = 0;
+	u32 dev_pend_set = 0;
+	int stat_err = 0;
 
 	status = cmdq_readl(cq_host, CQIS);
 
@@ -851,7 +876,9 @@
 	MMC_TRACE(mmc, "%s: CQIS: 0x%x err: %d\n",
 		__func__, status, err);
 
-	if (err || (status & CQIS_RED)) {
+	stat_err = status & (CQIS_RED | CQIS_GCE | CQIS_ICCE);
+
+	if (err || stat_err) {
 		err_info = cmdq_readl(cq_host, CQTERRI);
 		pr_err("%s: err: %d status: 0x%08x task-err-info (0x%08lx)\n",
 		       mmc_hostname(mmc), err, status, err_info);
@@ -954,7 +981,7 @@
 		 * CQE detected a response error from device
 		 * In most cases, this would require a reset.
 		 */
-		if (status & CQIS_RED) {
+		if (stat_err & CQIS_RED) {
 			/*
 			 * will check if the RED error is due to a bkops
 			 * exception once the queue is empty
@@ -973,6 +1000,62 @@
 			mrq->cmdq_req->resp_arg = cmdq_readl(cq_host, CQCRA);
 		}
 
+		/*
+		 * Generic Crypto error detected by CQE.
+		 * Its a fatal, would require cmdq reset.
+		 */
+		if (stat_err & CQIS_GCE) {
+			if (mrq->data)
+				mrq->data->error = -EIO;
+			pr_err("%s: Crypto generic error while processing task %lu!",
+				mmc_hostname(mmc), tag);
+			MMC_TRACE(mmc, "%s: GCE error detected with tag %lu\n",
+					__func__, tag);
+		}
+		/*
+		 * Invalid crypto config error detected by CQE, clear the task.
+		 * Task can be cleared only when CQE is halt state.
+		 */
+		if (stat_err & CQIS_ICCE) {
+			/*
+			 * Invalid Crypto Config Error is detected at the
+			 * beginning of the transfer before the actual execution
+			 * started. So just clear the task in CQE. No need to
+			 * clear in device. Only the task which caused ICCE has
+			 * to be cleared. Other tasks can be continue processing
+			 * The first task which is about to be prepared would
+			 * cause ICCE Error.
+			 */
+			dbr_set = cmdq_readl(cq_host, CQTDBR);
+			dev_pend_set = cmdq_readl(cq_host, CQDPT);
+			if (dbr_set ^ dev_pend_set)
+				tag = ffs(dbr_set ^ dev_pend_set) - 1;
+			mrq = get_req_by_tag(cq_host, tag);
+			pr_err("%s: Crypto config error while processing task %lu!",
+				mmc_hostname(mmc), tag);
+			MMC_TRACE(mmc, "%s: ICCE error with tag %lu\n",
+						__func__, tag);
+			if (mrq->data)
+				mrq->data->error = -EIO;
+			else if (mrq->cmd)
+				mrq->cmd->error = -EIO;
+			/*
+			 * If CQE is halted and tag is valid then clear the task
+			 * then un-halt CQE and set flag to skip error recovery.
+			 * If any of the condtions is not met thene it will
+			 * enter into default error recovery path.
+			 */
+			if (!ret && (dbr_set ^ dev_pend_set)) {
+				ret = cmdq_clear_task_poll(cq_host, tag);
+				if (ret) {
+					pr_err("%s: %s: task[%lu] clear failed ret=%d\n",
+						mmc_hostname(mmc),
+						__func__, tag, ret);
+				} else if (!cmdq_halt_poll(mmc, false)) {
+					mrq->cmdq_req->skip_err_handling = true;
+				}
+			}
+		}
 		cmdq_finish_data(mmc, tag);
 	} else {
 		cmdq_writel(cq_host, status, CQIS);
@@ -1043,6 +1126,7 @@
 			cq_host->ops->clear_set_irqs(mmc, true);
 		cmdq_writel(cq_host, cmdq_readl(cq_host, CQCTL) & ~HALT,
 			    CQCTL);
+		mmc_host_clr_halt(mmc);
 		return 0;
 	}
 
diff --git a/drivers/mmc/host/cmdq_hci.h b/drivers/mmc/host/cmdq_hci.h
index 4aa6ceb..551a7c5 100644
--- a/drivers/mmc/host/cmdq_hci.h
+++ b/drivers/mmc/host/cmdq_hci.h
@@ -37,6 +37,8 @@
 #define CQIS_TCC	(1 << 1)
 #define CQIS_RED	(1 << 2)
 #define CQIS_TCL	(1 << 3)
+#define CQIS_GCE	(1 << 4)
+#define CQIS_ICCE	(1 << 5)
 
 /* interrupt status enable */
 #define CQISTE		0x14
@@ -112,7 +114,7 @@
 /* command response argument */
 #define CQCRA		0x5C
 
-#define CQ_INT_ALL	0xF
+#define CQ_INT_ALL	0x3F
 #define CQIC_DEFAULT_ICCTH 31
 #define CQIC_DEFAULT_ICTOVAL 1