From 73f7122003fca0d08142370e5b6c25783a7b43e9 Mon Sep 17 00:00:00 2001
From: Peng Ma <peng.ma@nxp.com>
Date: Wed, 15 May 2019 05:52:44 +0000
Subject: [PATCH] ahci: qoriq: workaround for errata A-379364 on lx2160a

There is a erratum on lx2160a which is: "SATA link is
going down sometime during sata initialization"
The workaround for it is to reset the lane. This patch
implements this workaround.
This erratum only exists on lx2160 Rev1, will be addressed
on Rev2 and later.

Signed-off-by: Peng Ma <peng.ma@nxp.com>
---
 drivers/ata/ahci_qoriq.c | 144 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 144 insertions(+)

--- a/drivers/ata/ahci_qoriq.c
+++ b/drivers/ata/ahci_qoriq.c
@@ -48,6 +48,27 @@
 #define ECC_DIS_ARMV8_CH2	0x80000000
 #define ECC_DIS_LS1088A		0x40000000
 
+/* errata for lx2160 */
+#define RCWSR29_BASE			0x1E00170
+#define SERDES2_BASE			0x1EB0000
+#define DEVICE_CONFIG_REG_BASE		0x1E00000
+#define SERDES2_LNAX_RX_CR(x)		(0x840 + (0x100 * (x)))
+#define SERDES2_LNAX_RX_CBR(x)		(0x8C0 + (0x100 * (x)))
+#define SYS_VER_REG			0xA4
+#define LN_RX_RST			0x80000010
+#define LN_RX_RST_DONE			0x3
+#define LN_RX_MASK			0xf
+#define LX2160A_VER1			0x1
+
+#define SERDES2_LNAA 0
+#define SERDES2_LNAB 1
+#define SERDES2_LNAC 2
+#define SERDES2_LNAD 3
+#define SERDES2_LNAE 4
+#define SERDES2_LNAF 5
+#define SERDES2_LNAG 6
+#define SERDES2_LNAH 7
+
 enum ahci_qoriq_type {
 	AHCI_LS1021A,
 	AHCI_LS1028A,
@@ -87,6 +108,126 @@ static const struct acpi_device_id ahci_
 };
 MODULE_DEVICE_TABLE(acpi, ahci_qoriq_acpi_match);
 
+static void fsl_sata_errata_379364(bool select)
+{
+	int val = 0;
+	void __iomem *rcw_base = NULL;
+	void __iomem *serdes_base = NULL;
+	void __iomem *dev_con_base = NULL;
+
+	if (select) {
+		dev_con_base = ioremap(DEVICE_CONFIG_REG_BASE, PAGE_SIZE);
+		if (!dev_con_base)
+			return;
+
+		val = (readl(dev_con_base + SYS_VER_REG) & GENMASK(7, 4)) >> 4;
+		if (val != LX2160A_VER1)
+			goto dev_unmap;
+
+		/*
+		 * Add few msec delay.
+		 * Check for corresponding serdes lane RST_DONE .
+		 * apply lane reset.
+		 */
+
+		serdes_base = ioremap(SERDES2_BASE, PAGE_SIZE);
+		if (!serdes_base)
+			goto dev_unmap;
+
+		rcw_base = ioremap(RCWSR29_BASE, PAGE_SIZE);
+		if (!rcw_base)
+			goto serdes_unmap;
+
+		msleep(20);
+
+		val = (readl(rcw_base) & GENMASK(25, 21)) >> 21;
+
+		switch (val) {
+		case 1:
+			if ((readl(serdes_base + SERDES2_LNAX_RX_CBR(SERDES2_LNAC)) &
+				LN_RX_MASK) != LN_RX_RST_DONE)
+				writel(LN_RX_RST, serdes_base +
+					SERDES2_LNAX_RX_CR(SERDES2_LNAC));
+			if ((readl(serdes_base + SERDES2_LNAX_RX_CBR(SERDES2_LNAD)) &
+				LN_RX_MASK) != LN_RX_RST_DONE)
+				writel(LN_RX_RST, serdes_base +
+					SERDES2_LNAX_RX_CR(SERDES2_LNAD));
+			break;
+
+		case 4:
+			if ((readl(serdes_base + SERDES2_LNAX_RX_CBR(SERDES2_LNAG)) &
+				LN_RX_MASK) != LN_RX_RST_DONE)
+				writel(LN_RX_RST, serdes_base +
+					SERDES2_LNAX_RX_CR(SERDES2_LNAG));
+			if ((readl(serdes_base + SERDES2_LNAX_RX_CBR(SERDES2_LNAH)) &
+				LN_RX_MASK) != LN_RX_RST_DONE)
+				writel(LN_RX_RST, serdes_base +
+					SERDES2_LNAX_RX_CR(SERDES2_LNAH));
+			break;
+
+		case 5:
+			if ((readl(serdes_base + SERDES2_LNAX_RX_CBR(SERDES2_LNAE)) &
+				LN_RX_MASK) != LN_RX_RST_DONE)
+				writel(LN_RX_RST, serdes_base +
+					SERDES2_LNAX_RX_CR(SERDES2_LNAE));
+			if ((readl(serdes_base + SERDES2_LNAX_RX_CBR(SERDES2_LNAF)) &
+				LN_RX_MASK) != LN_RX_RST_DONE)
+				writel(LN_RX_RST, serdes_base +
+					SERDES2_LNAX_RX_CR(SERDES2_LNAF));
+			if ((readl(serdes_base + SERDES2_LNAX_RX_CBR(SERDES2_LNAG)) &
+				LN_RX_MASK) != LN_RX_RST_DONE)
+				writel(LN_RX_RST, serdes_base +
+					SERDES2_LNAX_RX_CR(SERDES2_LNAG));
+			if ((readl(serdes_base + SERDES2_LNAX_RX_CBR(SERDES2_LNAH)) &
+				LN_RX_MASK) != LN_RX_RST_DONE)
+				writel(LN_RX_RST, serdes_base +
+					SERDES2_LNAX_RX_CR(SERDES2_LNAH));
+			break;
+
+		case 8:
+			if ((readl(serdes_base + SERDES2_LNAX_RX_CBR(SERDES2_LNAC)) &
+				LN_RX_MASK) != LN_RX_RST_DONE)
+				writel(LN_RX_RST, serdes_base +
+					SERDES2_LNAX_RX_CR(SERDES2_LNAC));
+			if ((readl(serdes_base + SERDES2_LNAX_RX_CBR(SERDES2_LNAD)) &
+				LN_RX_MASK) != LN_RX_RST_DONE)
+				writel(LN_RX_RST, serdes_base +
+					SERDES2_LNAX_RX_CR(SERDES2_LNAD));
+			if ((readl(serdes_base + SERDES2_LNAX_RX_CBR(SERDES2_LNAE)) &
+				LN_RX_MASK) != LN_RX_RST_DONE)
+				writel(LN_RX_RST, serdes_base +
+					SERDES2_LNAX_RX_CR(SERDES2_LNAE));
+			if ((readl(serdes_base + SERDES2_LNAX_RX_CBR(SERDES2_LNAF)) &
+				LN_RX_MASK) != LN_RX_RST_DONE)
+				writel(LN_RX_RST, serdes_base +
+					SERDES2_LNAX_RX_CR(SERDES2_LNAF));
+			break;
+
+		case 12:
+			if ((readl(serdes_base + SERDES2_LNAX_RX_CBR(SERDES2_LNAG)) &
+				LN_RX_MASK) != LN_RX_RST_DONE)
+				writel(LN_RX_RST, serdes_base +
+					SERDES2_LNAX_RX_CR(SERDES2_LNAG));
+			if ((readl(serdes_base + SERDES2_LNAX_RX_CBR(SERDES2_LNAH)) &
+				LN_RX_MASK) != LN_RX_RST_DONE)
+				writel(LN_RX_RST, serdes_base +
+					SERDES2_LNAX_RX_CR(SERDES2_LNAH));
+			break;
+
+		default:
+			break;
+		}
+	} else {
+		return;
+	}
+
+	iounmap(rcw_base);
+serdes_unmap:
+	iounmap(serdes_base);
+dev_unmap:
+	iounmap(dev_con_base);
+}
+
 static int ahci_qoriq_hardreset(struct ata_link *link, unsigned int *class,
 			  unsigned long deadline)
 {
@@ -102,6 +243,7 @@ static int ahci_qoriq_hardreset(struct a
 	bool online;
 	int rc;
 	bool ls1021a_workaround = (qoriq_priv->type == AHCI_LS1021A);
+	bool lx2160a_workaround = (qoriq_priv->type == AHCI_LX2160A);
 
 	DPRINTK("ENTER\n");
 
@@ -128,6 +270,8 @@ static int ahci_qoriq_hardreset(struct a
 	tf.command = ATA_BUSY;
 	ata_tf_to_fis(&tf, 0, 0, d2h_fis);
 
+	fsl_sata_errata_379364(lx2160a_workaround);
+
 	rc = sata_link_hardreset(link, timing, deadline, &online,
 				 ahci_check_ready);