Message ID | 1481201455-3483-3-git-send-email-yamada.masahiro@socionext.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Thu, Dec 08, 2016 at 09:50:55PM +0900, Masahiro Yamada wrote: > Add a driver for the Cadence SD4HC SD/SDIO/eMMC Controller. > > For SD, it basically relies on the SDHCI standard code. > For eMMC, this driver provides some callbacks to support the > hardware part that is specific to this IP design. > > Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com> > Acked-by: Adrian Hunter <adrian.hunter@intel.com> > --- > > Changes in v5: > - Fix calculation of the center of the tuned window > > Changes in v4: > - Override mmc_host_ops.execute_tuning instead of the > .platform_execute_tuning implementation > > Changes in v3: > - Remove unneeded explanation about HRS and SRS from DT binding; > the offsets to HRS/SRS are fixed for this hardware and this is > quite normal, like each hardware has a fixed register view except > the register base. The detailed register map is what the driver > cares about, so no need to explain it in the binding. > > Changes in v2: > - Remove unnecessary "select MMC_SDHCI_IO_ACCESSORS" > > .../devicetree/bindings/mmc/sdhci-cadence.txt | 30 +++ > drivers/mmc/host/Kconfig | 11 + > drivers/mmc/host/Makefile | 1 + > drivers/mmc/host/sdhci-cadence.c | 283 +++++++++++++++++++++ > 4 files changed, 325 insertions(+) > create mode 100644 Documentation/devicetree/bindings/mmc/sdhci-cadence.txt > create mode 100644 drivers/mmc/host/sdhci-cadence.c > > diff --git a/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt b/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt > new file mode 100644 > index 0000000..750374f > --- /dev/null > +++ b/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt > @@ -0,0 +1,30 @@ > +* Cadence SD/SDIO/eMMC Host Controller > + > +Required properties: > +- compatible: should be "cdns,sd4hc". Needs SoC specific compatible strings too. > +- reg: offset and length of the register set for the device. > +- interrupts: a single interrupt specifier. > +- clocks: phandle to the input clock. > + > +Optional properties: > +For eMMC configuration, supported speed modes are not indicated by the SDHCI > +Capabilities Register. Instead, the following properties should be specified > +if supported. See mmc.txt for details. > +- mmc-ddr-1_8v > +- mmc-ddr-1_2v > +- mmc-hs200-1_8v > +- mmc-hs200-1_2v > +- mmc-hs400-1_8v > +- mmc-hs400-1_2v There's now a property to override SDHCI capabilities register. Maybe you should use that instead? I'll defer to Ulf. > + > +Example: > + emmc: sdhci@5a000000 { > + compatible = "cdns,sd4hc"; > + reg = <0x5a000000 0x400>; > + interrupts = <0 78 4>; > + clocks = <&clk 4>; > + bus-width = <8>; > + mmc-ddr-1_8v; > + mmc-hs200-1_8v; > + mmc-hs400-1_8v; > + }; -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi Rob. 2016-12-13 2:14 GMT+09:00 Rob Herring <robh@kernel.org>: >> diff --git a/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt b/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt >> new file mode 100644 >> index 0000000..750374f >> --- /dev/null >> +++ b/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt >> @@ -0,0 +1,30 @@ >> +* Cadence SD/SDIO/eMMC Host Controller >> + >> +Required properties: >> +- compatible: should be "cdns,sd4hc". > > Needs SoC specific compatible strings too. I remember you mentioned the vendor prefix stands for the SoC vendor, not the IP vendor. The compatible prefixed with the IP vendor is prepared for a fallback. I will add "socionext,sd4hc". >> +- reg: offset and length of the register set for the device. >> +- interrupts: a single interrupt specifier. >> +- clocks: phandle to the input clock. >> + >> +Optional properties: >> +For eMMC configuration, supported speed modes are not indicated by the SDHCI >> +Capabilities Register. Instead, the following properties should be specified >> +if supported. See mmc.txt for details. >> +- mmc-ddr-1_8v >> +- mmc-ddr-1_2v >> +- mmc-hs200-1_8v >> +- mmc-hs200-1_2v >> +- mmc-hs400-1_8v >> +- mmc-hs400-1_2v > > There's now a property to override SDHCI capabilities register. Maybe > you should use that instead? I'll defer to Ulf. > I did not know this new property. So, now we have two ways to specify MMC speed mode capabilities by only touching DT. [1] Add MMC mode flags directly, like I did. [2] Use "sdhci-caps-mask" and "sdhci-caps" The problem for [2] is that eMMC capabilities do not perfectly correspond to the SDHCI capabilities register. >> +- mmc-hs400-1_8v >> +- mmc-hs400-1_2v If the driver sets SDHCI_QUIRK2_CAPS_BIT63_FOR_HS400, we can use the bit63 of caps for specifying HS400. But, this is not defined in the SDHCI standard. #define SDHCI_SUPPORT_HS400 0x80000000 /* Non-standard */ >> +- mmc-ddr-1_8v For High Speed DDR, perhaps we can imply MMC_CAP_1_8V_DDR from MMC_CAP_UHS_DDR50 (bit34 of caps) This is not supported in the current code, but if this is a good idea, I can send a patch. >> +- mmc-ddr-1_2v This does not have the corresponding bit, but 1.2V is not commonly used, so this is not a fatal problem. What I can do at most now, is to delete the Optional properties section entirely so users can choose [1] or [2] as they like.
[...] >>> + >>> +Optional properties: >>> +For eMMC configuration, supported speed modes are not indicated by the SDHCI >>> +Capabilities Register. Instead, the following properties should be specified >>> +if supported. See mmc.txt for details. >>> +- mmc-ddr-1_8v >>> +- mmc-ddr-1_2v >>> +- mmc-hs200-1_8v >>> +- mmc-hs200-1_2v >>> +- mmc-hs400-1_8v >>> +- mmc-hs400-1_2v >> >> There's now a property to override SDHCI capabilities register. Maybe >> you should use that instead? I'll defer to Ulf. >> > > I did not know this new property. > > So, now we have two ways to specify MMC speed mode capabilities > by only touching DT. Let me clarify a bit. The point with the new bindings is to be able to override *broken* SDHCI caps bits. So, not only related to the speed modes. > > [1] Add MMC mode flags directly, like I did. > [2] Use "sdhci-caps-mask" and "sdhci-caps" > > > The problem for [2] is that eMMC capabilities > do not perfectly correspond to the SDHCI capabilities register. > > >>> +- mmc-hs400-1_8v >>> +- mmc-hs400-1_2v > > If the driver sets SDHCI_QUIRK2_CAPS_BIT63_FOR_HS400, > we can use the bit63 of caps for specifying HS400. > > But, this is not defined in the SDHCI standard. > #define SDHCI_SUPPORT_HS400 0x80000000 /* Non-standard */ > > > >>> +- mmc-ddr-1_8v > > For High Speed DDR, perhaps we can imply MMC_CAP_1_8V_DDR > from MMC_CAP_UHS_DDR50 (bit34 of caps) > > This is not supported in the current code, but > if this is a good idea, I can send a patch. > > >>> +- mmc-ddr-1_2v > > This does not have the corresponding bit, but > 1.2V is not commonly used, so this is not a fatal problem. > > > > What I can do at most now, is to delete the > Optional properties section entirely > so users can choose [1] or [2] as they like. > I think a better approach is to use the new sdhci-caps* bindings to mask those caps that can't be trusted. And then use the generic mmc bindings for speed modes instead. That should be a safer approach, right!? Kind regards Uffe -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi Ulf, 2016-12-13 16:52 GMT+09:00 Ulf Hansson <ulf.hansson@linaro.org>: > [...] > >>>> + >>>> +Optional properties: >>>> +For eMMC configuration, supported speed modes are not indicated by the SDHCI >>>> +Capabilities Register. Instead, the following properties should be specified >>>> +if supported. See mmc.txt for details. >>>> +- mmc-ddr-1_8v >>>> +- mmc-ddr-1_2v >>>> +- mmc-hs200-1_8v >>>> +- mmc-hs200-1_2v >>>> +- mmc-hs400-1_8v >>>> +- mmc-hs400-1_2v >>> >>> There's now a property to override SDHCI capabilities register. Maybe >>> you should use that instead? I'll defer to Ulf. >>> >> >> I did not know this new property. >> >> So, now we have two ways to specify MMC speed mode capabilities >> by only touching DT. > > Let me clarify a bit. > > The point with the new bindings is to be able to override *broken* > SDHCI caps bits. So, not only related to the speed modes. Right. So my approach ([1] Add MMC mode flags directly) basically sounds OK. >> >> [1] Add MMC mode flags directly, like I did. >> [2] Use "sdhci-caps-mask" and "sdhci-caps" >> >> >> The problem for [2] is that eMMC capabilities >> do not perfectly correspond to the SDHCI capabilities register. >> >> >>>> +- mmc-hs400-1_8v >>>> +- mmc-hs400-1_2v >> >> If the driver sets SDHCI_QUIRK2_CAPS_BIT63_FOR_HS400, >> we can use the bit63 of caps for specifying HS400. >> >> But, this is not defined in the SDHCI standard. >> #define SDHCI_SUPPORT_HS400 0x80000000 /* Non-standard */ >> >> >> >>>> +- mmc-ddr-1_8v >> >> For High Speed DDR, perhaps we can imply MMC_CAP_1_8V_DDR >> from MMC_CAP_UHS_DDR50 (bit34 of caps) >> >> This is not supported in the current code, but >> if this is a good idea, I can send a patch. >> >> >>>> +- mmc-ddr-1_2v >> >> This does not have the corresponding bit, but >> 1.2V is not commonly used, so this is not a fatal problem. >> >> >> >> What I can do at most now, is to delete the >> Optional properties section entirely >> so users can choose [1] or [2] as they like. >> > > I think a better approach is to use the new sdhci-caps* bindings to > mask those caps that can't be trusted. And then use the generic mmc > bindings for speed modes instead. > > That should be a safer approach, right!? Right. But, if I know the caps register bits 63-32 are all zero, I need not explicitly specify sdhci-caps-mask, right?
[...] >> >> I think a better approach is to use the new sdhci-caps* bindings to >> mask those caps that can't be trusted. And then use the generic mmc >> bindings for speed modes instead. >> >> That should be a safer approach, right!? > > Right. > > But, if I know the caps register bits 63-32 are all zero, > I need not explicitly specify sdhci-caps-mask, right? Maybe not. I don't have a strong opinion here, so l am fine with whatever you choose. Kind regards Uffe -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt b/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt new file mode 100644 index 0000000..750374f --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/sdhci-cadence.txt @@ -0,0 +1,30 @@ +* Cadence SD/SDIO/eMMC Host Controller + +Required properties: +- compatible: should be "cdns,sd4hc". +- reg: offset and length of the register set for the device. +- interrupts: a single interrupt specifier. +- clocks: phandle to the input clock. + +Optional properties: +For eMMC configuration, supported speed modes are not indicated by the SDHCI +Capabilities Register. Instead, the following properties should be specified +if supported. See mmc.txt for details. +- mmc-ddr-1_8v +- mmc-ddr-1_2v +- mmc-hs200-1_8v +- mmc-hs200-1_2v +- mmc-hs400-1_8v +- mmc-hs400-1_2v + +Example: + emmc: sdhci@5a000000 { + compatible = "cdns,sd4hc"; + reg = <0x5a000000 0x400>; + interrupts = <0 78 4>; + clocks = <&clk 4>; + bus-width = <8>; + mmc-ddr-1_8v; + mmc-hs200-1_8v; + mmc-hs400-1_8v; + }; diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index ab9181e..8ac1640 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -164,6 +164,17 @@ config MMC_SDHCI_OF_HLWD If unsure, say N. +config MMC_SDHCI_CADENCE + tristate "SDHCI support for the Cadence SD/SDIO/eMMC controller" + depends on MMC_SDHCI_PLTFM + depends on OF + help + This selects the Cadence SD/SDIO/eMMC driver. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + config MMC_SDHCI_CNS3XXX tristate "SDHCI support on the Cavium Networks CNS3xxx SoC" depends on ARCH_CNS3XXX diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index e49a82a..55f7193 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -63,6 +63,7 @@ obj-$(CONFIG_MMC_REALTEK_PCI) += rtsx_pci_sdmmc.o obj-$(CONFIG_MMC_REALTEK_USB) += rtsx_usb_sdmmc.o obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o +obj-$(CONFIG_MMC_SDHCI_CADENCE) += sdhci-cadence.o obj-$(CONFIG_MMC_SDHCI_CNS3XXX) += sdhci-cns3xxx.o obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o obj-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c new file mode 100644 index 0000000..1501cfd --- /dev/null +++ b/drivers/mmc/host/sdhci-cadence.c @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2016 Socionext Inc. + * Author: Masahiro Yamada <yamada.masahiro@socionext.com> + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include <linux/bitops.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/mmc/host.h> + +#include "sdhci-pltfm.h" + +/* HRS - Host Register Set (specific to Cadence) */ +#define SDHCI_CDNS_HRS04 0x10 /* PHY access port */ +#define SDHCI_CDNS_HRS04_ACK BIT(26) +#define SDHCI_CDNS_HRS04_RD BIT(25) +#define SDHCI_CDNS_HRS04_WR BIT(24) +#define SDHCI_CDNS_HRS04_RDATA_SHIFT 12 +#define SDHCI_CDNS_HRS04_WDATA_SHIFT 8 +#define SDHCI_CDNS_HRS04_ADDR_SHIFT 0 + +#define SDHCI_CDNS_HRS06 0x18 /* eMMC control */ +#define SDHCI_CDNS_HRS06_TUNE_UP BIT(15) +#define SDHCI_CDNS_HRS06_TUNE_SHIFT 8 +#define SDHCI_CDNS_HRS06_TUNE_MASK 0x3f +#define SDHCI_CDNS_HRS06_MODE_MASK 0x7 +#define SDHCI_CDNS_HRS06_MODE_SD 0x0 +#define SDHCI_CDNS_HRS06_MODE_MMC_SDR 0x2 +#define SDHCI_CDNS_HRS06_MODE_MMC_DDR 0x3 +#define SDHCI_CDNS_HRS06_MODE_MMC_HS200 0x4 +#define SDHCI_CDNS_HRS06_MODE_MMC_HS400 0x5 + +/* SRS - Slot Register Set (SDHCI-compatible) */ +#define SDHCI_CDNS_SRS_BASE 0x200 + +/* PHY */ +#define SDHCI_CDNS_PHY_DLY_SD_HS 0x00 +#define SDHCI_CDNS_PHY_DLY_SD_DEFAULT 0x01 +#define SDHCI_CDNS_PHY_DLY_UHS_SDR12 0x02 +#define SDHCI_CDNS_PHY_DLY_UHS_SDR25 0x03 +#define SDHCI_CDNS_PHY_DLY_UHS_SDR50 0x04 +#define SDHCI_CDNS_PHY_DLY_UHS_DDR50 0x05 +#define SDHCI_CDNS_PHY_DLY_EMMC_LEGACY 0x06 +#define SDHCI_CDNS_PHY_DLY_EMMC_SDR 0x07 +#define SDHCI_CDNS_PHY_DLY_EMMC_DDR 0x08 + +/* + * The tuned val register is 6 bit-wide, but not the whole of the range is + * available. The range 0-42 seems to be available (then 43 wraps around to 0) + * but I am not quite sure if it is official. Use only 0 to 39 for safety. + */ +#define SDHCI_CDNS_MAX_TUNING_LOOP 40 + +struct sdhci_cdns_priv { + void __iomem *hrs_addr; +}; + +static void sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv, + u8 addr, u8 data) +{ + void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04; + u32 tmp; + + tmp = (data << SDHCI_CDNS_HRS04_WDATA_SHIFT) | + (addr << SDHCI_CDNS_HRS04_ADDR_SHIFT); + writel(tmp, reg); + + tmp |= SDHCI_CDNS_HRS04_WR; + writel(tmp, reg); + + tmp &= ~SDHCI_CDNS_HRS04_WR; + writel(tmp, reg); +} + +static void sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv) +{ + sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_SD_HS, 4); + sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_SD_DEFAULT, 4); + sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_EMMC_LEGACY, 9); + sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_EMMC_SDR, 2); + sdhci_cdns_write_phy_reg(priv, SDHCI_CDNS_PHY_DLY_EMMC_DDR, 3); +} + +static inline void *sdhci_cdns_priv(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + + return sdhci_pltfm_priv(pltfm_host); +} + +static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host) +{ + /* + * Cadence's spec says the Timeout Clock Frequency is the same as the + * Base Clock Frequency. Divide it by 1000 to return a value in kHz. + */ + return host->max_clk / 1000; +} + +static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host, + unsigned int timing) +{ + struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); + u32 mode, tmp; + + switch (timing) { + case MMC_TIMING_MMC_HS: + mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR; + break; + case MMC_TIMING_MMC_DDR52: + mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR; + break; + case MMC_TIMING_MMC_HS200: + mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200; + break; + case MMC_TIMING_MMC_HS400: + mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400; + break; + default: + mode = SDHCI_CDNS_HRS06_MODE_SD; + break; + } + + /* The speed mode for eMMC is selected by HRS06 register */ + tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06); + tmp &= ~SDHCI_CDNS_HRS06_MODE_MASK; + tmp |= mode; + writel(tmp, priv->hrs_addr + SDHCI_CDNS_HRS06); + + /* For SD, fall back to the default handler */ + if (mode == SDHCI_CDNS_HRS06_MODE_SD) + sdhci_set_uhs_signaling(host, timing); +} + +static const struct sdhci_ops sdhci_cdns_ops = { + .set_clock = sdhci_set_clock, + .get_timeout_clock = sdhci_cdns_get_timeout_clock, + .set_bus_width = sdhci_set_bus_width, + .reset = sdhci_reset, + .set_uhs_signaling = sdhci_cdns_set_uhs_signaling, +}; + +static const struct sdhci_pltfm_data sdhci_cdns_pltfm_data = { + .ops = &sdhci_cdns_ops, +}; + +static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val) +{ + struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); + void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS06; + u32 tmp; + + if (WARN_ON(val > SDHCI_CDNS_HRS06_TUNE_MASK)) + return -EINVAL; + + tmp = readl(reg); + tmp &= ~(SDHCI_CDNS_HRS06_TUNE_MASK << SDHCI_CDNS_HRS06_TUNE_SHIFT); + tmp |= val << SDHCI_CDNS_HRS06_TUNE_SHIFT; + tmp |= SDHCI_CDNS_HRS06_TUNE_UP; + writel(tmp, reg); + + return readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_HRS06_TUNE_UP), + 0, 1); +} + +static int sdhci_cdns_execute_tuning(struct mmc_host *mmc, u32 opcode) +{ + struct sdhci_host *host = mmc_priv(mmc); + int cur_streak = 0; + int max_streak = 0; + int end_of_streak = 0; + int i; + + /* + * This handler only implements the eMMC tuning that is specific to + * this controller. Fall back to the standard method for SD timing. + */ + if (host->timing != MMC_TIMING_MMC_HS200) + return sdhci_execute_tuning(mmc, opcode); + + if (WARN_ON(opcode != MMC_SEND_TUNING_BLOCK_HS200)) + return -EINVAL; + + for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) { + if (sdhci_cdns_set_tune_val(host, i) || + mmc_send_tuning(host->mmc, opcode, NULL)) { /* bad */ + cur_streak = 0; + } else { /* good */ + cur_streak++; + if (cur_streak > max_streak) { + max_streak = cur_streak; + end_of_streak = i; + } + } + } + + if (!max_streak) { + dev_err(mmc_dev(host->mmc), "no tuning point found\n"); + return -EIO; + } + + return sdhci_cdns_set_tune_val(host, end_of_streak - max_streak / 2); +} + +static int sdhci_cdns_probe(struct platform_device *pdev) +{ + struct sdhci_host *host; + struct sdhci_pltfm_host *pltfm_host; + struct sdhci_cdns_priv *priv; + struct clk *clk; + int ret; + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ret = clk_prepare_enable(clk); + if (ret) + return ret; + + host = sdhci_pltfm_init(pdev, &sdhci_cdns_pltfm_data, sizeof(*priv)); + if (IS_ERR(host)) { + ret = PTR_ERR(host); + goto disable_clk; + } + + pltfm_host = sdhci_priv(host); + pltfm_host->clk = clk; + + priv = sdhci_cdns_priv(host); + priv->hrs_addr = host->ioaddr; + host->ioaddr += SDHCI_CDNS_SRS_BASE; + host->mmc_host_ops.execute_tuning = sdhci_cdns_execute_tuning; + + ret = mmc_of_parse(host->mmc); + if (ret) + goto free; + + sdhci_cdns_phy_init(priv); + + ret = sdhci_add_host(host); + if (ret) + goto free; + + return 0; +free: + sdhci_pltfm_free(pdev); +disable_clk: + clk_disable_unprepare(clk); + + return ret; +} + +static const struct of_device_id sdhci_cdns_match[] = { + { .compatible = "cdns,sd4hc" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sdhci_cdns_match); + +static struct platform_driver sdhci_cdns_driver = { + .driver = { + .name = "sdhci-cdns", + .pm = &sdhci_pltfm_pmops, + .of_match_table = sdhci_cdns_match, + }, + .probe = sdhci_cdns_probe, + .remove = sdhci_pltfm_unregister, +}; +module_platform_driver(sdhci_cdns_driver); + +MODULE_AUTHOR("Masahiro Yamada <yamada.masahiro@socionext.com>"); +MODULE_DESCRIPTION("Cadence SD/SDIO/eMMC Host Controller Driver"); +MODULE_LICENSE("GPL");