On 07/10/16 18:22, Gregory CLEMENT wrote: > From: Ziji Hu <huziji@xxxxxxxxxxx> > > Add Xenon eMMC/SD/SDIO host controller core functionality. > Add Xenon specific intialization process. > Add Xenon specific mmc_host_ops APIs. > Add Xenon specific register definitions. > > Add CONFIG_MMC_SDHCI_XENON support in drivers/mmc/host/Kconfig. > > Marvell Xenon SDHC conforms to SD Physical Layer Specification > Version 3.01 and is designed according to the guidelines provided > in the SD Host Controller Standard Specification Version 3.00. > > Signed-off-by: Hu Ziji <huziji@xxxxxxxxxxx> > Reviewed-by: Gregory CLEMENT <gregory.clement@xxxxxxxxxxxxxxxxxx> > Signed-off-by: Gregory CLEMENT <gregory.clement@xxxxxxxxxxxxxxxxxx> I looked at a couple of things but you need to sort out the issues with card_candidate before going further. > --- > MAINTAINERS | 1 +- > drivers/mmc/host/Kconfig | 9 +- > drivers/mmc/host/Makefile | 3 +- > drivers/mmc/host/sdhci-xenon.c | 599 ++++++++++++++++++++++++++++++++++- > drivers/mmc/host/sdhci-xenon.h | 134 ++++++++- > 5 files changed, 746 insertions(+), 0 deletions(-) > create mode 100644 drivers/mmc/host/sdhci-xenon.c > create mode 100644 drivers/mmc/host/sdhci-xenon.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index 4aa0eac9bfc7..859420e5dfd3 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -7582,6 +7582,7 @@ MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER > M: Ziji Hu <huziji@xxxxxxxxxxx> > L: linux-mmc@xxxxxxxxxxxxxxx > S: Supported > +F: drivers/mmc/host/sdhci-xenon.* > F: Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt > > MATROX FRAMEBUFFER DRIVER > diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig > index 5274f503a39a..85a53623526a 100644 > --- a/drivers/mmc/host/Kconfig > +++ b/drivers/mmc/host/Kconfig > @@ -798,3 +798,12 @@ config MMC_SDHCI_BRCMSTB > Broadcom STB SoCs. > > If unsure, say Y. > + > +config MMC_SDHCI_XENON > + tristate "Marvell Xenon eMMC/SD/SDIO SDHCI driver" > + depends on MMC_SDHCI && MMC_SDHCI_PLTFM > + help > + This selects Marvell Xenon eMMC/SD/SDIO SDHCI. > + If you have a machine with integrated Marvell Xenon SDHC IP, > + say Y or M here. > + If unsure, say N. > diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile > index e2bdaaf43184..75eaf743486c 100644 > --- a/drivers/mmc/host/Makefile > +++ b/drivers/mmc/host/Makefile > @@ -80,3 +80,6 @@ obj-$(CONFIG_MMC_SDHCI_BRCMSTB) += sdhci-brcmstb.o > ifeq ($(CONFIG_CB710_DEBUG),y) > CFLAGS-cb710-mmc += -DDEBUG > endif > + > +obj-$(CONFIG_MMC_SDHCI_XENON) += sdhci-xenon-driver.o > +sdhci-xenon-driver-y += sdhci-xenon.o > diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c > new file mode 100644 > index 000000000000..03ba183494d3 > --- /dev/null > +++ b/drivers/mmc/host/sdhci-xenon.c > @@ -0,0 +1,599 @@ > +/* > + * Driver for Marvell SOC Platform Group Xenon SDHC as a platform device > + * > + * Copyright (C) 2016 Marvell, All Rights Reserved. > + * > + * Author: Hu Ziji <huziji@xxxxxxxxxxx> > + * Date: 2016-8-24 > + * > + * 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. > + * > + * Inspired by Jisheng Zhang <jszhang@xxxxxxxxxxx> > + * Special thanks to Video BG4 project team. > + */ > + > +#include <linux/delay.h> > +#include <linux/err.h> > +#include <linux/io.h> > +#include <linux/mmc/host.h> > +#include <linux/mmc/mmc.h> > +#include <linux/mmc/sdio.h> > +#include <linux/mmc/card.h> > +#include <linux/mmc/host.h> > +#include <linux/module.h> > +#include <linux/of.h> > + > +#include "sdhci-pltfm.h" > +#include "sdhci.h" > +#include "sdhci-xenon.h" > + > +/* Set SDCLK-off-while-idle */ > +static void xenon_set_sdclk_off_idle(struct sdhci_host *host, > + unsigned char slot_idx, bool enable) > +{ > + u32 reg; > + u32 mask; > + > + reg = sdhci_readl(host, SDHC_SYS_OP_CTRL); > + /* Get the bit shift basing on the slot index */ > + mask = (0x1 << (SDCLK_IDLEOFF_ENABLE_SHIFT + slot_idx)); > + if (enable) > + reg |= mask; > + else > + reg &= ~mask; > + > + sdhci_writel(host, reg, SDHC_SYS_OP_CTRL); > +} > + > +/* Enable/Disable the Auto Clock Gating function */ > +static void xenon_set_acg(struct sdhci_host *host, bool enable) > +{ > + u32 reg; > + > + reg = sdhci_readl(host, SDHC_SYS_OP_CTRL); > + if (enable) > + reg &= ~AUTO_CLKGATE_DISABLE_MASK; > + else > + reg |= AUTO_CLKGATE_DISABLE_MASK; > + sdhci_writel(host, reg, SDHC_SYS_OP_CTRL); > +} > + > +/* Enable this slot */ > +static void xenon_enable_slot(struct sdhci_host *host, > + unsigned char slot_idx) > +{ > + u32 reg; > + > + reg = sdhci_readl(host, SDHC_SYS_OP_CTRL); > + reg |= (BIT(slot_idx) << SLOT_ENABLE_SHIFT); > + sdhci_writel(host, reg, SDHC_SYS_OP_CTRL); > + > + /* > + * Manually set the flag which all the slots require, > + * including SD, eMMC, SDIO > + */ > + host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY; > +} > + > +/* Disable this slot */ > +static void xenon_disable_slot(struct sdhci_host *host, > + unsigned char slot_idx) > +{ > + u32 reg; > + > + reg = sdhci_readl(host, SDHC_SYS_OP_CTRL); > + reg &= ~(BIT(slot_idx) << SLOT_ENABLE_SHIFT); > + sdhci_writel(host, reg, SDHC_SYS_OP_CTRL); > +} > + > +/* Enable Parallel Transfer Mode */ > +static void xenon_enable_slot_parallel_tran(struct sdhci_host *host, > + unsigned char slot_idx) > +{ > + u32 reg; > + > + reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL); > + reg |= BIT(slot_idx); > + sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL); > +} > + > +static void xenon_slot_tuning_setup(struct sdhci_host *host) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + u32 reg; > + > + /* Disable the Re-Tuning Request functionality */ > + reg = sdhci_readl(host, SDHC_SLOT_RETUNING_REQ_CTRL); > + reg &= ~RETUNING_COMPATIBLE; > + sdhci_writel(host, reg, SDHC_SLOT_RETUNING_REQ_CTRL); > + > + /* Disbale the Re-tuning Event Signal Enable */ Disbale -> Disable > + reg = sdhci_readl(host, SDHCI_SIGNAL_ENABLE); > + reg &= ~SDHCI_INT_RETUNE; > + sdhci_writel(host, reg, SDHCI_SIGNAL_ENABLE); > + > + /* Force to use Tuning Mode 1 */ > + host->tuning_mode = SDHCI_TUNING_MODE_1; > + /* Set re-tuning period */ > + host->tuning_count = 1 << (priv->tuning_count - 1); > +} > + > +/* > + * Operations inside struct sdhci_ops > + */ > +/* Recover the Register Setting cleared during SOFTWARE_RESET_ALL */ > +static void sdhci_xenon_reset_exit(struct sdhci_host *host, > + unsigned char slot_idx, u8 mask) > +{ > + /* Only SOFTWARE RESET ALL will clear the register setting */ > + if (!(mask & SDHCI_RESET_ALL)) > + return; > + > + /* Disable tuning request and auto-retuing again */ retuing -> retuning > + xenon_slot_tuning_setup(host); > + > + xenon_set_acg(host, true); > + > + xenon_set_sdclk_off_idle(host, slot_idx, false); > +} > + > +static void sdhci_xenon_reset(struct sdhci_host *host, u8 mask) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + > + sdhci_reset(host, mask); > + sdhci_xenon_reset_exit(host, priv->slot_idx, mask); > +} > + > +/* > + * Xenon defines different values for HS200 and SDR104 > + * in Host_Control_2 > + */ > +static void xenon_set_uhs_signaling(struct sdhci_host *host, > + unsigned int timing) > +{ > + 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) > + ctrl_2 |= XENON_SDHCI_CTRL_HS200; > + else if (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 |= XENON_SDHCI_CTRL_HS400; > + sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); > +} > + > +static const struct sdhci_ops sdhci_xenon_ops = { > + .set_clock = sdhci_set_clock, > + .set_bus_width = sdhci_set_bus_width, > + .reset = sdhci_xenon_reset, > + .set_uhs_signaling = xenon_set_uhs_signaling, > + .get_max_clock = sdhci_pltfm_clk_get_max_clock, > +}; > + > +static const struct sdhci_pltfm_data sdhci_xenon_pdata = { > + .ops = &sdhci_xenon_ops, > + .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC | > + SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 | > + SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER | > + SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, > +}; > + > +/* > + * Xenon Specific Operations in mmc_host_ops > + */ > +static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) > +{ > + struct sdhci_host *host = mmc_priv(mmc); > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + unsigned long flags; > + > + /* > + * HS400/HS200/eMMC HS doesn't have Preset Value register. > + * However, sdhci_set_ios will read HS400/HS200 Preset register. > + * Disable Preset Value register for HS400/HS200. > + * eMMC HS with preset_enabled set will trigger a bug in > + * get_preset_value(). > + */ > + spin_lock_irqsave(&host->lock, flags); > + if ((ios->timing == MMC_TIMING_MMC_HS400) || > + (ios->timing == MMC_TIMING_MMC_HS200) || > + (ios->timing == MMC_TIMING_MMC_HS)) { > + host->preset_enabled = false; > + host->quirks2 |= SDHCI_QUIRK2_PRESET_VALUE_BROKEN; > + } else { > + host->quirks2 &= ~SDHCI_QUIRK2_PRESET_VALUE_BROKEN; > + } > + spin_unlock_irqrestore(&host->lock, flags); > + > + sdhci_set_ios(mmc, ios); > + > + if (host->clock > DEFAULT_SDCLK_FREQ) { > + spin_lock_irqsave(&host->lock, flags); > + xenon_set_sdclk_off_idle(host, priv->slot_idx, true); > + spin_unlock_irqrestore(&host->lock, flags); > + } > +} > + > +static int __emmc_signal_voltage_switch(struct mmc_host *mmc, > + const unsigned char signal_voltage) > +{ > + u32 ctrl; > + unsigned char voltage_code; > + struct sdhci_host *host = mmc_priv(mmc); > + > + if (signal_voltage == MMC_SIGNAL_VOLTAGE_330) > + voltage_code = EMMC_VCCQ_3_3V; > + else if (signal_voltage == MMC_SIGNAL_VOLTAGE_180) > + voltage_code = EMMC_VCCQ_1_8V; > + else > + return -EINVAL; > + > + /* > + * This host is for eMMC, XENON self-defined > + * eMMC slot control register should be accessed > + * instead of Host Control 2 > + */ > + ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL); > + ctrl &= ~EMMC_VCCQ_MASK; > + ctrl |= voltage_code; > + sdhci_writel(host, ctrl, SDHC_SLOT_EMMC_CTRL); > + > + /* There is no standard to determine this waiting period */ > + usleep_range(1000, 2000); > + > + /* Check whether io voltage switch is done */ > + ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL); > + ctrl &= EMMC_VCCQ_MASK; > + /* > + * This bit is set only when regulator feeds back the voltage switch > + * results to Xenon SDHC. > + * However, in actaul implementation, regulator might not provide > + * this feedback. > + * Thus we shall not rely on this bit to determine if switch failed. > + * If the bit is not set, just throw a message. > + * Besides, error code should not be returned. > + */ > + if (ctrl != voltage_code) > + dev_info(mmc_dev(mmc), "fail to detect eMMC signal voltage stable\n"); > + return 0; > +} > + > +static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc, > + struct mmc_ios *ios) > +{ > + unsigned char voltage = ios->signal_voltage; > + > + if ((voltage == MMC_SIGNAL_VOLTAGE_330) || > + (voltage == MMC_SIGNAL_VOLTAGE_180)) > + return __emmc_signal_voltage_switch(mmc, voltage); > + > + dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n", > + voltage); > + return -EINVAL; > +} > + > +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc, > + struct mmc_ios *ios) > +{ > + struct sdhci_host *host = mmc_priv(mmc); > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + > + /* > + * Before SD/SDIO set signal voltage, SD bus clock should be > + * disabled. However, sdhci_set_clock will also disable the Internal > + * clock in mmc_set_signal_voltage(). > + * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated. > + * Thus here manually enable internal clock. > + * > + * After switch completes, it is unnecessary to disable internal clock, > + * since keeping internal clock active obeys SD spec. > + */ > + enable_xenon_internal_clk(host); > + > + if (priv->card_candidate) { mmc_power_up() calls __mmc_set_signal_voltage() calls host->ops->start_signal_voltage_switch so priv->card_candidate could be an invalid reference to an old card. So that's not going to work if the card changes - not only for removable cards but even for eMMC if init fails and retries. > + if (mmc_card_mmc(priv->card_candidate)) > + return xenon_emmc_signal_voltage_switch(mmc, ios); So if all you need to know is whether it is a eMMC, why can't DT tell you? > + } > + > + return sdhci_start_signal_voltage_switch(mmc, ios); > +} > + > +/* > + * After determining which slot is used for SDIO, > + * some additional task is required. > + */ > +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card) > +{ > + struct sdhci_host *host = mmc_priv(mmc); > + u32 reg; > + u8 slot_idx; > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + > + /* Link the card for delay adjustment */ > + priv->card_candidate = card; You really need a better way to get the card. I suggest you take up the issue with Ulf. One possibility is to have mmc core set host->card = card much earlier. > + /* Set tuning functionality of this slot */ > + xenon_slot_tuning_setup(host); > + > + slot_idx = priv->slot_idx; > + if (!mmc_card_sdio(card)) { > + /* Re-enable the Auto-CMD12 cap flag. */ > + host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12; > + host->flags |= SDHCI_AUTO_CMD12; > + > + /* Clear SDIO Card Inserted indication */ > + reg = sdhci_readl(host, SDHC_SYS_CFG_INFO); > + reg &= ~(1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT)); > + sdhci_writel(host, reg, SDHC_SYS_CFG_INFO); > + > + if (mmc_card_mmc(card)) { > + mmc->caps |= MMC_CAP_NONREMOVABLE; > + if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V)) > + mmc->caps |= MMC_CAP_1_8V_DDR; > + /* > + * Force to clear BUS_TEST to > + * skip bus_test_pre and bus_test_post > + */ > + mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST; > + mmc->caps2 |= MMC_CAP2_HC_ERASE_SZ | > + MMC_CAP2_PACKED_CMD; > + if (mmc->caps & MMC_CAP_8_BIT_DATA) > + mmc->caps2 |= MMC_CAP2_HS400_1_8V; > + } > + } else { > + /* > + * Delete the Auto-CMD12 cap flag. > + * Otherwise, when sending multi-block CMD53, > + * Driver will set Transfer Mode Register to enable Auto CMD12. > + * However, SDIO device cannot recognize CMD12. > + * Thus SDHC will time-out for waiting for CMD12 response. > + */ > + host->quirks &= ~SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12; > + host->flags &= ~SDHCI_AUTO_CMD12; sdhci_set_transfer_mode() won't enable auto-CMD12 for CMD53 anyway, so is this needed? > + > + /* > + * Set SDIO Card Inserted indication > + * to inform that the current slot is for SDIO > + */ > + reg = sdhci_readl(host, SDHC_SYS_CFG_INFO); > + reg |= (1 << (slot_idx + SLOT_TYPE_SDIO_SHIFT)); > + sdhci_writel(host, reg, SDHC_SYS_CFG_INFO); > + } > +} > + > +static int xenon_execute_tuning(struct mmc_host *mmc, u32 opcode) > +{ > + struct sdhci_host *host = mmc_priv(mmc); > + > + if (host->timing == MMC_TIMING_UHS_DDR50) > + return 0; > + > + return sdhci_execute_tuning(mmc, opcode); > +} > + > +static void xenon_replace_mmc_host_ops(struct sdhci_host *host) > +{ > + host->mmc_host_ops.set_ios = xenon_set_ios; > + host->mmc_host_ops.start_signal_voltage_switch = > + xenon_start_signal_voltage_switch; > + host->mmc_host_ops.init_card = xenon_init_card; > + host->mmc_host_ops.execute_tuning = xenon_execute_tuning; > +} > + > +static int xenon_probe_dt(struct platform_device *pdev) > +{ > + struct device_node *np = pdev->dev.of_node; > + struct sdhci_host *host = platform_get_drvdata(pdev); > + struct mmc_host *mmc = host->mmc; > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + int err; > + u32 slot_idx, nr_slot; > + u32 tuning_count; > + u32 reg; > + > + /* Standard MMC property */ > + err = mmc_of_parse(mmc); > + if (err) > + return err; > + > + /* Standard SDHCI property */ > + sdhci_get_of_property(pdev); > + > + /* > + * Xenon Specific property: > + * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register > + * tuning-count: the interval between re-tuning > + * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1" > + */ > + if (!of_property_read_u32(np, "xenon,slotno", &slot_idx)) { > + nr_slot = sdhci_readl(host, SDHC_SYS_CFG_INFO); > + nr_slot &= NR_SUPPORTED_SLOT_MASK; > + if (unlikely(slot_idx > nr_slot)) { > + dev_err(mmc_dev(mmc), "Slot Index %d exceeds Number of slots %d\n", > + slot_idx, nr_slot); > + return -EINVAL; > + } > + } else { > + priv->slot_idx = 0x0; > + } > + > + if (!of_property_read_u32(np, "xenon,tuning-count", &tuning_count)) { > + if (unlikely(tuning_count >= TMR_RETUN_NO_PRESENT)) { > + dev_err(mmc_dev(mmc), "Wrong Re-tuning Count. Set default value %d\n", > + DEF_TUNING_COUNT); > + tuning_count = DEF_TUNING_COUNT; > + } > + } else { > + priv->tuning_count = DEF_TUNING_COUNT; > + } > + > + if (of_property_read_bool(np, "xenon,mask-conflict-err")) { > + reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL); > + reg |= MASK_CMD_CONFLICT_ERROR; > + sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL); > + } > + > + return err; > +} > + > +static int xenon_slot_probe(struct sdhci_host *host) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + u8 slot_idx = priv->slot_idx; > + > + /* Enable slot */ > + xenon_enable_slot(host, slot_idx); > + > + /* Enable ACG */ > + xenon_set_acg(host, true); > + > + /* Enable Parallel Transfer Mode */ > + xenon_enable_slot_parallel_tran(host, slot_idx); > + > + priv->timing = MMC_TIMING_FAKE; > + priv->clock = 0; > + > + return 0; > +} > + > +static void xenon_slot_remove(struct sdhci_host *host) > +{ > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + u8 slot_idx = priv->slot_idx; > + > + /* disable slot */ > + xenon_disable_slot(host, slot_idx); > +} > + > +static int sdhci_xenon_probe(struct platform_device *pdev) > +{ > + struct sdhci_pltfm_host *pltfm_host; > + struct sdhci_host *host; > + struct clk *clk, *axi_clk; > + struct sdhci_xenon_priv *priv; > + int err; > + > + host = sdhci_pltfm_init(pdev, &sdhci_xenon_pdata, > + sizeof(struct sdhci_xenon_priv)); > + if (IS_ERR(host)) > + return PTR_ERR(host); > + > + pltfm_host = sdhci_priv(host); > + priv = sdhci_pltfm_priv(pltfm_host); > + > + xenon_set_acg(host, false); > + > + /* > + * Link Xenon specific mmc_host_ops function, > + * to replace standard ones in sdhci_ops. > + */ > + xenon_replace_mmc_host_ops(host); > + > + clk = devm_clk_get(&pdev->dev, "core"); > + if (IS_ERR(clk)) { > + dev_err(&pdev->dev, "Failed to setup input clk.\n"); > + err = PTR_ERR(clk); > + goto free_pltfm; > + } > + clk_prepare_enable(clk); > + pltfm_host->clk = clk; > + > + /* > + * Some SOCs require additional clock to > + * manage AXI bus clock. > + * It is optional. > + */ > + axi_clk = devm_clk_get(&pdev->dev, "axi"); > + if (!IS_ERR(axi_clk)) { > + clk_prepare_enable(axi_clk); > + priv->axi_clk = axi_clk; > + } > + > + err = xenon_probe_dt(pdev); > + if (err) > + goto err_clk; > + > + err = xenon_slot_probe(host); > + if (err) > + goto err_clk; > + > + err = sdhci_add_host(host); > + if (err) > + goto remove_slot; > + > + return 0; > + > +remove_slot: > + xenon_slot_remove(host); > +err_clk: > + clk_disable_unprepare(pltfm_host->clk); > + if (!IS_ERR(axi_clk)) > + clk_disable_unprepare(axi_clk); > +free_pltfm: > + sdhci_pltfm_free(pdev); > + return err; > +} > + > +static int sdhci_xenon_remove(struct platform_device *pdev) > +{ > + struct sdhci_host *host = platform_get_drvdata(pdev); > + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host); > + int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xFFFFFFFF); > + > + xenon_slot_remove(host); > + > + sdhci_remove_host(host, dead); > + > + clk_disable_unprepare(pltfm_host->clk); > + clk_disable_unprepare(priv->axi_clk); > + > + sdhci_pltfm_free(pdev); > + > + return 0; > +} > + > +static const struct of_device_id sdhci_xenon_dt_ids[] = { > + { .compatible = "marvell,sdhci-xenon",}, > + { .compatible = "marvell,armada-3700-sdhci",}, > + {} > +}; > +MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids); > + > +static struct platform_driver sdhci_xenon_driver = { > + .driver = { > + .name = "sdhci-xenon", > + .of_match_table = sdhci_xenon_dt_ids, > + .pm = &sdhci_pltfm_pmops, > + }, > + .probe = sdhci_xenon_probe, > + .remove = sdhci_xenon_remove, > +}; > + > +module_platform_driver(sdhci_xenon_driver); > + > +MODULE_DESCRIPTION("SDHCI platform driver for Marvell Xenon SDHC"); > +MODULE_AUTHOR("Hu Ziji <huziji@xxxxxxxxxxx>"); > +MODULE_LICENSE("GPL v2"); > diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h > new file mode 100644 > index 000000000000..c2370493fbe8 > --- /dev/null > +++ b/drivers/mmc/host/sdhci-xenon.h > @@ -0,0 +1,134 @@ > +/* > + * Copyright (C) 2016 Marvell, All Rights Reserved. > + * > + * Author: Hu Ziji <huziji@xxxxxxxxxxx> > + * Date: 2016-8-24 > + * > + * 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. > + */ > +#ifndef SDHCI_XENON_H_ > +#define SDHCI_XENON_H_ > + > +#include <linux/clk.h> > +#include <linux/mmc/card.h> > +#include <linux/of.h> > +#include "sdhci.h" > + > +/* Register Offset of SD Host Controller SOCP self-defined register */ > +#define SDHC_SYS_CFG_INFO 0x0104 > +#define SLOT_TYPE_SDIO_SHIFT 24 > +#define SLOT_TYPE_EMMC_MASK 0xFF > +#define SLOT_TYPE_EMMC_SHIFT 16 > +#define SLOT_TYPE_SD_SDIO_MMC_MASK 0xFF > +#define SLOT_TYPE_SD_SDIO_MMC_SHIFT 8 > +#define NR_SUPPORTED_SLOT_MASK 0x7 > + > +#define SDHC_SYS_OP_CTRL 0x0108 > +#define AUTO_CLKGATE_DISABLE_MASK BIT(20) > +#define SDCLK_IDLEOFF_ENABLE_SHIFT 8 > +#define SLOT_ENABLE_SHIFT 0 > + > +#define SDHC_SYS_EXT_OP_CTRL 0x010C > +#define MASK_CMD_CONFLICT_ERROR BIT(8) > + > +#define SDHC_SLOT_OP_STATUS_CTRL 0x0128 > +#define DELAY_90_DEGREE_MASK_EMMC5 BIT(7) > +#define DELAY_90_DEGREE_SHIFT_EMMC5 7 > +#define EMMC_5_0_PHY_FIXED_DELAY_MASK 0x7F > +#define EMMC_PHY_FIXED_DELAY_MASK 0xFF > +#define EMMC_PHY_FIXED_DELAY_WINDOW_MIN (EMMC_PHY_FIXED_DELAY_MASK >> 3) > +#define SDH_PHY_FIXED_DELAY_MASK 0x1FF > +#define SDH_PHY_FIXED_DELAY_WINDOW_MIN (SDH_PHY_FIXED_DELAY_MASK >> 4) > + > +#define TUN_CONSECUTIVE_TIMES_SHIFT 16 > +#define TUN_CONSECUTIVE_TIMES_MASK 0x7 > +#define TUN_CONSECUTIVE_TIMES 0x4 > +#define TUNING_STEP_SHIFT 12 > +#define TUNING_STEP_MASK 0xF > +#define TUNING_STEP_DIVIDER BIT(6) > + > +#define FORCE_SEL_INVERSE_CLK_SHIFT 11 > + > +#define SDHC_SLOT_EMMC_CTRL 0x0130 > +#define ENABLE_DATA_STROBE BIT(24) > +#define SET_EMMC_RSTN BIT(16) > +#define DISABLE_RD_DATA_CRC BIT(14) > +#define DISABLE_CRC_STAT_TOKEN BIT(13) > +#define EMMC_VCCQ_MASK 0x3 > +#define EMMC_VCCQ_1_8V 0x1 > +#define EMMC_VCCQ_3_3V 0x3 > + > +#define SDHC_SLOT_RETUNING_REQ_CTRL 0x0144 > +/* retuning compatible */ > +#define RETUNING_COMPATIBLE 0x1 > + > +#define SDHC_SLOT_EXT_PRESENT_STATE 0x014C > +#define LOCK_STATE 0x1 > + > +#define SDHC_SLOT_DLL_CUR_DLY_VAL 0x0150 > + > +/* Tuning Parameter */ > +#define TMR_RETUN_NO_PRESENT 0xF > +#define DEF_TUNING_COUNT 0x9 > + > +#define MMC_TIMING_FAKE 0xFF > + > +#define DEFAULT_SDCLK_FREQ (400000) > + > +/* Xenon specific Mode Select value */ > +#define XENON_SDHCI_CTRL_HS200 0x5 > +#define XENON_SDHCI_CTRL_HS400 0x6 > + > +struct sdhci_xenon_priv { > + /* > + * The bus_width, timing, and clock fields in below > + * record the current setting of Xenon SDHC. > + * Driver will call a Sampling Fixed Delay Adjustment > + * if any setting is changed. > + */ > + unsigned char bus_width; > + unsigned char timing; > + unsigned char tuning_count; > + unsigned int clock; > + struct clk *axi_clk; > + > + /* Slot idx */ > + u8 slot_idx; > + > + /* > + * When initializing card, Xenon has to determine card type and > + * adjust Sampling Fixed delay. > + * However, at that time, card structure is not linked to mmc_host. > + * Thus a card pointer is added here to provide > + * the delay adjustment function with the card structure > + * of the card during initialization > + */ > + struct mmc_card *card_candidate; > +}; > + > +static inline int enable_xenon_internal_clk(struct sdhci_host *host) > +{ > + u32 reg; > + u8 timeout; > + > + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL); > + reg |= SDHCI_CLOCK_INT_EN; > + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL); > + /* Wait max 20 ms */ > + timeout = 20; > + while (!((reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) > + & SDHCI_CLOCK_INT_STABLE)) { > + if (timeout == 0) { > + pr_err("%s: Internal clock never stabilised.\n", > + mmc_hostname(host->mmc)); > + return -ETIMEDOUT; > + } > + timeout--; > + mdelay(1); > + } > + > + return 0; > +} > +#endif > -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html