|
@@ -47,6 +47,11 @@
|
|
|
|
|
|
#define host_to_priv(host) container_of((host)->pdata, struct sh_mobile_sdhi, mmc_data)
|
|
|
|
|
|
+struct sh_mobile_sdhi_scc {
|
|
|
+ unsigned long clk_rate; /* clock rate for SDR104 */
|
|
|
+ u32 tap; /* sampling clock position for SDR104 */
|
|
|
+};
|
|
|
+
|
|
|
struct sh_mobile_sdhi_of_data {
|
|
|
unsigned long tmio_flags;
|
|
|
u32 tmio_ocr_mask;
|
|
@@ -55,6 +60,9 @@ struct sh_mobile_sdhi_of_data {
|
|
|
enum dma_slave_buswidth dma_buswidth;
|
|
|
dma_addr_t dma_rx_offset;
|
|
|
unsigned bus_shift;
|
|
|
+ int scc_offset;
|
|
|
+ struct sh_mobile_sdhi_scc *taps;
|
|
|
+ int taps_num;
|
|
|
};
|
|
|
|
|
|
static const struct sh_mobile_sdhi_of_data of_default_cfg = {
|
|
@@ -73,12 +81,35 @@ static const struct sh_mobile_sdhi_of_data of_rcar_gen1_compatible = {
|
|
|
.capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ,
|
|
|
};
|
|
|
|
|
|
+/* Definitions for sampling clocks */
|
|
|
+static struct sh_mobile_sdhi_scc rcar_gen2_scc_taps[] = {
|
|
|
+ {
|
|
|
+ .clk_rate = 156000000,
|
|
|
+ .tap = 0x00000703,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .clk_rate = 0,
|
|
|
+ .tap = 0x00000300,
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
static const struct sh_mobile_sdhi_of_data of_rcar_gen2_compatible = {
|
|
|
.tmio_flags = TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_WRPROTECT_DISABLE |
|
|
|
TMIO_MMC_CLK_ACTUAL | TMIO_MMC_MIN_RCAR2,
|
|
|
.capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ,
|
|
|
.dma_buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES,
|
|
|
.dma_rx_offset = 0x2000,
|
|
|
+ .scc_offset = 0x0300,
|
|
|
+ .taps = rcar_gen2_scc_taps,
|
|
|
+ .taps_num = ARRAY_SIZE(rcar_gen2_scc_taps),
|
|
|
+};
|
|
|
+
|
|
|
+/* Definitions for sampling clocks */
|
|
|
+static struct sh_mobile_sdhi_scc rcar_gen3_scc_taps[] = {
|
|
|
+ {
|
|
|
+ .clk_rate = 0,
|
|
|
+ .tap = 0x00000300,
|
|
|
+ },
|
|
|
};
|
|
|
|
|
|
static const struct sh_mobile_sdhi_of_data of_rcar_gen3_compatible = {
|
|
@@ -86,6 +117,9 @@ static const struct sh_mobile_sdhi_of_data of_rcar_gen3_compatible = {
|
|
|
TMIO_MMC_CLK_ACTUAL | TMIO_MMC_MIN_RCAR2,
|
|
|
.capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ,
|
|
|
.bus_shift = 2,
|
|
|
+ .scc_offset = 0x1000,
|
|
|
+ .taps = rcar_gen3_scc_taps,
|
|
|
+ .taps_num = ARRAY_SIZE(rcar_gen3_scc_taps),
|
|
|
};
|
|
|
|
|
|
static const struct of_device_id sh_mobile_sdhi_of_match[] = {
|
|
@@ -113,6 +147,7 @@ struct sh_mobile_sdhi {
|
|
|
struct tmio_mmc_dma dma_priv;
|
|
|
struct pinctrl *pinctrl;
|
|
|
struct pinctrl_state *pins_default, *pins_uhs;
|
|
|
+ void __iomem *scc_ctl;
|
|
|
};
|
|
|
|
|
|
static void sh_mobile_sdhi_sdbuf_width(struct tmio_mmc_host *host, int width)
|
|
@@ -263,6 +298,201 @@ static int sh_mobile_sdhi_start_signal_voltage_switch(struct mmc_host *mmc,
|
|
|
return pinctrl_select_state(priv->pinctrl, pin_state);
|
|
|
}
|
|
|
|
|
|
+/* SCC registers */
|
|
|
+#define SH_MOBILE_SDHI_SCC_DTCNTL 0x000
|
|
|
+#define SH_MOBILE_SDHI_SCC_TAPSET 0x002
|
|
|
+#define SH_MOBILE_SDHI_SCC_DT2FF 0x004
|
|
|
+#define SH_MOBILE_SDHI_SCC_CKSEL 0x006
|
|
|
+#define SH_MOBILE_SDHI_SCC_RVSCNTL 0x008
|
|
|
+#define SH_MOBILE_SDHI_SCC_RVSREQ 0x00A
|
|
|
+
|
|
|
+/* Definitions for values the SH_MOBILE_SDHI_SCC_DTCNTL register */
|
|
|
+#define SH_MOBILE_SDHI_SCC_DTCNTL_TAPEN BIT(0)
|
|
|
+#define SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_SHIFT 16
|
|
|
+#define SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_MASK 0xff
|
|
|
+
|
|
|
+/* Definitions for values the SH_MOBILE_SDHI_SCC_CKSEL register */
|
|
|
+#define SH_MOBILE_SDHI_SCC_CKSEL_DTSEL BIT(0)
|
|
|
+/* Definitions for values the SH_MOBILE_SDHI_SCC_RVSCNTL register */
|
|
|
+#define SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN BIT(0)
|
|
|
+/* Definitions for values the SH_MOBILE_SDHI_SCC_RVSREQ register */
|
|
|
+#define SH_MOBILE_SDHI_SCC_RVSREQ_RVSERR BIT(2)
|
|
|
+
|
|
|
+static inline u32 sd_scc_read32(struct tmio_mmc_host *host,
|
|
|
+ struct sh_mobile_sdhi *priv, int addr)
|
|
|
+{
|
|
|
+ return readl(priv->scc_ctl + (addr << host->bus_shift));
|
|
|
+}
|
|
|
+
|
|
|
+static inline void sd_scc_write32(struct tmio_mmc_host *host,
|
|
|
+ struct sh_mobile_sdhi *priv,
|
|
|
+ int addr, u32 val)
|
|
|
+{
|
|
|
+ writel(val, priv->scc_ctl + (addr << host->bus_shift));
|
|
|
+}
|
|
|
+
|
|
|
+static unsigned int sh_mobile_sdhi_init_tuning(struct tmio_mmc_host *host)
|
|
|
+{
|
|
|
+ struct sh_mobile_sdhi *priv;
|
|
|
+
|
|
|
+ if (!(host->mmc->caps & MMC_CAP_UHS_SDR104))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ priv = host_to_priv(host);
|
|
|
+
|
|
|
+ /* set sampling clock selection range */
|
|
|
+ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL,
|
|
|
+ 0x8 << SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_SHIFT);
|
|
|
+
|
|
|
+ /* Initialize SCC */
|
|
|
+ sd_ctrl_write32_as_16_and_16(host, CTL_STATUS, 0x0);
|
|
|
+
|
|
|
+ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL,
|
|
|
+ SH_MOBILE_SDHI_SCC_DTCNTL_TAPEN |
|
|
|
+ sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL));
|
|
|
+
|
|
|
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
|
|
|
+ sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
|
|
|
+
|
|
|
+ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL,
|
|
|
+ SH_MOBILE_SDHI_SCC_CKSEL_DTSEL |
|
|
|
+ sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL));
|
|
|
+
|
|
|
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN |
|
|
|
+ sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
|
|
|
+
|
|
|
+ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL,
|
|
|
+ ~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN &
|
|
|
+ sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL));
|
|
|
+
|
|
|
+ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_DT2FF, host->scc_tappos);
|
|
|
+
|
|
|
+ /* Read TAPNUM */
|
|
|
+ return (sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL) >>
|
|
|
+ SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_SHIFT) &
|
|
|
+ SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_MASK;
|
|
|
+}
|
|
|
+
|
|
|
+static void sh_mobile_sdhi_prepare_tuning(struct tmio_mmc_host *host,
|
|
|
+ unsigned long tap)
|
|
|
+{
|
|
|
+ struct sh_mobile_sdhi *priv = host_to_priv(host);
|
|
|
+
|
|
|
+ /* Set sampling clock position */
|
|
|
+ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, tap);
|
|
|
+}
|
|
|
+
|
|
|
+#define SH_MOBILE_SDHI_MAX_TAP 3
|
|
|
+
|
|
|
+static int sh_mobile_sdhi_select_tuning(struct tmio_mmc_host *host)
|
|
|
+{
|
|
|
+ struct sh_mobile_sdhi *priv = host_to_priv(host);
|
|
|
+ unsigned long tap_cnt; /* counter of tuning success */
|
|
|
+ unsigned long tap_set; /* tap position */
|
|
|
+ unsigned long tap_start;/* start position of tuning success */
|
|
|
+ unsigned long tap_end; /* end position of tuning success */
|
|
|
+ unsigned long ntap; /* temporary counter of tuning success */
|
|
|
+ unsigned long i;
|
|
|
+
|
|
|
+ /* Clear SCC_RVSREQ */
|
|
|
+ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSREQ, 0);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Find the longest consecutive run of successful probes. If that
|
|
|
+ * is more than SH_MOBILE_SDHI_MAX_TAP probes long then use the
|
|
|
+ * center index as the tap.
|
|
|
+ */
|
|
|
+ tap_cnt = 0;
|
|
|
+ ntap = 0;
|
|
|
+ tap_start = 0;
|
|
|
+ tap_end = 0;
|
|
|
+ for (i = 0; i < host->tap_num * 2; i++) {
|
|
|
+ if (test_bit(i, host->taps))
|
|
|
+ ntap++;
|
|
|
+ else {
|
|
|
+ if (ntap > tap_cnt) {
|
|
|
+ tap_start = i - ntap;
|
|
|
+ tap_end = i - 1;
|
|
|
+ tap_cnt = ntap;
|
|
|
+ }
|
|
|
+ ntap = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ntap > tap_cnt) {
|
|
|
+ tap_start = i - ntap;
|
|
|
+ tap_end = i - 1;
|
|
|
+ tap_cnt = ntap;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tap_cnt >= SH_MOBILE_SDHI_MAX_TAP)
|
|
|
+ tap_set = (tap_start + tap_end) / 2 % host->tap_num;
|
|
|
+ else
|
|
|
+ return -EIO;
|
|
|
+
|
|
|
+ /* Set SCC */
|
|
|
+ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, tap_set);
|
|
|
+
|
|
|
+ /* Enable auto re-tuning */
|
|
|
+ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL,
|
|
|
+ SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN |
|
|
|
+ sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL));
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static bool sh_mobile_sdhi_check_scc_error(struct tmio_mmc_host *host)
|
|
|
+{
|
|
|
+ struct sh_mobile_sdhi *priv;
|
|
|
+
|
|
|
+ if (!(host->mmc->caps & MMC_CAP_UHS_SDR104))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ priv = host_to_priv(host);
|
|
|
+
|
|
|
+ /* Check SCC error */
|
|
|
+ if (sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL) &
|
|
|
+ SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN &&
|
|
|
+ sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSREQ) &
|
|
|
+ SH_MOBILE_SDHI_SCC_RVSREQ_RVSERR) {
|
|
|
+ /* Clear SCC error */
|
|
|
+ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSREQ, 0);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static void sh_mobile_sdhi_hw_reset(struct tmio_mmc_host *host)
|
|
|
+{
|
|
|
+ struct sh_mobile_sdhi *priv;
|
|
|
+
|
|
|
+ if (!(host->mmc->caps & MMC_CAP_UHS_SDR104))
|
|
|
+ return;
|
|
|
+
|
|
|
+ priv = host_to_priv(host);
|
|
|
+
|
|
|
+ /* Reset SCC */
|
|
|
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
|
|
|
+ sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
|
|
|
+
|
|
|
+ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL,
|
|
|
+ ~SH_MOBILE_SDHI_SCC_CKSEL_DTSEL &
|
|
|
+ sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL));
|
|
|
+
|
|
|
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN |
|
|
|
+ sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
|
|
|
+
|
|
|
+ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL,
|
|
|
+ ~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN &
|
|
|
+ sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL));
|
|
|
+
|
|
|
+ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL,
|
|
|
+ ~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN &
|
|
|
+ sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL));
|
|
|
+}
|
|
|
+
|
|
|
static int sh_mobile_sdhi_wait_idle(struct tmio_mmc_host *host)
|
|
|
{
|
|
|
int timeout = 1000;
|
|
@@ -333,7 +563,7 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev)
|
|
|
struct tmio_mmc_data *mmd = pdev->dev.platform_data;
|
|
|
struct tmio_mmc_host *host;
|
|
|
struct resource *res;
|
|
|
- int irq, ret, i = 0;
|
|
|
+ int irq, ret, i;
|
|
|
struct tmio_mmc_dma *dma_priv;
|
|
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
@@ -393,6 +623,11 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev)
|
|
|
host->card_busy = sh_mobile_sdhi_card_busy;
|
|
|
host->start_signal_voltage_switch =
|
|
|
sh_mobile_sdhi_start_signal_voltage_switch;
|
|
|
+ host->init_tuning = sh_mobile_sdhi_init_tuning;
|
|
|
+ host->prepare_tuning = sh_mobile_sdhi_prepare_tuning;
|
|
|
+ host->select_tuning = sh_mobile_sdhi_select_tuning;
|
|
|
+ host->check_scc_error = sh_mobile_sdhi_check_scc_error;
|
|
|
+ host->hw_reset = sh_mobile_sdhi_hw_reset;
|
|
|
}
|
|
|
|
|
|
/* Orginally registers were 16 bit apart, could be 32 or 64 nowadays */
|
|
@@ -433,6 +668,34 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev)
|
|
|
if (ret < 0)
|
|
|
goto efree;
|
|
|
|
|
|
+ if (host->mmc->caps & MMC_CAP_UHS_SDR104) {
|
|
|
+ host->mmc->caps |= MMC_CAP_HW_RESET;
|
|
|
+
|
|
|
+ if (of_id && of_id->data) {
|
|
|
+ const struct sh_mobile_sdhi_of_data *of_data;
|
|
|
+ const struct sh_mobile_sdhi_scc *taps;
|
|
|
+ bool hit = false;
|
|
|
+
|
|
|
+ of_data = of_id->data;
|
|
|
+ taps = of_data->taps;
|
|
|
+
|
|
|
+ for (i = 0; i < of_data->taps_num; i++) {
|
|
|
+ if (taps[i].clk_rate == 0 ||
|
|
|
+ taps[i].clk_rate == host->mmc->f_max) {
|
|
|
+ host->scc_tappos = taps->tap;
|
|
|
+ hit = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!hit)
|
|
|
+ dev_warn(&host->pdev->dev, "Unknown clock rate for SDR104\n");
|
|
|
+
|
|
|
+ priv->scc_ctl = host->ctl + of_data->scc_offset;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ i = 0;
|
|
|
while (1) {
|
|
|
irq = platform_get_irq(pdev, i);
|
|
|
if (irq < 0)
|