|
@@ -0,0 +1,331 @@
|
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
|
+/*
|
|
|
+ * sdhci-pci-arasan.c - Driver for Arasan PCI Controller with
|
|
|
+ * integrated phy.
|
|
|
+ *
|
|
|
+ * Copyright (C) 2017 Arasan Chip Systems Inc.
|
|
|
+ *
|
|
|
+ * Author: Atul Garg <agarg@arasan.com>
|
|
|
+ */
|
|
|
+
|
|
|
+#include <linux/pci.h>
|
|
|
+#include <linux/delay.h>
|
|
|
+
|
|
|
+#include "sdhci.h"
|
|
|
+#include "sdhci-pci.h"
|
|
|
+
|
|
|
+/* Extra registers for Arasan SD/SDIO/MMC Host Controller with PHY */
|
|
|
+#define PHY_ADDR_REG 0x300
|
|
|
+#define PHY_DAT_REG 0x304
|
|
|
+
|
|
|
+#define PHY_WRITE BIT(8)
|
|
|
+#define PHY_BUSY BIT(9)
|
|
|
+#define DATA_MASK 0xFF
|
|
|
+
|
|
|
+/* PHY Specific Registers */
|
|
|
+#define DLL_STATUS 0x00
|
|
|
+#define IPAD_CTRL1 0x01
|
|
|
+#define IPAD_CTRL2 0x02
|
|
|
+#define IPAD_STS 0x03
|
|
|
+#define IOREN_CTRL1 0x06
|
|
|
+#define IOREN_CTRL2 0x07
|
|
|
+#define IOPU_CTRL1 0x08
|
|
|
+#define IOPU_CTRL2 0x09
|
|
|
+#define ITAP_DELAY 0x0C
|
|
|
+#define OTAP_DELAY 0x0D
|
|
|
+#define STRB_SEL 0x0E
|
|
|
+#define CLKBUF_SEL 0x0F
|
|
|
+#define MODE_CTRL 0x11
|
|
|
+#define DLL_TRIM 0x12
|
|
|
+#define CMD_CTRL 0x20
|
|
|
+#define DATA_CTRL 0x21
|
|
|
+#define STRB_CTRL 0x22
|
|
|
+#define CLK_CTRL 0x23
|
|
|
+#define PHY_CTRL 0x24
|
|
|
+
|
|
|
+#define DLL_ENBL BIT(3)
|
|
|
+#define RTRIM_EN BIT(1)
|
|
|
+#define PDB_ENBL BIT(1)
|
|
|
+#define RETB_ENBL BIT(6)
|
|
|
+#define ODEN_CMD BIT(1)
|
|
|
+#define ODEN_DAT 0xFF
|
|
|
+#define REN_STRB BIT(0)
|
|
|
+#define REN_CMND BIT(1)
|
|
|
+#define REN_DATA 0xFF
|
|
|
+#define PU_CMD BIT(1)
|
|
|
+#define PU_DAT 0xFF
|
|
|
+#define ITAPDLY_EN BIT(0)
|
|
|
+#define OTAPDLY_EN BIT(0)
|
|
|
+#define OD_REL_CMD BIT(1)
|
|
|
+#define OD_REL_DAT 0xFF
|
|
|
+#define DLLTRM_ICP 0x8
|
|
|
+#define PDB_CMND BIT(0)
|
|
|
+#define PDB_DATA 0xFF
|
|
|
+#define PDB_STRB BIT(0)
|
|
|
+#define PDB_CLOCK BIT(0)
|
|
|
+#define CALDONE_MASK 0x10
|
|
|
+#define DLL_RDY_MASK 0x10
|
|
|
+#define MAX_CLK_BUF 0x7
|
|
|
+
|
|
|
+/* Mode Controls */
|
|
|
+#define ENHSTRB_MODE BIT(0)
|
|
|
+#define HS400_MODE BIT(1)
|
|
|
+#define LEGACY_MODE BIT(2)
|
|
|
+#define DDR50_MODE BIT(3)
|
|
|
+
|
|
|
+/*
|
|
|
+ * Controller has no specific bits for HS200/HS.
|
|
|
+ * Used BIT(4), BIT(5) for software programming.
|
|
|
+ */
|
|
|
+#define HS200_MODE BIT(4)
|
|
|
+#define HISPD_MODE BIT(5)
|
|
|
+
|
|
|
+#define OTAPDLY(x) (((x) << 1) | OTAPDLY_EN)
|
|
|
+#define ITAPDLY(x) (((x) << 1) | ITAPDLY_EN)
|
|
|
+#define FREQSEL(x) (((x) << 5) | DLL_ENBL)
|
|
|
+#define IOPAD(x, y) ((x) | ((y) << 2))
|
|
|
+
|
|
|
+/* Arasan private data */
|
|
|
+struct arasan_host {
|
|
|
+ u32 chg_clk;
|
|
|
+};
|
|
|
+
|
|
|
+static int arasan_phy_addr_poll(struct sdhci_host *host, u32 offset, u32 mask)
|
|
|
+{
|
|
|
+ ktime_t timeout = ktime_add_us(ktime_get(), 100);
|
|
|
+ bool failed;
|
|
|
+ u8 val = 0;
|
|
|
+
|
|
|
+ while (1) {
|
|
|
+ failed = ktime_after(ktime_get(), timeout);
|
|
|
+ val = sdhci_readw(host, PHY_ADDR_REG);
|
|
|
+ if (!(val & mask))
|
|
|
+ return 0;
|
|
|
+ if (failed)
|
|
|
+ return -EBUSY;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int arasan_phy_write(struct sdhci_host *host, u8 data, u8 offset)
|
|
|
+{
|
|
|
+ sdhci_writew(host, data, PHY_DAT_REG);
|
|
|
+ sdhci_writew(host, (PHY_WRITE | offset), PHY_ADDR_REG);
|
|
|
+ return arasan_phy_addr_poll(host, PHY_ADDR_REG, PHY_BUSY);
|
|
|
+}
|
|
|
+
|
|
|
+static int arasan_phy_read(struct sdhci_host *host, u8 offset, u8 *data)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ sdhci_writew(host, 0, PHY_DAT_REG);
|
|
|
+ sdhci_writew(host, offset, PHY_ADDR_REG);
|
|
|
+ ret = arasan_phy_addr_poll(host, PHY_ADDR_REG, PHY_BUSY);
|
|
|
+
|
|
|
+ /* Masking valid data bits */
|
|
|
+ *data = sdhci_readw(host, PHY_DAT_REG) & DATA_MASK;
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static int arasan_phy_sts_poll(struct sdhci_host *host, u32 offset, u32 mask)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ ktime_t timeout = ktime_add_us(ktime_get(), 100);
|
|
|
+ bool failed;
|
|
|
+ u8 val = 0;
|
|
|
+
|
|
|
+ while (1) {
|
|
|
+ failed = ktime_after(ktime_get(), timeout);
|
|
|
+ ret = arasan_phy_read(host, offset, &val);
|
|
|
+ if (ret)
|
|
|
+ return -EBUSY;
|
|
|
+ else if (val & mask)
|
|
|
+ return 0;
|
|
|
+ if (failed)
|
|
|
+ return -EBUSY;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* Initialize the Arasan PHY */
|
|
|
+static int arasan_phy_init(struct sdhci_host *host)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ u8 val;
|
|
|
+
|
|
|
+ /* Program IOPADs and wait for calibration to be done */
|
|
|
+ if (arasan_phy_read(host, IPAD_CTRL1, &val) ||
|
|
|
+ arasan_phy_write(host, val | RETB_ENBL | PDB_ENBL, IPAD_CTRL1) ||
|
|
|
+ arasan_phy_read(host, IPAD_CTRL2, &val) ||
|
|
|
+ arasan_phy_write(host, val | RTRIM_EN, IPAD_CTRL2))
|
|
|
+ return -EBUSY;
|
|
|
+ ret = arasan_phy_sts_poll(host, IPAD_STS, CALDONE_MASK);
|
|
|
+ if (ret)
|
|
|
+ return -EBUSY;
|
|
|
+
|
|
|
+ /* Program CMD/Data lines */
|
|
|
+ if (arasan_phy_read(host, IOREN_CTRL1, &val) ||
|
|
|
+ arasan_phy_write(host, val | REN_CMND | REN_STRB, IOREN_CTRL1) ||
|
|
|
+ arasan_phy_read(host, IOPU_CTRL1, &val) ||
|
|
|
+ arasan_phy_write(host, val | PU_CMD, IOPU_CTRL1) ||
|
|
|
+ arasan_phy_read(host, CMD_CTRL, &val) ||
|
|
|
+ arasan_phy_write(host, val | PDB_CMND, CMD_CTRL) ||
|
|
|
+ arasan_phy_read(host, IOREN_CTRL2, &val) ||
|
|
|
+ arasan_phy_write(host, val | REN_DATA, IOREN_CTRL2) ||
|
|
|
+ arasan_phy_read(host, IOPU_CTRL2, &val) ||
|
|
|
+ arasan_phy_write(host, val | PU_DAT, IOPU_CTRL2) ||
|
|
|
+ arasan_phy_read(host, DATA_CTRL, &val) ||
|
|
|
+ arasan_phy_write(host, val | PDB_DATA, DATA_CTRL) ||
|
|
|
+ arasan_phy_read(host, STRB_CTRL, &val) ||
|
|
|
+ arasan_phy_write(host, val | PDB_STRB, STRB_CTRL) ||
|
|
|
+ arasan_phy_read(host, CLK_CTRL, &val) ||
|
|
|
+ arasan_phy_write(host, val | PDB_CLOCK, CLK_CTRL) ||
|
|
|
+ arasan_phy_read(host, CLKBUF_SEL, &val) ||
|
|
|
+ arasan_phy_write(host, val | MAX_CLK_BUF, CLKBUF_SEL) ||
|
|
|
+ arasan_phy_write(host, LEGACY_MODE, MODE_CTRL))
|
|
|
+ return -EBUSY;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* Set Arasan PHY for different modes */
|
|
|
+static int arasan_phy_set(struct sdhci_host *host, u8 mode, u8 otap,
|
|
|
+ u8 drv_type, u8 itap, u8 trim, u8 clk)
|
|
|
+{
|
|
|
+ u8 val;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (mode == HISPD_MODE || mode == HS200_MODE)
|
|
|
+ ret = arasan_phy_write(host, 0x0, MODE_CTRL);
|
|
|
+ else
|
|
|
+ ret = arasan_phy_write(host, mode, MODE_CTRL);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+ if (mode == HS400_MODE || mode == HS200_MODE) {
|
|
|
+ ret = arasan_phy_read(host, IPAD_CTRL1, &val);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+ ret = arasan_phy_write(host, IOPAD(val, drv_type), IPAD_CTRL1);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ if (mode == LEGACY_MODE) {
|
|
|
+ ret = arasan_phy_write(host, 0x0, OTAP_DELAY);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+ ret = arasan_phy_write(host, 0x0, ITAP_DELAY);
|
|
|
+ } else {
|
|
|
+ ret = arasan_phy_write(host, OTAPDLY(otap), OTAP_DELAY);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+ if (mode != HS200_MODE)
|
|
|
+ ret = arasan_phy_write(host, ITAPDLY(itap), ITAP_DELAY);
|
|
|
+ else
|
|
|
+ ret = arasan_phy_write(host, 0x0, ITAP_DELAY);
|
|
|
+ }
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+ if (mode != LEGACY_MODE) {
|
|
|
+ ret = arasan_phy_write(host, trim, DLL_TRIM);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ ret = arasan_phy_write(host, 0, DLL_STATUS);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+ if (mode != LEGACY_MODE) {
|
|
|
+ ret = arasan_phy_write(host, FREQSEL(clk), DLL_STATUS);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+ ret = arasan_phy_sts_poll(host, DLL_STATUS, DLL_RDY_MASK);
|
|
|
+ if (ret)
|
|
|
+ return -EBUSY;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int arasan_select_phy_clock(struct sdhci_host *host)
|
|
|
+{
|
|
|
+ struct sdhci_pci_slot *slot = sdhci_priv(host);
|
|
|
+ struct arasan_host *arasan_host = sdhci_pci_priv(slot);
|
|
|
+ u8 clk;
|
|
|
+
|
|
|
+ if (arasan_host->chg_clk == host->mmc->ios.clock)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ arasan_host->chg_clk = host->mmc->ios.clock;
|
|
|
+ if (host->mmc->ios.clock == 200000000)
|
|
|
+ clk = 0x0;
|
|
|
+ else if (host->mmc->ios.clock == 100000000)
|
|
|
+ clk = 0x2;
|
|
|
+ else if (host->mmc->ios.clock == 50000000)
|
|
|
+ clk = 0x1;
|
|
|
+ else
|
|
|
+ clk = 0x0;
|
|
|
+
|
|
|
+ if (host->mmc_host_ops.hs400_enhanced_strobe) {
|
|
|
+ arasan_phy_set(host, ENHSTRB_MODE, 1, 0x0, 0x0,
|
|
|
+ DLLTRM_ICP, clk);
|
|
|
+ } else {
|
|
|
+ switch (host->mmc->ios.timing) {
|
|
|
+ case MMC_TIMING_LEGACY:
|
|
|
+ arasan_phy_set(host, LEGACY_MODE, 0x0, 0x0, 0x0,
|
|
|
+ 0x0, 0x0);
|
|
|
+ break;
|
|
|
+ case MMC_TIMING_MMC_HS:
|
|
|
+ case MMC_TIMING_SD_HS:
|
|
|
+ arasan_phy_set(host, HISPD_MODE, 0x3, 0x0, 0x2,
|
|
|
+ DLLTRM_ICP, clk);
|
|
|
+ break;
|
|
|
+ case MMC_TIMING_MMC_HS200:
|
|
|
+ case MMC_TIMING_UHS_SDR104:
|
|
|
+ arasan_phy_set(host, HS200_MODE, 0x2,
|
|
|
+ host->mmc->ios.drv_type, 0x0,
|
|
|
+ DLLTRM_ICP, clk);
|
|
|
+ break;
|
|
|
+ case MMC_TIMING_MMC_DDR52:
|
|
|
+ case MMC_TIMING_UHS_DDR50:
|
|
|
+ arasan_phy_set(host, DDR50_MODE, 0x1, 0x0,
|
|
|
+ 0x0, DLLTRM_ICP, clk);
|
|
|
+ break;
|
|
|
+ case MMC_TIMING_MMC_HS400:
|
|
|
+ arasan_phy_set(host, HS400_MODE, 0x1,
|
|
|
+ host->mmc->ios.drv_type, 0xa,
|
|
|
+ DLLTRM_ICP, clk);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int arasan_pci_probe_slot(struct sdhci_pci_slot *slot)
|
|
|
+{
|
|
|
+ int err;
|
|
|
+
|
|
|
+ slot->host->mmc->caps |= MMC_CAP_NONREMOVABLE | MMC_CAP_8_BIT_DATA;
|
|
|
+ err = arasan_phy_init(slot->host);
|
|
|
+ if (err)
|
|
|
+ return -ENODEV;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void arasan_sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
|
|
|
+{
|
|
|
+ sdhci_set_clock(host, clock);
|
|
|
+
|
|
|
+ /* Change phy settings for the new clock */
|
|
|
+ arasan_select_phy_clock(host);
|
|
|
+}
|
|
|
+
|
|
|
+static const struct sdhci_ops arasan_sdhci_pci_ops = {
|
|
|
+ .set_clock = arasan_sdhci_set_clock,
|
|
|
+ .enable_dma = sdhci_pci_enable_dma,
|
|
|
+ .set_bus_width = sdhci_set_bus_width,
|
|
|
+ .reset = sdhci_reset,
|
|
|
+ .set_uhs_signaling = sdhci_set_uhs_signaling,
|
|
|
+};
|
|
|
+
|
|
|
+const struct sdhci_pci_fixes sdhci_arasan = {
|
|
|
+ .probe_slot = arasan_pci_probe_slot,
|
|
|
+ .ops = &arasan_sdhci_pci_ops,
|
|
|
+ .priv_size = sizeof(struct arasan_host),
|
|
|
+};
|