@@ -775,6 +775,15 @@ config MMC_DW_CQE
If unsure, say Y.
+config MMC_DW_CQE_RTK
+ tristate "Realtek Soc's specific extensions for Synopsys DW Memory Card Interface"
+ depends on MMC_DW_CQE
+ select REGULATOR_FIXED_VOLTAGE
+ help
+ This selects support for Realtek SoC specific extensions to the
+ Synopsys DesignWare Memory Card Interface driver. Select this option
+ for platforms after RTD16xxb SoC's.
+
config MMC_DW_BLUEFIELD
tristate "BlueField specific extensions for Synopsys DW Memory Card Interface"
depends on MMC_DW
@@ -56,6 +56,7 @@ obj-$(CONFIG_MMC_DW_PCI) += dw_mmc-pci.o
obj-$(CONFIG_MMC_DW_ROCKCHIP) += dw_mmc-rockchip.o
obj-$(CONFIG_MMC_DW_STARFIVE) += dw_mmc-starfive.o
obj-$(CONFIG_MMC_DW_CQE) += dw_mmc_cqe.o
+obj-$(CONFIG_MMC_DW_CQE_RTK) += dw_mmc_cqe-rtk.o
obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o
obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o
obj-$(CONFIG_MMC_VUB300) += vub300.o
new file mode 100644
@@ -0,0 +1,979 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Realtek Semiconductor Corp.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mmc/core.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of_address.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include "dw_mmc_cqe.h"
+#include "dw_mmc_cqe-rtk.h"
+
+#define DW_MCI_TIMEOUT 200000
+#define HS400_WINDOW_ALL_PASS 0xffff
+#define HS200_WINDOW_ALL_PASS 0xffffffff
+#define hs400_dqs_delay_val 0x88
+
+static void dw_mci_rtk_hs400_complete(struct mmc_host *mmc);
+static int dw_mci_rtk_execute_tuning(struct dw_mci_slot *slot, u32 opcode);
+
+static void reset_fifo(struct dw_mci *host)
+{
+ mcq_writel(host, OTHER1, mcq_readl(host, OTHER1) & (~SDMMC_TOP_RST_N_FIFO));
+ udelay(1);
+ mcq_writel(host, OTHER1, mcq_readl(host, OTHER1) | SDMMC_TOP_RST_N_FIFO);
+}
+
+static int dw_mci_rtk_set_pinstates(struct dw_mci_rtkemmc_host *priv,
+ unsigned char timing)
+{
+ switch (timing) {
+ case MMC_TIMING_UHS_SDR50:
+ return pinctrl_select_state(priv->pinctrl,
+ priv->pins_sdr50);
+
+ case MMC_TIMING_UHS_DDR50:
+ return pinctrl_select_state(priv->pinctrl,
+ priv->pins_ddr50);
+ case MMC_TIMING_MMC_HS200:
+ return pinctrl_select_state(priv->pinctrl,
+ priv->pins_hs200);
+
+ case MMC_TIMING_MMC_HS400:
+ return pinctrl_select_state(priv->pinctrl,
+ priv->pins_hs400);
+ default:
+ return pinctrl_select_state(priv->pinctrl,
+ priv->pins_default);
+ }
+}
+
+static void dqs_delay_tap_setting(struct dw_mci *host,
+ u32 dqs_dly)
+{
+ u32 regs;
+
+ regs = mcq_readl(host, DQS_CTRL1) & ~SDMMC_FW_EN;
+ mcq_writel(host, DQS_CTRL1, regs);
+ mcq_writel(host, DQS_CTRL1, dqs_dly);
+
+ regs = dqs_dly | SDMMC_FW_EN;
+ mcq_writel(host, DQS_CTRL1, regs);
+}
+
+static void cmd_delay_tap_setting(struct dw_mci *host,
+ u32 cmd_dly_tape)
+{
+ u32 regs;
+
+ regs = mcq_readl(host, WCMD_CTRL) & ~SDMMC_FW_SET_EN;
+ mcq_writel(host, WCMD_CTRL, regs);
+
+ if (cmd_dly_tape == 0)
+ return;
+
+ mcq_writel(host, WCMD_CTRL, cmd_dly_tape);
+
+ regs = cmd_dly_tape | SDMMC_FW_SET_EN;
+ mcq_writel(host, WCMD_CTRL, regs);
+}
+
+static void data_delay_tap_setting(struct dw_mci *host)
+{
+ u32 regs;
+ struct dw_mci_rtkemmc_host *priv = host->priv;
+
+ regs = mcq_readl(host, RDQ_CTRL0) & (~SDMMC_FW_SET);
+ mcq_writel(host, RDQ_CTRL0, regs);
+
+ if (priv->rdq_ctrl == 0)
+ return;
+
+ mcq_writel(host, RDQ_CTRL0, priv->rdq_ctrl);
+ mcq_writel(host, RDQ_CTRL1, priv->rdq_ctrl);
+ mcq_writel(host, RDQ_CTRL2, priv->rdq_ctrl);
+ mcq_writel(host, RDQ_CTRL3, priv->rdq_ctrl);
+ mcq_writel(host, RDQ_CTRL4, priv->rdq_ctrl);
+ mcq_writel(host, RDQ_CTRL5, priv->rdq_ctrl);
+ mcq_writel(host, RDQ_CTRL6, priv->rdq_ctrl);
+ mcq_writel(host, RDQ_CTRL7, priv->rdq_ctrl);
+
+ regs = mcq_readl(host, RDQ_CTRL0) | SDMMC_FW_SET;
+ mcq_writel(host, RDQ_CTRL0, regs);
+}
+
+static void setup_clk_div(struct dw_mci_slot *slot)
+{
+ struct dw_mci *host = slot->host;
+ unsigned int clock = slot->clock;
+ u32 div = 0;
+
+ slot->mmc->actual_clock = 0;
+
+ if (clock != host->current_speed) {
+ div = host->bus_hz / clock;
+ if (host->bus_hz % clock)
+ div += 1;
+
+ if (clock != slot->__clk_old) {
+ /* Silent the verbose log if calling from PM context */
+ dev_info(&slot->mmc->class_dev, "Bus speed (slot %d) = %dHz (slot req %dHz, actual %dHZ div = %d)\n",
+ slot->id, host->bus_hz, clock,
+ host->bus_hz / div, div);
+ }
+
+ slot->__clk_old = clock;
+ slot->mmc->actual_clock = host->bus_hz / div;
+
+ /*In RealTek Stark platform, they only support div 1,4, and 512*/
+ if (div > 4)
+ div = 512;
+
+ switch (div) {
+ case SDMMC_CLK_DIV1:
+ mcq_writel(host, CKGEN_CTL,
+ mcq_readl(host, CKGEN_CTL) & (~SDMMC_CRC_CLK_DIV_EN));
+ break;
+ case SDMMC_CLK_DIV4:
+ mcq_writel(host, CKGEN_CTL,
+ mcq_readl(host, CKGEN_CTL) | SDMMC_CLK_INV_DIV_SEL);
+ mcq_writel(host, CKGEN_CTL,
+ mcq_readl(host, CKGEN_CTL) | SDMMC_CRC_CLK_DIV_EN);
+ break;
+ case SDMMC_CLK_DIV512:
+ mcq_writel(host, CKGEN_CTL,
+ mcq_readl(host, CKGEN_CTL) & (~SDMMC_CLK_INV_DIV_SEL));
+ mcq_writel(host, CKGEN_CTL,
+ mcq_readl(host, CKGEN_CTL) | SDMMC_CRC_CLK_DIV_EN);
+ break;
+ default:
+ dev_info(&slot->mmc->class_dev, "default case div = %d\n", div);
+ if (div != 0)
+ mcq_writel(host, CKGEN_CTL,
+ mcq_readl(host, CKGEN_CTL) & (~SDMMC_CRC_CLK_DIV_EN));
+ break;
+ }
+ }
+
+ host->current_speed = clock;
+}
+
+static void dw_mci_rtk_phase_tuning(struct dw_mci *host,
+ u32 tx_phase, u32 rx_phase)
+{
+ struct dw_mci_rtkemmc_host *priv = host->priv;
+ u32 t1 = 10;
+ u32 t2 = 3;
+ int ret;
+ u32 status;
+
+ clk_disable(host->ciu_clk);
+ udelay(t1);
+
+ mcq_writel(host, OTHER1, mcq_readl(host, OTHER1) | SDMMC_CLK_DIV_RST);
+
+ if (!IS_ERR(priv->vp0) && !IS_ERR(priv->vp1)) {
+ if (tx_phase != 0xff)
+ clk_set_phase(priv->vp0, (tx_phase * 360) / 32);
+ if (rx_phase != 0xff)
+ clk_set_phase(priv->vp1, (rx_phase * 360) / 32);
+ }
+
+ udelay(t2);
+
+ mcq_writel(host, OTHER1, mcq_readl(host, OTHER1) & ~SDMMC_CLK_DIV_RST);
+
+ clk_enable(host->ciu_clk);
+
+ ret = readl_poll_timeout(host->regs + SDMMC_CLK_CTRL_R, status,
+ (status & SDMMC_INTERNAL_CLK_MSK) == 0x3, 10, DW_MCI_TIMEOUT);
+ if (ret)
+ dev_err(host->dev, "Timeout waiting emmc phase tuning.\n");
+
+ mcq_writeb(host, SW_RST_R, SDMMC_RST_CMD | SDMMC_RST_DAT);
+
+ ret = readl_poll_timeout(host->regs + SDMMC_CLK_CTRL_R, status,
+ (status & SW_RST_BOTH_DONE) == 0x0, 10, DW_MCI_TIMEOUT);
+ if (ret)
+ dev_err(host->dev, "Timeout waiting phase tuning sw rest\n");
+
+ reset_fifo(host);
+}
+
+static void adjust_window(struct dw_mci_rtkemmc_host *priv,
+ const char *w_type, unsigned int loop_cnt, unsigned int window)
+{
+ switch (loop_cnt) {
+ case 10:
+ pr_err("try pad driving 3: %s = 0x%08x\n", w_type, window);
+ pinctrl_select_state(priv->pinctrl, priv->pins_tune3);
+ break;
+ case 20:
+ pr_err("try pad driving 2: %s = 0x%08x\n", w_type, window);
+ pinctrl_select_state(priv->pinctrl, priv->pins_tune2);
+ break;
+ case 30:
+ pr_err("try pad driving 1: %s = 0x%08x\n", w_type, window);
+ pinctrl_select_state(priv->pinctrl, priv->pins_tune1);
+ break;
+ case 40:
+ pr_err("try pad driving 0: %s = 0x%08x\n", w_type, window);
+ pinctrl_select_state(priv->pinctrl, priv->pins_tune0);
+ break;
+ case 60:
+ pr_err("loop cnt %d: %s = 0x%08x, cannot find a proper phase\n",
+ loop_cnt, w_type, window);
+ default:
+ break;
+ }
+}
+
+static int search_best(u32 window, u32 range)
+{
+ int i = 0, j = 1, k = 0, max = 0;
+ int window_temp[32];
+ int window_start[32];
+ int window_end[32];
+ int window_max = 0;
+ int window_best = 0;
+ int parse_end = 1;
+
+ for (i = 0; i < 0x20; i++) {
+ window_temp[i] = 0;
+ window_start[i] = 0;
+ window_end[i] = -1;
+ }
+
+ i = 0;
+
+ while ((i < (range - 1)) && (k < (range - 1))) {
+ parse_end = 0;
+ for (i = window_end[j - 1] + 1; i < range; i++) {
+ if (((window >> i) & 1) == 1) {
+ window_start[j] = i;
+ break;
+ }
+ }
+ if (i == range)
+ break;
+
+ for (k = window_start[j] + 1; k < range; k++) {
+ if (((window >> k) & 1) == 0) {
+ window_end[j] = k - 1;
+ parse_end = 1;
+ break;
+ }
+ }
+
+ if (parse_end == 0)
+ window_end[j] = range - 1;
+ j++;
+ }
+
+ for (i = 1; i < j; i++)
+ window_temp[i] = window_end[i] - window_start[i] + 1;
+
+ if ((((window) & 1) == 1) && (((window >> (range - 1)) & 1) == 1)) {
+ window_temp[1] = window_temp[1] + window_temp[j - 1];
+ window_start[1] = window_start[j - 1];
+ }
+
+ for (i = 1; i < j; i++) {
+ if (window_temp[i] > window_max) {
+ window_max = window_temp[i];
+ max = i;
+ }
+ }
+
+ if (window == ~0UL)
+ window_best = 0x10;
+ else if ((window == HS400_WINDOW_ALL_PASS) && (range == 0x10))
+ window_best = 0x8;
+ else if ((((window & 1) == 1) && (((window >> (range - 1)) & 1) == 1)) && (max == 1))
+ window_best = (((window_start[max] + window_end[max] +
+ range) / 2) & (range - 1));
+ else
+ window_best = ((window_start[max] + window_end[max]) / 2) & 0x1f;
+
+ /*We set a criteria for 6 continuous points*/
+ if (window_max > 6)
+ return window_best;
+ else
+ return 0xff;
+}
+
+static int dw_mci_rtk_send_tuning(struct mmc_host *mmc, u32 opcode, u32 arg, int size)
+{
+ struct mmc_request mrq = {};
+ struct mmc_command cmd = {};
+ struct mmc_data data = {};
+ struct scatterlist sg;
+ int i, err = 0;
+ u8 *data_buf = NULL;
+
+ cmd.opcode = opcode;
+ cmd.arg = arg;
+
+ if (opcode == MMC_SEND_STATUS)
+ cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
+ else
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ mrq.cmd = &cmd;
+ mrq.data = NULL;
+
+ if (opcode == MMC_WRITE_MULTIPLE_BLOCK || opcode == MMC_READ_MULTIPLE_BLOCK) {
+ data_buf = kzalloc(size, GFP_KERNEL);
+ if (!data_buf)
+ return -ENOMEM;
+
+ mrq.data = &data;
+
+ if (opcode == MMC_WRITE_MULTIPLE_BLOCK) {
+ for (i = 0; i < size / 4; i++)
+ *(u32 *)(data_buf + i * 4) = i;
+ data.flags = MMC_DATA_WRITE;
+ } else {
+ data.flags = MMC_DATA_READ;
+ }
+
+ data.blksz = 512;
+ data.blocks = size / data.blksz;
+
+ data.timeout_ns = 150 * NSEC_PER_MSEC;
+
+ data.sg = &sg;
+ data.sg_len = 1;
+ sg_init_one(&sg, data_buf, size);
+ }
+
+ mmc_wait_for_req(mmc, &mrq);
+
+ if (cmd.error) {
+ err = cmd.error;
+ goto out;
+ }
+
+ if (data.error) {
+ err = data.error;
+ goto out;
+ }
+
+out:
+ return err;
+}
+
+static int dw_mci_rtk_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
+{
+ struct dw_mci *host = slot->host;
+ struct dw_mci_rtkemmc_host *priv = host->priv;
+ struct mmc_host *mmc = slot->mmc;
+ u32 tx_window = 0, rx_window = 0;
+ u32 tx_best = 0, rx_best = 0;
+ u32 tx_range, rx_range = 0x20;
+ unsigned int bitmap = 0;
+ unsigned int max = 0;
+ unsigned int loop_cnt = 0;
+ unsigned int reg;
+ int i, j, ret = 0, rsp = 0;
+ bool fail = false;
+ bool dqs_retry = false;
+
+ if (mmc->doing_retune == 1) {
+ dev_err(mmc_dev(mmc), "Retune case !!!\n");
+ goto out;
+ }
+
+ ret = dw_mci_rtk_set_pinstates(priv, mmc->ios.timing);
+ if (ret) {
+ dev_err(mmc_dev(mmc),
+ "Failed to set pinstate err=%d\n", ret);
+ ret = -ENODEV;
+ goto out;
+ }
+
+ host->tuning = 1;
+
+ do {
+ if (mmc->ios.timing == MMC_TIMING_MMC_HS400)
+ loop_cnt = 0;
+ else if (mmc->ios.timing == MMC_TIMING_MMC_HS200)
+ loop_cnt = 20;
+ else
+ loop_cnt = 30;
+
+ /* rx tuning */
+ do {
+ for (i = 0; i < rx_range; i++) {
+ dw_mci_rtk_phase_tuning(host, 0xff, i);
+
+ if (mmc->ios.timing == MMC_TIMING_MMC_HS200)
+ rsp = mmc_send_tuning(mmc,
+ MMC_SEND_TUNING_BLOCK_HS200, NULL);
+ else
+ rsp = dw_mci_rtk_send_tuning(mmc,
+ MMC_SEND_STATUS, 0x10000, 0);
+ if (rsp)
+ rx_window = rx_window & ~BIT(i);
+ else
+ rx_window = rx_window | BIT(i);
+ }
+
+ if (rx_window == 0) {
+ dev_err(mmc_dev(mmc), "rx_window = 0, cannot find a proper rx phase\n");
+ ret = -EFAULT;
+ goto out;
+ } else if (rx_window == HS200_WINDOW_ALL_PASS) {
+ loop_cnt++;
+ adjust_window(priv, "rx_window", loop_cnt, rx_window);
+ }
+ } while (rx_window == HS400_WINDOW_ALL_PASS && loop_cnt < 60);
+
+ rx_best = search_best(rx_window, rx_range);
+ if (rx_best == 0xff) {
+ dev_err(mmc_dev(mmc), "no 6 continuous points for rx phase\n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ dw_mci_rtk_phase_tuning(host, 0xff, rx_best);
+ dev_info(mmc_dev(mmc), "rx_window = 0x%x, rx_best = 0x%x\n", rx_window, rx_best);
+
+ /*dqs tuning in HS400 mode*/
+ if (mmc->ios.timing == MMC_TIMING_MMC_HS400) {
+ if (fail)
+ pinctrl_select_state(priv->pinctrl, priv->pins_tune4);
+ else
+ pinctrl_select_state(priv->pinctrl, priv->pins_hs400);
+
+ /*reset the bitmap, max, and j for each wcmd_rty*/
+ bitmap = 0;
+ max = 0;
+ j = 0;
+
+ for (i = 0; i < 0x20; i++) {
+ /*criteria is more than 5 continuous tap sample point*/
+ if (j >= 5)
+ max = j;
+ /*find the max tap length*/
+ if (j == 0 && max != 0)
+ break;
+
+ dqs_delay_tap_setting(host, (i << 1));
+
+ if (dw_mci_rtk_send_tuning(mmc, MMC_READ_MULTIPLE_BLOCK,
+ 0x100, 1024) != 0) {
+ j = 0;
+ } else {
+ j++;
+ bitmap |= BIT(i);
+ }
+ }
+
+ if (max == 0) {
+ if (!fail) {
+ dev_err(mmc_dev(mmc), "DQS_RETRY:bitmap= 0x%x\n", bitmap);
+ fail = true;
+ dqs_retry = true;
+ dev_err(mmc_dev(mmc), "try pad driving 7\n");
+ pinctrl_select_state(priv->pinctrl, priv->pins_tune4);
+ } else {
+ ret = -EFAULT;
+ dev_err(mmc_dev(mmc),
+ "No proper dqs window, dqs bitmap=0x%x\n", bitmap);
+ goto out;
+ }
+ } else {
+ reg = mcq_readl(host, DQS_CTRL1) - 2 - ((max / 2) * 2);
+ /*set the dqs dly tape*/
+ dqs_delay_tap_setting(host, reg);
+ dqs_retry = false;
+ dev_info(mmc_dev(mmc), "max sample point = %d, bitmap = 0x%x, DQS = 0x%x\n",
+ max, bitmap, mcq_readl(host, DQS_CTRL1));
+ }
+ }
+ } while (dqs_retry);
+
+ /*tx tuning */
+ if (mmc->ios.timing == MMC_TIMING_MMC_HS400) {
+ tx_range = 0x10;
+ loop_cnt = 0;
+ if (fail)
+ pinctrl_select_state(priv->pinctrl, priv->pins_tune4);
+ else
+ pinctrl_select_state(priv->pinctrl, priv->pins_hs400);
+ } else if (mmc->ios.timing == MMC_TIMING_MMC_HS200) {
+ tx_range = 0x20;
+ loop_cnt = 20;
+ if (fail)
+ pinctrl_select_state(priv->pinctrl, priv->pins_tune4);
+ else
+ pinctrl_select_state(priv->pinctrl, priv->pins_hs200);
+ } else {
+ tx_range = 0x10;
+ loop_cnt = 30;
+ }
+
+ do {
+ for (i = 0; i < tx_range; i++) {
+ dw_mci_rtk_phase_tuning(host, i, 0xff);
+ if (dw_mci_rtk_send_tuning(mmc, MMC_WRITE_MULTIPLE_BLOCK, 0xfe, 1024))
+ tx_window = tx_window & ~BIT(i);
+ else
+ tx_window = tx_window | BIT(i);
+ }
+
+ if (tx_window == 0) {
+ dev_err(mmc_dev(mmc), "tx_window = 0, cannot find a proper tx phase\n");
+ ret = -EFAULT;
+ goto out;
+ } else if ((tx_window == HS400_WINDOW_ALL_PASS &&
+ mmc->ios.timing == MMC_TIMING_MMC_HS400) ||
+ (tx_window == HS200_WINDOW_ALL_PASS &&
+ mmc->ios.timing == MMC_TIMING_MMC_HS200)) {
+ loop_cnt++;
+ adjust_window(priv, "tx_window", loop_cnt, tx_window);
+ }
+ } while (((tx_window == HS400_WINDOW_ALL_PASS && mmc->ios.timing == MMC_TIMING_MMC_HS400) ||
+ (tx_window == HS200_WINDOW_ALL_PASS && mmc->ios.timing == MMC_TIMING_MMC_HS200)) &&
+ loop_cnt < 60);
+
+ if (fail)
+ pinctrl_select_state(priv->pinctrl, priv->pins_tune4);
+ else if (mmc->ios.timing == MMC_TIMING_MMC_HS400)
+ pinctrl_select_state(priv->pinctrl, priv->pins_hs400);
+ else if (mmc->ios.timing == MMC_TIMING_MMC_HS200)
+ pinctrl_select_state(priv->pinctrl, priv->pins_hs200);
+
+ tx_best = search_best(tx_window, tx_range);
+ if (tx_best == 0xff) {
+ dev_err(mmc_dev(mmc), "no 6 continuous points for tx phase\n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ dw_mci_rtk_phase_tuning(host, tx_best, 0xff);
+ dev_info(mmc_dev(mmc), "tx_window = 0x%x, tx_best = 0x%x\n", tx_window, tx_best);
+
+out:
+ /* We send cmd 13 again because the eMMC handling might send command 12 more than twice.
+ * After kernel 5.4, system might send cmd13 first before issuing any command,
+ * user will see the illegal command status because the emmc tuning error handling
+ */
+
+ dw_mci_rtk_send_tuning(mmc, MMC_SEND_STATUS, 0x10000, 0);
+ host->tuning = 0;
+
+ return ret;
+}
+
+static void dw_mci_rtk_set_ios(struct dw_mci_slot *slot, struct mmc_ios *ios)
+{
+ struct dw_mci *host = slot->host;
+ int ret;
+ u32 status;
+
+ clk_disable(host->ciu_clk);
+ udelay(10);
+
+ ret = clk_set_rate(host->ciu_clk, ios->clock);
+
+ if (ret)
+ dev_warn(host->dev, "failed to set rate %uHz\n", ios->clock);
+
+ host->bus_hz = clk_get_rate(host->ciu_clk);
+
+ udelay(10);
+ clk_enable(host->ciu_clk);
+
+ ret = readl_poll_timeout(host->regs + SDMMC_CLK_CTRL_R, status,
+ (status & SDMMC_INTERNAL_CLK_MSK) == 0x3, 10, DW_MCI_TIMEOUT);
+ if (ret)
+ dev_err(host->dev, "Waiting emmc set_ios timeout\n");
+
+ mcq_writeb(host, SW_RST_R, SDMMC_RST_CMD | SDMMC_RST_DAT);
+
+ ret = readl_poll_timeout(host->regs + SDMMC_CLK_CTRL_R, status,
+ (status & SW_RST_BOTH_DONE) == 0x0, 10, DW_MCI_TIMEOUT);
+ if (ret)
+ dev_err(host->dev, "Waiting sw reset timeout\n");
+
+ clk_disable(host->ciu_clk);
+ udelay(6);
+
+ setup_clk_div(slot);
+
+ mcq_writel(host, CKGEN_CTL, mcq_readl(host, CKGEN_CTL) & ~SDMMC_CLK_DIV_SEL);
+ udelay(6);
+
+ clk_enable(host->ciu_clk);
+ udelay(6);
+
+ ret = readl_poll_timeout(host->regs + SDMMC_CLK_CTRL_R, status,
+ (status & SDMMC_INTERNAL_CLK_MSK) == 0x3, 10, DW_MCI_TIMEOUT);
+ if (ret)
+ dev_err(host->dev, "Waiting emmc set_ios timeout\n");
+
+ mcq_writeb(host, SW_RST_R, SDMMC_RST_CMD | SDMMC_RST_DAT);
+
+ ret = readl_poll_timeout(host->regs + SDMMC_CLK_CTRL_R, status,
+ (status & SW_RST_BOTH_DONE) == 0x0, 10, DW_MCI_TIMEOUT);
+ if (ret)
+ dev_err(host->dev, "Waiting sw reset timeout\n");
+
+ mcq_writel(host, OTHER1, mcq_readl(host, OTHER1) & (~SDMMC_TOP_RST_N_FIFO));
+ udelay(10);
+ mcq_writel(host, OTHER1, mcq_readl(host, OTHER1) | SDMMC_TOP_RST_N_FIFO);
+ udelay(10);
+}
+
+static int dw_mci_rtk_prepare_hs400_tuning(struct dw_mci *host, struct mmc_ios *ios)
+{
+ dqs_delay_tap_setting(host, hs400_dqs_delay_val);
+
+ data_delay_tap_setting(host);
+
+ cmd_delay_tap_setting(host, 0);
+
+ return 0;
+}
+
+static void dw_mci_rtk_hs400_complete(struct mmc_host *mmc)
+{
+ struct dw_mci_slot *slot = mmc_priv(mmc);
+ struct dw_mci *host = slot->host;
+ const struct dw_mci_drv_data *drv_data = host->drv_data;
+ struct dw_mci_rtkemmc_host *priv = host->priv;
+ int err = -EINVAL;
+
+ if (drv_data && drv_data->execute_tuning)
+ err = drv_data->execute_tuning(slot,
+ MMC_SEND_TUNING_BLOCK_HS200);
+
+ if (err != 0) {
+ dev_err(mmc_dev(mmc), "HS400 tuning failed and down speed to hs200, err=%d\n", err);
+ host->pdata->caps &= ~MMC_CAP_1_8V_DDR;
+ host->pdata->caps2 &= ~MMC_CAP2_HS400_1_8V;
+ mmc->card->mmc_avail_type &= ~EXT_CSD_CARD_TYPE_HS400_1_8V;
+ priv->emmc_mode = 2;
+ mmc_hw_reset(mmc->card);
+ }
+}
+
+static void dw_mci_rtk_init_card(struct mmc_host *host, struct mmc_card *card)
+{
+ /* In Realtek Platform, we need to attach eMMC card onto mmc host
+ * during eMMC initialization because of the following reason:
+ * When system cannot run the hs400, we need to down speed to hs200
+ * and call mmc_hw_reset and modify the mmc card attribute through mmc host.
+ * At this moment, system will show errors if host->card = NULL.
+ */
+ host->card = card;
+}
+
+static int dw_mci_rtk_parse_dt(struct dw_mci *host)
+{
+ struct dw_mci_rtkemmc_host *priv;
+ const u32 *prop;
+ int size;
+
+ priv = devm_kzalloc(host->dev, sizeof(struct dw_mci_rtkemmc_host), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pinctrl = devm_pinctrl_get(host->dev);
+ if (IS_ERR(priv->pinctrl))
+ dev_dbg(host->dev, "no pinctrl\n");
+
+ priv->pins_default = pinctrl_lookup_state(priv->pinctrl,
+ PINCTRL_STATE_DEFAULT);
+ if (IS_ERR(priv->pins_default))
+ dev_warn(host->dev, "could not get default state\n");
+
+ priv->pins_sdr50 = pinctrl_lookup_state(priv->pinctrl,
+ "sdr50");
+ if (IS_ERR(priv->pins_sdr50))
+ dev_warn(host->dev, "could not get sdr50 state\n");
+
+ priv->pins_hs200 = pinctrl_lookup_state(priv->pinctrl,
+ "hs200");
+ if (IS_ERR(priv->pins_hs200))
+ dev_warn(host->dev, "could not get hs200 state\n");
+
+ priv->pins_hs400 = pinctrl_lookup_state(priv->pinctrl,
+ "hs400");
+ if (IS_ERR(priv->pins_hs400))
+ dev_warn(host->dev, "could not get hs400 state\n");
+
+ priv->pins_tune0 = pinctrl_lookup_state(priv->pinctrl,
+ "tune0");
+ if (IS_ERR(priv->pins_tune0))
+ dev_warn(host->dev, "could not get tune0 state\n");
+
+ priv->pins_tune1 = pinctrl_lookup_state(priv->pinctrl,
+ "tune1");
+ if (IS_ERR(priv->pins_tune1))
+ dev_warn(host->dev, "could not get tune1 state\n");
+
+ priv->pins_tune2 = pinctrl_lookup_state(priv->pinctrl,
+ "tune2");
+ if (IS_ERR(priv->pins_tune2))
+ dev_warn(host->dev, "could not get tune2 state\n");
+
+ priv->pins_tune3 = pinctrl_lookup_state(priv->pinctrl,
+ "tune3");
+ if (IS_ERR(priv->pins_tune3))
+ dev_warn(host->dev, "could not get tune3 state\n");
+
+ priv->pins_tune4 = pinctrl_lookup_state(priv->pinctrl,
+ "tune4");
+
+ if (IS_ERR(priv->pins_tune4))
+ dev_warn(host->dev, "could not get tune4 state\n");
+
+ priv->vp0 = devm_clk_get(host->dev, "vp0");
+ if (IS_ERR(priv->vp0))
+ dev_err(host->dev, "could not get vp0 clk\n");
+
+ priv->vp1 = devm_clk_get(host->dev, "vp1");
+ if (IS_ERR(priv->vp1))
+ dev_err(host->dev, "could not get vp1 clk\n");
+
+ priv->emmc_mode = 0;
+ prop = of_get_property(host->dev->of_node, "speed-step", &size);
+ if (prop) {
+ priv->emmc_mode = of_read_number(prop, 1);
+ dev_info(host->dev, "emmc mode : %d\n", priv->emmc_mode);
+ } else {
+ dev_info(host->dev, "use default emmc sdr50 mode !\n");
+ }
+
+ priv->is_cqe = 0;
+ prop = of_get_property(host->dev->of_node, "cqe", &size);
+ if (prop) {
+ priv->is_cqe = of_read_number(prop, 1);
+ dev_info(host->dev, "cmdq mode : %d\n", priv->is_cqe);
+ } else {
+ dev_info(host->dev, "use default eMMC legacy mode !\n");
+ }
+
+ prop = of_get_property(host->dev->of_node, "rdq-ctrl", &size);
+ if (prop) {
+ priv->rdq_ctrl = of_read_number(prop, 1);
+ dev_info(host->dev, "get rdq-ctrl : %u\n", priv->rdq_ctrl);
+ } else {
+ priv->rdq_ctrl = 0;
+ dev_info(host->dev, "no dqs_dly_tape switch node, use default 0x0 !!\n");
+ }
+
+ priv->m2tmx = syscon_regmap_lookup_by_phandle(host->dev->of_node, "realtek,m2tmx");
+ if (IS_ERR_OR_NULL(priv->m2tmx))
+ dev_err(host->dev, "can not get m2mtx node.\n");
+
+ host->priv = priv;
+
+ return 0;
+}
+
+static int dw_mci_rtk_init(struct dw_mci *host)
+{
+ struct dw_mci_rtkemmc_host *priv = host->priv;
+
+ host->pdata->caps2 = MMC_CAP2_NO_SDIO | MMC_CAP2_NO_SD;
+
+ if (priv->emmc_mode >= 2)
+ host->pdata->caps2 |= MMC_CAP2_HS200_1_8V_SDR;
+ if (priv->emmc_mode >= 3) {
+ host->pdata->caps |= MMC_CAP_1_8V_DDR;
+ host->pdata->caps2 |= MMC_CAP2_HS400_1_8V;
+ }
+
+ if (priv->is_cqe > 0)
+ host->pdata->caps2 |= (MMC_CAP2_CQE | MMC_CAP2_CQE_DCMD);
+
+ host->irq_flags = IRQF_SHARED;
+
+ mcq_writel(host, CP, 0x0);
+
+ /*Enable L4 gated*/
+ mcq_writel(host, OTHER1, mcq_readl(host, OTHER1) &
+ ~(SDMMC_L4_GATED_DIS | SDMMC_L4_GATED_DIS1));
+
+ mcq_writel(host, OTHER1, mcq_readl(host, OTHER1) &
+ (~(SDMMC_DQS_CTRL_GATE_DIS | SDMMC_DBUS_MAS_GATING_DIS)));
+
+ /*Set the eMMC wrapper little Endian*/
+ mcq_writel(host, AHB, mcq_readl(host, AHB) | SDMMC_AHB_BIG);
+
+ mcq_writel(host, OTHER1,
+ mcq_readl(host, OTHER1) | SDMMC_STARK_CARD_STOP_ENABLE);
+
+ /*set eMMC instead of nand*/
+ regmap_update_bits_base(priv->m2tmx, SDMMC_NAND_DMA_SEL,
+ SDMMC_SRAM_DMA_SEL, SDMMC_SRAM_DMA_SEL, NULL, false, true);
+
+ /*Set the clk initial phase*/
+ dw_mci_rtk_phase_tuning(host, 0, 0);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int dw_mci_rtk_suspend(struct device *dev)
+{
+ struct dw_mci *host = dev_get_drvdata(dev);
+ int ret = 0;
+
+ ret = dw_mci_cqe_runtime_suspend(dev);
+ mcq_writel(host, AHB, 0);
+
+ return ret;
+}
+
+static int dw_mci_rtk_resume(struct device *dev)
+{
+ struct dw_mci *host = dev_get_drvdata(dev);
+ int ret = 0;
+
+ mcq_writel(host, AHB, mcq_readl(host, AHB) | SDMMC_AHB_BIG);
+ ret = dw_mci_cqe_runtime_resume(dev);
+
+ return ret;
+}
+#else
+static int dw_mci_rtk_suspend(struct device *dev)
+{
+ dev_info(dev, "User should enable CONFIG_PM kernel config\n");
+
+ return 0;
+}
+
+static int dw_mci_rtk_resume(struct device *dev)
+{
+ dev_info(dev, "User should enable CONFIG_PM kernel config\n");
+
+ return 0;
+}
+#endif /*CONFIG_PM*/
+static const struct dev_pm_ops rtk_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(dw_mci_rtk_suspend,
+ dw_mci_rtk_resume)
+ SET_RUNTIME_PM_OPS(dw_mci_cqe_runtime_suspend,
+ dw_mci_cqe_runtime_resume,
+ NULL)
+};
+
+static void dw_mci_rtk_shutdown(struct platform_device *pdev)
+{
+ dev_info(&pdev->dev, "[eMMC] Shutdown\n");
+ dw_mci_cqe_runtime_resume(&pdev->dev);
+}
+
+static unsigned long dw_mci_rtk_dwmmc_caps[1] = {
+ MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA |
+ MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED |
+ MMC_CAP_NONREMOVABLE | MMC_CAP_CMD23,
+};
+
+static const struct dw_mci_drv_data rtk_drv_data = {
+ .caps = dw_mci_rtk_dwmmc_caps,
+ .num_caps = ARRAY_SIZE(dw_mci_rtk_dwmmc_caps),
+ .set_ios = dw_mci_rtk_set_ios,
+ .execute_tuning = dw_mci_rtk_execute_tuning,
+ .parse_dt = dw_mci_rtk_parse_dt,
+ .init = dw_mci_rtk_init,
+ .prepare_hs400_tuning = dw_mci_rtk_prepare_hs400_tuning,
+ .hs400_complete = dw_mci_rtk_hs400_complete,
+ .init_card = dw_mci_rtk_init_card,
+};
+
+static const struct of_device_id dw_mci_rtk_match[] = {
+ { .compatible = "realtek,rtd1325-dw-cqe-emmc",
+ .data = &rtk_drv_data },
+ {},
+};
+MODULE_DEVICE_TABLE(of, dw_mci_rtk_match);
+
+int dw_mci_cqe_pltfm_register(struct platform_device *pdev,
+ const struct dw_mci_drv_data *drv_data)
+{
+ struct dw_mci *host;
+ struct resource *regs;
+
+ host = devm_kzalloc(&pdev->dev, sizeof(struct dw_mci), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+
+ host->irq = platform_get_irq(pdev, 0);
+ if (host->irq < 0)
+ return host->irq;
+
+ host->drv_data = drv_data;
+ host->pdev = pdev;
+ host->dev = &pdev->dev;
+ host->irq_flags = 0;
+ host->pdata = pdev->dev.platform_data;
+
+ regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ host->regs = devm_ioremap_resource(&pdev->dev, regs);
+ if (IS_ERR(host->regs))
+ return PTR_ERR(host->regs);
+
+ /* Get registers' physical base address */
+ host->phy_regs = regs->start;
+
+ platform_set_drvdata(pdev, host);
+
+ return dw_mci_cqe_probe(host);
+}
+
+static int dw_mci_rtk_probe(struct platform_device *pdev)
+{
+ const struct dw_mci_drv_data *drv_data;
+ const struct of_device_id *match;
+
+ if (!pdev->dev.of_node)
+ return -ENODEV;
+
+ match = of_match_node(dw_mci_rtk_match, pdev->dev.of_node);
+ drv_data = match->data;
+
+ return dw_mci_cqe_pltfm_register(pdev, drv_data);
+}
+
+int dw_mci_rtk_remove(struct platform_device *pdev)
+{
+ struct dw_mci *host = platform_get_drvdata(pdev);
+
+ dw_mci_cqe_remove(host);
+ return 0;
+}
+
+static struct platform_driver dw_mci_rtk_pltfm_driver = {
+ .probe = dw_mci_rtk_probe,
+ .remove = dw_mci_rtk_remove,
+ .driver = {
+ .name = "dwmmc_cqe_rtk",
+ .of_match_table = dw_mci_rtk_match,
+ .pm = &rtk_dev_pm_ops,
+ },
+ .shutdown = dw_mci_rtk_shutdown,
+};
+
+module_platform_driver(dw_mci_rtk_pltfm_driver);
+
+MODULE_AUTHOR("<jyanchou@realtek.com>");
+MODULE_DESCRIPTION("Specific Driver Extension");
+MODULE_LICENSE("GPL");
new file mode 100644
@@ -0,0 +1,160 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Realtek Semiconductors, All Rights Reserved.
+ *
+ */
+
+#ifndef __DW_RTK_MMC_CQE_H
+#define __DW_RTK_MMC_CQE_H
+
+#define SDMMC_CP 0x41c
+#define SDMMC_OTHER1 0x420
+#define SDMMC_DUMMY_SYS 0x42c
+#define SDMMC_AHB 0x430
+#define SDMMC_CKGEN_CTL 0x478
+#define SDMMC_DQS_CTRL1 0x498
+#define SDMMC_IP_DESC0 0x4a0
+#define SDMMC_IP_DESC1 0x4a4
+#define SDMMC_IP_DESC2 0x4a8
+#define SDMMC_IP_DESC3 0x4ac
+#define SDMMC_DQ_CTRL_SET 0x50c
+#define SDMMC_WDQ_CTRL0 0x510
+#define SDMMC_WDQ_CTRL1 0x514
+#define SDMMC_WDQ_CTRL2 0x518
+#define SDMMC_WDQ_CTRL3 0x51c
+#define SDMMC_WDQ_CTRL4 0x520
+#define SDMMC_WDQ_CTRL5 0x524
+#define SDMMC_WDQ_CTRL6 0x528
+#define SDMMC_WDQ_CTRL7 0x52c
+#define SDMMC_RDQ_CTRL0 0x530
+#define SDMMC_RDQ_CTRL1 0x534
+#define SDMMC_RDQ_CTRL2 0x538
+#define SDMMC_RDQ_CTRL3 0x53c
+#define SDMMC_RDQ_CTRL4 0x540
+#define SDMMC_RDQ_CTRL5 0x544
+#define SDMMC_RDQ_CTRL6 0x548
+#define SDMMC_RDQ_CTRL7 0x54c
+#define SDMMC_CMD_CTRL_SET 0x550
+#define SDMMC_WCMD_CTRL 0x554
+#define SDMMC_RCMD_CTRL 0x558
+#define SDMMC_PLL_STATUS 0x55c
+
+#define SDMMC_NAND_DMA_SEL 0x54
+#define SDMMC_SRAM_DMA_SEL BIT(0)
+
+#define SDMMC_CLK_O_ICG_EN BIT(3)
+#define SDMMC_CARD_STOP_ENABLE BIT(23)
+#define SDMMC_STARK_CARD_STOP_ENABLE BIT(11)
+#define SDMMC_TOP_RST_N_FIFO BIT(3)
+
+#define SDMMC_FW_SET BIT(7)
+#define SDMMC_FW_SET_CMD_W BIT(0)
+
+#define SDMMC_CLK4M ((BIT(0) | BIT(1) | BIT(2)) << EMMC_CRC_CLK_CHANGE_SHIFT)
+#define SDMMC_CRC_CLK_DIV (BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | \
+ BIT(5) | BIT(6) | BIT(7))
+#define SDMMC_CRC_CLK_DIV_MASK (~SDMMC_CRC_CLK_DIV)
+#define SDMMC_CRC_CLK_DIV_EN BIT(8)
+#define SDMMC_CLK_INV_DIV_SEL BIT(9)
+#define SDMMC_SSC_CLK_DIV_SEL (BIT(19) | BIT(20))
+
+#define SDMMC_CLK_DIV512 (0x200)
+#define SDMMC_CLK_DIV4 (0x4)
+#define SDMMC_CLK_DIV1 (0x1)
+#define SDMMC_INTERNAL_CLK_MSK (0x3)
+
+/*0x2C CLK_CTRL_R*/
+#define SDMMC_INTERNAL_CLK_STABLE BIT(1)
+#define SDMMC_INTERNAL_CLK_EN BIT(0)
+
+/*0x2f SW_RST_R*/
+#define SDMMC_RST_DAT BIT(2)
+#define SDMMC_RST_CMD BIT(1)
+
+/*0x420 OTHER1*/
+#define SDMMC_DBUS_MAS_GATING_DIS BIT(13)
+#define SDMMC_DQS_CTRL_GATE_DIS BIT(12)
+#define SDMMC_CLK_DIV_RST BIT(10)
+#define SDMMC_L4_GATED_DIS1 BIT(2)
+#define SDMMC_L4_GATED_DIS BIT(0)
+
+/*0x430 */
+#define SDMMC_AHB_BIG BIT(2)
+
+/*0x478 */
+#define SDMMC_CLK_DIV_SEL BIT(20)
+
+/*0x498 */
+#define SDMMC_FW_EN BIT(8)
+
+/*0x554 */
+#define SDMMC_FW_SET_EN BIT(7)
+
+struct backupRegs {
+ u32 sdmasa_r; /*0x000*/
+ u16 blocksize_r; /*0x004*/
+ u16 blockcount_r; /*0x006*/
+ u16 xfer_mode_r; /*0x00c*/
+ u8 host_ctrl1_r; /*0x028*/
+ u8 pwr_ctrl_r; /*0x029*/
+ u8 bgap_ctrl_r; /*0x02a*/
+ u16 clk_ctrl_r; /*0x02c*/
+ u8 tout_ctrl_r; /*0x02e*/
+
+ u16 normal_int_stat_en_r; /*0x034*/
+ u16 error_int_stat_en_r; /*0x036*/
+ u16 normal_int_signal_en_r; /*0x038*/
+ u16 error_int_signal_en_r; /*0x03a*/
+ u16 auto_cmd_stat_r; /*0x03c*/
+ u16 host_ctrl2_r; /*0x03e*/
+ u32 adma_sa_low_r; /*0x058*/
+ u8 mshc_ctrl_r; /*0x208*/
+ u8 ctrl_r; /*0x22c*/
+ u32 other1; /*0x420*/
+ u32 dummy_sys; /*0x42c*/
+ u32 dqs_ctrl1; /*0x498*/
+ u32 wcmd_ctrl; /*0x554*/
+
+ u32 rdq_ctrl0; /*0x530*/
+ u32 rdq_ctrl1; /*0x534*/
+ u32 rdq_ctrl2; /*0x538*/
+ u32 rdq_ctrl3; /*0x53c*/
+ u32 rdq_ctrl4; /*0x540*/
+ u32 rdq_ctrl5; /*0x544*/
+ u32 rdq_ctrl6; /*0x548*/
+ u32 rdq_ctrl7; /*0x54c*/
+ u32 dq_ctrl_set; /*0x50c*/
+ u32 ahb;
+ u32 ckgen_ctl;
+};
+
+struct dw_mci_rtkemmc_host {
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *pins_default;
+ struct pinctrl_state *pins_sdr50;
+ struct pinctrl_state *pins_ddr50;
+ struct pinctrl_state *pins_hs200;
+ struct pinctrl_state *pins_hs400;
+ struct pinctrl_state *pins_tune0;
+ struct pinctrl_state *pins_tune1;
+ struct pinctrl_state *pins_tune2;
+ struct pinctrl_state *pins_tune3;
+ struct pinctrl_state *pins_tune4;
+ struct regmap *m2tmx;
+ struct clk *vp0;
+ struct clk *vp1;
+ struct backupRegs gRegTbl;
+ int emmc_mode;
+ unsigned int rdq_ctrl;
+ unsigned int is_cqe;
+};
+
+int mmc_cmdq_enable(struct mmc_card *card);
+int mmc_cmdq_disable(struct mmc_card *card);
+int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
+ unsigned int timeout_ms);
+int __mmc_claim_host(struct mmc_host *host, struct mmc_ctx *ctx,
+ atomic_t *abort);
+void mmc_release_host(struct mmc_host *host);
+
+#endif
Add Realtek mmc driver to make good use Synopsys DesignWare mmc cmdq host driver. Signed-off-by: Jyan Chou <jyanchou@realtek.com> --- v3 -> v4: - Modify dma setting's code to fix linux coding style. - Drop useless function messages. - Remove MODULE_ALIAS(). v0 -> v1: - Seperate different support into single patch. - Fix the compiler complains. --- --- drivers/mmc/host/Kconfig | 9 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/dw_mmc_cqe-rtk.c | 979 ++++++++++++++++++++++++++++++ drivers/mmc/host/dw_mmc_cqe-rtk.h | 160 +++++ 4 files changed, 1149 insertions(+) create mode 100644 drivers/mmc/host/dw_mmc_cqe-rtk.c create mode 100644 drivers/mmc/host/dw_mmc_cqe-rtk.h