diff options
author | Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> | 2019-05-14 22:34:50 +0530 |
---|---|---|
committer | Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> | 2019-05-15 19:09:00 +0530 |
commit | 3cd219061add4a4ab8cfe4d370683af5703f2a9b (patch) | |
tree | f4d4093c5e7c1b331778de0508c5befb7786121a | |
parent | 73ece2e0f7c7f2ab1373e652ea5d34b9f607fe73 (diff) | |
download | 96b-common-bm1880-mmc.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.dts | 51 | ||||
-rw-r--r-- | arch/arm64/boot/dts/bitmain/bm1880.dtsi | 28 | ||||
-rw-r--r-- | drivers/mmc/host/Kconfig | 13 | ||||
-rw-r--r-- | drivers/mmc/host/Makefile | 1 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci-bitmain.c | 406 |
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 = <®_3p3v>; + vqmmc-supply = <®_1p8v>; +}; + +&sd { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_sdio_default>; + no-sdio; + no-mmc; + vmmc-supply = <®_3p3v>; + vqmmc-supply = <®_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"); |