From patchwork Fri Oct 7 15:22:52 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gregory CLEMENT X-Patchwork-Id: 9366341 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id B077B6075E for ; Fri, 7 Oct 2016 15:28:34 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 9E267296ED for ; Fri, 7 Oct 2016 15:28:34 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 92A5C296F0; Fri, 7 Oct 2016 15:28:34 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 3CA22296ED for ; Fri, 7 Oct 2016 15:28:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932291AbcJGP0T (ORCPT ); Fri, 7 Oct 2016 11:26:19 -0400 Received: from down.free-electrons.com ([37.187.137.238]:34932 "EHLO mail.free-electrons.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1757106AbcJGPZE (ORCPT ); Fri, 7 Oct 2016 11:25:04 -0400 Received: by mail.free-electrons.com (Postfix, from userid 110) id 15A6A47E; Fri, 7 Oct 2016 17:24:38 +0200 (CEST) Received: from localhost (83.146.29.93.rev.sfr.net [93.29.146.83]) by mail.free-electrons.com (Postfix) with ESMTPSA id 643E726FF; Fri, 7 Oct 2016 17:24:19 +0200 (CEST) From: Gregory CLEMENT To: Ulf Hansson , Adrian Hunter , linux-mmc@vger.kernel.org Cc: Jason Cooper , Andrew Lunn , Sebastian Hesselbarth , Gregory CLEMENT , Rob Herring , devicetree@vger.kernel.org, Thomas Petazzoni , linux-arm-kernel@lists.infradead.org, Ziji Hu , "Jack(SH) Zhu" , Jimmy Xu , Jisheng Zhang , Nadav Haklai , Ryan Gao , Doug Jones , Shiwu Zhang , Victor Gu , "Wei(SOCP) Liu" , Wilson Ding , Xueping Liu , Hilbert Zhang , Keji Zhang , Liuliu Zhao , Peng Zhu , Yu Cao , Romain Perier , Yehuda Yitschak , Marcin Wojtas , Hanna Hawa , Kostya Porotchkin , linux-kernel@vger.kernel.org Subject: [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality Date: Fri, 7 Oct 2016 17:22:52 +0200 Message-Id: X-Mailer: git-send-email 2.9.3 In-Reply-To: References: In-Reply-To: References: Sender: linux-mmc-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-mmc@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Ziji Hu 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 Reviewed-by: Gregory CLEMENT Signed-off-by: Gregory CLEMENT --- 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 L: linux-mmc@vger.kernel.org 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 + * 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 + * Special thanks to Video BG4 project team. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 */ + 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 */ + 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) { + if (mmc_card_mmc(priv->card_candidate)) + return xenon_emmc_signal_voltage_switch(mmc, ios); + } + + 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; + /* 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; + + /* + * 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 "); +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 + * 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 +#include +#include +#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