summaryrefslogtreecommitdiff
path: root/drivers/ata/ahci_xgene.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/ata/ahci_xgene.c')
-rw-r--r--drivers/ata/ahci_xgene.c177
1 files changed, 176 insertions, 1 deletions
diff --git a/drivers/ata/ahci_xgene.c b/drivers/ata/ahci_xgene.c
index feeb8f1e2fe8..79d83c1ef50d 100644
--- a/drivers/ata/ahci_xgene.c
+++ b/drivers/ata/ahci_xgene.c
@@ -81,16 +81,138 @@
/* Max retry for link down */
#define MAX_LINK_DOWN_RETRY 3
+#define RXTX_REG7 0x00e
+#define RXTX_REG7_RESETB_RXD_MASK 0x00000100
+#define RXTX_REG7_RESETB_RXA_MASK 0x00000080
+#define SATA_ENET_SDS_IND_CMD_REG 0x0000003c
+#define CFG_IND_WR_CMD_MASK 0x00000001
+#define CFG_IND_RD_CMD_MASK 0x00000002
+#define CFG_IND_CMD_DONE_MASK 0x00000004
+#define CFG_IND_ADDR_SET(dst, src) \
+ (((dst) & ~0x003ffff0) | (((u32) (src) << 4) & 0x003ffff0))
+#define SATA_ENET_SDS_IND_RDATA_REG 0x00000040
+#define SATA_ENET_SDS_IND_WDATA_REG 0x00000044
+#define SERDES_PLL_INDIRECT_OFFSET 0x0000
+#define SERDES_PLL_REF_INDIRECT_OFFSET 0x20000
+#define SERDES_INDIRECT_OFFSET 0x0400
+#define SERDES_LANE_STRIDE 0x0200
+#define SERDES_LANE_X4_STRIDE 0x30000
+
struct xgene_ahci_context {
struct ahci_host_priv *hpriv;
struct device *dev;
u8 last_cmd[MAX_AHCI_CHN_PERCTR]; /* tracking the last command issued*/
void __iomem *csr_core; /* Core CSR address of IP */
+ void __iomem *csr_sds; /* serdes CSR address of IP */
void __iomem *csr_diag; /* Diag CSR address of IP */
void __iomem *csr_axi; /* AXI CSR address of IP */
void __iomem *csr_mux; /* MUX CSR address of IP */
};
+static void ahci_sds_wr(void __iomem *csr_base, u32 indirect_cmd_reg,
+ u32 indirect_data_reg, u32 addr, u32 data)
+{
+ unsigned long deadline = jiffies + HZ;
+ u32 val;
+ u32 cmd;
+
+ cmd = CFG_IND_WR_CMD_MASK | CFG_IND_CMD_DONE_MASK;
+ cmd = CFG_IND_ADDR_SET(cmd, addr);
+ writel(data, csr_base + indirect_data_reg);
+ readl(csr_base + indirect_data_reg); /* Force a barrier */
+ writel(cmd, csr_base + indirect_cmd_reg);
+ readl(csr_base + indirect_cmd_reg); /* Force a barrier */
+ do {
+ val = readl(csr_base + indirect_cmd_reg);
+ } while (!(val & CFG_IND_CMD_DONE_MASK)&&
+ time_before(jiffies, deadline));
+ if (!(val & CFG_IND_CMD_DONE_MASK))
+ pr_err("SDS WR timeout at 0x%p offset 0x%08X value 0x%08X\n",
+ csr_base + indirect_cmd_reg, addr, data);
+}
+
+static void ahci_sds_rd(void __iomem *csr_base, u32 indirect_cmd_reg,
+ u32 indirect_data_reg, u32 addr, u32 *data)
+{
+ unsigned long deadline = jiffies + HZ;
+ u32 val;
+ u32 cmd;
+
+ cmd = CFG_IND_RD_CMD_MASK | CFG_IND_CMD_DONE_MASK;
+ cmd = CFG_IND_ADDR_SET(cmd, addr);
+ writel(cmd, csr_base + indirect_cmd_reg);
+ readl(csr_base + indirect_cmd_reg); /* Force a barrier */
+ do {
+ val = readl(csr_base + indirect_cmd_reg);
+ } while (!(val & CFG_IND_CMD_DONE_MASK)&&
+ time_before(jiffies, deadline));
+ *data = readl(csr_base + indirect_data_reg);
+ if (!(val & CFG_IND_CMD_DONE_MASK))
+ pr_err("SDS WR timeout at 0x%p offset 0x%08X value 0x%08X\n",
+ csr_base + indirect_cmd_reg, addr, *data);
+}
+
+static void ahci_serdes_wr(struct xgene_ahci_context *ctx, int lane,
+ u32 reg, u32 data)
+{
+ void __iomem *sds_base = ctx->csr_sds;
+ u32 cmd_reg;
+ u32 wr_reg;
+ u32 rd_reg;
+ u32 val;
+
+ cmd_reg = SATA_ENET_SDS_IND_CMD_REG;
+ wr_reg = SATA_ENET_SDS_IND_WDATA_REG;
+ rd_reg = SATA_ENET_SDS_IND_RDATA_REG;
+
+ reg += (lane / 4) * SERDES_LANE_X4_STRIDE;
+ reg += SERDES_INDIRECT_OFFSET;
+ reg += (lane % 4) * SERDES_LANE_STRIDE;
+ ahci_sds_wr(sds_base, cmd_reg, wr_reg, reg, data);
+ ahci_sds_rd(sds_base, cmd_reg, rd_reg, reg, &val);
+ pr_debug("SERDES WR addr 0x%X value 0x%08X <-> 0x%08X\n", reg, data,
+ val);
+}
+
+static void ahci_serdes_rd(struct xgene_ahci_context *ctx, int lane,
+ u32 reg, u32 *data)
+{
+ void __iomem *sds_base = ctx->csr_sds;
+ u32 cmd_reg;
+ u32 rd_reg;
+
+ cmd_reg = SATA_ENET_SDS_IND_CMD_REG;
+ rd_reg = SATA_ENET_SDS_IND_RDATA_REG;
+
+ reg += (lane / 4) * SERDES_LANE_X4_STRIDE;
+ reg += SERDES_INDIRECT_OFFSET;
+ reg += (lane % 4) * SERDES_LANE_STRIDE;
+ ahci_sds_rd(sds_base, cmd_reg, rd_reg, reg, data);
+ pr_debug("SERDES RD addr 0x%X value 0x%08X\n", reg, *data);
+}
+
+static void ahci_serdes_clrbits(struct xgene_ahci_context *ctx, int lane,
+ u32 reg, u32 bits)
+{
+ u32 val;
+
+ ahci_serdes_rd(ctx, lane, reg, &val);
+ val &= ~bits;
+ ahci_serdes_wr(ctx, lane, reg, val);
+}
+
+static void ahci_serdes_setbits(struct xgene_ahci_context *ctx, int lane,
+ u32 reg, u32 bits)
+{
+ u32 val;
+
+ ahci_serdes_rd(ctx, lane, reg, &val);
+ val |= bits;
+ ahci_serdes_wr(ctx, lane, reg, val);
+}
+
+
+
static int xgene_ahci_init_memram(struct xgene_ahci_context *ctx)
{
dev_dbg(ctx->dev, "Release memory from shutdown\n");
@@ -229,6 +351,30 @@ static void xgene_ahci_set_phy_cfg(struct xgene_ahci_context *ctx, int channel)
writel(val, mmio + PORTRANSCFG);
}
+static void xgene_ahci_force_port_phy_rdy(struct xgene_ahci_context *ctx,
+ int channel, int force)
+{
+ void __iomem *mmio = ctx->hpriv->mmio;
+ u32 val;
+
+ val = readl(mmio + PORTCFG);
+ val = PORTADDR_SET(val, channel == 0 ? 2 : 3);
+ writel(val, mmio + PORTCFG);
+ readl(mmio + PORTCFG); /* Force a barrier */
+ val = readl(mmio + PORTPHY1CFG);
+ val = PORTPHY1CFG_FRCPHYRDY_SET(val, force);
+ writel(val, mmio + PORTPHY1CFG);
+}
+
+static void xgene_ahci_phy_reset_rxd(struct xgene_ahci_context *ctx, int lane)
+{
+ /* Reset digital Rx */
+ ahci_serdes_clrbits(ctx, lane, RXTX_REG7, RXTX_REG7_RESETB_RXD_MASK);
+ /* As per PHY design spec, the reset requires a minimum of 100us. */
+ usleep_range(100, 150);
+ ahci_serdes_setbits(ctx, lane, RXTX_REG7, RXTX_REG7_RESETB_RXD_MASK);
+}
+
/**
* xgene_ahci_do_hardreset - Issue the actual COMRESET
* @link: link to reset
@@ -290,6 +436,7 @@ static int xgene_ahci_do_hardreset(struct ata_link *link,
int link_down_retry = 0;
int rc;
u32 val, sstatus;
+ int i;
do {
/* clear D2H reception area to properly wait for D2H FIS */
@@ -309,6 +456,28 @@ static int xgene_ahci_do_hardreset(struct ata_link *link,
} while (link_down_retry++ < MAX_LINK_DOWN_RETRY &&
(sstatus & 0xff) == 0x1);
+ if (*online) {
+ /* Clear SER_DISPARITY/SER_10B_8B_ERR if set due to errata */
+ for (i = 0; i < 5; i++) {
+ /* Check if error bit set */
+ val = readl(port_mmio + PORT_SCR_ERR);
+ if (!(val & (SERR_DISPARITY | SERR_10B_8B_ERR)))
+ break;
+ /* Clear any error due to errata */
+ xgene_ahci_force_port_phy_rdy(ctx, ap->port_no, 1);
+ /* Reset the PHY Rx path */
+ xgene_ahci_phy_reset_rxd(ctx, ap->port_no);
+ xgene_ahci_force_port_phy_rdy(ctx, ap->port_no, 0);
+ /* Clear all errors */
+ val = readl(port_mmio + PORT_SCR_ERR);
+ writel(val, port_mmio + PORT_SCR_ERR);
+ }
+ }
+
+ val = readl(port_mmio + PORT_SCR_ERR);
+ if (val & (SERR_DISPARITY | SERR_10B_8B_ERR))
+ dev_warn(ctx->dev, "link has error\n");
+
/* clear all errors if any pending */
val = readl(port_mmio + PORT_SCR_ERR);
writel(val, port_mmio + PORT_SCR_ERR);
@@ -482,8 +651,14 @@ static int xgene_ahci_probe(struct platform_device *pdev)
if (IS_ERR(ctx->csr_axi))
return PTR_ERR(ctx->csr_axi);
- /* Retrieve the optional IP mux resource */
+ /* Retrive the serdes csr resource */
res = platform_get_resource(pdev, IORESOURCE_MEM, 4);
+ ctx->csr_sds = devm_ioremap(dev, res->start, resource_size(res));
+ if (IS_ERR(ctx->csr_sds))
+ return PTR_ERR(ctx->csr_sds);
+
+ /* Retrieve the optional IP mux resource */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 5);
if (res) {
void __iomem *csr = devm_ioremap_resource(dev, res);
if (IS_ERR(csr))