aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorManivannan Sadhasivam <manivannan.sadhasivam@linaro.org>2019-05-14 22:34:50 +0530
committerManivannan Sadhasivam <manivannan.sadhasivam@linaro.org>2019-05-15 19:09:00 +0530
commit3cd219061add4a4ab8cfe4d370683af5703f2a9b (patch)
treef4d4093c5e7c1b331778de0508c5befb7786121a
parent73ece2e0f7c7f2ab1373e652ea5d34b9f607fe73 (diff)
download96b-common-3cd219061add4a4ab8cfe4d370683af5703f2a9b.tar.gz
mmc: Add Bitmain BM1880 SDHCI driverbm1880-mmc
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
-rw-r--r--arch/arm64/boot/dts/bitmain/bm1880-sophon-edge.dts51
-rw-r--r--arch/arm64/boot/dts/bitmain/bm1880.dtsi28
-rw-r--r--drivers/mmc/host/Kconfig13
-rw-r--r--drivers/mmc/host/Makefile1
-rw-r--r--drivers/mmc/host/sdhci-bitmain.c406
5 files changed, 499 insertions, 0 deletions
diff --git a/arch/arm64/boot/dts/bitmain/bm1880-sophon-edge.dts b/arch/arm64/boot/dts/bitmain/bm1880-sophon-edge.dts
index 22baf7a7a216..1a9f1c734be8 100644
--- a/arch/arm64/boot/dts/bitmain/bm1880-sophon-edge.dts
+++ b/arch/arm64/boot/dts/bitmain/bm1880-sophon-edge.dts
@@ -26,9 +26,39 @@
device_type = "memory";
reg = <0x1 0x00000000 0x0 0x40000000>; // 1GB
};
+
+ reg_1p8v: regulator-1p8v {
+ compatible = "regulator-fixed";
+ regulator-name = "fixed-1.8V";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-always-on;
+ };
+
+ reg_3p3v: regulator-3p3v {
+ compatible = "regulator-fixed";
+ regulator-name = "fixed-3.3V";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-always-on;
+ };
};
&pinctrl {
+ pinctrl_emmc_default: pinctrl-emmc-default {
+ pinmux {
+ groups = "emmc_grp";
+ function = "emmc";
+ };
+ };
+
+ pinctrl_sdio_default: pinctrl-sdio-default {
+ pinmux {
+ groups = "sdio_grp";
+ function = "sdio";
+ };
+ };
+
pinctrl_uart0_default: pinctrl-uart0-default {
pinmux {
groups = "uart0_grp";
@@ -51,6 +81,27 @@
};
};
+&emmc {
+ status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_emmc_default>;
+ non-removable;
+ no-sdio;
+ no-sd;
+ vmmc-supply = <&reg_3p3v>;
+ vqmmc-supply = <&reg_1p8v>;
+};
+
+&sd {
+ status = "okay";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_sdio_default>;
+ no-sdio;
+ no-mmc;
+ vmmc-supply = <&reg_3p3v>;
+ vqmmc-supply = <&reg_3p3v>;
+};
+
&uart0 {
status = "okay";
// clocks = <&uart_clk>;
diff --git a/arch/arm64/boot/dts/bitmain/bm1880.dtsi b/arch/arm64/boot/dts/bitmain/bm1880.dtsi
index dc574bced19f..6ed25f3e8b81 100644
--- a/arch/arm64/boot/dts/bitmain/bm1880.dtsi
+++ b/arch/arm64/boot/dts/bitmain/bm1880.dtsi
@@ -82,6 +82,34 @@
#interrupt-cells = <3>;
};
+ emmc: sdhci@50100000 {
+ compatible = "bitmain,bm1880-sdhci";
+ reg = <0x0 0x50100000 0x0 0x1000>;
+ clocks = <&clk BM1880_CLK_AXI_EMMC>,
+ <&clk BM1880_CLK_EMMC>,
+ <&clk BM1880_CLK_100K_EMMC>;
+ clock-names = "axi_clk", "periph_clk", "core_clk";
+ interrupts = <GIC_SPI 61 IRQ_TYPE_LEVEL_HIGH>;
+ resets = <&rst BM1880_RST_EMMC>;
+ max-frequency = <125000000>;
+ bus-width = <4>;
+ status = "disabled";
+ };
+
+ sd: sdhci@50101000 {
+ compatible = "bitmain,bm1880-sdhci";
+ reg = <0x0 0x50101000 0x0 0x1000>;
+ clocks = <&clk BM1880_CLK_AXI_SD>,
+ <&clk BM1880_CLK_SD>,
+ <&clk BM1880_CLK_100K_SD>;
+ clock-names = "axi_clk", "periph_clk", "core_clk";
+ interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH>;
+ resets = <&rst BM1880_RST_SD>;
+ max-frequency = <100000000>;
+ bus-width = <4>;
+ status = "disabled";
+ };
+
sctrl: system-controller@50010000 {
compatible = "bitmain,bm1880-sctrl", "syscon",
"simple-mfd";
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 28fcd8f580a1..b6373e49b871 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -1001,3 +1001,16 @@ config MMC_SDHCI_AM654
If you have a controller with this interface, say Y or M here.
If unsure, say N.
+
+config MMC_SDHCI_BITMAIN
+ tristate "Support for Bitmain SDHCI Controller in BM1880 SoCs"
+ depends on ARCH_BITMAIN
+ depends on MMC_SDHCI_PLTFM && OF
+ help
+ This selects the Secure Digital Host Controller Interface (SDHCI)
+ support present in Bitmain BM1880 SOCs. The controller supports
+ SD and MMC devices.
+
+ If you have a controller with this interface, say Y or M here.
+
+ If unsure, say N.
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 73578718f119..8e79a2a1ba89 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -97,6 +97,7 @@ obj-$(CONFIG_MMC_SDHCI_BRCMSTB) += sdhci-brcmstb.o
obj-$(CONFIG_MMC_SDHCI_OMAP) += sdhci-omap.o
obj-$(CONFIG_MMC_SDHCI_SPRD) += sdhci-sprd.o
obj-$(CONFIG_MMC_CQHCI) += cqhci.o
+obj-$(CONFIG_MMC_SDHCI_BITMAIN) += sdhci-bitmain.o
ifeq ($(CONFIG_CB710_DEBUG),y)
CFLAGS-cb710-mmc += -DDEBUG
diff --git a/drivers/mmc/host/sdhci-bitmain.c b/drivers/mmc/host/sdhci-bitmain.c
new file mode 100644
index 000000000000..d1ae803bf820
--- /dev/null
+++ b/drivers/mmc/host/sdhci-bitmain.c
@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Bitmain SDHCI Platform driver
+ *
+ * Copyright (C) 2013-2014, The Linux Foundation.
+ * Copyright (C) 2019 Linaro Ltd.
+ * Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+ */
+
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#include "sdhci-pltfm.h"
+
+/*register macro */
+#define SDHCI_SW_RST_R 0x2F
+#define SDHCI_NORMAL_INT_STATUS 0x30
+#define SDHCI_ERR_INT_STATUS 0x32
+#define SDHCI_ERR_INT_STATUS_EN 0x36
+#define SDHCI_HOST_CTRL2_R 0x3E
+#define SDHCI_MSHC_CTRL 0x508
+#define SDHCI_AT_CTRL 0x540
+#define SDHCI_AT_STAT 0x544
+
+/* PHY register */
+#define SDHCI_PHY_R_OFFSET 0x300
+
+#define SDHCI_P_PHY_CNFG (SDHCI_PHY_R_OFFSET + 0x00)
+#define SDHCI_P_CMDPAD_CNFG (SDHCI_PHY_R_OFFSET + 0x04)
+#define SDHCI_P_DATPAD_CNFG (SDHCI_PHY_R_OFFSET + 0x06)
+#define SDHCI_P_CLKPAD_CNFG (SDHCI_PHY_R_OFFSET + 0x08)
+#define SDHCI_P_STBPAD_CNFG (SDHCI_PHY_R_OFFSET + 0x0A)
+#define SDHCI_P_RSTNPAD_CNFG (SDHCI_PHY_R_OFFSET + 0x0C)
+#define SDHCI_P_PADTEST_CNFG (SDHCI_PHY_R_OFFSET + 0x0E)
+#define SDHCI_P_PADTEST_OUT (SDHCI_PHY_R_OFFSET + 0x10)
+#define SDHCI_P_PADTEST_IN (SDHCI_PHY_R_OFFSET + 0x12)
+#define SDHCI_P_COMMDL_CNFG (SDHCI_PHY_R_OFFSET + 0x1C)
+#define SDHCI_P_SDCLKDL_CNFG (SDHCI_PHY_R_OFFSET + 0x1D)
+#define SDHCI_P_SDCLKDL_DC (SDHCI_PHY_R_OFFSET + 0x1E)
+#define SDHCI_P_SMPLDL_CNFG (SDHCI_PHY_R_OFFSET + 0x20)
+#define SDHCI_P_ATDL_CNFG (SDHCI_PHY_R_OFFSET + 0x21)
+#define SDHCI_P_DLL_CTRL (SDHCI_PHY_R_OFFSET + 0x24)
+#define SDHCI_P_DLL_CNFG1 (SDHCI_PHY_R_OFFSET + 0x25)
+#define SDHCI_P_DLL_CNFG2 (SDHCI_PHY_R_OFFSET + 0x26)
+#define SDHCI_P_DLLDL_CNFG (SDHCI_PHY_R_OFFSET + 0x28)
+#define SDHCI_P_DLL_OFFST (SDHCI_PHY_R_OFFSET + 0x29)
+#define SDHCI_P_DLLMST_TSTDC (SDHCI_PHY_R_OFFSET + 0x2A)
+#define SDHCI_P_DLLLBT_CNFG (SDHCI_PHY_R_OFFSET + 0x2C)
+#define SDHCI_P_DLL_STATUS (SDHCI_PHY_R_OFFSET + 0x2E)
+#define SDHCI_P_DLLDBG_MLKDC (SDHCI_PHY_R_OFFSET + 0x30)
+#define SDHCI_P_DLLDBG_SLKDC (SDHCI_PHY_R_OFFSET + 0x32)
+
+#define PHY_CNFG_PHY_RSTN 0
+#define PHY_CNFG_PHY_PWRGOOD 1
+#define PHY_CNFG_PAD_SP 16
+#define PHY_CNFG_PAD_SP_MSK 0xf
+#define PHY_CNFG_PAD_SN 20
+#define PHY_CNFG_PAD_SN_MSK 0xf
+
+#define PAD_CNFG_RXSEL 0
+#define PAD_CNFG_RXSEL_MSK 0x7
+#define PAD_CNFG_WEAKPULL_EN 3
+#define PAD_CNFG_WEAKPULL_EN_MSK 0x3
+#define PAD_CNFG_TXSLEW_CTRL_P 5
+#define PAD_CNFG_TXSLEW_CTRL_P_MSK 0xf
+#define PAD_CNFG_TXSLEW_CTRL_N 9
+#define PAD_CNFG_TXSLEW_CTRL_N_MSK 0xf
+
+#define SDCLKDL_CNFG_EXTDLY_EN 0
+#define SDCLKDL_CNFG_BYPASS_EN 1
+#define SDCLKDL_CNFG_INPSEL_CNFG 2
+#define SDCLKDL_CNFG_INPSEL_CNFG_MSK 0x3
+#define SDCLKDL_CNFG_UPDATE_DC 4
+
+#define SMPLDL_CNFG_EXTDLY_EN 0
+#define SMPLDL_CNFG_BYPASS_EN 1
+#define SMPLDL_CNFG_INPSEL_CNFG 2
+#define SMPLDL_CNFG_INPSEL_CNFG_MSK 0x3
+#define SMPLDL_CNFG_INPSEL_OVERRIDE 4
+
+#define ATDL_CNFG_EXTDLY_EN 0
+#define ATDL_CNFG_BYPASS_EN 1
+#define ATDL_CNFG_INPSEL_CNFG 2
+#define ATDL_CNFG_INPSEL_CNFG_MSK 0x3
+
+#define BM_SDHCI_VENDOR_OFFSET 0x500
+#define BM_SDHCI_VENDOR_MSHC_CTRL_R (BM_SDHCI_VENDOR_OFFSET + 0x8)
+#define BM_SDHCI_VENDOR_A_CTRL_R (BM_SDHCI_VENDOR_OFFSET + 0x40)
+#define BM_SDHCI_VENDOR_A_STAT_R (BM_SDHCI_VENDOR_OFFSET + 0x44)
+
+#define MAX_TUNING_STEP 128
+#define MAX_EMMC_FREQ 125000000
+#define MAX_SD_FREQ 100000000
+
+#define TO_BITMAIN_HOST(host) sdhci_pltfm_priv(sdhci_priv(host))
+
+struct sdhci_bitmain_host {
+ struct platform_device *pdev;
+ struct mmc_host *mmc;
+ struct reset_control *rst;
+ struct clk *axi_clk;
+ struct clk *periph_clk;
+ struct clk *core_clk;
+ unsigned long rate;
+};
+
+static unsigned int sdhci_bitmain_get_max_clock(struct sdhci_host *host)
+{
+ struct sdhci_bitmain_host *bm_host = TO_BITMAIN_HOST(host);
+
+ return bm_host->rate;
+}
+
+static void sdhci_bitmain_set_uhs_signaling(struct sdhci_host *host,
+ unsigned int uhs)
+{
+ struct mmc_host *mmc = host->mmc;
+ u16 ctrl_2;
+
+ ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ /* Select Bus Speed Mode for host */
+ ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
+ switch (uhs) {
+ case MMC_TIMING_UHS_SDR12:
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
+ break;
+ case MMC_TIMING_UHS_SDR25:
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
+ break;
+ case MMC_TIMING_UHS_SDR50:
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
+ break;
+ case MMC_TIMING_MMC_HS200:
+ case MMC_TIMING_UHS_SDR104:
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
+ break;
+ case MMC_TIMING_UHS_DDR50:
+ case MMC_TIMING_MMC_DDR52:
+ ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
+ break;
+ }
+
+ /*
+ * When clock frequency is less than 100MHz, the feedback clock must be
+ * provided and DLL must not be used so that tuning can be skipped. To
+ * provide feedback clock, the mode selection can be any value less
+ * than 3'b011 in bits [2:0] of HOST CONTROL2 register.
+ */
+ if (host->clock <= 100000000 &&
+ (uhs == MMC_TIMING_MMC_HS400 ||
+ uhs == MMC_TIMING_MMC_HS200 ||
+ uhs == MMC_TIMING_UHS_SDR104))
+ ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
+
+ dev_dbg(mmc_dev(mmc), "%s: clock=%u uhs=%u ctrl_2=0x%x\n",
+ mmc_hostname(host->mmc), host->clock, uhs, ctrl_2);
+ sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+}
+
+static void bm_sdhci_set_tap(struct sdhci_host *host, unsigned int tap)
+{
+ sdhci_writel(host, 0x0, BM_SDHCI_VENDOR_MSHC_CTRL_R);
+ sdhci_writel(host, 0x18, BM_SDHCI_VENDOR_A_CTRL_R);
+ sdhci_writel(host, tap, BM_SDHCI_VENDOR_A_STAT_R);
+}
+
+static void sdhci_bitmain_set_tap(struct sdhci_host *host, unsigned int tap)
+{
+ sdhci_writel(host, tap, BM_SDHCI_VENDOR_A_STAT_R);
+}
+
+static void sdhci_bitmain_reset_after_tuning_pass(struct sdhci_host *host)
+{
+ /* Clear BUF_RD_READY intr */
+ sdhci_writew(host, sdhci_readw(host, SDHCI_NORMAL_INT_STATUS) & (~(0x1<<5)),
+ SDHCI_NORMAL_INT_STATUS);
+
+ /* Set SDHCI_SW_RST_R.SW_RST_DAT = 1 to clear buffered tuning block */
+ sdhci_writeb(host, sdhci_readb(host, SDHCI_SW_RST_R) | (0x1<<2), SDHCI_SW_RST_R);
+
+ /* Set SDHCI_SW_RST_R.SW_RST_CMD = 1 */
+ sdhci_writeb(host, sdhci_readb(host, SDHCI_SW_RST_R) | (0x1<<1), SDHCI_SW_RST_R);
+
+ while (sdhci_readb(host, SDHCI_SW_RST_R) & 0x3);
+}
+
+#define TAP_MAX_VALUE_BM1880 7
+static int sdhci_bitmain_execute_tuning(struct sdhci_host *host, u32 opcode)
+{
+ unsigned int min, max;
+ uint32_t reg = 0;
+
+ sdhci_writel(host, 0x0, BM_SDHCI_VENDOR_MSHC_CTRL_R); // ??
+
+ reg = sdhci_readw(host, SDHCI_ERR_INT_STATUS);
+
+ reg = sdhci_readw(host, SDHCI_HOST_CTRL2_R);
+ /* Set Host_CTRL2_R.SAMPLE_CLK_SEL=0 */
+ sdhci_writew(host, sdhci_readw(host, SDHCI_HOST_CTRL2_R) & (~(0x1<<7)), SDHCI_HOST_CTRL2_R);
+ sdhci_writew(host, sdhci_readw(host, SDHCI_HOST_CTRL2_R) & (~(0x3<<4)), SDHCI_HOST_CTRL2_R);
+
+ reg = sdhci_readw(host, SDHCI_HOST_CTRL2_R);
+
+ /* Set ATR_CTRL_R.SW_TNE_EN=1 */
+ reg = sdhci_readl(host, BM_SDHCI_VENDOR_A_CTRL_R);
+ sdhci_writel(host, sdhci_readl(host, BM_SDHCI_VENDOR_A_CTRL_R) | (0x1<<4), BM_SDHCI_VENDOR_A_CTRL_R);
+ reg = sdhci_readl(host, BM_SDHCI_VENDOR_A_CTRL_R);
+
+ /*
+ * Start search for minimum tap value at 10, as smaller values are
+ * may wrongly be reported as working but fail at higher speeds,
+ * according to the TRM.
+ */
+ min = 0;
+ while (min <= TAP_MAX_VALUE_BM1880) {
+ sdhci_bitmain_set_tap(host, min);
+ if (!mmc_send_tuning(host->mmc, opcode, NULL)) {
+ sdhci_bitmain_reset_after_tuning_pass(host);
+ break;
+ }
+
+ min++;
+ }
+
+ WARN_ON(min > TAP_MAX_VALUE_BM1880);
+
+ /* Find the maximum tap value that still passes. */
+ max = min + 1;
+ while (max <= TAP_MAX_VALUE_BM1880) {
+ sdhci_bitmain_set_tap(host, max);
+ if (mmc_send_tuning(host->mmc, opcode, NULL)) {
+ max--;
+ break;
+ }
+
+ sdhci_bitmain_reset_after_tuning_pass(host);
+ max++;
+ }
+
+ if (max > TAP_MAX_VALUE_BM1880)
+ max = TAP_MAX_VALUE_BM1880;
+
+
+ /* The TRM states the ideal tap value is at center in the passing range. */
+ sdhci_bitmain_set_tap(host, min + ((max - min) / 2));
+
+ return mmc_send_tuning(host->mmc, opcode, NULL);
+}
+
+static void sdhci_bitmain_reset(struct sdhci_host *host)
+{
+ struct sdhci_bitmain_host *bm_host = TO_BITMAIN_HOST(host);
+
+ sdhci_writeb(host, 0x10, SDHCI_P_SDCLKDL_DC);
+ // revert rx
+ bm_sdhci_set_tap(host, 0x00);
+
+ reset_control_assert(bm_host->rst);
+ udelay(10);
+ reset_control_deassert(bm_host->rst);
+}
+
+static struct sdhci_ops sdhci_bitmain_ops = {
+ .reset = sdhci_reset,
+ .hw_reset = sdhci_bitmain_reset,
+ .set_clock = sdhci_set_clock,
+ .get_max_clock = sdhci_bitmain_get_max_clock,
+ .set_bus_width = sdhci_set_bus_width,
+ .set_uhs_signaling = sdhci_bitmain_set_uhs_signaling,
+ .platform_execute_tuning = sdhci_bitmain_execute_tuning,
+};
+
+static const struct sdhci_pltfm_data sdhci_bitmain_pdata = {
+ .quirks = SDHCI_QUIRK_INVERTED_WRITE_PROTECT |
+ SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
+ .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
+ .ops = &sdhci_bitmain_ops,
+};
+
+static int sdhci_bitmain_probe(struct platform_device *pdev)
+{
+ struct sdhci_host *host;
+ struct device *dev = &pdev->dev;
+ struct sdhci_bitmain_host *bm_host;
+ u32 reg;
+ int ret;
+
+ host = sdhci_pltfm_init(pdev, &sdhci_bitmain_pdata, sizeof(*bm_host));
+ if (IS_ERR(host))
+ return PTR_ERR(host);
+
+ host->dma_mask = DMA_BIT_MASK(64);
+ pdev->dev.dma_mask = &host->dma_mask;
+
+ bm_host = TO_BITMAIN_HOST(host);
+
+ ret = mmc_of_parse(host->mmc);
+ if (ret)
+ goto pltfm_free;
+
+ if (host->mmc->caps2 & MMC_CAP2_NO_MMC)
+ bm_host->rate = MAX_SD_FREQ;
+ else
+ bm_host->rate = MAX_EMMC_FREQ;
+
+ bm_host->axi_clk = devm_clk_get(&pdev->dev, "axi_clk");
+ if (IS_ERR(bm_host->axi_clk)) {
+ ret = PTR_ERR(bm_host->axi_clk);
+ goto pltfm_free;
+ }
+
+ bm_host->periph_clk = devm_clk_get(&pdev->dev, "periph_clk");
+ if (IS_ERR(bm_host->periph_clk)) {
+ ret = PTR_ERR(bm_host->periph_clk);
+ goto pltfm_free;
+ }
+
+ bm_host->core_clk = devm_clk_get(&pdev->dev, "core_clk");
+ if (IS_ERR(bm_host->core_clk)) {
+ ret = PTR_ERR(bm_host->core_clk);
+ goto pltfm_free;
+ }
+
+ ret = clk_prepare_enable(bm_host->axi_clk);
+ if (ret)
+ goto pltfm_free;
+
+ ret = clk_prepare_enable(bm_host->periph_clk);
+ if (ret)
+ goto disable_axi_clk;
+
+ ret = clk_prepare_enable(bm_host->core_clk);
+ if (ret)
+ goto disable_periph_clk;
+
+ bm_host->rst = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(bm_host->rst)) {
+ dev_err(dev, "failed to get reset\n");
+ ret = PTR_ERR(bm_host->rst);
+ goto disable_core_clk;
+ }
+
+ /* PHY config */
+ reg = (1 << PHY_CNFG_PHY_PWRGOOD) | (0xe << PHY_CNFG_PAD_SP) |
+ (0xe << PHY_CNFG_PAD_SN) | (1 << PHY_CNFG_PHY_RSTN);
+ sdhci_writel(host, reg, SDHCI_P_PHY_CNFG);
+
+ ret = sdhci_add_host(host);
+ if (ret)
+ goto cleanup_host;
+
+ return 0;
+
+cleanup_host:
+ sdhci_cleanup_host(host);
+disable_core_clk:
+ clk_disable_unprepare(bm_host->core_clk);
+disable_axi_clk:
+ clk_disable_unprepare(bm_host->axi_clk);
+disable_periph_clk:
+ clk_disable_unprepare(bm_host->periph_clk);
+pltfm_free:
+ sdhci_pltfm_free(pdev);
+
+ return ret;
+}
+
+static int sdhci_bitmain_remove(struct platform_device *pdev)
+{
+ struct sdhci_host *host = platform_get_drvdata(pdev);
+ struct sdhci_bitmain_host *bm_host = TO_BITMAIN_HOST(host);
+
+ clk_disable_unprepare(bm_host->axi_clk);
+ clk_disable_unprepare(bm_host->periph_clk);
+ clk_disable_unprepare(bm_host->core_clk);
+ sdhci_pltfm_free(pdev);
+
+ return 0;
+}
+
+static const struct of_device_id sdhci_bitmain_of_match[] = {
+ { .compatible = "bitmain,bm1880-sdhci", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sdhci_bitmain_of_match);
+
+static struct platform_driver sdhci_bitmain_driver = {
+ .probe = sdhci_bitmain_probe,
+ .remove = sdhci_bitmain_remove,
+ .driver = {
+ .name = "sdhci_bitmain",
+ .of_match_table = of_match_ptr(sdhci_bitmain_of_match),
+ },
+};
+module_platform_driver(sdhci_bitmain_driver);
+
+MODULE_DESCRIPTION("Bitmain SDHCI platform driver");
+MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>");
+MODULE_LICENSE("GPL v2");