fg-memif: update IMA error handling and clear sequence

Based on the hardware documentation, update the IMA error
handling and clear sequence. In addition, check for DMA errors
and clear it before SRAM transactions begin. Also, check for IMA
hardware status to run the IMA clear sequence during ima_init and
not just based on IMA exception status alone. This is to help
with FG SRAM access to resume again properly in case of an error
encountered.

Change-Id: I583fa51599a1cbbd029cb45c075429730d2e071b
Signed-off-by: Subbaraman Narayanamurthy <subbaram@codeaurora.org>
diff --git a/drivers/power/qcom-charger/fg-core.h b/drivers/power/qcom-charger/fg-core.h
index 3f8f66f..2167eb8 100644
--- a/drivers/power/qcom-charger/fg-core.h
+++ b/drivers/power/qcom-charger/fg-core.h
@@ -342,6 +342,8 @@
 extern int fg_write(struct fg_chip *chip, int addr, u8 *val, int len);
 extern int fg_masked_write(struct fg_chip *chip, int addr, u8 mask, u8 val);
 extern int fg_ima_init(struct fg_chip *chip);
+extern int fg_clear_ima_errors_if_any(struct fg_chip *chip, bool check_hw_sts);
+extern int fg_clear_dma_errors_if_any(struct fg_chip *chip);
 extern int fg_debugfs_create(struct fg_chip *chip);
 extern void fill_string(char *str, size_t str_len, u8 *buf, int buf_len);
 extern int64_t twos_compliment_extend(int64_t val, int s_bit_pos);
diff --git a/drivers/power/qcom-charger/fg-memif.c b/drivers/power/qcom-charger/fg-memif.c
index c271b24..88c96bc 100644
--- a/drivers/power/qcom-charger/fg-memif.c
+++ b/drivers/power/qcom-charger/fg-memif.c
@@ -64,44 +64,90 @@
 
 static int fg_run_iacs_clear_sequence(struct fg_chip *chip)
 {
-	u8 tmp;
-	int rc;
+	u8 val, hw_sts, exp_sts;
+	int rc, tries = 250;
 
 	/*
 	 * Values to write for running IACS clear sequence comes from
 	 * hardware documentation.
 	 */
-	rc = fg_masked_write(chip, MEM_IF_IMA_CFG(chip), IACS_CLR_BIT,
-				IACS_CLR_BIT);
+	rc = fg_masked_write(chip, MEM_IF_IMA_CFG(chip),
+			IACS_CLR_BIT | STATIC_CLK_EN_BIT,
+			IACS_CLR_BIT | STATIC_CLK_EN_BIT);
 	if (rc < 0) {
 		pr_err("failed to write 0x%04x, rc=%d\n", MEM_IF_IMA_CFG(chip),
 			rc);
 		return rc;
 	}
 
-	tmp = 0x4;
-	rc = fg_write(chip, MEM_IF_ADDR_MSB(chip), &tmp, 1);
+	rc = fg_config_access_mode(chip, FG_READ, false);
 	if (rc < 0) {
-		pr_err("failed to write 0x%04x, rc=%d\n", MEM_IF_ADDR_LSB(chip),
-			rc);
+		pr_err("failed to write to 0x%04x, rc=%d\n",
+			MEM_IF_IMA_CTL(chip), rc);
 		return rc;
 	}
 
-	tmp = 0x0;
-	rc = fg_write(chip, MEM_IF_WR_DATA3(chip), &tmp, 1);
+	rc = fg_masked_write(chip, MEM_IF_MEM_INTF_CFG(chip),
+				MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT,
+				MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT);
 	if (rc < 0) {
-		pr_err("failed to write 0x%04x, rc=%d\n", MEM_IF_WR_DATA3(chip),
-			rc);
+		pr_err("failed to set ima_req_access bit rc=%d\n", rc);
 		return rc;
 	}
 
-	rc = fg_read(chip, MEM_IF_RD_DATA3(chip), &tmp, 1);
-	if (rc < 0) {
-		pr_err("failed to read 0x%04x, rc=%d\n", MEM_IF_RD_DATA3(chip),
-			rc);
-		return rc;
+	/* Delay for the clock to reach FG */
+	usleep_range(35, 40);
+
+	while (1) {
+		val = 0;
+		rc = fg_write(chip, MEM_IF_ADDR_MSB(chip), &val, 1);
+		if (rc < 0) {
+			pr_err("failed to write 0x%04x, rc=%d\n",
+				MEM_IF_ADDR_MSB(chip), rc);
+			return rc;
+		}
+
+		val = 0;
+		rc = fg_write(chip, MEM_IF_WR_DATA3(chip), &val, 1);
+		if (rc < 0) {
+			pr_err("failed to write 0x%04x, rc=%d\n",
+				MEM_IF_WR_DATA3(chip), rc);
+			return rc;
+		}
+
+		rc = fg_read(chip, MEM_IF_RD_DATA3(chip), &val, 1);
+		if (rc < 0) {
+			pr_err("failed to read 0x%04x, rc=%d\n",
+				MEM_IF_RD_DATA3(chip), rc);
+			return rc;
+		}
+
+		/* Delay for IMA hardware to clear */
+		usleep_range(35, 40);
+
+		rc = fg_read(chip, MEM_IF_IMA_HW_STS(chip), &hw_sts, 1);
+		if (rc < 0) {
+			pr_err("failed to read ima_hw_sts rc=%d\n", rc);
+			return rc;
+		}
+
+		if (hw_sts != 0)
+			continue;
+
+		rc = fg_read(chip, MEM_IF_IMA_EXP_STS(chip), &exp_sts, 1);
+		if (rc < 0) {
+			pr_err("failed to read ima_exp_sts rc=%d\n", rc);
+			return rc;
+		}
+
+		if (exp_sts == 0 || !(--tries))
+			break;
 	}
 
+	if (!tries)
+		pr_err("Failed to clear the error? hw_sts: %x exp_sts: %d\n",
+			hw_sts, exp_sts);
+
 	rc = fg_masked_write(chip, MEM_IF_IMA_CFG(chip), IACS_CLR_BIT, 0);
 	if (rc < 0) {
 		pr_err("failed to write 0x%04x, rc=%d\n", MEM_IF_IMA_CFG(chip),
@@ -109,14 +155,65 @@
 		return rc;
 	}
 
+	udelay(5);
+
+	rc = fg_masked_write(chip, MEM_IF_MEM_INTF_CFG(chip),
+				MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT, 0);
+	if (rc < 0) {
+		pr_err("failed to write to 0x%04x, rc=%d\n",
+			MEM_IF_MEM_INTF_CFG(chip), rc);
+		return rc;
+	}
+
+	/* Delay before next transaction is attempted */
+	usleep_range(35, 40);
 	fg_dbg(chip, FG_SRAM_READ | FG_SRAM_WRITE, "IACS clear sequence complete\n");
 	return rc;
 }
 
-static int fg_check_for_ima_errors(struct fg_chip *chip)
+int fg_clear_dma_errors_if_any(struct fg_chip *chip)
+{
+	int rc;
+	u8 dma_sts;
+
+	rc = fg_read(chip, MEM_IF_DMA_STS(chip), &dma_sts, 1);
+	if (rc < 0) {
+		pr_err("failed to read addr=0x%04x, rc=%d\n",
+			MEM_IF_DMA_STS(chip), rc);
+		return rc;
+	}
+	fg_dbg(chip, FG_STATUS, "dma_sts: %x\n", dma_sts);
+
+	if (dma_sts & (DMA_WRITE_ERROR_BIT | DMA_READ_ERROR_BIT)) {
+		rc = fg_masked_write(chip, MEM_IF_DMA_CTL(chip),
+				DMA_CLEAR_LOG_BIT, DMA_CLEAR_LOG_BIT);
+		if (rc < 0) {
+			pr_err("failed to write addr=0x%04x, rc=%d\n",
+				MEM_IF_DMA_CTL(chip), rc);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+int fg_clear_ima_errors_if_any(struct fg_chip *chip, bool check_hw_sts)
 {
 	int rc = 0;
 	u8 err_sts, exp_sts = 0, hw_sts = 0;
+	bool run_err_clr_seq = false;
+
+	rc = fg_read(chip, MEM_IF_IMA_EXP_STS(chip), &exp_sts, 1);
+	if (rc < 0) {
+		pr_err("failed to read ima_exp_sts rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = fg_read(chip, MEM_IF_IMA_HW_STS(chip), &hw_sts, 1);
+	if (rc < 0) {
+		pr_err("failed to read ima_hw_sts rc=%d\n", rc);
+		return rc;
+	}
 
 	rc = fg_read(chip, MEM_IF_IMA_ERR_STS(chip), &err_sts, 1);
 	if (rc < 0) {
@@ -124,22 +221,30 @@
 		return rc;
 	}
 
-	if (err_sts & (ADDR_STBL_ERR_BIT | WR_ACS_ERR_BIT | RD_ACS_ERR_BIT)) {
-		rc = fg_read(chip, MEM_IF_IMA_EXP_STS(chip), &exp_sts, 1);
-		if (rc < 0) {
-			pr_err("failed to read ima_exp_sts rc=%d\n", rc);
-			return rc;
+	fg_dbg(chip, FG_STATUS, "ima_err_sts=%x ima_exp_sts=%x ima_hw_sts=%x\n",
+		err_sts, exp_sts, hw_sts);
+
+	if (check_hw_sts) {
+		/*
+		 * Lower nibble should be equal to upper nibble before SRAM
+		 * transactions begins from SW side. If they are unequal, then
+		 * the error clear sequence should be run irrespective of IMA
+		 * exception errors.
+		 */
+		if ((hw_sts & 0x0F) != hw_sts >> 4) {
+			pr_err("IMA HW not in correct state, hw_sts=%x\n",
+				hw_sts);
+			run_err_clr_seq = true;
 		}
+	}
 
-		rc = fg_read(chip, MEM_IF_IMA_HW_STS(chip), &hw_sts, 1);
-		if (rc < 0) {
-			pr_err("failed to read ima_hw_sts rc=%d\n", rc);
-			return rc;
-		}
+	if (exp_sts & (IACS_ERR_BIT | XCT_TYPE_ERR_BIT | DATA_RD_ERR_BIT |
+		DATA_WR_ERR_BIT | ADDR_BURST_WRAP_BIT | ADDR_STABLE_ERR_BIT)) {
+		pr_err("IMA exception bit set, exp_sts=%x\n", exp_sts);
+		run_err_clr_seq = true;
+	}
 
-		pr_err("ima_err_sts=%x ima_exp_sts=%x ima_hw_sts=%x\n",
-			err_sts, exp_sts, hw_sts);
-
+	if (run_err_clr_seq) {
 		/* clear the error */
 		rc = fg_run_iacs_clear_sequence(chip);
 		if (rc < 0) {
@@ -156,7 +261,7 @@
 
 static int fg_check_iacs_ready(struct fg_chip *chip)
 {
-	int rc = 0, timeout = 250;
+	int rc = 0, tries = 250;
 	u8 ima_opr_sts = 0;
 
 	/*
@@ -176,17 +281,17 @@
 		if (ima_opr_sts & IACS_RDY_BIT)
 			break;
 
-		if (!(--timeout))
+		if (!(--tries))
 			break;
 
 		/* delay for iacs_ready to be asserted */
 		usleep_range(5000, 7000);
 	}
 
-	if (!timeout) {
+	if (!tries) {
 		pr_err("IACS_RDY not set\n");
-
-		rc = fg_check_for_ima_errors(chip);
+		/* check for error condition */
+		rc = fg_clear_ima_errors_if_any(chip, false);
 		if (rc < 0) {
 			pr_err("Failed to check for ima errors rc=%d\n", rc);
 			return rc;
@@ -250,7 +355,7 @@
 		}
 
 		/* check for error condition */
-		rc = fg_check_for_ima_errors(chip);
+		rc = fg_clear_ima_errors_if_any(chip, false);
 		if (rc < 0) {
 			pr_err("Failed to check for ima errors rc=%d\n", rc);
 			return rc;
@@ -296,7 +401,7 @@
 		offset = 0;
 
 		/* check for error condition */
-		rc = fg_check_for_ima_errors(chip);
+		rc = fg_clear_ima_errors_if_any(chip, false);
 		if (rc < 0) {
 			pr_err("Failed to check for ima errors rc=%d\n", rc);
 			return rc;
@@ -581,5 +686,19 @@
 		return rc;
 	}
 
+	/* Clear DMA errors if any before clearing IMA errors */
+	rc = fg_clear_dma_errors_if_any(chip);
+	if (rc < 0) {
+		pr_err("Error in checking DMA errors rc:%d\n", rc);
+		return rc;
+	}
+
+	/* Clear IMA errors if any before SRAM transactions can begin */
+	rc = fg_clear_ima_errors_if_any(chip, true);
+	if (rc < 0 && rc != -EAGAIN) {
+		pr_err("Error in checking IMA errors rc:%d\n", rc);
+		return rc;
+	}
+
 	return 0;
 }
diff --git a/drivers/power/qcom-charger/fg-reg.h b/drivers/power/qcom-charger/fg-reg.h
index 431e28a..183a181 100644
--- a/drivers/power/qcom-charger/fg-reg.h
+++ b/drivers/power/qcom-charger/fg-reg.h
@@ -258,6 +258,7 @@
 #define ESR_REQ_CTL_EN_BIT			BIT(0)
 
 /* FG_MEM_IF register and bit definitions */
+#define MEM_IF_INT_RT_STS(chip)			((chip->mem_if_base) + 0x10)
 #define MEM_IF_MEM_INTF_CFG(chip)		((chip->mem_if_base) + 0x50)
 #define MEM_IF_IMA_CTL(chip)			((chip->mem_if_base) + 0x51)
 #define MEM_IF_IMA_CFG(chip)			((chip->mem_if_base) + 0x52)
@@ -273,6 +274,11 @@
 #define MEM_IF_WR_DATA3(chip)			((chip->mem_if_base) + 0x66)
 #define MEM_IF_RD_DATA0(chip)			((chip->mem_if_base) + 0x67)
 #define MEM_IF_RD_DATA3(chip)			((chip->mem_if_base) + 0x6A)
+#define MEM_IF_DMA_STS(chip)			((chip->mem_if_base) + 0x70)
+#define MEM_IF_DMA_CTL(chip)			((chip->mem_if_base) + 0x71)
+
+/* MEM_IF_INT_RT_STS */
+#define MEM_XCP_BIT				BIT(1)
 
 /* MEM_IF_MEM_INTF_CFG */
 #define MEM_ACCESS_REQ_BIT			BIT(7)
@@ -286,10 +292,19 @@
 /* MEM_IF_IMA_CFG */
 #define IACS_CLR_BIT				BIT(2)
 #define IACS_INTR_SRC_SLCT_BIT			BIT(3)
+#define STATIC_CLK_EN_BIT			BIT(4)
 
 /* MEM_IF_IMA_OPR_STS */
 #define IACS_RDY_BIT				BIT(1)
 
+/* MEM_IF_IMA_EXP_STS */
+#define IACS_ERR_BIT				BIT(0)
+#define XCT_TYPE_ERR_BIT			BIT(1)
+#define DATA_RD_ERR_BIT				BIT(3)
+#define DATA_WR_ERR_BIT				BIT(4)
+#define ADDR_BURST_WRAP_BIT			BIT(5)
+#define ADDR_STABLE_ERR_BIT			BIT(7)
+
 /* MEM_IF_IMA_ERR_STS */
 #define ADDR_STBL_ERR_BIT			BIT(7)
 #define WR_ACS_ERR_BIT				BIT(6)
@@ -297,4 +312,11 @@
 
 /* MEM_IF_FG_BEAT_COUNT */
 #define BEAT_COUNT_MASK				GENMASK(3, 0)
+
+/* MEM_IF_DMA_STS */
+#define DMA_WRITE_ERROR_BIT			BIT(1)
+#define DMA_READ_ERROR_BIT			BIT(2)
+
+/* MEM_IF_DMA_CTL */
+#define DMA_CLEAR_LOG_BIT			BIT(0)
 #endif
diff --git a/drivers/power/qcom-charger/qpnp-fg-gen3.c b/drivers/power/qcom-charger/qpnp-fg-gen3.c
index 0f13c45..da4b548 100644
--- a/drivers/power/qcom-charger/qpnp-fg-gen3.c
+++ b/drivers/power/qcom-charger/qpnp-fg-gen3.c
@@ -2267,6 +2267,37 @@
 
 /* INTERRUPT HANDLERS STAY HERE */
 
+static irqreturn_t fg_mem_xcp_irq_handler(int irq, void *data)
+{
+	struct fg_chip *chip = data;
+	u8 status;
+	int rc;
+
+	rc = fg_read(chip, MEM_IF_INT_RT_STS(chip), &status, 1);
+	if (rc < 0) {
+		pr_err("failed to read addr=0x%04x, rc=%d\n",
+			MEM_IF_INT_RT_STS(chip), rc);
+		return IRQ_HANDLED;
+	}
+
+	fg_dbg(chip, FG_IRQ, "irq %d triggered, status:%d\n", irq, status);
+	if (status & MEM_XCP_BIT) {
+		rc = fg_clear_dma_errors_if_any(chip);
+		if (rc < 0) {
+			pr_err("Error in clearing DMA error, rc=%d\n", rc);
+			return IRQ_HANDLED;
+		}
+
+		mutex_lock(&chip->sram_rw_lock);
+		rc = fg_clear_ima_errors_if_any(chip, true);
+		if (rc < 0 && rc != -EAGAIN)
+			pr_err("Error in checking IMA errors rc:%d\n", rc);
+		mutex_unlock(&chip->sram_rw_lock);
+	}
+
+	return IRQ_HANDLED;
+}
+
 static irqreturn_t fg_vbatt_low_irq_handler(int irq, void *data)
 {
 	struct fg_chip *chip = data;
@@ -2483,7 +2514,7 @@
 	},
 	[MEM_XCP_IRQ] = {
 		.name		= "mem-xcp",
-		.handler	= fg_dummy_irq_handler,
+		.handler	= fg_mem_xcp_irq_handler,
 	},
 	[IMA_RDY_IRQ] = {
 		.name		= "ima-rdy",