2899 lines
85 KiB
Diff
2899 lines
85 KiB
Diff
From d21cdea66ba57c43a85df7abc560f1ba771c191b Mon Sep 17 00:00:00 2001
|
|
From: liangshuang <liangshuang@eswincomputing.com>
|
|
Date: Wed, 22 May 2024 17:27:11 +0800
|
|
Subject: [PATCH 014/219] feat:Add emmc and sdio driver.
|
|
|
|
Changelogs:
|
|
1.Add emmc and sdio driver for linux-6.6.
|
|
2.enable sw tunning in dts.
|
|
---
|
|
arch/riscv/boot/dts/eswin/eic7700-evb-a2.dts | 7 +-
|
|
arch/riscv/boot/dts/eswin/eic7700-evb.dts | 1 +
|
|
.../boot/dts/eswin/hifive-premier-550.dts | 1 +
|
|
drivers/mmc/host/Kconfig | 28 +
|
|
drivers/mmc/host/Makefile | 4 +
|
|
drivers/mmc/host/sdhci-eswin.c | 301 +++++
|
|
drivers/mmc/host/sdhci-eswin.h | 237 ++++
|
|
drivers/mmc/host/sdhci-of-eswin-sdio.c | 1095 ++++++++++++++++
|
|
drivers/mmc/host/sdhci-of-eswin.c | 1109 +++++++++++++++++
|
|
9 files changed, 2781 insertions(+), 2 deletions(-)
|
|
create mode 100644 drivers/mmc/host/sdhci-eswin.c
|
|
create mode 100644 drivers/mmc/host/sdhci-eswin.h
|
|
create mode 100644 drivers/mmc/host/sdhci-of-eswin-sdio.c
|
|
create mode 100644 drivers/mmc/host/sdhci-of-eswin.c
|
|
|
|
diff --git a/arch/riscv/boot/dts/eswin/eic7700-evb-a2.dts b/arch/riscv/boot/dts/eswin/eic7700-evb-a2.dts
|
|
index 300eed57dca6..00154660ab7c 100644
|
|
--- a/arch/riscv/boot/dts/eswin/eic7700-evb-a2.dts
|
|
+++ b/arch/riscv/boot/dts/eswin/eic7700-evb-a2.dts
|
|
@@ -491,17 +491,20 @@ &sdhci_emmc {
|
|
&sdio0 {
|
|
/* sd card */
|
|
status = "okay";
|
|
- delay_code = <0x16>;
|
|
+ delay_code = <0x23>;
|
|
+ phase_code = <0x5>;
|
|
drive-impedance-ohm = <33>;
|
|
enable-cmd-pullup;
|
|
enable-data-pullup;
|
|
+ enable_sw_tuning;
|
|
no-sdio;
|
|
};
|
|
|
|
&sdio1 {
|
|
/* wifi module */
|
|
status = "okay";
|
|
- delay_code = <0x21>;
|
|
+ delay_code = <0x23>;
|
|
+ phase_code = <0x5>;
|
|
drive-impedance-ohm = <33>;
|
|
enable-cmd-pullup;
|
|
enable-data-pullup;
|
|
diff --git a/arch/riscv/boot/dts/eswin/eic7700-evb.dts b/arch/riscv/boot/dts/eswin/eic7700-evb.dts
|
|
index ba2b6a3ea3b5..7a9883a89dc3 100644
|
|
--- a/arch/riscv/boot/dts/eswin/eic7700-evb.dts
|
|
+++ b/arch/riscv/boot/dts/eswin/eic7700-evb.dts
|
|
@@ -466,6 +466,7 @@ &sdio0 {
|
|
drive-impedance-ohm = <33>;
|
|
enable-cmd-pullup;
|
|
enable-data-pullup;
|
|
+ enable_sw_tuning;
|
|
no-sdio;
|
|
};
|
|
|
|
diff --git a/arch/riscv/boot/dts/eswin/hifive-premier-550.dts b/arch/riscv/boot/dts/eswin/hifive-premier-550.dts
|
|
index d3baea543371..550a8cc2fbaf 100644
|
|
--- a/arch/riscv/boot/dts/eswin/hifive-premier-550.dts
|
|
+++ b/arch/riscv/boot/dts/eswin/hifive-premier-550.dts
|
|
@@ -400,6 +400,7 @@ &sdio0 {
|
|
/* sd card */
|
|
status = "okay";
|
|
delay_code = <0x16>;
|
|
+ phase_code = <0x1f>;
|
|
drive-impedance-ohm = <33>;
|
|
enable-cmd-pullup;
|
|
enable-data-pullup;
|
|
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
|
|
index bc7e2ad37002..2fb029a80648 100644
|
|
--- a/drivers/mmc/host/Kconfig
|
|
+++ b/drivers/mmc/host/Kconfig
|
|
@@ -150,6 +150,34 @@ config MMC_SDHCI_PLTFM
|
|
|
|
If unsure, say N.
|
|
|
|
+config MMC_SDHCI_OF_ESWIN
|
|
+ tristate "SDHCI OF support for the Eswin SDHCI controllers"
|
|
+ depends on MMC_SDHCI_PLTFM
|
|
+ depends on OF
|
|
+ depends on COMMON_CLK
|
|
+ select MMC_CQHCI
|
|
+ help
|
|
+ This selects the Eswin Secure Digital Host Controller Interface
|
|
+ (SDHCI).
|
|
+
|
|
+ If you have a controller with this interface, say Y or M here.
|
|
+
|
|
+ If unsure, say N.
|
|
+
|
|
+config MMC_SDHCI_OF_SDIO_ESWIN
|
|
+ tristate "SDHCI OF SDIO support for the Eswin SDHCI controllers"
|
|
+ depends on MMC_SDHCI_PLTFM
|
|
+ depends on OF
|
|
+ depends on COMMON_CLK
|
|
+ select MMC_CQHCI
|
|
+ help
|
|
+ This selects the Eswin Secure Digital Host Controller Interface
|
|
+ (SDHCI-SDIO).
|
|
+
|
|
+ If you have a controller with this interface, say Y or M here.
|
|
+
|
|
+ If unsure, say N.
|
|
+
|
|
config MMC_SDHCI_OF_ARASAN
|
|
tristate "SDHCI OF support for the Arasan SDHCI controllers"
|
|
depends on MMC_SDHCI_PLTFM
|
|
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
|
|
index a693fa3d3f1c..bcab602d3ea5 100644
|
|
--- a/drivers/mmc/host/Makefile
|
|
+++ b/drivers/mmc/host/Makefile
|
|
@@ -81,6 +81,10 @@ obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o
|
|
obj-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o
|
|
obj-$(CONFIG_MMC_SDHCI_TEGRA) += sdhci-tegra.o
|
|
obj-$(CONFIG_MMC_SDHCI_OF_ARASAN) += sdhci-of-arasan.o
|
|
+eswin-sdhci-emmc-objs := sdhci-of-eswin.o sdhci-eswin.o
|
|
+obj-$(CONFIG_MMC_SDHCI_OF_ESWIN) += eswin-sdhci-emmc.o
|
|
+eswin-sdhci-sdio-objs := sdhci-of-eswin-sdio.o sdhci-eswin.o
|
|
+obj-$(CONFIG_MMC_SDHCI_OF_SDIO_ESWIN) += eswin-sdhci-sdio.o
|
|
obj-$(CONFIG_MMC_SDHCI_OF_ASPEED) += sdhci-of-aspeed.o
|
|
obj-$(CONFIG_MMC_SDHCI_OF_AT91) += sdhci-of-at91.o
|
|
obj-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o
|
|
diff --git a/drivers/mmc/host/sdhci-eswin.c b/drivers/mmc/host/sdhci-eswin.c
|
|
new file mode 100644
|
|
index 000000000000..c823d034ce6b
|
|
--- /dev/null
|
|
+++ b/drivers/mmc/host/sdhci-eswin.c
|
|
@@ -0,0 +1,301 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * ESWIN Emmc Driver
|
|
+ *
|
|
+ * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved.
|
|
+ * SPDX-License-Identifier: GPL-2.0
|
|
+ *
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation, version 2.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
+ *
|
|
+ * Authors: liangshuang <liangshuang@eswincomputing.com>
|
|
+ */
|
|
+#include <linux/delay.h>
|
|
+#include <linux/reset.h>
|
|
+#include "sdhci-eswin.h"
|
|
+
|
|
+static void eswin_mshc_coreclk_config(struct sdhci_host *host, uint16_t divisor,
|
|
+ unsigned int flag_sel)
|
|
+{
|
|
+ struct sdhci_pltfm_host *pltfm_host;
|
|
+ struct eswin_sdhci_data *eswin_sdhci;
|
|
+ u32 val = 0;
|
|
+ u32 delay = 0xfffff;
|
|
+
|
|
+ pltfm_host = sdhci_priv(host);
|
|
+ eswin_sdhci = sdhci_pltfm_priv(pltfm_host);
|
|
+
|
|
+ val = readl(eswin_sdhci->core_clk_reg);
|
|
+ val &= ~MSHC_CORE_CLK_ENABLE;
|
|
+ writel(val, eswin_sdhci->core_clk_reg);
|
|
+ while (delay--)
|
|
+ ;
|
|
+ val &= ~(MSHC_CORE_CLK_FREQ_BIT_MASK << MSHC_CORE_CLK_FREQ_BIT_SHIFT);
|
|
+ val |= (divisor & MSHC_CORE_CLK_FREQ_BIT_MASK)
|
|
+ << MSHC_CORE_CLK_FREQ_BIT_SHIFT;
|
|
+ val &= ~(MSHC_CORE_CLK_SEL_BIT);
|
|
+ val |= flag_sel;
|
|
+ writel(val, eswin_sdhci->core_clk_reg);
|
|
+
|
|
+ udelay(100);
|
|
+ val |= MSHC_CORE_CLK_ENABLE;
|
|
+ writel(val, eswin_sdhci->core_clk_reg);
|
|
+ mdelay(1);
|
|
+}
|
|
+
|
|
+static void eswin_mshc_coreclk_disable(struct sdhci_host *host)
|
|
+{
|
|
+ struct sdhci_pltfm_host *pltfm_host;
|
|
+ struct eswin_sdhci_data *eswin_sdhci;
|
|
+ u32 val = 0;
|
|
+
|
|
+ pltfm_host = sdhci_priv(host);
|
|
+ eswin_sdhci = sdhci_pltfm_priv(pltfm_host);
|
|
+
|
|
+ val = readl(eswin_sdhci->core_clk_reg);
|
|
+ val &= ~MSHC_CORE_CLK_ENABLE;
|
|
+ writel(val, eswin_sdhci->core_clk_reg);
|
|
+}
|
|
+
|
|
+void eswin_sdhci_disable_card_clk(struct sdhci_host *host)
|
|
+{
|
|
+ unsigned int clk;
|
|
+
|
|
+ /* Reset SD Clock Enable */
|
|
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
|
|
+ clk &= ~SDHCI_CLOCK_CARD_EN;
|
|
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
|
|
+}
|
|
+
|
|
+void eswin_sdhci_enable_card_clk(struct sdhci_host *host)
|
|
+{
|
|
+ ktime_t timeout;
|
|
+ unsigned int clk;
|
|
+
|
|
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
|
|
+
|
|
+ clk |= SDHCI_CLOCK_INT_EN;
|
|
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
|
|
+
|
|
+ /* Wait max 150 ms */
|
|
+ timeout = ktime_add_ms(ktime_get(), 150);
|
|
+ while (1) {
|
|
+ bool timedout = ktime_after(ktime_get(), timeout);
|
|
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
|
|
+ if (clk & SDHCI_CLOCK_INT_STABLE)
|
|
+ break;
|
|
+ if (timedout) {
|
|
+ pr_err("%s: Internal clock never stabilised.\n",
|
|
+ mmc_hostname(host->mmc));
|
|
+ return;
|
|
+ }
|
|
+ udelay(10);
|
|
+ }
|
|
+
|
|
+ clk |= SDHCI_CLOCK_CARD_EN;
|
|
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
|
|
+ mdelay(1);
|
|
+}
|
|
+
|
|
+void eswin_sdhci_set_core_clock(struct sdhci_host *host,
|
|
+ unsigned int clock)
|
|
+{
|
|
+ unsigned int div, divide;
|
|
+ unsigned int flag_sel, max_clk;
|
|
+
|
|
+ if (clock == 0) {
|
|
+ eswin_mshc_coreclk_disable(host);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (SDHCI_CLK_208M % clock == 0) {
|
|
+ flag_sel = 1;
|
|
+ max_clk = SDHCI_CLK_208M;
|
|
+ } else {
|
|
+ flag_sel = 0;
|
|
+ max_clk = SDHCI_CLK_200M;
|
|
+ }
|
|
+
|
|
+ for (div = 1; div <= MAX_CORE_CLK_DIV; div++) {
|
|
+ if ((max_clk / div) <= clock)
|
|
+ break;
|
|
+ }
|
|
+ div--;
|
|
+
|
|
+ if (div == 0 || div == 1) {
|
|
+ divide = 2;
|
|
+ } else {
|
|
+ divide = (div + 1) * 2;
|
|
+ }
|
|
+ pr_debug("%s: clock:%d timing:%d\n", mmc_hostname(host->mmc), clock, host->timing);
|
|
+
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+ eswin_mshc_coreclk_config(host, divide, flag_sel);
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+ mdelay(2);
|
|
+}
|
|
+
|
|
+static void eswin_sdhci_set_clk_delays(struct sdhci_host *host)
|
|
+{
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host);
|
|
+ struct eswin_sdhci_clk_data *clk_data = &eswin_sdhci->clk_data;
|
|
+
|
|
+ clk_set_phase(clk_data->sampleclk,
|
|
+ clk_data->clk_phase_in[host->timing]);
|
|
+ clk_set_phase(clk_data->sdcardclk,
|
|
+ clk_data->clk_phase_out[host->timing]);
|
|
+}
|
|
+
|
|
+static void eswin_sdhci_dt_read_clk_phase(struct device *dev,
|
|
+ struct eswin_sdhci_clk_data *clk_data,
|
|
+ unsigned int timing, const char *prop)
|
|
+{
|
|
+ struct device_node *np = dev->of_node;
|
|
+
|
|
+ int clk_phase[2] = { 0 };
|
|
+
|
|
+ /*
|
|
+ * Read Tap Delay values from DT, if the DT does not contain the
|
|
+ * Tap Values then use the pre-defined values.
|
|
+ */
|
|
+ if (of_property_read_variable_u32_array(np, prop, &clk_phase[0], 2,
|
|
+ 0)) {
|
|
+ dev_dbg(dev, "Using predefined clock phase for %s = %d %d\n",
|
|
+ prop, clk_data->clk_phase_in[timing],
|
|
+ clk_data->clk_phase_out[timing]);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* The values read are Input and Output Clock Delays in order */
|
|
+ clk_data->clk_phase_in[timing] = clk_phase[0];
|
|
+ clk_data->clk_phase_out[timing] = clk_phase[1];
|
|
+}
|
|
+
|
|
+/**
|
|
+ * eswin_dt_parse_clk_phases - Read Clock Delay values from DT
|
|
+ *
|
|
+ * @dev: Pointer to our struct device.
|
|
+ * @clk_data: Pointer to the Clock Data structure
|
|
+ *
|
|
+ * Called at initialization to parse the values of Clock Delays.
|
|
+ */
|
|
+void eswin_sdhci_dt_parse_clk_phases(struct device *dev,
|
|
+ struct eswin_sdhci_clk_data *clk_data)
|
|
+{
|
|
+ clk_data->set_clk_delays = eswin_sdhci_set_clk_delays;
|
|
+
|
|
+ eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_LEGACY,
|
|
+ "clk-phase-legacy");
|
|
+ eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_MMC_HS,
|
|
+ "clk-phase-mmc-hs");
|
|
+ eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_SD_HS,
|
|
+ "clk-phase-sd-hs");
|
|
+ eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_UHS_SDR12,
|
|
+ "clk-phase-uhs-sdr12");
|
|
+ eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_UHS_SDR25,
|
|
+ "clk-phase-uhs-sdr25");
|
|
+ eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_UHS_SDR50,
|
|
+ "clk-phase-uhs-sdr50");
|
|
+ eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_UHS_SDR104,
|
|
+ "clk-phase-uhs-sdr104");
|
|
+ eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_UHS_DDR50,
|
|
+ "clk-phase-uhs-ddr50");
|
|
+ eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_MMC_DDR52,
|
|
+ "clk-phase-mmc-ddr52");
|
|
+ eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_MMC_HS200,
|
|
+ "clk-phase-mmc-hs200");
|
|
+ eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_MMC_HS400,
|
|
+ "clk-phase-mmc-hs400");
|
|
+}
|
|
+
|
|
+unsigned int eswin_convert_drive_impedance_ohm(struct platform_device *pdev,
|
|
+ unsigned int dr_ohm)
|
|
+{
|
|
+ switch (dr_ohm) {
|
|
+ case 100:
|
|
+ return PHYCTRL_DR_100OHM;
|
|
+ case 66:
|
|
+ return PHYCTRL_DR_66OHM;
|
|
+ case 50:
|
|
+ return PHYCTRL_DR_50OHM;
|
|
+ case 40:
|
|
+ return PHYCTRL_DR_40OHM;
|
|
+ case 33:
|
|
+ return PHYCTRL_DR_33OHM;
|
|
+ }
|
|
+
|
|
+ dev_warn(&pdev->dev, "Invalid value %u for drive-impedance-ohm.\n",
|
|
+ dr_ohm);
|
|
+ return PHYCTRL_DR_50OHM;
|
|
+}
|
|
+
|
|
+static void eswin_sdhci_do_reset(struct eswin_sdhci_data *eswin_sdhci)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = reset_control_assert(eswin_sdhci->txrx_rst);
|
|
+ WARN_ON(0 != ret);
|
|
+ ret = reset_control_assert(eswin_sdhci->phy_rst);
|
|
+ WARN_ON(0 != ret);
|
|
+ ret = reset_control_assert(eswin_sdhci->prstn);
|
|
+ WARN_ON(0 != ret);
|
|
+ ret = reset_control_assert(eswin_sdhci->arstn);
|
|
+ WARN_ON(0 != ret);
|
|
+
|
|
+ mdelay(2);
|
|
+
|
|
+ ret = reset_control_deassert(eswin_sdhci->txrx_rst);
|
|
+ WARN_ON(0 != ret);
|
|
+ ret = reset_control_deassert(eswin_sdhci->phy_rst);
|
|
+ WARN_ON(0 != ret);
|
|
+ ret = reset_control_deassert(eswin_sdhci->prstn);
|
|
+ WARN_ON(0 != ret);
|
|
+ ret = reset_control_deassert(eswin_sdhci->arstn);
|
|
+ WARN_ON(0 != ret);
|
|
+}
|
|
+
|
|
+int eswin_sdhci_reset_init(struct device *dev,
|
|
+ struct eswin_sdhci_data *eswin_sdhci)
|
|
+{
|
|
+ int ret = 0;
|
|
+ eswin_sdhci->txrx_rst = devm_reset_control_get_optional(dev, "txrx_rst");
|
|
+ if (IS_ERR_OR_NULL(eswin_sdhci->txrx_rst)) {
|
|
+ dev_err_probe(dev, PTR_ERR(eswin_sdhci->txrx_rst),
|
|
+ "txrx_rst reset not found.\n");
|
|
+ return -EFAULT;
|
|
+ }
|
|
+
|
|
+ eswin_sdhci->phy_rst = devm_reset_control_get_optional(dev, "phy_rst");
|
|
+ if (IS_ERR_OR_NULL(eswin_sdhci->phy_rst)) {
|
|
+ dev_err_probe(dev, PTR_ERR(eswin_sdhci->phy_rst),
|
|
+ "phy_rst reset not found.\n");
|
|
+ return -EFAULT;
|
|
+ }
|
|
+
|
|
+ eswin_sdhci->prstn = devm_reset_control_get_optional(dev, "prstn");
|
|
+ if (IS_ERR_OR_NULL(eswin_sdhci->prstn)) {
|
|
+ dev_err_probe(dev, PTR_ERR(eswin_sdhci->prstn),
|
|
+ "prstn reset not found.\n");
|
|
+ return -EFAULT;
|
|
+ }
|
|
+
|
|
+ eswin_sdhci->arstn = devm_reset_control_get_optional(dev, "arstn");
|
|
+ if (IS_ERR_OR_NULL(eswin_sdhci->arstn)) {
|
|
+ dev_err_probe(dev, PTR_ERR(eswin_sdhci->arstn),
|
|
+ "arstn reset not found.\n");
|
|
+ return -EFAULT;
|
|
+ }
|
|
+ eswin_sdhci_do_reset(eswin_sdhci);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
diff --git a/drivers/mmc/host/sdhci-eswin.h b/drivers/mmc/host/sdhci-eswin.h
|
|
new file mode 100644
|
|
index 000000000000..08ddfdf8932e
|
|
--- /dev/null
|
|
+++ b/drivers/mmc/host/sdhci-eswin.h
|
|
@@ -0,0 +1,237 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * ESWIN SDHCI Driver
|
|
+ *
|
|
+ * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved.
|
|
+ * SPDX-License-Identifier: GPL-2.0
|
|
+ *
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation, version 2.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
+ *
|
|
+ * Authors: liangshuang <liangshuang@eswincomputing.com>
|
|
+ */
|
|
+#ifndef _DRIVERS_MMC_SDHCI_ESWIN_H
|
|
+#define _DRIVERS_MMC_SDHCI_ESWIN_H
|
|
+
|
|
+#include <linux/reset.h>
|
|
+#include <linux/clk-provider.h>
|
|
+#include "sdhci-pltfm.h"
|
|
+
|
|
+#define MSHC_CARD_CLK_STABLE BIT(28)
|
|
+#define MSHC_INT_BCLK_STABLE BIT(16)
|
|
+#define MSHC_INT_ACLK_STABLE BIT(8)
|
|
+#define MSHC_INT_TMCLK_STABLE BIT(0)
|
|
+#define MSHC_INT_CLK_STABLE \
|
|
+ (MSHC_CARD_CLK_STABLE | MSHC_INT_ACLK_STABLE | \
|
|
+ MSHC_INT_BCLK_STABLE | MSHC_INT_TMCLK_STABLE)
|
|
+#define MSHC_HOST_VAL_STABLE BIT(0)
|
|
+#define EMMC0_CARD_DETECT BIT(9)
|
|
+#define EMMC0_CARD_WRITE_PROT BIT(8)
|
|
+
|
|
+#define MSHC_CORE_CLK_ENABLE BIT(16)
|
|
+#define MSHC_CORE_CLK_FREQ_BIT_SHIFT 4
|
|
+#define MSHC_CORE_CLK_FREQ_BIT_MASK 0xfffu
|
|
+#define MSHC_CORE_CLK_SEL_BIT BIT(0)
|
|
+
|
|
+/* Controller does not have CD wired and will not function normally without */
|
|
+#define SDHCI_ESWIN_QUIRK_FORCE_CDTEST BIT(0)
|
|
+/* Controller immediately reports SDHCI_CLOCK_INT_STABLE after enabling the
|
|
+ * internal clock even when the clock isn't stable */
|
|
+#define SDHCI_ESWIN_QUIRK_CLOCK_UNSTABLE BIT(1)
|
|
+
|
|
+#define ESWIN_SDHCI_CTRL_HS400 0x0007 // Non-standard, for eswin,these bits are 0x7
|
|
+
|
|
+#define SDHCI_CLK_208M 208000000
|
|
+#define SDHCI_CLK_200M 200000000
|
|
+
|
|
+#define AWSMMUSID GENMASK(31, 24) // The sid of write operation
|
|
+#define AWSMMUSSID GENMASK(23, 16) // The ssid of write operation
|
|
+#define ARSMMUSID GENMASK(15, 8) // The sid of read operation
|
|
+#define ARSMMUSSID GENMASK(7, 0) // The ssid of read operation
|
|
+
|
|
+/* DWC_mshc_map/DWC_mshc_phy_block register */
|
|
+#define DWC_MSHC_PTR_PHY_R 0x300
|
|
+#define PHY_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x00)
|
|
+#define PHY_CMDPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x04)
|
|
+#define PHY_DATAPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x06)
|
|
+#define PHY_CLKPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x08)
|
|
+#define PHY_STBPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x0a)
|
|
+#define PHY_RSTNPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x0c)
|
|
+#define PHY_PADTEST_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x0e)
|
|
+#define PHY_PADTEST_OUT_R (DWC_MSHC_PTR_PHY_R + 0x10)
|
|
+#define PHY_PADTEST_IN_R (DWC_MSHC_PTR_PHY_R + 0x12)
|
|
+#define PHY_PRBS_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x18)
|
|
+#define PHY_PHYLBK_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x1a)
|
|
+#define PHY_COMMDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x1c)
|
|
+#define PHY_SDCLKDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x1d)
|
|
+#define PHY_SDCLKDL_DC_R (DWC_MSHC_PTR_PHY_R + 0x1e)
|
|
+#define PHY_SMPLDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x20)
|
|
+#define PHY_ATDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x21)
|
|
+#define PHY_DLL_CTRL_R (DWC_MSHC_PTR_PHY_R + 0x24)
|
|
+#define PHY_DLL_CNFG1_R (DWC_MSHC_PTR_PHY_R + 0x25)
|
|
+#define PHY_DLL_CNFG2_R (DWC_MSHC_PTR_PHY_R + 0x26)
|
|
+#define PHY_DLLDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x28)
|
|
+#define PHY_DLL_OFFST_R (DWC_MSHC_PTR_PHY_R + 0x29)
|
|
+#define PHY_DLLMST_TSTDC_R (DWC_MSHC_PTR_PHY_R + 0x2a)
|
|
+#define PHY_DLLBT_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x2c)
|
|
+#define PHY_DLL_STATUS_R (DWC_MSHC_PTR_PHY_R + 0x2e)
|
|
+#define PHY_DLLDBG_MLKDC_R (DWC_MSHC_PTR_PHY_R + 0x30)
|
|
+#define PHY_DLLDBG_SLKDC_R (DWC_MSHC_PTR_PHY_R + 0x32)
|
|
+
|
|
+#define ENABLE 1
|
|
+#define DISABLE 0
|
|
+/* strength definition */
|
|
+#define PHYCTRL_DR_33OHM 0xee
|
|
+#define PHYCTRL_DR_40OHM 0xcc
|
|
+#define PHYCTRL_DR_50OHM 0x88
|
|
+#define PHYCTRL_DR_66OHM 0x44
|
|
+#define PHYCTRL_DR_100OHM 0x00
|
|
+
|
|
+#define PHY_PAD_MAX_DRIVE_STRENGTH 0xf
|
|
+#define PHY_CLK_MAX_DELAY_MASK 0x7f
|
|
+#define PHY_PAD_SP_DRIVE_SHIF 16
|
|
+#define PHY_PAD_SN_DRIVE_SHIF 20
|
|
+
|
|
+#define PHY_RSTN BIT(0)
|
|
+#define PHY_UPDATE_DELAY_CODE BIT(4)
|
|
+
|
|
+#define VENDOR_EMMC_CTRL_R 0x52c
|
|
+#define EMMC_CRAD_PRESENT BIT(0)
|
|
+#define EMMC_RST_N_OE BIT(3)
|
|
+#define EMMC_RST_N BIT(2)
|
|
+
|
|
+#define PHY_SLEW_0 0x0
|
|
+#define PHY_SLEW_1 0x1
|
|
+#define PHY_SLEW_2 0x2
|
|
+#define PHY_SLEW_3 0x3
|
|
+#define PHY_TX_SLEW_CTRL_P_BIT_SHIFT 5
|
|
+#define PHY_TX_SLEW_CTRL_N_BIT_SHIFT 9
|
|
+
|
|
+#define PHY_PULL_BIT_SHIF 0x3
|
|
+#define PHY_PULL_DISABLED 0x0
|
|
+#define PHY_PULL_UP 0x1
|
|
+#define PHY_PULL_DOWN 0x2
|
|
+#define PHY_PULL_MASK 0x3
|
|
+
|
|
+#define PHY_PAD_RXSEL_0 0x0
|
|
+#define PHY_PAD_RXSEL_1 0x1
|
|
+
|
|
+#define VENDOR_AT_CTRL_R 0x540
|
|
+#define LATENCY_LT_BIT_OFFSET 19
|
|
+#define LATENCY_LT_MASK 0x3
|
|
+
|
|
+#define LATENCY_LT_1 0x0
|
|
+#define LATENCY_LT_2 0x1
|
|
+#define LATENCY_LT_3 0x2
|
|
+#define LATENCY_LT_4 0x3
|
|
+#define SW_TUNE_ENABLE BIT(4)
|
|
+
|
|
+#define VENDOR_AT_SATA_R 0x544
|
|
+#define MAX_PHASE_CODE 0xff
|
|
+
|
|
+#define DLL_ENABEL BIT(0)
|
|
+#define DLL_LOCK_STS BIT(0)
|
|
+#define DLL_ERROR_STS BIT(1)
|
|
+#define PHY_DELAY_CODE_MASK 0x7f
|
|
+#define PHY_DELAY_CODE_MAX 0x7f
|
|
+
|
|
+#define MAX_CORE_CLK_DIV 0xfff
|
|
+
|
|
+/**
|
|
+ * struct eswin_sdhci_clk_ops - Clock Operations for eswin SD controller
|
|
+ *
|
|
+ * @sdcardclk_ops: The output clock related operations
|
|
+ * @sampleclk_ops: The sample clock related operations
|
|
+ */
|
|
+struct eswin_sdhci_clk_ops {
|
|
+ const struct clk_ops *sdcardclk_ops;
|
|
+ const struct clk_ops *sampleclk_ops;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * struct eswin_sdhci_clk_data - ESWIN Controller Clock Data.
|
|
+ *
|
|
+ * @sdcardclk_hw: Struct for the clock we might provide to a PHY.
|
|
+ * @sdcardclk: Pointer to normal 'struct clock' for sdcardclk_hw.
|
|
+ * @sampleclk_hw: Struct for the clock we might provide to a PHY.
|
|
+ * @sampleclk: Pointer to normal 'struct clock' for sampleclk_hw.
|
|
+ * @clk_phase_in: Array of Input Clock Phase Delays for all speed modes
|
|
+ * @clk_phase_out: Array of Output Clock Phase Delays for all speed modes
|
|
+ * @set_clk_delays: Function pointer for setting Clock Delays
|
|
+ * @clk_of_data: Platform specific runtime clock data storage pointer
|
|
+ */
|
|
+struct eswin_sdhci_clk_data {
|
|
+ struct clk_hw sdcardclk_hw;
|
|
+ struct clk *sdcardclk;
|
|
+ struct clk_hw sampleclk_hw;
|
|
+ struct clk *sampleclk;
|
|
+ int clk_phase_in[MMC_TIMING_MMC_HS400 + 1];
|
|
+ int clk_phase_out[MMC_TIMING_MMC_HS400 + 1];
|
|
+ void (*set_clk_delays)(struct sdhci_host *host);
|
|
+ void *clk_of_data;
|
|
+};
|
|
+
|
|
+struct eswin_sdhci_phy_data {
|
|
+ unsigned int drive_impedance;
|
|
+ unsigned int enable_strobe_pulldown;
|
|
+ unsigned int enable_data_pullup;
|
|
+ unsigned int enable_cmd_pullup;
|
|
+ unsigned int delay_code;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * struct eswin_sdhci_data - ESWIN Controller Data
|
|
+ *
|
|
+ * @host: Pointer to the main SDHCI host structure.
|
|
+ * @clk_ahb: Pointer to the AHB clock
|
|
+ * @has_cqe: True if controller has command queuing engine.
|
|
+ * @clk_data: Struct for the ESWIN Controller Clock Data.
|
|
+ * @clk_ops: Struct for the ESWIN Controller Clock Operations.
|
|
+ * @soc_ctl_base: Pointer to regmap for syscon for soc_ctl registers.
|
|
+ * @soc_ctl_map: Map to get offsets into soc_ctl registers.
|
|
+ * @quirks: ESWIN deviations from spec.
|
|
+ * @phy: ESWIN sdhci phy configs.
|
|
+ * @private: private for spec driver.
|
|
+ */
|
|
+struct eswin_sdhci_data {
|
|
+ struct sdhci_host *host;
|
|
+ struct clk *clk_ahb;
|
|
+ bool has_cqe;
|
|
+ struct eswin_sdhci_clk_data clk_data;
|
|
+ const struct eswin_sdhci_clk_ops *clk_ops;
|
|
+ unsigned int quirks;
|
|
+ void __iomem *core_clk_reg;
|
|
+ struct reset_control *txrx_rst;
|
|
+ struct reset_control *phy_rst;
|
|
+ struct reset_control *prstn;
|
|
+ struct reset_control *arstn;
|
|
+ struct eswin_sdhci_phy_data phy;
|
|
+ unsigned long private[] ____cacheline_aligned;
|
|
+};
|
|
+
|
|
+struct eswin_sdhci_of_data {
|
|
+ const struct sdhci_pltfm_data *pdata;
|
|
+ const struct eswin_sdhci_clk_ops *clk_ops;
|
|
+};
|
|
+
|
|
+void eswin_sdhci_set_core_clock(struct sdhci_host *host,
|
|
+ unsigned int clock);
|
|
+void eswin_sdhci_disable_card_clk(struct sdhci_host *host);
|
|
+void eswin_sdhci_enable_card_clk(struct sdhci_host *host);
|
|
+void eswin_sdhci_dt_parse_clk_phases(struct device *dev,
|
|
+ struct eswin_sdhci_clk_data *clk_data);
|
|
+unsigned int eswin_convert_drive_impedance_ohm(struct platform_device *pdev,
|
|
+ unsigned int dr_ohm);
|
|
+int eswin_sdhci_reset_init(struct device *dev,
|
|
+ struct eswin_sdhci_data *eswin_sdhci);
|
|
+
|
|
+#endif /* _DRIVERS_MMC_SDHCI_ESWIN_H */
|
|
diff --git a/drivers/mmc/host/sdhci-of-eswin-sdio.c b/drivers/mmc/host/sdhci-of-eswin-sdio.c
|
|
new file mode 100644
|
|
index 000000000000..fa23d1ded51c
|
|
--- /dev/null
|
|
+++ b/drivers/mmc/host/sdhci-of-eswin-sdio.c
|
|
@@ -0,0 +1,1095 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * ESWIN sdio Driver
|
|
+ *
|
|
+ * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved.
|
|
+ * SPDX-License-Identifier: GPL-2.0
|
|
+ *
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation, version 2.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
+ *
|
|
+ * Authors: liangshuang <liangshuang@eswincomputing.com>
|
|
+ */
|
|
+
|
|
+#include <linux/clk-provider.h>
|
|
+#include <linux/mfd/syscon.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of_device.h>
|
|
+#include <linux/phy/phy.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/reset.h>
|
|
+#include "cqhci.h"
|
|
+#include "sdhci-pltfm.h"
|
|
+
|
|
+#include <linux/eswin-win2030-sid-cfg.h>
|
|
+#include <linux/bitfield.h>
|
|
+#include <linux/iommu.h>
|
|
+#include "sdhci-eswin.h"
|
|
+
|
|
+#define ESWIN_SDHCI_SD_CQE_BASE_ADDR 0x180
|
|
+#define ESWIN_SDHCI_SD0_INT_STATUS 0x608
|
|
+#define ESWIN_SDHCI_SD0_PWR_CTRL 0x60c
|
|
+#define ESWIN_SDHCI_SD1_INT_STATUS 0x708
|
|
+#define ESWIN_SDHCI_SD1_PWR_CTRL 0x70c
|
|
+
|
|
+#define DELAY_RANGE_THRESHOLD 40
|
|
+
|
|
+struct eswin_sdio_private {
|
|
+ int phase_code;
|
|
+ unsigned int enable_sw_tuning;
|
|
+};
|
|
+
|
|
+static inline void *sdhci_sdio_priv(struct eswin_sdhci_data *sdio)
|
|
+{
|
|
+ return sdio->private;
|
|
+}
|
|
+
|
|
+static void eswin_sdhci_sdio_set_clock(struct sdhci_host *host,
|
|
+ unsigned int clock)
|
|
+{
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct eswin_sdhci_data *eswin_sdhci_sdio =
|
|
+ sdhci_pltfm_priv(pltfm_host);
|
|
+ struct eswin_sdhci_clk_data *clk_data =
|
|
+ &eswin_sdhci_sdio->clk_data;
|
|
+
|
|
+ /* Set the Input and Output Clock Phase Delays */
|
|
+ if (clk_data->set_clk_delays) {
|
|
+ clk_data->set_clk_delays(host);
|
|
+ }
|
|
+
|
|
+ eswin_sdhci_set_core_clock(host, clock);
|
|
+ sdhci_set_clock(host, clock);
|
|
+
|
|
+ if (eswin_sdhci_sdio->quirks & SDHCI_ESWIN_QUIRK_CLOCK_UNSTABLE)
|
|
+ /*
|
|
+ * Some controllers immediately report SDHCI_CLOCK_INT_STABLE
|
|
+ * after enabling the clock even though the clock is not
|
|
+ * stable. Trying to use a clock without waiting here results
|
|
+ * in EILSEQ while detecting some older/slower cards. The
|
|
+ * chosen delay is the maximum delay from sdhci_set_clock.
|
|
+ */
|
|
+ msleep(20);
|
|
+}
|
|
+
|
|
+static void eswin_sdhci_sdio_config_phy_delay(struct sdhci_host *host,
|
|
+ int delay)
|
|
+{
|
|
+ delay &= PHY_CLK_MAX_DELAY_MASK;
|
|
+
|
|
+ /*phy clk delay line config*/
|
|
+ sdhci_writeb(host, PHY_UPDATE_DELAY_CODE, PHY_SDCLKDL_CNFG_R);
|
|
+ sdhci_writeb(host, delay, PHY_SDCLKDL_DC_R);
|
|
+ sdhci_writeb(host, 0x0, PHY_SDCLKDL_CNFG_R);
|
|
+}
|
|
+
|
|
+static void eswin_sdhci_sdio_config_phy(struct sdhci_host *host)
|
|
+{
|
|
+ unsigned int val = 0;
|
|
+ unsigned int drv = 0;
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct eswin_sdhci_data *eswin_sdhci =
|
|
+ sdhci_pltfm_priv(pltfm_host);
|
|
+ struct eswin_sdhci_phy_data *phy = &eswin_sdhci->phy;
|
|
+
|
|
+ drv = phy->drive_impedance << PHY_PAD_SP_DRIVE_SHIF;
|
|
+ pr_debug("%s: phy drv=0x%x \n",mmc_hostname(host->mmc), drv);
|
|
+
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+
|
|
+ /* reset phy,config phy's pad */
|
|
+ sdhci_writel(host, drv | (~PHY_RSTN), PHY_CNFG_R);
|
|
+ /*CMDPAD_CNFS*/
|
|
+ val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) |
|
|
+ (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) |
|
|
+ (phy->enable_cmd_pullup << PHY_PULL_BIT_SHIF) | PHY_PAD_RXSEL_1;
|
|
+ sdhci_writew(host, val, PHY_CMDPAD_CNFG_R);
|
|
+ pr_debug("%s: phy cmd=0x%x\n",mmc_hostname(host->mmc), val);
|
|
+
|
|
+ /*DATA PAD CNFG*/
|
|
+ val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) |
|
|
+ (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) |
|
|
+ (phy->enable_data_pullup << PHY_PULL_BIT_SHIF) | PHY_PAD_RXSEL_1;
|
|
+ sdhci_writew(host, val, PHY_DATAPAD_CNFG_R);
|
|
+ pr_debug("%s: phy data=0x%x\n",mmc_hostname(host->mmc), val);
|
|
+
|
|
+ /*Clock PAD Setting*/
|
|
+ val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) |
|
|
+ (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) | PHY_PAD_RXSEL_0;
|
|
+ sdhci_writew(host, val, PHY_CLKPAD_CNFG_R);
|
|
+ pr_debug("%s: phy clk=0x%x\n",mmc_hostname(host->mmc), val);
|
|
+ mdelay(2);
|
|
+
|
|
+ /*PHY RSTN PAD setting*/
|
|
+ val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) |
|
|
+ (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) |
|
|
+ (PHY_PULL_UP << PHY_PULL_BIT_SHIF) | PHY_PAD_RXSEL_1;
|
|
+ sdhci_writew(host, val, PHY_RSTNPAD_CNFG_R);
|
|
+
|
|
+ sdhci_writel(host, drv | PHY_RSTN, PHY_CNFG_R);
|
|
+
|
|
+ eswin_sdhci_sdio_config_phy_delay(host, phy->delay_code);
|
|
+
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+}
|
|
+
|
|
+static void eswin_sdhci_sdio_reset(struct sdhci_host *host, u8 mask)
|
|
+{
|
|
+ u8 ctrl;
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct eswin_sdhci_data *eswin_sdhci_sdio =
|
|
+ sdhci_pltfm_priv(pltfm_host);
|
|
+
|
|
+ sdhci_reset(host, mask);
|
|
+
|
|
+ if (eswin_sdhci_sdio->quirks & SDHCI_ESWIN_QUIRK_FORCE_CDTEST) {
|
|
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
|
|
+ ctrl |= SDHCI_CTRL_CDTEST_INS | SDHCI_CTRL_CDTEST_EN;
|
|
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
|
|
+ }
|
|
+ if (mask == SDHCI_RESET_ALL) { // after reset all,the phy`s config will be clear.
|
|
+ eswin_sdhci_sdio_config_phy(host);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int eswin_sdhci_sdio_delay_tuning(struct sdhci_host *host, u32 opcode)
|
|
+{
|
|
+ int ret;
|
|
+ int delay = -1;
|
|
+ int i = 0;
|
|
+ int delay_min = -1;
|
|
+ int delay_max = -1;
|
|
+ int delay_range = -1;
|
|
+
|
|
+ int cmd_error = 0;
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct eswin_sdhci_data *eswin_sdhci =
|
|
+ sdhci_pltfm_priv(pltfm_host);
|
|
+
|
|
+ for (i = 0; i <= PHY_DELAY_CODE_MAX; i++) {
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+ eswin_sdhci_sdio_config_phy_delay(host, i);
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+ ret = mmc_send_tuning(host->mmc, opcode, &cmd_error);
|
|
+ if (ret) {
|
|
+ pr_debug("%s: bad delay:0x%x\n", mmc_hostname(host->mmc), i);
|
|
+ sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
|
|
+ udelay(200);
|
|
+ if (delay_min != -1 && delay_max != -1) {
|
|
+ if (delay_max - delay_min > delay_range) {
|
|
+ delay_range = delay_max - delay_min;
|
|
+ delay = (delay_min + delay_max) / 2;
|
|
+ if (delay_range > DELAY_RANGE_THRESHOLD)
|
|
+ break;
|
|
+ }
|
|
+ delay_min = -1;
|
|
+ delay_max = -1;
|
|
+ }
|
|
+ } else {
|
|
+ pr_debug("%s: ok delay:0x%x\n", mmc_hostname(host->mmc), i);
|
|
+ if (delay_min == -1) {
|
|
+ delay_min = i;
|
|
+ }
|
|
+ delay_max = i;
|
|
+ if (i == PHY_DELAY_CODE_MAX) {
|
|
+ if (delay_max - delay_min > delay_range) {
|
|
+ delay_range = delay_max - delay_min;
|
|
+ delay = (delay_min + delay_max) / 2;
|
|
+ }
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (delay == -1) {
|
|
+ pr_err("%s: delay code tuning failed!\n",
|
|
+ mmc_hostname(host->mmc));
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+ eswin_sdhci_sdio_config_phy_delay(host,
|
|
+ eswin_sdhci->phy.delay_code);
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ pr_info("%s: set delay:0x%x\n", mmc_hostname(host->mmc), delay);
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+ eswin_sdhci_sdio_config_phy_delay(host, delay);
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int eswin_sdhci_sdio_phase_code_tuning(struct sdhci_host *host,
|
|
+ u32 opcode)
|
|
+{
|
|
+ int cmd_error = 0;
|
|
+ int ret = 0;
|
|
+ int phase_code = 0;
|
|
+ int code_min = -1;
|
|
+ int code_max = -1;
|
|
+
|
|
+ for (phase_code = 0; phase_code <= MAX_PHASE_CODE; phase_code++) {
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+ sdhci_writew(host, phase_code, VENDOR_AT_SATA_R);
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+
|
|
+ ret = mmc_send_tuning(host->mmc, opcode, &cmd_error);
|
|
+ if (ret) {
|
|
+ sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
|
|
+ udelay(200);
|
|
+ if (code_min != -1 && code_max != -1)
|
|
+ break;
|
|
+ } else {
|
|
+ if (code_min == -1) {
|
|
+ code_min = phase_code;
|
|
+ }
|
|
+ code_max = phase_code;
|
|
+ }
|
|
+ }
|
|
+ if (code_min == -1 && code_max == -1) {
|
|
+ pr_err("%s: phase code tuning failed!\n",
|
|
+ mmc_hostname(host->mmc));
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+ sdhci_writew(host, 0, VENDOR_AT_SATA_R);
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ phase_code = (code_min + code_max) / 2;
|
|
+ pr_info("%s: set phase_code:0x%x\n", mmc_hostname(host->mmc), phase_code);
|
|
+
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+ sdhci_writew(host, phase_code, VENDOR_AT_SATA_R);
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int eswin_sdhci_sdio_executing_tuning(struct sdhci_host *host,
|
|
+ u32 opcode)
|
|
+{
|
|
+ u32 ctrl;
|
|
+ u32 val;
|
|
+ int ret = 0;
|
|
+ struct sdhci_pltfm_host *pltfm_host;
|
|
+ struct eswin_sdhci_data *eswin_sdhci_sdio;
|
|
+ struct eswin_sdio_private *eswin_sdio_priv;
|
|
+
|
|
+ pltfm_host = sdhci_priv(host);
|
|
+ eswin_sdhci_sdio = sdhci_pltfm_priv(pltfm_host);
|
|
+ eswin_sdio_priv = sdhci_sdio_priv(eswin_sdhci_sdio);
|
|
+
|
|
+ if (!eswin_sdio_priv->enable_sw_tuning) {
|
|
+ if (eswin_sdio_priv->phase_code != -1) {
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+ sdhci_writew(host, eswin_sdio_priv->phase_code, VENDOR_AT_SATA_R);
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+ }
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+
|
|
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
+ ctrl &= ~SDHCI_CTRL_TUNED_CLK;
|
|
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
|
|
+
|
|
+ val = sdhci_readl(host, VENDOR_AT_CTRL_R);
|
|
+ val |= SW_TUNE_ENABLE;
|
|
+ sdhci_writew(host, val, VENDOR_AT_CTRL_R);
|
|
+ sdhci_writew(host, 0, VENDOR_AT_SATA_R);
|
|
+
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+
|
|
+ sdhci_writew(host, 0x0, SDHCI_CMD_DATA);
|
|
+
|
|
+ ret = eswin_sdhci_sdio_delay_tuning(host, opcode);
|
|
+ if (ret < 0) {
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = eswin_sdhci_sdio_phase_code_tuning(host, opcode);
|
|
+ if (ret < 0) {
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static u32 eswin_sdhci_sdio_cqhci_irq(struct sdhci_host *host, u32 intmask)
|
|
+{
|
|
+ int cmd_error = 0;
|
|
+ int data_error = 0;
|
|
+
|
|
+ if (!sdhci_cqe_irq(host, intmask, &cmd_error, &data_error))
|
|
+ return intmask;
|
|
+
|
|
+ cqhci_irq(host->mmc, intmask, cmd_error, data_error);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void eswin_sdhci_sdio_dumpregs(struct mmc_host *mmc)
|
|
+{
|
|
+ sdhci_dumpregs(mmc_priv(mmc));
|
|
+}
|
|
+
|
|
+static void eswin_sdhci_sdio_cqe_enable(struct mmc_host *mmc)
|
|
+{
|
|
+ struct sdhci_host *host = mmc_priv(mmc);
|
|
+ u32 reg;
|
|
+
|
|
+ reg = sdhci_readl(host, SDHCI_PRESENT_STATE);
|
|
+ while (reg & SDHCI_DATA_AVAILABLE) {
|
|
+ sdhci_readl(host, SDHCI_BUFFER);
|
|
+ reg = sdhci_readl(host, SDHCI_PRESENT_STATE);
|
|
+ }
|
|
+
|
|
+ sdhci_cqe_enable(mmc);
|
|
+}
|
|
+
|
|
+static const struct cqhci_host_ops eswin_sdhci_sdio_cqhci_ops = {
|
|
+ .enable = eswin_sdhci_sdio_cqe_enable,
|
|
+ .disable = sdhci_cqe_disable,
|
|
+ .dumpregs = eswin_sdhci_sdio_dumpregs,
|
|
+};
|
|
+
|
|
+static const struct sdhci_ops eswin_sdhci_sdio_cqe_ops = {
|
|
+ .set_clock = eswin_sdhci_sdio_set_clock,
|
|
+ .get_max_clock = sdhci_pltfm_clk_get_max_clock,
|
|
+ .get_timeout_clock = sdhci_pltfm_clk_get_max_clock,
|
|
+ .set_bus_width = sdhci_set_bus_width,
|
|
+ .reset = eswin_sdhci_sdio_reset,
|
|
+ .set_uhs_signaling = sdhci_set_uhs_signaling,
|
|
+ .set_power = sdhci_set_power_and_bus_voltage,
|
|
+ .irq = eswin_sdhci_sdio_cqhci_irq,
|
|
+ .platform_execute_tuning = eswin_sdhci_sdio_executing_tuning,
|
|
+
|
|
+};
|
|
+
|
|
+static const struct sdhci_pltfm_data eswin_sdhci_sdio_cqe_pdata = {
|
|
+ .ops = &eswin_sdhci_sdio_cqe_ops,
|
|
+ .quirks = SDHCI_QUIRK_BROKEN_CQE |
|
|
+ SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
|
|
+ SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
|
|
+ .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
|
|
+ SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN,
|
|
+};
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+/**
|
|
+ * eswin_sdhci_sdio_suspend- Suspend method for the driver
|
|
+ * @dev: Address of the device structure
|
|
+ *
|
|
+ * Put the device in a low power state.
|
|
+ *
|
|
+ * Return: 0 on success and error value on error
|
|
+ */
|
|
+static int eswin_sdhci_sdio_suspend(struct device *dev)
|
|
+{
|
|
+ struct sdhci_host *host = dev_get_drvdata(dev);
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct eswin_sdhci_data *eswin_sdhci_sdio =
|
|
+ sdhci_pltfm_priv(pltfm_host);
|
|
+ int ret;
|
|
+
|
|
+ if (host->tuning_mode != SDHCI_TUNING_MODE_3)
|
|
+ mmc_retune_needed(host->mmc);
|
|
+
|
|
+ if (eswin_sdhci_sdio->has_cqe) {
|
|
+ ret = cqhci_suspend(host->mmc);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = sdhci_suspend_host(host);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ clk_disable(pltfm_host->clk);
|
|
+ clk_disable(eswin_sdhci_sdio->clk_ahb);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * eswin_sdhci_sdio_resume- Resume method for the driver
|
|
+ * @dev: Address of the device structure
|
|
+ *
|
|
+ * Resume operation after suspend
|
|
+ *
|
|
+ * Return: 0 on success and error value on error
|
|
+ */
|
|
+static int eswin_sdhci_sdio_resume(struct device *dev)
|
|
+{
|
|
+ struct sdhci_host *host = dev_get_drvdata(dev);
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct eswin_sdhci_data *eswin_sdhci_sdio =
|
|
+ sdhci_pltfm_priv(pltfm_host);
|
|
+ int ret;
|
|
+
|
|
+ ret = clk_enable(eswin_sdhci_sdio->clk_ahb);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Cannot enable AHB clock.\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = clk_enable(pltfm_host->clk);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Cannot enable SD clock.\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = sdhci_resume_host(host);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Cannot resume host.\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (eswin_sdhci_sdio->has_cqe)
|
|
+ return cqhci_resume(host->mmc);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#endif /* ! CONFIG_PM_SLEEP */
|
|
+
|
|
+static SIMPLE_DEV_PM_OPS(eswin_sdhci_sdio_dev_pm_ops, eswin_sdhci_sdio_suspend,
|
|
+ eswin_sdhci_sdio_resume);
|
|
+
|
|
+/**
|
|
+ * eswin_sdhci_sdio_sdcardclk_recalc_rate- Return the card clock rate
|
|
+ *
|
|
+ * @hw: Pointer to the hardware clock structure.
|
|
+ * @parent_rate: The parent rate (should be rate of clk_xin).
|
|
+ *
|
|
+ * Return the current actual rate of the SD card clock. This can be used
|
|
+ * to communicate with out PHY.
|
|
+ *
|
|
+ * Return: The card clock rate.
|
|
+ */
|
|
+static unsigned long
|
|
+eswin_sdhci_sdio_sdcardclk_recalc_rate(struct clk_hw *hw,
|
|
+ unsigned long parent_rate)
|
|
+{
|
|
+ struct eswin_sdhci_clk_data *clk_data = container_of(
|
|
+ hw, struct eswin_sdhci_clk_data, sdcardclk_hw);
|
|
+ struct eswin_sdhci_data *eswin_sdhci_sdio =
|
|
+ container_of(clk_data, struct eswin_sdhci_data, clk_data);
|
|
+ struct sdhci_host *host = eswin_sdhci_sdio->host;
|
|
+
|
|
+ return host->mmc->actual_clock;
|
|
+}
|
|
+
|
|
+static const struct clk_ops eswin_sdio_sdcardclk_ops = {
|
|
+ .recalc_rate = eswin_sdhci_sdio_sdcardclk_recalc_rate,
|
|
+};
|
|
+
|
|
+/**
|
|
+ * eswin_sdhci_sdio_sampleclk_recalc_rate- Return the sampling clock rate
|
|
+ *
|
|
+ * @hw: Pointer to the hardware clock structure.
|
|
+ * @parent_rate: The parent rate (should be rate of clk_xin).
|
|
+ *
|
|
+ * Return the current actual rate of the sampling clock. This can be used
|
|
+ * to communicate with out PHY.
|
|
+ *
|
|
+ * Return: The sample clock rate.
|
|
+ */
|
|
+static unsigned long
|
|
+eswin_sdhci_sdio_sampleclk_recalc_rate(struct clk_hw *hw,
|
|
+ unsigned long parent_rate)
|
|
+{
|
|
+ struct eswin_sdhci_clk_data *clk_data = container_of(
|
|
+ hw, struct eswin_sdhci_clk_data, sampleclk_hw);
|
|
+ struct eswin_sdhci_data *eswin_sdhci_sdio =
|
|
+ container_of(clk_data, struct eswin_sdhci_data, clk_data);
|
|
+ struct sdhci_host *host = eswin_sdhci_sdio->host;
|
|
+
|
|
+ return host->mmc->actual_clock;
|
|
+}
|
|
+
|
|
+static const struct clk_ops eswin_sdio_sampleclk_ops = {
|
|
+ .recalc_rate = eswin_sdhci_sdio_sampleclk_recalc_rate,
|
|
+};
|
|
+
|
|
+static const struct eswin_sdhci_clk_ops eswin_sdio_clk_ops = {
|
|
+ .sdcardclk_ops = &eswin_sdio_sdcardclk_ops,
|
|
+ .sampleclk_ops = &eswin_sdio_sampleclk_ops,
|
|
+};
|
|
+
|
|
+static struct eswin_sdhci_of_data eswin_sdhci_fu800_sdio_data = {
|
|
+ .pdata = &eswin_sdhci_sdio_cqe_pdata,
|
|
+ .clk_ops = &eswin_sdio_clk_ops,
|
|
+};
|
|
+
|
|
+static const struct of_device_id eswin_sdhci_sdio_of_match[] = {
|
|
+ /* SoC-specific compatible strings*/
|
|
+ {
|
|
+ .compatible = "eswin,sdhci-sdio",
|
|
+ .data = &eswin_sdhci_fu800_sdio_data,
|
|
+ },
|
|
+ { /* sentinel */ }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, eswin_sdhci_sdio_of_match);
|
|
+
|
|
+/**
|
|
+ * eswin_sdhci_sdio_register_sdcardclk- Register the sdcardclk for a PHY to use
|
|
+ *
|
|
+ * @sdhci_arasan: Our private data structure.
|
|
+ * @clk_xin: Pointer to the functional clock
|
|
+ * @dev: Pointer to our struct device.
|
|
+ *
|
|
+ * Some PHY devices need to know what the actual card clock is. In order for
|
|
+ * them to find out, we'll provide a clock through the common clock framework
|
|
+ * for them to query.
|
|
+ *
|
|
+ * Return: 0 on success and error value on error
|
|
+ */
|
|
+static int eswin_sdhci_sdio_register_sdcardclk(
|
|
+ struct eswin_sdhci_data *eswin_sdhci_sdio, struct clk *clk_xin,
|
|
+ struct device *dev)
|
|
+{
|
|
+ struct eswin_sdhci_clk_data *clk_data =
|
|
+ &eswin_sdhci_sdio->clk_data;
|
|
+ struct device_node *np = dev->of_node;
|
|
+ struct clk_init_data sdcardclk_init;
|
|
+ const char *parent_clk_name;
|
|
+ int ret;
|
|
+
|
|
+ ret = of_property_read_string_index(np, "clock-output-names", 0,
|
|
+ &sdcardclk_init.name);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "DT has #clock-cells but no clock-output-names\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ parent_clk_name = __clk_get_name(clk_xin);
|
|
+ sdcardclk_init.parent_names = &parent_clk_name;
|
|
+ sdcardclk_init.num_parents = 1;
|
|
+ sdcardclk_init.flags = CLK_GET_RATE_NOCACHE;
|
|
+ sdcardclk_init.ops = eswin_sdhci_sdio->clk_ops->sdcardclk_ops;
|
|
+
|
|
+ clk_data->sdcardclk_hw.init = &sdcardclk_init;
|
|
+ clk_data->sdcardclk = devm_clk_register(dev, &clk_data->sdcardclk_hw);
|
|
+ if (IS_ERR(clk_data->sdcardclk))
|
|
+ return PTR_ERR(clk_data->sdcardclk);
|
|
+
|
|
+ clk_data->sdcardclk_hw.init = NULL;
|
|
+
|
|
+ ret = of_clk_add_provider(np, of_clk_src_simple_get,
|
|
+ clk_data->sdcardclk);
|
|
+ if (ret)
|
|
+ dev_err(dev, "Failed to add sdcard clock provider\n");
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * eswin_sdhci_sdio_register_sampleclk - Register the sampleclk for a PHY to use
|
|
+ *
|
|
+ * @sdhci_arasan: Our private data structure.
|
|
+ * @clk_xin: Pointer to the functional clock
|
|
+ * @dev: Pointer to our struct device.
|
|
+ *
|
|
+ * Some PHY devices need to know what the actual card clock is. In order for
|
|
+ * them to find out, we'll provide a clock through the common clock framework
|
|
+ * for them to query.
|
|
+ *
|
|
+ * Return: 0 on success and error value on error
|
|
+ */
|
|
+static int eswin_sdhci_sdio_register_sampleclk(
|
|
+ struct eswin_sdhci_data *eswin_sdhci_sdio, struct clk *clk_xin,
|
|
+ struct device *dev)
|
|
+{
|
|
+ struct eswin_sdhci_clk_data *clk_data =
|
|
+ &eswin_sdhci_sdio->clk_data;
|
|
+ struct device_node *np = dev->of_node;
|
|
+ struct clk_init_data sampleclk_init;
|
|
+ const char *parent_clk_name;
|
|
+ int ret;
|
|
+
|
|
+ ret = of_property_read_string_index(np, "clock-output-names", 1,
|
|
+ &sampleclk_init.name);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "DT has #clock-cells but no clock-output-names\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ parent_clk_name = __clk_get_name(clk_xin);
|
|
+ sampleclk_init.parent_names = &parent_clk_name;
|
|
+ sampleclk_init.num_parents = 1;
|
|
+ sampleclk_init.flags = CLK_GET_RATE_NOCACHE;
|
|
+ sampleclk_init.ops = eswin_sdhci_sdio->clk_ops->sampleclk_ops;
|
|
+
|
|
+ clk_data->sampleclk_hw.init = &sampleclk_init;
|
|
+ clk_data->sampleclk = devm_clk_register(dev, &clk_data->sampleclk_hw);
|
|
+ if (IS_ERR(clk_data->sampleclk))
|
|
+ return PTR_ERR(clk_data->sampleclk);
|
|
+ clk_data->sampleclk_hw.init = NULL;
|
|
+
|
|
+ ret = of_clk_add_provider(np, of_clk_src_simple_get,
|
|
+ clk_data->sampleclk);
|
|
+ if (ret)
|
|
+ dev_err(dev, "Failed to add sample clock provider\n");
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * eswin_sdhci_sdio_unregister_sdclk- Undoes sdhci_arasan_register_sdclk()
|
|
+ *
|
|
+ * @dev: Pointer to our struct device.
|
|
+ *
|
|
+ * Should be called any time we're exiting and sdhci_arasan_register_sdclk()
|
|
+ * returned success.
|
|
+ */
|
|
+static void eswin_sdhci_sdio_unregister_sdclk(struct device *dev)
|
|
+{
|
|
+ struct device_node *np = dev->of_node;
|
|
+
|
|
+ if (!of_find_property(np, "#clock-cells", NULL))
|
|
+ return;
|
|
+
|
|
+ of_clk_del_provider(dev->of_node);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * eswin_sdhci_sdio_register_sdclk- Register the sdcardclk for a PHY to use
|
|
+ *
|
|
+ * @eswin_sdhci_sdio: Our private data structure.
|
|
+ * @clk_xin: Pointer to the functional clock
|
|
+ * @dev: Pointer to our struct device.
|
|
+ *
|
|
+ * Some PHY devices need to know what the actual card clock is. In order for
|
|
+ * them to find out, we'll provide a clock through the common clock framework
|
|
+ * for them to query.
|
|
+ *
|
|
+ * Note: without seriously re-architecting SDHCI's clock code and testing on
|
|
+ * all platforms, there's no way to create a totally beautiful clock here
|
|
+ * with all clock ops implemented. Instead, we'll just create a clock that can
|
|
+ * be queried and set the CLK_GET_RATE_NOCACHE attribute to tell common clock
|
|
+ * framework that we're doing things behind its back. This should be sufficient
|
|
+ * to create nice clean device tree bindings and later (if needed) we can try
|
|
+ * re-architecting SDHCI if we see some benefit to it.
|
|
+ *
|
|
+ * Return: 0 on success and error value on error
|
|
+ */
|
|
+static int
|
|
+eswin_sdhci_sdio_register_sdclk(struct eswin_sdhci_data *eswin_sdhci_sdio,
|
|
+ struct clk *clk_xin, struct device *dev)
|
|
+{
|
|
+ struct device_node *np = dev->of_node;
|
|
+ u32 num_clks = 0;
|
|
+ int ret;
|
|
+
|
|
+ /* Providing a clock to the PHY is optional; no error if missing */
|
|
+ if (of_property_read_u32(np, "#clock-cells", &num_clks) < 0)
|
|
+ return 0;
|
|
+
|
|
+ ret = eswin_sdhci_sdio_register_sdcardclk(eswin_sdhci_sdio, clk_xin,
|
|
+ dev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (num_clks) {
|
|
+ ret = eswin_sdhci_sdio_register_sampleclk(eswin_sdhci_sdio,
|
|
+ clk_xin, dev);
|
|
+ if (ret) {
|
|
+ eswin_sdhci_sdio_unregister_sdclk(dev);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+eswin_sdhci_sdio_add_host(struct eswin_sdhci_data *eswin_sdhci_sdio)
|
|
+{
|
|
+ struct sdhci_host *host = eswin_sdhci_sdio->host;
|
|
+ struct cqhci_host *cq_host;
|
|
+ bool dma64;
|
|
+ int ret;
|
|
+
|
|
+ if (!eswin_sdhci_sdio->has_cqe)
|
|
+ return sdhci_add_host(host);
|
|
+
|
|
+ ret = sdhci_setup_host(host);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ cq_host = devm_kzalloc(host->mmc->parent, sizeof(*cq_host), GFP_KERNEL);
|
|
+ if (!cq_host) {
|
|
+ ret = -ENOMEM;
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ cq_host->mmio = host->ioaddr + ESWIN_SDHCI_SD_CQE_BASE_ADDR;
|
|
+ cq_host->ops = &eswin_sdhci_sdio_cqhci_ops;
|
|
+
|
|
+ dma64 = host->flags & SDHCI_USE_64_BIT_DMA;
|
|
+ if (dma64)
|
|
+ cq_host->caps |= CQHCI_TASK_DESC_SZ_128;
|
|
+
|
|
+ ret = cqhci_init(cq_host, host->mmc, dma64);
|
|
+ if (ret)
|
|
+ goto cleanup;
|
|
+
|
|
+ ret = __sdhci_add_host(host);
|
|
+ if (ret)
|
|
+ goto cleanup;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+cleanup:
|
|
+ sdhci_cleanup_host(host);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int eswin_sdhci_sdio_sid_cfg(struct device *dev)
|
|
+{
|
|
+ int ret;
|
|
+ struct regmap *regmap;
|
|
+ int hsp_mmu_sdio_reg;
|
|
+ u32 rdwr_sid_ssid;
|
|
+ u32 sid;
|
|
+ struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
|
|
+
|
|
+ /* not behind smmu, use the default reset value(0x0) of the reg as streamID*/
|
|
+ if (fwspec == NULL) {
|
|
+ dev_dbg(dev,
|
|
+ "dev is not behind smmu, skip configuration of sid\n");
|
|
+ return 0;
|
|
+ }
|
|
+ sid = fwspec->ids[0];
|
|
+
|
|
+ regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
|
|
+ "eswin,hsp_sp_csr");
|
|
+ if (IS_ERR(regmap)) {
|
|
+ dev_dbg(dev, "No hsp_sp_csr phandle specified\n");
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ ret = of_property_read_u32_index(dev->of_node, "eswin,hsp_sp_csr", 1,
|
|
+ &hsp_mmu_sdio_reg);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "can't get sdio sid cfg reg offset (%d)\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* make the reading sid the same as writing sid, ssid is fixed to zero */
|
|
+ rdwr_sid_ssid = FIELD_PREP(AWSMMUSID, sid);
|
|
+ rdwr_sid_ssid |= FIELD_PREP(ARSMMUSID, sid);
|
|
+ rdwr_sid_ssid |= FIELD_PREP(AWSMMUSSID, 0);
|
|
+ rdwr_sid_ssid |= FIELD_PREP(ARSMMUSSID, 0);
|
|
+ regmap_write(regmap, hsp_mmu_sdio_reg, rdwr_sid_ssid);
|
|
+
|
|
+ ret = win2030_dynm_sid_enable(dev_to_node(dev));
|
|
+ if (ret < 0)
|
|
+ dev_err(dev, "failed to config sdio streamID(%d)!\n", sid);
|
|
+ else
|
|
+ dev_dbg(dev, "success to config sdio streamID(%d)!\n", sid);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int eswin_sdhci_sdio_probe(struct platform_device *pdev)
|
|
+{
|
|
+ int ret;
|
|
+ struct clk *clk_xin;
|
|
+ struct clk *clk_spll2_fout3;
|
|
+ struct clk *clk_mux;
|
|
+ struct sdhci_host *host;
|
|
+ struct sdhci_pltfm_host *pltfm_host;
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct device_node *np = dev->of_node;
|
|
+ struct eswin_sdhci_data *eswin_sdhci_sdio;
|
|
+ struct eswin_sdio_private *eswin_sdio_priv;
|
|
+ struct regmap *regmap;
|
|
+ const struct eswin_sdhci_of_data *data;
|
|
+ unsigned int sdio_id = 0;
|
|
+ unsigned int val = 0;
|
|
+
|
|
+ data = of_device_get_match_data(dev);
|
|
+ host = sdhci_pltfm_init(pdev, data->pdata, sizeof(*eswin_sdhci_sdio) + sizeof(*eswin_sdio_priv));
|
|
+
|
|
+ if (IS_ERR(host))
|
|
+ return PTR_ERR(host);
|
|
+
|
|
+ pltfm_host = sdhci_priv(host);
|
|
+ eswin_sdhci_sdio = sdhci_pltfm_priv(pltfm_host);
|
|
+ eswin_sdhci_sdio->host = host;
|
|
+ eswin_sdhci_sdio->has_cqe = false;
|
|
+ eswin_sdio_priv = sdhci_sdio_priv(eswin_sdhci_sdio);
|
|
+
|
|
+ ret = of_property_read_u32(dev->of_node, "core-clk-reg", &val);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "get core clk reg failed.\n");
|
|
+ goto err_pltfm_free;
|
|
+ }
|
|
+
|
|
+ eswin_sdhci_sdio->core_clk_reg = ioremap(val, 0x4);
|
|
+ if (!eswin_sdhci_sdio->core_clk_reg) {
|
|
+ dev_err(dev, "ioremap core clk reg failed.\n");
|
|
+ goto err_pltfm_free;
|
|
+ }
|
|
+
|
|
+ ret = of_property_read_u32(dev->of_node, "sdio-id", &sdio_id);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "get sdio-id failed.\n");
|
|
+ goto err_pltfm_free;
|
|
+ }
|
|
+
|
|
+#if defined(__SDIO_HAPS) || defined(__SDIO_ZEBU)
|
|
+#if !defined(__SDIO_UHS)
|
|
+ /* This macro is only for setting 3.3v speed mode(HAPS) , you can delete it later*/
|
|
+ eswin_sdhci_sdio->host->quirks2 |= SDHCI_QUIRK2_NO_1_8_V;
|
|
+#endif
|
|
+#if defined(__SDIO_PIO)
|
|
+ /* This macro is only for testing PIO , you can delete it later*/
|
|
+ eswin_sdhci_sdio->host->quirks |= SDHCI_QUIRK_BROKEN_DMA |
|
|
+ SDHCI_QUIRK_BROKEN_ADMA;
|
|
+#elif defined(__SDIO_SDMA)
|
|
+ /* This macro is only for testing SDMA ,you can delete it later*/
|
|
+ eswin_sdhci_sdio->host->quirks |= SDHCI_QUIRK_BROKEN_ADMA;
|
|
+#elif defined(__SDIO_ADMA3)
|
|
+ /* This macro is only for testing ADMA3 ,you can delete it later*/
|
|
+ sdhci_enable_v4_mode(eswin_sdhci_sdio->host);
|
|
+#endif
|
|
+#endif
|
|
+ sdhci_get_of_property(pdev);
|
|
+
|
|
+ eswin_sdhci_sdio->clk_ops = data->clk_ops;
|
|
+ eswin_sdhci_sdio->clk_ahb = devm_clk_get(dev, "clk_ahb");
|
|
+ if (IS_ERR(eswin_sdhci_sdio->clk_ahb)) {
|
|
+ ret = dev_err_probe(dev, PTR_ERR(eswin_sdhci_sdio->clk_ahb),
|
|
+ "clk_ahb clock not found.\n");
|
|
+ goto err_pltfm_free;
|
|
+ }
|
|
+
|
|
+ clk_xin = devm_clk_get(dev, "clk_xin");
|
|
+ if (IS_ERR(clk_xin)) {
|
|
+ ret = dev_err_probe(dev, PTR_ERR(clk_xin),
|
|
+ "clk_xin clock not found.\n");
|
|
+ goto err_pltfm_free;
|
|
+ }
|
|
+
|
|
+ clk_spll2_fout3 = devm_clk_get(dev, "clk_spll2_fout3");
|
|
+
|
|
+ if (IS_ERR(clk_spll2_fout3)) {
|
|
+ ret = dev_err_probe(dev, PTR_ERR(clk_spll2_fout3),
|
|
+ "clk_spll2_fout3 clock not found.\n");
|
|
+ goto err_pltfm_free;
|
|
+ }
|
|
+
|
|
+ if (of_device_is_compatible(np, "eswin,sdhci-sdio")) {
|
|
+ clk_mux = devm_clk_get(dev, "clk_mux1_1");
|
|
+ if (IS_ERR(clk_mux)) {
|
|
+ ret = dev_err_probe(dev, PTR_ERR(clk_mux),
|
|
+ "clk_mux1_1 clock not found.\n");
|
|
+ goto err_pltfm_free;
|
|
+ }
|
|
+ /*switch the core clk source*/
|
|
+ clk_set_parent(clk_mux, clk_spll2_fout3);
|
|
+ }
|
|
+
|
|
+ ret = clk_prepare_enable(eswin_sdhci_sdio->clk_ahb);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Unable to enable AHB clock.\n");
|
|
+ goto err_pltfm_free;
|
|
+ }
|
|
+ /* If clock-frequency property is set, use the provided value */
|
|
+ if (pltfm_host->clock && pltfm_host->clock != clk_get_rate(clk_xin)) {
|
|
+ ret = clk_set_rate(clk_xin, pltfm_host->clock);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Failed to set SD clock rate\n");
|
|
+ goto clk_dis_ahb;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ ret = clk_prepare_enable(clk_xin);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Unable to enable SD clock.\n");
|
|
+ goto clk_dis_ahb;
|
|
+ }
|
|
+
|
|
+ pltfm_host->clk = clk_xin;
|
|
+ ret = eswin_sdhci_sdio_register_sdclk(eswin_sdhci_sdio, clk_xin, dev);
|
|
+ if (ret)
|
|
+ goto clk_disable_all;
|
|
+
|
|
+ ret = eswin_sdhci_reset_init(dev, eswin_sdhci_sdio);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to reset\n");
|
|
+ goto clk_disable_all;
|
|
+ }
|
|
+
|
|
+ regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
|
|
+ "eswin,hsp_sp_csr");
|
|
+ if (IS_ERR(regmap)) {
|
|
+ dev_err(dev, "No hsp_sp_csr phandle specified\n");
|
|
+ ret = -EFAULT;
|
|
+ goto clk_disable_all;
|
|
+ }
|
|
+
|
|
+ if (sdio_id == 0) {
|
|
+ regmap_write(regmap, ESWIN_SDHCI_SD0_INT_STATUS,
|
|
+ MSHC_INT_CLK_STABLE);
|
|
+ regmap_write(regmap, ESWIN_SDHCI_SD0_PWR_CTRL, MSHC_HOST_VAL_STABLE);
|
|
+ } else {
|
|
+ regmap_write(regmap, ESWIN_SDHCI_SD1_INT_STATUS,
|
|
+ MSHC_INT_CLK_STABLE);
|
|
+ regmap_write(regmap, ESWIN_SDHCI_SD1_PWR_CTRL, MSHC_HOST_VAL_STABLE);
|
|
+ }
|
|
+
|
|
+ ret = eswin_sdhci_sdio_sid_cfg(dev);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to use smmu\n");
|
|
+ goto clk_disable_all;
|
|
+ }
|
|
+
|
|
+ if (!of_property_read_u32(dev->of_node, "delay_code", &val)) {
|
|
+ eswin_sdhci_sdio->phy.delay_code = val;
|
|
+ }
|
|
+
|
|
+ if (!of_property_read_u32(dev->of_node, "drive-impedance-ohm", &val))
|
|
+ eswin_sdhci_sdio->phy.drive_impedance =
|
|
+ eswin_convert_drive_impedance_ohm(pdev, val);
|
|
+
|
|
+ if (of_property_read_bool(dev->of_node, "enable-cmd-pullup"))
|
|
+ eswin_sdhci_sdio->phy.enable_cmd_pullup = ENABLE;
|
|
+ else
|
|
+ eswin_sdhci_sdio->phy.enable_cmd_pullup = DISABLE;
|
|
+
|
|
+ if (of_property_read_bool(dev->of_node, "enable-data-pullup"))
|
|
+ eswin_sdhci_sdio->phy.enable_data_pullup = ENABLE;
|
|
+ else
|
|
+ eswin_sdhci_sdio->phy.enable_data_pullup = DISABLE;
|
|
+
|
|
+ if (of_property_read_bool(dev->of_node, "enable_sw_tuning"))
|
|
+ eswin_sdio_priv->enable_sw_tuning = ENABLE;
|
|
+ else
|
|
+ eswin_sdio_priv->enable_sw_tuning = DISABLE;
|
|
+
|
|
+ if (!of_property_read_u32(dev->of_node, "phase_code", &val)) {
|
|
+ eswin_sdio_priv->phase_code = val;
|
|
+ } else {
|
|
+ eswin_sdio_priv->phase_code = -1;
|
|
+ }
|
|
+
|
|
+ eswin_sdhci_dt_parse_clk_phases(dev, &eswin_sdhci_sdio->clk_data);
|
|
+ ret = mmc_of_parse(host->mmc);
|
|
+ if (ret) {
|
|
+ ret = dev_err_probe(dev, ret, "parsing dt failed.\n");
|
|
+ goto unreg_clk;
|
|
+ }
|
|
+
|
|
+ win2030_tbu_power(&pdev->dev, true);
|
|
+
|
|
+ ret = eswin_sdhci_sdio_add_host(eswin_sdhci_sdio);
|
|
+ if (ret)
|
|
+ goto unreg_clk;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+unreg_clk:
|
|
+ eswin_sdhci_sdio_unregister_sdclk(dev);
|
|
+clk_disable_all:
|
|
+ clk_disable_unprepare(clk_xin);
|
|
+clk_dis_ahb:
|
|
+ clk_disable_unprepare(eswin_sdhci_sdio->clk_ahb);
|
|
+err_pltfm_free:
|
|
+ if (eswin_sdhci_sdio->core_clk_reg)
|
|
+ iounmap(eswin_sdhci_sdio->core_clk_reg);
|
|
+
|
|
+ sdhci_pltfm_free(pdev);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int eswin_sdhci_sdio_remove(struct platform_device *pdev)
|
|
+{
|
|
+ int ret;
|
|
+ struct sdhci_host *host = platform_get_drvdata(pdev);
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct eswin_sdhci_data *eswin_sdhci_sdio =
|
|
+ sdhci_pltfm_priv(pltfm_host);
|
|
+ struct clk *clk_ahb = eswin_sdhci_sdio->clk_ahb;
|
|
+ void __iomem *core_clk_reg = eswin_sdhci_sdio->core_clk_reg;
|
|
+
|
|
+ sdhci_pltfm_remove(pdev);
|
|
+ win2030_tbu_power(&pdev->dev, false);
|
|
+
|
|
+ if (eswin_sdhci_sdio->txrx_rst) {
|
|
+ ret = reset_control_assert(eswin_sdhci_sdio->txrx_rst);
|
|
+ WARN_ON(0 != ret);
|
|
+ }
|
|
+
|
|
+ if (eswin_sdhci_sdio->phy_rst) {
|
|
+ ret = reset_control_assert(eswin_sdhci_sdio->phy_rst);
|
|
+ WARN_ON(0 != ret);
|
|
+ }
|
|
+
|
|
+ if (eswin_sdhci_sdio->prstn) {
|
|
+ ret = reset_control_assert(eswin_sdhci_sdio->prstn);
|
|
+ WARN_ON(0 != ret);
|
|
+ }
|
|
+
|
|
+ if (eswin_sdhci_sdio->arstn) {
|
|
+ ret = reset_control_assert(eswin_sdhci_sdio->arstn);
|
|
+ WARN_ON(0 != ret);
|
|
+ }
|
|
+
|
|
+ eswin_sdhci_sdio_unregister_sdclk(&pdev->dev);
|
|
+ clk_disable_unprepare(clk_ahb);
|
|
+ iounmap(core_clk_reg);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct platform_driver eswin_sdhci_sdio_driver = {
|
|
+ .driver = {
|
|
+ .name = "eswin-sdhci-sdio",
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
+ .of_match_table = eswin_sdhci_sdio_of_match,
|
|
+ .pm = &eswin_sdhci_sdio_dev_pm_ops,
|
|
+ },
|
|
+ .probe = eswin_sdhci_sdio_probe,
|
|
+ .remove = eswin_sdhci_sdio_remove,
|
|
+};
|
|
+
|
|
+static __init int eswin_sdhci_sdio_init(void)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = platform_driver_register(&eswin_sdhci_sdio_driver);
|
|
+ if (ret) {
|
|
+ pr_err("%s: failed to register platform driver\n",
|
|
+ __func__);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void __exit eswin_sdhci_sdio_exit(void)
|
|
+{
|
|
+ platform_driver_unregister(&eswin_sdhci_sdio_driver);
|
|
+}
|
|
+
|
|
+/*Cause EMMC is often used as a system disk(mmc0), we need the SD driver to run later than the EMMC driver*/
|
|
+late_initcall(eswin_sdhci_sdio_init);
|
|
+module_exit(eswin_sdhci_sdio_exit);
|
|
+
|
|
+MODULE_DESCRIPTION("Driver for the Eswin SDHCI Controller");
|
|
+MODULE_AUTHOR("Eswin");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/mmc/host/sdhci-of-eswin.c b/drivers/mmc/host/sdhci-of-eswin.c
|
|
new file mode 100644
|
|
index 000000000000..591315f48cbc
|
|
--- /dev/null
|
|
+++ b/drivers/mmc/host/sdhci-of-eswin.c
|
|
@@ -0,0 +1,1109 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * ESWIN Emmc Driver
|
|
+ *
|
|
+ * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved.
|
|
+ * SPDX-License-Identifier: GPL-2.0
|
|
+ *
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation, version 2.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
+ *
|
|
+ * Authors: liangshuang <liangshuang@eswincomputing.com>
|
|
+ */
|
|
+
|
|
+#include <linux/clk-provider.h>
|
|
+#include <linux/mfd/syscon.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of_device.h>
|
|
+#include <linux/phy/phy.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/of.h>
|
|
+#include "cqhci.h"
|
|
+#include "sdhci-pltfm.h"
|
|
+#include <linux/reset.h>
|
|
+#include <linux/iommu.h>
|
|
+#include <linux/bitfield.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/eswin-win2030-sid-cfg.h>
|
|
+#include "sdhci-eswin.h"
|
|
+
|
|
+#define SDHCI_EMMC0_INT_STATUS 0x508
|
|
+#define SDHCI_EMMC0_PWR_CLEAR 0x50c
|
|
+
|
|
+//EMMC_DWC_MSHC_CRYPTO_CFG_PTR 8 -- parameter
|
|
+#define eswin_sdhci_VENDOR_REGISTER_BASEADDR 0x800
|
|
+#define eswin_sdhci_VENDOR_EMMC_CTRL_REGISTER 0x2c
|
|
+#define VENDOR_ENHANCED_STROBE BIT(8)
|
|
+
|
|
+#define eswin_sdhci_CQE_BASE_ADDR eswin_sdhci_VENDOR_REGISTER_BASEADDR
|
|
+
|
|
+/* Controller does not have CD wired and will not function normally without */
|
|
+#define eswin_sdhci_QUIRK_FORCE_CDTEST BIT(0)
|
|
+/* Controller immediately reports SDHCI_CLOCK_INT_STABLE after enabling the
|
|
+ * internal clock even when the clock isn't stable */
|
|
+#define eswin_sdhci_QUIRK_CLOCK_UNSTABLE BIT(1)
|
|
+
|
|
+/*
|
|
+ * On some SoCs the syscon area has a feature where the upper 16-bits of
|
|
+ * each 32-bit register act as a write mask for the lower 16-bits. This allows
|
|
+ * atomic updates of the register without locking. This macro is used on SoCs
|
|
+ * that have that feature.
|
|
+ */
|
|
+#define HIWORD_UPDATE(val, mask, shift) \
|
|
+ ((val) << (shift) | (mask) << ((shift) + 16))
|
|
+
|
|
+#define ESWIN_EMMC_CORE_CLK_REG 0x51828160
|
|
+
|
|
+static void eswin_sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
|
|
+{
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host);
|
|
+ struct eswin_sdhci_clk_data *clk_data = &eswin_sdhci->clk_data;
|
|
+
|
|
+ /* Set the Input and Output Clock Phase Delays */
|
|
+ if (clk_data->set_clk_delays)
|
|
+ clk_data->set_clk_delays(host);
|
|
+
|
|
+ eswin_sdhci_set_core_clock(host, clock);
|
|
+ sdhci_set_clock(host, clock);
|
|
+
|
|
+ /*
|
|
+ * Some controllers immediately report SDHCI_CLOCK_INT_STABLE
|
|
+ * after enabling the clock even though the clock is not
|
|
+ * stable. Trying to use a clock without waiting here results
|
|
+ * in EILSEQ while detecting some older/slower cards. The
|
|
+ * chosen delay is the maximum delay from sdhci_set_clock.
|
|
+ */
|
|
+ if (eswin_sdhci->quirks & SDHCI_ESWIN_QUIRK_CLOCK_UNSTABLE)
|
|
+ msleep(20);
|
|
+}
|
|
+
|
|
+#if !defined(__FPGA) && !defined(__ZEBU)
|
|
+static void eswin_sdhci_hs400_enhanced_strobe(struct mmc_host *mmc,
|
|
+ struct mmc_ios *ios)
|
|
+{
|
|
+ u32 vendor;
|
|
+ struct sdhci_host *host = mmc_priv(mmc);
|
|
+
|
|
+ vendor = sdhci_readl(host, eswin_sdhci_VENDOR_EMMC_CTRL_REGISTER);
|
|
+ if (ios->enhanced_strobe)
|
|
+ vendor |= VENDOR_ENHANCED_STROBE;
|
|
+ else
|
|
+ vendor &= ~VENDOR_ENHANCED_STROBE;
|
|
+
|
|
+ sdhci_writel(host, vendor, eswin_sdhci_VENDOR_EMMC_CTRL_REGISTER);
|
|
+}
|
|
+#endif
|
|
+
|
|
+static void eswin_sdhci_config_phy_delay(struct sdhci_host *host, int delay)
|
|
+{
|
|
+ delay &= PHY_CLK_MAX_DELAY_MASK;
|
|
+
|
|
+ /*phy clk delay line config*/
|
|
+ sdhci_writeb(host, PHY_UPDATE_DELAY_CODE, PHY_SDCLKDL_CNFG_R);
|
|
+ sdhci_writeb(host, delay, PHY_SDCLKDL_DC_R);
|
|
+ sdhci_writeb(host, 0x0, PHY_SDCLKDL_CNFG_R);
|
|
+}
|
|
+
|
|
+static void eswin_sdhci_config_phy(struct sdhci_host *host)
|
|
+{
|
|
+ unsigned int val = 0;
|
|
+ unsigned int drv = 0;
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host);
|
|
+ struct eswin_sdhci_phy_data *phy = &eswin_sdhci->phy;
|
|
+
|
|
+ drv = phy->drive_impedance << PHY_PAD_SP_DRIVE_SHIF;
|
|
+
|
|
+ pr_debug("%s: phy drv=0x%x\n", mmc_hostname(host->mmc), drv);
|
|
+
|
|
+ val = sdhci_readw(host, VENDOR_EMMC_CTRL_R);
|
|
+ val |= EMMC_CRAD_PRESENT; // emmc card
|
|
+ sdhci_writew(host, val, VENDOR_EMMC_CTRL_R);
|
|
+
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+
|
|
+ /* reset phy,config phy's pad */
|
|
+ sdhci_writel(host, drv | (~PHY_RSTN), PHY_CNFG_R);
|
|
+ /*CMDPAD_CNFS*/
|
|
+ val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) |
|
|
+ (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) |
|
|
+ (phy->enable_cmd_pullup << PHY_PULL_BIT_SHIF) | PHY_PAD_RXSEL_1;
|
|
+ sdhci_writew(host, val, PHY_CMDPAD_CNFG_R);
|
|
+ pr_debug("%s: phy cmd=0x%x\n", mmc_hostname(host->mmc), val);
|
|
+
|
|
+ /*DATA PAD CNFG*/
|
|
+ val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) |
|
|
+ (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) |
|
|
+ (phy->enable_data_pullup << PHY_PULL_BIT_SHIF) | PHY_PAD_RXSEL_1;
|
|
+ sdhci_writew(host, val, PHY_DATAPAD_CNFG_R);
|
|
+ pr_debug("%s: phy data=0x%x\n", mmc_hostname(host->mmc), val);
|
|
+
|
|
+ /*Clock PAD Setting*/
|
|
+ val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) |
|
|
+ (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) | PHY_PAD_RXSEL_0;
|
|
+ sdhci_writew(host, val, PHY_CLKPAD_CNFG_R);
|
|
+ pr_debug("%s: phy clk=0x%x\n", mmc_hostname(host->mmc), val);
|
|
+
|
|
+ /*PHY strobe PAD setting*/
|
|
+ val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) |
|
|
+ (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) |
|
|
+ ((phy->enable_strobe_pulldown * PHY_PULL_DOWN) << PHY_PULL_BIT_SHIF) |
|
|
+ PHY_PAD_RXSEL_1;
|
|
+ sdhci_writew(host, val, PHY_STBPAD_CNFG_R);
|
|
+ pr_debug("%s: phy strobe=0x%x\n", mmc_hostname(host->mmc), val);
|
|
+ mdelay(2);
|
|
+
|
|
+ /*PHY RSTN PAD setting*/
|
|
+ val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) |
|
|
+ (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) |
|
|
+ (PHY_PULL_UP << PHY_PULL_BIT_SHIF) | PHY_PAD_RXSEL_1;
|
|
+ sdhci_writew(host, val, PHY_RSTNPAD_CNFG_R);
|
|
+ pr_debug("%s: phy rstn=0x%x\n", mmc_hostname(host->mmc), val);
|
|
+
|
|
+ sdhci_writel(host, drv | PHY_RSTN, PHY_CNFG_R);
|
|
+
|
|
+ eswin_sdhci_config_phy_delay(host, phy->delay_code);
|
|
+
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+}
|
|
+
|
|
+static void eswin_sdhci_reset(struct sdhci_host *host, u8 mask)
|
|
+{
|
|
+ u8 ctrl;
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host);
|
|
+
|
|
+ sdhci_reset(host, mask);
|
|
+
|
|
+ if (eswin_sdhci->quirks & SDHCI_ESWIN_QUIRK_FORCE_CDTEST) {
|
|
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
|
|
+ ctrl |= SDHCI_CTRL_CDTEST_INS | SDHCI_CTRL_CDTEST_EN;
|
|
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
|
|
+ }
|
|
+
|
|
+ if (mask == SDHCI_RESET_ALL) { // after reset all,the phy`s config will be clear.
|
|
+ eswin_sdhci_config_phy(host);
|
|
+ }
|
|
+}
|
|
+
|
|
+static u32 eswin_sdhci_cqhci_irq(struct sdhci_host *host, u32 intmask)
|
|
+{
|
|
+ int cmd_error = 0;
|
|
+ int data_error = 0;
|
|
+
|
|
+ if (!sdhci_cqe_irq(host, intmask, &cmd_error, &data_error))
|
|
+ return intmask;
|
|
+
|
|
+ cqhci_irq(host->mmc, intmask, cmd_error, data_error);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void eswin_sdhci_dumpregs(struct mmc_host *mmc)
|
|
+{
|
|
+ sdhci_dumpregs(mmc_priv(mmc));
|
|
+}
|
|
+
|
|
+static void eswin_sdhci_cqe_enable(struct mmc_host *mmc)
|
|
+{
|
|
+ struct sdhci_host *host = mmc_priv(mmc);
|
|
+ u32 reg;
|
|
+
|
|
+ reg = sdhci_readl(host, SDHCI_PRESENT_STATE);
|
|
+ while (reg & SDHCI_DATA_AVAILABLE) {
|
|
+ sdhci_readl(host, SDHCI_BUFFER);
|
|
+ reg = sdhci_readl(host, SDHCI_PRESENT_STATE);
|
|
+ }
|
|
+
|
|
+ sdhci_cqe_enable(mmc);
|
|
+}
|
|
+
|
|
+static int eswin_sdhci_delay_tuning(struct sdhci_host *host, u32 opcode)
|
|
+{
|
|
+ int ret;
|
|
+ int delay = 0;
|
|
+ int i = 0;
|
|
+ int delay_min = -1;
|
|
+ int delay_max = -1;
|
|
+ int cmd_error = 0;
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host);
|
|
+
|
|
+ for (i = 0; i <= PHY_DELAY_CODE_MAX; i++) {
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+ eswin_sdhci_config_phy_delay(host, i);
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+ ret = mmc_send_tuning(host->mmc, opcode, &cmd_error);
|
|
+ if (ret) {
|
|
+ sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
|
|
+ udelay(200);
|
|
+ if (delay_min != -1 && delay_max != -1)
|
|
+ break;
|
|
+ } else {
|
|
+ if (delay_min == -1) {
|
|
+ delay_min = i;
|
|
+ continue;
|
|
+ } else {
|
|
+ delay_max = i;
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (delay_min == -1 && delay_max == -1) {
|
|
+ pr_err("%s: delay code tuning failed!\n",
|
|
+ mmc_hostname(host->mmc));
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+ eswin_sdhci_config_phy_delay(host, eswin_sdhci->phy.delay_code);
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ delay = (delay_min + delay_max) / 2;
|
|
+ pr_info("%s: set delay:0x%x\n", mmc_hostname(host->mmc), delay);
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+ eswin_sdhci_config_phy_delay(host, delay);
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int eswin_sdhci_phase_code_tuning(struct sdhci_host *host, u32 opcode)
|
|
+{
|
|
+ int cmd_error = 0;
|
|
+ int ret = 0;
|
|
+ int phase_code = 0;
|
|
+ int code_min = -1;
|
|
+ int code_max = -1;
|
|
+
|
|
+ for (phase_code = 0; phase_code <= MAX_PHASE_CODE; phase_code++) {
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+ sdhci_writew(host, phase_code, VENDOR_AT_SATA_R);
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+
|
|
+ ret = mmc_send_tuning(host->mmc, opcode, &cmd_error);
|
|
+ if (ret) {
|
|
+ sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
|
|
+ udelay(200);
|
|
+ if (code_min != -1 && code_max != -1)
|
|
+ break;
|
|
+ } else {
|
|
+ if (code_min == -1) {
|
|
+ code_min = phase_code;
|
|
+ continue;
|
|
+ } else {
|
|
+ code_max = phase_code;
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (code_min == -1 && code_max == -1) {
|
|
+ pr_err("%s: phase code tuning failed!\n",
|
|
+ mmc_hostname(host->mmc));
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+ sdhci_writew(host, 0, VENDOR_AT_SATA_R);
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ phase_code = (code_min + code_max) / 2;
|
|
+ pr_info("%s: set phase_code:0x%x\n", mmc_hostname(host->mmc), phase_code);
|
|
+
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+ sdhci_writew(host, phase_code, VENDOR_AT_SATA_R);
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int eswin_sdhci_executing_tuning(struct sdhci_host *host, u32 opcode)
|
|
+{
|
|
+ u32 ctrl;
|
|
+ u32 val;
|
|
+ int ret = 0;
|
|
+
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+
|
|
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
+ ctrl &= ~SDHCI_CTRL_TUNED_CLK;
|
|
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
|
|
+
|
|
+ val = sdhci_readl(host, VENDOR_AT_CTRL_R);
|
|
+ val |= SW_TUNE_ENABLE;
|
|
+ sdhci_writew(host, val, VENDOR_AT_CTRL_R);
|
|
+ sdhci_writew(host, 0, VENDOR_AT_SATA_R);
|
|
+
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+
|
|
+ sdhci_writew(host, 0x0, SDHCI_CMD_DATA);
|
|
+
|
|
+ ret = eswin_sdhci_delay_tuning(host, opcode);
|
|
+ if (ret < 0) {
|
|
+ return ret;
|
|
+ }
|
|
+ ret = eswin_sdhci_phase_code_tuning(host, opcode);
|
|
+ if (ret < 0) {
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void eswin_sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing)
|
|
+{
|
|
+ u32 val;
|
|
+ u32 status;
|
|
+ u32 timeout = 0;
|
|
+ u16 ctrl_2;
|
|
+
|
|
+ ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
+ /* Select Bus Speed Mode for host */
|
|
+ ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
|
|
+ if ((timing == MMC_TIMING_MMC_HS200) ||
|
|
+ (timing == MMC_TIMING_UHS_SDR104))
|
|
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
|
|
+ else if (timing == MMC_TIMING_UHS_SDR12)
|
|
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
|
|
+ else if (timing == MMC_TIMING_UHS_SDR25)
|
|
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
|
|
+ else if (timing == MMC_TIMING_UHS_SDR50)
|
|
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
|
|
+ else if ((timing == MMC_TIMING_UHS_DDR50) ||
|
|
+ (timing == MMC_TIMING_MMC_DDR52))
|
|
+ ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
|
|
+ else if (timing == MMC_TIMING_MMC_HS400)
|
|
+ ctrl_2 |= ESWIN_SDHCI_CTRL_HS400; /* Non-standard */
|
|
+ sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
|
|
+
|
|
+ /*
|
|
+ * here need make dll locked when in hs400 at 200MHz
|
|
+ */
|
|
+ if ((timing == MMC_TIMING_MMC_HS400) && (host->clock == 200000000)) {
|
|
+ eswin_sdhci_disable_card_clk(host);
|
|
+
|
|
+ val = sdhci_readl(host, VENDOR_AT_CTRL_R);
|
|
+ val &= ~(LATENCY_LT_MASK << LATENCY_LT_BIT_OFFSET);
|
|
+ val |= (LATENCY_LT_3 << LATENCY_LT_MASK);
|
|
+ sdhci_writel(host, val, VENDOR_AT_CTRL_R);
|
|
+
|
|
+ sdhci_writeb(host, 0x23, PHY_DLL_CNFG1_R);
|
|
+ sdhci_writeb(host, 0x02, PHY_DLL_CNFG2_R);
|
|
+ sdhci_writeb(host, 0x60, PHY_DLLDL_CNFG_R);
|
|
+ sdhci_writeb(host, 0x00, PHY_DLL_OFFST_R);
|
|
+ sdhci_writew(host, 0xffff, PHY_DLLBT_CNFG_R);
|
|
+
|
|
+ eswin_sdhci_enable_card_clk(host);
|
|
+ sdhci_writeb(host, DLL_ENABEL, PHY_DLL_CTRL_R);
|
|
+ udelay(100);
|
|
+
|
|
+ while (1) {
|
|
+ status = sdhci_readb(host, PHY_DLL_STATUS_R);
|
|
+ if (status & DLL_LOCK_STS) {
|
|
+ pr_debug("%s: locked status:0x%x\n", mmc_hostname(host->mmc), status);
|
|
+ break;
|
|
+ }
|
|
+ timeout++;
|
|
+ udelay(100);
|
|
+ if (timeout > 10000) {
|
|
+ pr_err("%s: DLL lock failed!status:0x%x\n",
|
|
+ mmc_hostname(host->mmc), status);
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ status = sdhci_readb(host, PHY_DLL_STATUS_R);
|
|
+ if (status & DLL_ERROR_STS) {
|
|
+ pr_err("%s: DLL lock failed!err_status:0x%x\n",
|
|
+ mmc_hostname(host->mmc), status);
|
|
+ } else {
|
|
+ pr_debug("%s: DLL lock is success\n", mmc_hostname(host->mmc));
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static const struct cqhci_host_ops eswin_sdhci_cqhci_ops = {
|
|
+ .enable = eswin_sdhci_cqe_enable,
|
|
+ .disable = sdhci_cqe_disable,
|
|
+ .dumpregs = eswin_sdhci_dumpregs,
|
|
+};
|
|
+
|
|
+static const struct sdhci_ops eswin_sdhci_cqe_ops = {
|
|
+ .set_clock = eswin_sdhci_set_clock,
|
|
+ .get_max_clock = sdhci_pltfm_clk_get_max_clock,
|
|
+ .get_timeout_clock = sdhci_pltfm_clk_get_max_clock,
|
|
+ .set_bus_width = sdhci_set_bus_width,
|
|
+ .reset = eswin_sdhci_reset,
|
|
+ .set_uhs_signaling = eswin_sdhci_set_uhs_signaling,
|
|
+ .set_power = sdhci_set_power_and_bus_voltage,
|
|
+ .irq = eswin_sdhci_cqhci_irq,
|
|
+ .platform_execute_tuning = eswin_sdhci_executing_tuning,
|
|
+};
|
|
+
|
|
+static const struct sdhci_pltfm_data eswin_sdhci_cqe_pdata = {
|
|
+ .ops = &eswin_sdhci_cqe_ops,
|
|
+ .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
|
+ .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
|
|
+#if defined(__DISABLE_HS200)
|
|
+ SDHCI_QUIRK2_BROKEN_HS200 |
|
|
+#endif
|
|
+ SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN,
|
|
+};
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+/**
|
|
+ * eswin_sdhci_suspend - Suspend method for the driver
|
|
+ * @dev: Address of the device structure
|
|
+ *
|
|
+ * Put the device in a low power state.
|
|
+ *
|
|
+ * Return: 0 on success and error value on error
|
|
+ */
|
|
+static int eswin_sdhci_suspend(struct device *dev)
|
|
+{
|
|
+ struct sdhci_host *host = dev_get_drvdata(dev);
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host);
|
|
+ int ret;
|
|
+
|
|
+ if (host->tuning_mode != SDHCI_TUNING_MODE_3)
|
|
+ mmc_retune_needed(host->mmc);
|
|
+
|
|
+ if (eswin_sdhci->has_cqe) {
|
|
+ ret = cqhci_suspend(host->mmc);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = sdhci_suspend_host(host);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ clk_disable(pltfm_host->clk);
|
|
+ clk_disable(eswin_sdhci->clk_ahb);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * eswin_sdhci_resume - Resume method for the driver
|
|
+ * @dev: Address of the device structure
|
|
+ *
|
|
+ * Resume operation after suspend
|
|
+ *
|
|
+ * Return: 0 on success and error value on error
|
|
+ */
|
|
+static int eswin_sdhci_resume(struct device *dev)
|
|
+{
|
|
+ struct sdhci_host *host = dev_get_drvdata(dev);
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host);
|
|
+ int ret;
|
|
+
|
|
+ ret = clk_enable(eswin_sdhci->clk_ahb);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Cannot enable AHB clock.\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = clk_enable(pltfm_host->clk);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Cannot enable SD clock.\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = sdhci_resume_host(host);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Cannot resume host.\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (eswin_sdhci->has_cqe)
|
|
+ return cqhci_resume(host->mmc);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#endif /* ! CONFIG_PM_SLEEP */
|
|
+
|
|
+static SIMPLE_DEV_PM_OPS(eswin_sdhci_dev_pm_ops, eswin_sdhci_suspend,
|
|
+ eswin_sdhci_resume);
|
|
+
|
|
+/**
|
|
+ * eswin_sdhci_sdcardclk_recalc_rate - Return the card clock rate
|
|
+ *
|
|
+ * @hw: Pointer to the hardware clock structure.
|
|
+ * @parent_rate: The parent rate (should be rate of clk_xin).
|
|
+ *
|
|
+ * Return the current actual rate of the SD card clock. This can be used
|
|
+ * to communicate with out PHY.
|
|
+ *
|
|
+ * Return: The card clock rate.
|
|
+ */
|
|
+static unsigned long
|
|
+eswin_sdhci_sdcardclk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
|
+{
|
|
+ struct eswin_sdhci_clk_data *clk_data =
|
|
+ container_of(hw, struct eswin_sdhci_clk_data, sdcardclk_hw);
|
|
+ struct eswin_sdhci_data *eswin_sdhci =
|
|
+ container_of(clk_data, struct eswin_sdhci_data, clk_data);
|
|
+ struct sdhci_host *host = eswin_sdhci->host;
|
|
+
|
|
+ return host->mmc->actual_clock;
|
|
+}
|
|
+
|
|
+static const struct clk_ops eswin_sdcardclk_ops = {
|
|
+ .recalc_rate = eswin_sdhci_sdcardclk_recalc_rate,
|
|
+};
|
|
+
|
|
+/**
|
|
+ * eswin_sdhci_sampleclk_recalc_rate - Return the sampling clock rate
|
|
+ *
|
|
+ * @hw: Pointer to the hardware clock structure.
|
|
+ * @parent_rate: The parent rate (should be rate of clk_xin).
|
|
+ *
|
|
+ * Return the current actual rate of the sampling clock. This can be used
|
|
+ * to communicate with out PHY.
|
|
+ *
|
|
+ * Return: The sample clock rate.
|
|
+ */
|
|
+static unsigned long
|
|
+eswin_sdhci_sampleclk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
|
+{
|
|
+ struct eswin_sdhci_clk_data *clk_data =
|
|
+ container_of(hw, struct eswin_sdhci_clk_data, sampleclk_hw);
|
|
+ struct eswin_sdhci_data *eswin_sdhci =
|
|
+ container_of(clk_data, struct eswin_sdhci_data, clk_data);
|
|
+ struct sdhci_host *host = eswin_sdhci->host;
|
|
+
|
|
+ return host->mmc->actual_clock;
|
|
+}
|
|
+
|
|
+static const struct clk_ops eswin_sampleclk_ops = {
|
|
+ .recalc_rate = eswin_sdhci_sampleclk_recalc_rate,
|
|
+};
|
|
+
|
|
+static const struct eswin_sdhci_clk_ops eswin_clk_ops = {
|
|
+ .sdcardclk_ops = &eswin_sdcardclk_ops,
|
|
+ .sampleclk_ops = &eswin_sampleclk_ops,
|
|
+};
|
|
+
|
|
+static struct eswin_sdhci_of_data eswin_sdhci_fu800_data = {
|
|
+ .pdata = &eswin_sdhci_cqe_pdata,
|
|
+ .clk_ops = &eswin_clk_ops,
|
|
+};
|
|
+
|
|
+static const struct of_device_id eswin_sdhci_of_match[] = {
|
|
+ {
|
|
+ .compatible = "eswin,emmc-sdhci-5.1",
|
|
+ .data = &eswin_sdhci_fu800_data,
|
|
+ },
|
|
+ { /* sentinel */ }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, eswin_sdhci_of_match);
|
|
+
|
|
+/**
|
|
+ * eswin_sdhci_register_sdcardclk - Register the sdcardclk for a PHY to use
|
|
+ *
|
|
+ * @eswin_sdhci: Our private data structure.
|
|
+ * @clk_xin: Pointer to the functional clock
|
|
+ * @dev: Pointer to our struct device.
|
|
+ *
|
|
+ * Some PHY devices need to know what the actual card clock is. In order for
|
|
+ * them to find out, we'll provide a clock through the common clock framework
|
|
+ * for them to query.
|
|
+ *
|
|
+ * Return: 0 on success and error value on error
|
|
+ */
|
|
+static int eswin_sdhci_register_sdcardclk(struct eswin_sdhci_data *eswin_sdhci,
|
|
+ struct clk *clk_xin, struct device *dev)
|
|
+{
|
|
+ struct eswin_sdhci_clk_data *clk_data = &eswin_sdhci->clk_data;
|
|
+ struct device_node *np = dev->of_node;
|
|
+ struct clk_init_data sdcardclk_init;
|
|
+ const char *parent_clk_name;
|
|
+ int ret;
|
|
+
|
|
+ ret = of_property_read_string_index(np, "clock-output-names", 0,
|
|
+ &sdcardclk_init.name);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "DT has #clock-cells but no clock-output-names\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ parent_clk_name = __clk_get_name(clk_xin);
|
|
+ sdcardclk_init.parent_names = &parent_clk_name;
|
|
+ sdcardclk_init.num_parents = 1;
|
|
+ sdcardclk_init.flags = CLK_GET_RATE_NOCACHE;
|
|
+ sdcardclk_init.ops = eswin_sdhci->clk_ops->sdcardclk_ops;
|
|
+
|
|
+ clk_data->sdcardclk_hw.init = &sdcardclk_init;
|
|
+ clk_data->sdcardclk = devm_clk_register(dev, &clk_data->sdcardclk_hw);
|
|
+ if (IS_ERR(clk_data->sdcardclk))
|
|
+ return PTR_ERR(clk_data->sdcardclk);
|
|
+ clk_data->sdcardclk_hw.init = NULL;
|
|
+
|
|
+ ret = of_clk_add_provider(np, of_clk_src_simple_get,
|
|
+ clk_data->sdcardclk);
|
|
+ if (ret)
|
|
+ dev_err(dev, "Failed to add sdcard clock provider\n");
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * eswin_sdhci_register_sampleclk - Register the sampleclk for a PHY to use
|
|
+ *
|
|
+ * @eswin_sdhci: Our private data structure.
|
|
+ * @clk_xin: Pointer to the functional clock
|
|
+ * @dev: Pointer to our struct device.
|
|
+ *
|
|
+ * Some PHY devices need to know what the actual card clock is. In order for
|
|
+ * them to find out, we'll provide a clock through the common clock framework
|
|
+ * for them to query.
|
|
+ *
|
|
+ * Return: 0 on success and error value on error
|
|
+ */
|
|
+static int eswin_sdhci_register_sampleclk(struct eswin_sdhci_data *eswin_sdhci,
|
|
+ struct clk *clk_xin, struct device *dev)
|
|
+{
|
|
+ struct eswin_sdhci_clk_data *clk_data = &eswin_sdhci->clk_data;
|
|
+ struct device_node *np = dev->of_node;
|
|
+ struct clk_init_data sampleclk_init;
|
|
+ const char *parent_clk_name;
|
|
+ int ret;
|
|
+
|
|
+ ret = of_property_read_string_index(np, "clock-output-names", 1,
|
|
+ &sampleclk_init.name);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "DT has #clock-cells but no clock-output-names\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ parent_clk_name = __clk_get_name(clk_xin);
|
|
+ sampleclk_init.parent_names = &parent_clk_name;
|
|
+ sampleclk_init.num_parents = 1;
|
|
+ sampleclk_init.flags = CLK_GET_RATE_NOCACHE;
|
|
+ sampleclk_init.ops = eswin_sdhci->clk_ops->sampleclk_ops;
|
|
+
|
|
+ clk_data->sampleclk_hw.init = &sampleclk_init;
|
|
+ clk_data->sampleclk = devm_clk_register(dev, &clk_data->sampleclk_hw);
|
|
+ if (IS_ERR(clk_data->sampleclk))
|
|
+ return PTR_ERR(clk_data->sampleclk);
|
|
+ clk_data->sampleclk_hw.init = NULL;
|
|
+
|
|
+ ret = of_clk_add_provider(np, of_clk_src_simple_get,
|
|
+ clk_data->sampleclk);
|
|
+ if (ret)
|
|
+ dev_err(dev, "Failed to add sample clock provider\n");
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * eswin_sdhci_unregister_sdclk - Undoes eswin_sdhci_register_sdclk()
|
|
+ *
|
|
+ * @dev: Pointer to our struct device.
|
|
+ *
|
|
+ * Should be called any time we're exiting and eswin_sdhci_register_sdclk()
|
|
+ * returned success.
|
|
+ */
|
|
+static void eswin_sdhci_unregister_sdclk(struct device *dev)
|
|
+{
|
|
+ struct device_node *np = dev->of_node;
|
|
+
|
|
+ if (!of_find_property(np, "#clock-cells", NULL))
|
|
+ return;
|
|
+
|
|
+ of_clk_del_provider(dev->of_node);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * eswin_sdhci_register_sdclk - Register the sdcardclk for a PHY to use
|
|
+ *
|
|
+ * @eswin_sdhci: Our private data structure.
|
|
+ * @clk_xin: Pointer to the functional clock
|
|
+ * @dev: Pointer to our struct device.
|
|
+ *
|
|
+ * Some PHY devices need to know what the actual card clock is. In order for
|
|
+ * them to find out, we'll provide a clock through the common clock framework
|
|
+ * for them to query.
|
|
+ *
|
|
+ * Note: without seriously re-architecting SDHCI's clock code and testing on
|
|
+ * all platforms, there's no way to create a totally beautiful clock here
|
|
+ * with all clock ops implemented. Instead, we'll just create a clock that can
|
|
+ * be queried and set the CLK_GET_RATE_NOCACHE attribute to tell common clock
|
|
+ * framework that we're doing things behind its back. This should be sufficient
|
|
+ * to create nice clean device tree bindings and later (if needed) we can try
|
|
+ * re-architecting SDHCI if we see some benefit to it.
|
|
+ *
|
|
+ * Return: 0 on success and error value on error
|
|
+ */
|
|
+static int eswin_sdhci_register_sdclk(struct eswin_sdhci_data *eswin_sdhci,
|
|
+ struct clk *clk_xin, struct device *dev)
|
|
+{
|
|
+ struct device_node *np = dev->of_node;
|
|
+ u32 num_clks = 0;
|
|
+ int ret;
|
|
+
|
|
+ /* Providing a clock to the PHY is optional; no error if missing */
|
|
+ if (of_property_read_u32(np, "#clock-cells", &num_clks) < 0)
|
|
+ return 0;
|
|
+
|
|
+ ret = eswin_sdhci_register_sdcardclk(eswin_sdhci, clk_xin, dev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (num_clks) {
|
|
+ ret = eswin_sdhci_register_sampleclk(eswin_sdhci, clk_xin, dev);
|
|
+ if (ret) {
|
|
+ eswin_sdhci_unregister_sdclk(dev);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int eswin_sdhci_add_host(struct eswin_sdhci_data *eswin_sdhci)
|
|
+{
|
|
+ struct sdhci_host *host = eswin_sdhci->host;
|
|
+ struct cqhci_host *cq_host;
|
|
+ bool dma64;
|
|
+ int ret;
|
|
+
|
|
+ if (!eswin_sdhci->has_cqe)
|
|
+ return sdhci_add_host(host);
|
|
+
|
|
+ ret = sdhci_setup_host(host);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ cq_host = devm_kzalloc(host->mmc->parent, sizeof(*cq_host), GFP_KERNEL);
|
|
+ if (!cq_host) {
|
|
+ ret = -ENOMEM;
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ cq_host->mmio = host->ioaddr + eswin_sdhci_CQE_BASE_ADDR;
|
|
+ cq_host->ops = &eswin_sdhci_cqhci_ops;
|
|
+
|
|
+ dma64 = host->flags & SDHCI_USE_64_BIT_DMA;
|
|
+ if (dma64)
|
|
+ cq_host->caps |= CQHCI_TASK_DESC_SZ_128;
|
|
+
|
|
+ ret = cqhci_init(cq_host, host->mmc, dma64);
|
|
+ if (ret)
|
|
+ goto cleanup;
|
|
+
|
|
+ ret = __sdhci_add_host(host);
|
|
+ if (ret)
|
|
+ goto cleanup;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+cleanup:
|
|
+ sdhci_cleanup_host(host);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int eswin_emmc_sid_cfg(struct device *dev)
|
|
+{
|
|
+ int ret;
|
|
+ struct regmap *regmap;
|
|
+ int hsp_mmu_emmc_reg;
|
|
+ u32 rdwr_sid_ssid;
|
|
+ u32 sid;
|
|
+ struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
|
|
+
|
|
+ /* not behind smmu, use the default reset value(0x0) of the reg as streamID*/
|
|
+ if (fwspec == NULL) {
|
|
+ dev_dbg(dev,
|
|
+ "dev is not behind smmu, skip configuration of sid\n");
|
|
+ return 0;
|
|
+ }
|
|
+ sid = fwspec->ids[0];
|
|
+
|
|
+ regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
|
|
+ "eswin,hsp_sp_csr");
|
|
+ if (IS_ERR(regmap)) {
|
|
+ dev_dbg(dev, "No hsp_sp_csr phandle specified\n");
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ ret = of_property_read_u32_index(dev->of_node, "eswin,hsp_sp_csr", 1,
|
|
+ &hsp_mmu_emmc_reg);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "can't get emmc sid cfg reg offset (%d)\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* make the reading sid the same as writing sid, ssid is fixed to zero */
|
|
+ rdwr_sid_ssid = FIELD_PREP(AWSMMUSID, sid);
|
|
+ rdwr_sid_ssid |= FIELD_PREP(ARSMMUSID, sid);
|
|
+ rdwr_sid_ssid |= FIELD_PREP(AWSMMUSSID, 0);
|
|
+ rdwr_sid_ssid |= FIELD_PREP(ARSMMUSSID, 0);
|
|
+ regmap_write(regmap, hsp_mmu_emmc_reg, rdwr_sid_ssid);
|
|
+
|
|
+ ret = win2030_dynm_sid_enable(dev_to_node(dev));
|
|
+ if (ret < 0)
|
|
+ dev_err(dev, "failed to config emmc streamID(%d)!\n", sid);
|
|
+ else
|
|
+ dev_dbg(dev, "success to config emmc streamID(%d)!\n", sid);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int eswin_sdhci_probe(struct platform_device *pdev)
|
|
+{
|
|
+ int ret;
|
|
+ struct clk *clk_xin;
|
|
+ struct sdhci_host *host;
|
|
+ struct sdhci_pltfm_host *pltfm_host;
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct eswin_sdhci_data *eswin_sdhci;
|
|
+ const struct eswin_sdhci_of_data *data;
|
|
+ struct regmap *regmap;
|
|
+ unsigned int val = 0;
|
|
+
|
|
+ data = of_device_get_match_data(dev);
|
|
+ host = sdhci_pltfm_init(pdev, data->pdata, sizeof(*eswin_sdhci));
|
|
+ if (IS_ERR(host))
|
|
+ return PTR_ERR(host);
|
|
+
|
|
+ pltfm_host = sdhci_priv(host);
|
|
+ eswin_sdhci = sdhci_pltfm_priv(pltfm_host);
|
|
+ eswin_sdhci->host = host;
|
|
+ eswin_sdhci->clk_ops = data->clk_ops;
|
|
+
|
|
+ eswin_sdhci->core_clk_reg = ioremap(ESWIN_EMMC_CORE_CLK_REG, 0x4);
|
|
+ if (!eswin_sdhci->core_clk_reg) {
|
|
+ dev_err(dev, "ioremap core clk reg failed.\n");
|
|
+ goto err_pltfm_free;
|
|
+ }
|
|
+
|
|
+#if defined(__FPGA) || defined(__ZEBU)
|
|
+#if defined __SDMA
|
|
+ eswin_sdhci->host->quirks |= SDHCI_QUIRK_BROKEN_ADMA;
|
|
+#elif defined(__ADMA2) || defined(__ADMA3)
|
|
+#else
|
|
+ eswin_sdhci->host->quirks |= SDHCI_QUIRK_BROKEN_ADMA |
|
|
+ SDHCI_QUIRK_BROKEN_DMA;
|
|
+#endif
|
|
+#endif
|
|
+
|
|
+#if defined(__FORCE_1BIT)
|
|
+ eswin_sdhci->host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA;
|
|
+#endif
|
|
+
|
|
+ eswin_sdhci->clk_ahb = devm_clk_get(dev, "clk_ahb");
|
|
+ if (IS_ERR(eswin_sdhci->clk_ahb)) {
|
|
+ ret = dev_err_probe(dev, PTR_ERR(eswin_sdhci->clk_ahb),
|
|
+ "clk_ahb clock not found.\n");
|
|
+ goto err_pltfm_free;
|
|
+ }
|
|
+
|
|
+ clk_xin = devm_clk_get(dev, "clk_xin");
|
|
+ if (IS_ERR(clk_xin)) {
|
|
+ ret = dev_err_probe(dev, PTR_ERR(clk_xin),
|
|
+ "clk_xin clock not found.\n");
|
|
+ goto err_pltfm_free;
|
|
+ }
|
|
+
|
|
+ ret = clk_prepare_enable(eswin_sdhci->clk_ahb);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Unable to enable AHB clock.\n");
|
|
+ goto err_pltfm_free;
|
|
+ }
|
|
+
|
|
+ ret = clk_prepare_enable(clk_xin);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Unable to enable SD clock.\n");
|
|
+ goto clk_dis_ahb;
|
|
+ }
|
|
+
|
|
+ ret = eswin_sdhci_reset_init(dev, eswin_sdhci);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to reset\n");
|
|
+ goto clk_disable_all;
|
|
+ }
|
|
+
|
|
+ win2030_tbu_power(dev, true);
|
|
+
|
|
+ regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
|
|
+ "eswin,hsp_sp_csr");
|
|
+ if (IS_ERR(regmap)) {
|
|
+ dev_dbg(dev, "No hsp_sp_csr phandle specified\n");
|
|
+ goto clk_disable_all;
|
|
+ }
|
|
+
|
|
+ regmap_write(regmap, SDHCI_EMMC0_INT_STATUS, MSHC_INT_CLK_STABLE);
|
|
+ regmap_write(regmap, SDHCI_EMMC0_PWR_CLEAR, MSHC_HOST_VAL_STABLE);
|
|
+
|
|
+ /* smmu */
|
|
+ eswin_emmc_sid_cfg(dev);
|
|
+
|
|
+ if (!of_property_read_u32(dev->of_node, "delay_code", &val)) {
|
|
+ eswin_sdhci->phy.delay_code = val;
|
|
+ }
|
|
+
|
|
+ if (!of_property_read_u32(dev->of_node, "drive-impedance-ohm", &val))
|
|
+ eswin_sdhci->phy.drive_impedance =
|
|
+ eswin_convert_drive_impedance_ohm(pdev, val);
|
|
+
|
|
+ if (of_property_read_bool(dev->of_node, "enable-cmd-pullup"))
|
|
+ eswin_sdhci->phy.enable_cmd_pullup = ENABLE;
|
|
+ else
|
|
+ eswin_sdhci->phy.enable_cmd_pullup = DISABLE;
|
|
+
|
|
+ if (of_property_read_bool(dev->of_node, "enable-data-pullup"))
|
|
+ eswin_sdhci->phy.enable_data_pullup = ENABLE;
|
|
+ else
|
|
+ eswin_sdhci->phy.enable_data_pullup = DISABLE;
|
|
+
|
|
+ if (of_property_read_bool(dev->of_node, "enable-strobe-pulldown"))
|
|
+ eswin_sdhci->phy.enable_strobe_pulldown = ENABLE;
|
|
+ else
|
|
+ eswin_sdhci->phy.enable_strobe_pulldown = DISABLE;
|
|
+
|
|
+ sdhci_get_of_property(pdev);
|
|
+
|
|
+ pltfm_host->clk = clk_xin;
|
|
+
|
|
+ ret = eswin_sdhci_register_sdclk(eswin_sdhci, clk_xin, dev);
|
|
+ if (ret)
|
|
+ goto clk_disable_all;
|
|
+
|
|
+ eswin_sdhci_dt_parse_clk_phases(dev, &eswin_sdhci->clk_data);
|
|
+
|
|
+ ret = mmc_of_parse(host->mmc);
|
|
+ if (ret) {
|
|
+ ret = dev_err_probe(dev, ret, "parsing dt failed.\n");
|
|
+ goto unreg_clk;
|
|
+ }
|
|
+
|
|
+#if !defined(__FPGA) && !defined(__ZEBU)
|
|
+ if (of_device_is_compatible(dev->of_node, "eswin,sdhci-5.1")) {
|
|
+ host->mmc_host_ops.hs400_enhanced_strobe =
|
|
+ eswin_sdhci_hs400_enhanced_strobe;
|
|
+ eswin_sdhci->has_cqe = true;
|
|
+ host->mmc->caps2 |= MMC_CAP2_CQE;
|
|
+
|
|
+ if (!of_property_read_bool(dev->of_node, "disable-cqe-dcmd"))
|
|
+ host->mmc->caps2 |= MMC_CAP2_CQE_DCMD;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+#if !defined(__ADMA3_DISABLE)
|
|
+ sdhci_enable_v4_mode(eswin_sdhci->host);
|
|
+#endif
|
|
+
|
|
+ ret = eswin_sdhci_add_host(eswin_sdhci);
|
|
+ if (ret)
|
|
+ goto unreg_clk;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+unreg_clk:
|
|
+ eswin_sdhci_unregister_sdclk(dev);
|
|
+clk_disable_all:
|
|
+ clk_disable_unprepare(clk_xin);
|
|
+clk_dis_ahb:
|
|
+ clk_disable_unprepare(eswin_sdhci->clk_ahb);
|
|
+err_pltfm_free:
|
|
+ if (eswin_sdhci->core_clk_reg)
|
|
+ iounmap(eswin_sdhci->core_clk_reg);
|
|
+ sdhci_pltfm_free(pdev);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int eswin_sdhci_remove(struct platform_device *pdev)
|
|
+{
|
|
+ int ret;
|
|
+ struct sdhci_host *host = platform_get_drvdata(pdev);
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host);
|
|
+ struct clk *clk_ahb = eswin_sdhci->clk_ahb;
|
|
+ void __iomem *core_clk_reg = eswin_sdhci->core_clk_reg;
|
|
+
|
|
+ sdhci_pltfm_remove(pdev);
|
|
+ win2030_tbu_power(&pdev->dev, false);
|
|
+
|
|
+ if (eswin_sdhci->txrx_rst) {
|
|
+ ret = reset_control_assert(eswin_sdhci->txrx_rst);
|
|
+ WARN_ON(0 != ret);
|
|
+ }
|
|
+
|
|
+ if (eswin_sdhci->phy_rst) {
|
|
+ ret = reset_control_assert(eswin_sdhci->phy_rst);
|
|
+ WARN_ON(0 != ret);
|
|
+ }
|
|
+
|
|
+ if (eswin_sdhci->prstn) {
|
|
+ ret = reset_control_assert(eswin_sdhci->prstn);
|
|
+ WARN_ON(0 != ret);
|
|
+ }
|
|
+
|
|
+ if (eswin_sdhci->arstn) {
|
|
+ ret = reset_control_assert(eswin_sdhci->arstn);
|
|
+ WARN_ON(0 != ret);
|
|
+ }
|
|
+ eswin_sdhci_unregister_sdclk(&pdev->dev);
|
|
+ clk_disable_unprepare(clk_ahb);
|
|
+ iounmap(core_clk_reg);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void emmc_hard_reset(struct sdhci_host *host)
|
|
+{
|
|
+ unsigned int val;
|
|
+
|
|
+ val = sdhci_readw(host, VENDOR_EMMC_CTRL_R);
|
|
+ val |= EMMC_RST_N_OE;
|
|
+ sdhci_writew(host, val, VENDOR_EMMC_CTRL_R);
|
|
+ val &= ~EMMC_RST_N;
|
|
+ sdhci_writew(host, val, VENDOR_EMMC_CTRL_R);
|
|
+ mdelay(20);
|
|
+ val |= EMMC_RST_N;
|
|
+ sdhci_writew(host, val, VENDOR_EMMC_CTRL_R);
|
|
+}
|
|
+
|
|
+static void eswin_sdhci_shutdown(struct platform_device *pdev)
|
|
+{
|
|
+ struct sdhci_host *host = platform_get_drvdata(pdev);
|
|
+
|
|
+ if (!host)
|
|
+ return;
|
|
+
|
|
+ emmc_hard_reset(host);
|
|
+ host->ops->reset(host, SDHCI_RESET_ALL);
|
|
+ platform_set_drvdata(pdev, NULL);
|
|
+}
|
|
+
|
|
+static struct platform_driver eswin_sdhci_driver =
|
|
+{
|
|
+ .driver = {
|
|
+ .name = "sdhci-eswin",
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
+ .of_match_table = eswin_sdhci_of_match,
|
|
+ .pm = &eswin_sdhci_dev_pm_ops,
|
|
+ },
|
|
+ .probe = eswin_sdhci_probe,
|
|
+ .remove = eswin_sdhci_remove,
|
|
+ .shutdown = eswin_sdhci_shutdown,
|
|
+};
|
|
+
|
|
+module_platform_driver(eswin_sdhci_driver);
|
|
+
|
|
+MODULE_DESCRIPTION("Driver for the ESWIN SDHCI Controller");
|
|
+MODULE_AUTHOR("liangshuang@eswin.com");
|
|
+MODULE_LICENSE("GPL v2");
|
|
--
|
|
2.47.0
|
|
|