[PATCH 2/4] sdhci-st: Add support for stih407 family silicon.
Ulf Hansson
ulf.hansson at linaro.org
Wed Jan 21 02:26:44 PST 2015
On 20 January 2015 at 16:32, Peter Griffin <peter.griffin at linaro.org> wrote:
> This patch adds support for the extra registers found on
> stih407 family silicon which has the flashSS subsystem.
>
> This mainly consists of some extra glue registers which are
> used to correctly configure the controller hardware.
>
> This patch also adds support for UHS modes for eMMC. To allow
> UHS HS200/SD104 modes to function correctly, due to the
> tight timing constriants, and data tuning requirement support
> for PVT independent delay management is also added. Two types
> of delay management are supported, static delay management and
> dynamic delay management (dynamic delay loop), this delay
> management is only available on eMMC pads on stih410 and later
> silicon.
>
> Testing on stih410-b2120 board achieves the following speeds
> with HS200 eMMC card.
>
> max-frequency = 200Mhz
> /dev/mmcblk0p1:
> Timing buffered disk reads: 270 MB in 3.02 seconds = 89.54 MB/sec
>
> max-frequency = 100Mhz
> root at debian-armhf:~# hdparm -t /dev/mmcblk0p1
> /dev/mmcblk0p1:
> Timing buffered disk reads: 210 MB in 3.00 seconds = 70.00 MB/sec
>
> max-frequency = 50Mhz
> root at debian-armhf:~# hdparm -t /dev/mmcblk0p1
> /dev/mmcblk0p1:
> Timing buffered disk reads: 118 MB in 3.00 seconds = 39.28 MB/sec
>
> This is better than the 3.10 kernel which achieves 77.59 MB/sec
> at 200Mhz clock (same board/soc/eMMC).
>
> Signed-off-by: Peter Griffin <peter.griffin at linaro.org>
> Signed-off-by: Giuseppe Cavallaro <peppe.cavallaro at st.com>
> ---
> drivers/mmc/host/sdhci-st.c | 351 +++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 343 insertions(+), 8 deletions(-)
Would it be possible to split this patch, I think it's easier to
review it in smaller pieces.
>
> diff --git a/drivers/mmc/host/sdhci-st.c b/drivers/mmc/host/sdhci-st.c
> index 328f348..6a4f46c 100644
> --- a/drivers/mmc/host/sdhci-st.c
> +++ b/drivers/mmc/host/sdhci-st.c
> @@ -1,7 +1,7 @@
> /*
> * Support for SDHCI on STMicroelectronics SoCs
> *
> - * Copyright (C) 2014 STMicroelectronics Ltd
> + * Copyright (C) 2015 STMicroelectronics Ltd
> * Author: Giuseppe Cavallaro <peppe.cavallaro at st.com>
> * Contributors: Peter Griffin <peter.griffin at linaro.org>
> *
> @@ -23,9 +23,293 @@
> #include <linux/module.h>
> #include <linux/err.h>
> #include <linux/mmc/host.h>
> -
> +#include <linux/reset.h>
> #include "sdhci-pltfm.h"
>
> +struct st_mmc_platform_data {
Please rename this to st_mmc_data. We don't want this driver to
support "platform data", since it use DT right. :-)
> + struct reset_control *rstc;
> + void __iomem *top_ioaddr;
> +};
> +
> +/* MMCSS glue logic to setup the HC on some ST SoCs (e.g. STiH407 family) */
> +
> +#define ST_MMC_CCONFIG_REG_1 0x400
> +#define ST_MMC_CCONFIG_TIMEOUT_CLK_UNIT BIT(24)
> +#define ST_MMC_CCONFIG_TIMEOUT_CLK_FREQ BIT(12)
> +#define ST_MMC_CCONFIG_TUNING_COUNT_DEFAULT BIT(8)
> +#define ST_MMC_CCONFIG_ASYNC_WAKEUP BIT(0)
> +#define ST_MMC_CCONFIG_1_DEFAULT \
> + ((ST_MMC_CCONFIG_TIMEOUT_CLK_UNIT) | \
> + (ST_MMC_CCONFIG_TIMEOUT_CLK_FREQ) | \
> + (ST_MMC_CCONFIG_TUNING_COUNT_DEFAULT))
> +
> +#define ST_MMC_CCONFIG_REG_2 0x404
> +#define ST_MMC_CCONFIG_HIGH_SPEED BIT(28)
> +#define ST_MMC_CCONFIG_ADMA2 BIT(24)
> +#define ST_MMC_CCONFIG_8BIT BIT(20)
> +#define ST_MMC_CCONFIG_MAX_BLK_LEN 16
> +#define MAX_BLK_LEN_1024 1
> +#define MAX_BLK_LEN_2048 2
> +#define BASE_CLK_FREQ_200 0xc8
> +#define BASE_CLK_FREQ_100 0x64
> +#define BASE_CLK_FREQ_50 0x32
> +#define ST_MMC_CCONFIG_2_DEFAULT \
> + (ST_MMC_CCONFIG_HIGH_SPEED | ST_MMC_CCONFIG_ADMA2 | \
> + ST_MMC_CCONFIG_8BIT | \
> + (MAX_BLK_LEN_1024 << ST_MMC_CCONFIG_MAX_BLK_LEN))
> +
> +#define ST_MMC_CCONFIG_REG_3 0x408
> +#define ST_MMC_CCONFIG_EMMC_SLOT_TYPE BIT(28)
> +#define ST_MMC_CCONFIG_64BIT BIT(24)
> +#define ST_MMC_CCONFIG_ASYNCH_INTR_SUPPORT BIT(20)
> +#define ST_MMC_CCONFIG_1P8_VOLT BIT(16)
> +#define ST_MMC_CCONFIG_3P0_VOLT BIT(12)
> +#define ST_MMC_CCONFIG_3P3_VOLT BIT(8)
> +#define ST_MMC_CCONFIG_SUSP_RES_SUPPORT BIT(4)
> +#define ST_MMC_CCONFIG_SDMA BIT(0)
> +#define ST_MMC_CCONFIG_3_DEFAULT \
> + (ST_MMC_CCONFIG_ASYNCH_INTR_SUPPORT | \
> + ST_MMC_CCONFIG_3P3_VOLT | \
> + ST_MMC_CCONFIG_SUSP_RES_SUPPORT | \
> + ST_MMC_CCONFIG_SDMA)
> +
> +#define ST_MMC_CCONFIG_REG_4 0x40c
> +#define ST_MMC_CCONFIG_D_DRIVER BIT(20)
> +#define ST_MMC_CCONFIG_C_DRIVER BIT(16)
> +#define ST_MMC_CCONFIG_A_DRIVER BIT(12)
> +#define ST_MMC_CCONFIG_DDR50 BIT(8)
> +#define ST_MMC_CCONFIG_SDR104 BIT(4)
> +#define ST_MMC_CCONFIG_SDR50 BIT(0)
> +#define ST_MMC_CCONFIG_4_DEFAULT 0
> +
> +#define ST_MMC_CCONFIG_REG_5 0x410
> +#define ST_MMC_CCONFIG_TUNING_FOR_SDR50 BIT(8)
> +#define RETUNING_TIMER_CNT_MAX 0xf
> +#define ST_MMC_CCONFIG_5_DEFAULT 0
> +
> +/* I/O configuration for Arasan IP */
> +#define ST_MMC_GP_OUTPUT 0x450
> +#define ST_MMC_GP_OUTPUT_CD BIT(12)
> +
> +#define ST_MMC_STATUS_R 0x460
> +
> +#define ST_TOP_MMC_DLY_FIX_OFF(x) (x - 0x8)
> +
> +/* TOP config registers to manage static and dynamic delay */
> +#define ST_TOP_MMC_TX_CLK_DLY ST_TOP_MMC_DLY_FIX_OFF(0x8)
> +#define ST_TOP_MMC_RX_CLK_DLY ST_TOP_MMC_DLY_FIX_OFF(0xc)
> +/* MMC delay control register */
> +#define ST_TOP_MMC_DLY_CTRL ST_TOP_MMC_DLY_FIX_OFF(0x18)
> +#define ST_TOP_MMC_DLY_CTRL_DLL_BYPASS_CMD BIT(0)
> +#define ST_TOP_MMC_DLY_CTRL_DLL_BYPASS_PH_SEL BIT(1)
> +#define ST_TOP_MMC_DLY_CTRL_TX_DLL_ENABLE BIT(8)
> +#define ST_TOP_MMC_DLY_CTRL_RX_DLL_ENABLE BIT(9)
> +#define ST_TOP_MMC_DLY_CTRL_ATUNE_NOT_CFG_DLY BIT(10)
> +#define ST_TOP_MMC_START_DLL_LOCK BIT(11)
> +
> +/* register to provide the phase-shift value for DLL */
> +#define ST_TOP_MMC_TX_DLL_STEP_DLY ST_TOP_MMC_DLY_FIX_OFF(0x1c)
> +#define ST_TOP_MMC_RX_DLL_STEP_DLY ST_TOP_MMC_DLY_FIX_OFF(0x20)
> +#define ST_TOP_MMC_RX_CMD_STEP_DLY ST_TOP_MMC_DLY_FIX_OFF(0x24)
> +
> +/* phase shift delay on the tx clk 2.188ns */
> +#define ST_TOP_MMC_TX_DLL_STEP_DLY_VALID 0x6
> +
> +#define ST_TOP_MMC_DLY_MAX 0xf
> +
> +#define ST_TOP_MMC_DYN_DLY_CONF \
> + (ST_TOP_MMC_DLY_CTRL_TX_DLL_ENABLE | \
> + ST_TOP_MMC_DLY_CTRL_ATUNE_NOT_CFG_DLY | \
> + ST_TOP_MMC_START_DLL_LOCK)
> +
> +/*
> + * For clock speeds greater than 90MHz, we need to check that the
> + * DLL procedure has finished before switching to ultra-speed modes.
> + */
> +#define CLK_TO_CHECK_DLL_LOCK 90000000
> +
> +static inline void st_mmcss_set_static_delay(void __iomem *ioaddr)
> +{
> + if (ioaddr) {
> + writel_relaxed(0x0, ioaddr + ST_TOP_MMC_DLY_CTRL);
> + writel_relaxed(ST_TOP_MMC_DLY_MAX,
> + ioaddr + ST_TOP_MMC_TX_CLK_DLY);
> + }
> +}
> +
> +/**
> + * st_mmcss_cconfig: configure the Arasan HC inside the flashSS.
> + * @np: dt device node.
> + * @host: sdhci host
> + * Description: this function is to configure the Arasan host controller.
> + * On some ST SoCs, i.e. STiH407 family, the MMC devices inside a dedicated
> + * flashSS sub-system which needs to be configured to be compliant to eMMC 4.5
> + * or eMMC4.3. This has to be done before registering the sdhci host.
> + */
> +static void st_mmcss_cconfig(struct device_node *np, struct sdhci_host *host)
> +{
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct mmc_host *mhost = host->mmc;
> + u32 cconf2, cconf3, cconf4, cconf5;
> +
> + if (!of_device_is_compatible(np, "st,sdhci-stih407"))
> + return;
I think I would prefer to have this check done only once, during ->probe().
Such a check, would then need to handle assigning corresponding
function pointers/callbacks in the struct st_mmc_data, which is what
enables support for this feature.
Those functions pointers then needs to be validated before they are
invoked, of course.
> +
> + cconf2 = ST_MMC_CCONFIG_2_DEFAULT;
> + cconf3 = ST_MMC_CCONFIG_3_DEFAULT;
> + cconf4 = ST_MMC_CCONFIG_4_DEFAULT;
> + cconf5 = ST_MMC_CCONFIG_5_DEFAULT;
> +
> + writel_relaxed(ST_MMC_CCONFIG_1_DEFAULT,
> + host->ioaddr + ST_MMC_CCONFIG_REG_1);
> +
> + /* Set clock frequency, default to 50MHz if max-frequency is not
> + * provided */
> +
> + switch (mhost->f_max) {
> + case 200000000:
> + clk_set_rate(pltfm_host->clk, mhost->f_max);
> + cconf2 |= BASE_CLK_FREQ_200;
> + break;
> + case 100000000:
> + clk_set_rate(pltfm_host->clk, mhost->f_max);
> + cconf2 |= BASE_CLK_FREQ_100;
> + break;
> + default:
> + clk_set_rate(pltfm_host->clk, 50000000);
> + cconf2 |= BASE_CLK_FREQ_50;
> + }
> +
> + writel_relaxed(cconf2, host->ioaddr + ST_MMC_CCONFIG_REG_2);
> +
> + if (mhost->caps & MMC_CAP_NONREMOVABLE)
> + cconf3 |= ST_MMC_CCONFIG_EMMC_SLOT_TYPE;
> + else
> + /* CARD _D ET_CTRL */
> + writel_relaxed(ST_MMC_GP_OUTPUT_CD,
> + host->ioaddr + ST_MMC_GP_OUTPUT);
> +
> + if (mhost->caps & MMC_CAP_UHS_SDR50) {
> + /* use 1.8V */
> + cconf3 |= ST_MMC_CCONFIG_1P8_VOLT;
> + cconf4 |= ST_MMC_CCONFIG_SDR50;
> + /* Use tuning */
> + cconf5 |= ST_MMC_CCONFIG_TUNING_FOR_SDR50;
> + /* Max timeout for retuning */
> + cconf5 |= RETUNING_TIMER_CNT_MAX;
> + }
> +
> + if (mhost->caps & MMC_CAP_UHS_SDR104) {
> + /*
> + * SDR104 implies the HC can support HS200 mode, so
> + * it's mandatory to use 1.8V
> + */
> + cconf3 |= ST_MMC_CCONFIG_1P8_VOLT;
> + cconf4 |= ST_MMC_CCONFIG_SDR104;
> + /* Max timeout for retuning */
> + cconf5 |= RETUNING_TIMER_CNT_MAX;
> + }
> +
> + if (mhost->caps & MMC_CAP_UHS_DDR50)
> + cconf4 |= ST_MMC_CCONFIG_DDR50;
> +
> + writel_relaxed(cconf3, host->ioaddr + ST_MMC_CCONFIG_REG_3);
> + writel_relaxed(cconf4, host->ioaddr + ST_MMC_CCONFIG_REG_4);
> + writel_relaxed(cconf5, host->ioaddr + ST_MMC_CCONFIG_REG_5);
> +}
> +
> +static inline void st_mmcss_set_dll(void __iomem *ioaddr)
> +{
> + if (ioaddr) {
> + writel_relaxed(ST_TOP_MMC_DYN_DLY_CONF,
> + ioaddr + ST_TOP_MMC_DLY_CTRL);
> + writel_relaxed(ST_TOP_MMC_TX_DLL_STEP_DLY_VALID,
> + ioaddr + ST_TOP_MMC_TX_DLL_STEP_DLY);
> + }
> +}
> +
> +static int st_mmcss_lock_dll(void __iomem *ioaddr)
> +{
> + unsigned long curr, value;
> + unsigned long finish = jiffies + HZ;
> +
> + /* Checks if the DLL procedure is finished */
> + do {
> + curr = jiffies;
> + value = readl(ioaddr + ST_MMC_STATUS_R);
> + if (value & 0x1)
> + return 0;
> +
> + cpu_relax();
> + } while (!time_after_eq(curr, finish));
> +
> + return -EBUSY;
> +}
> +
> +static int sdhci_st_set_dll_for_clock(struct sdhci_host *host)
> +{
> + int ret = 0;
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct st_mmc_platform_data *pdata = pltfm_host->priv;
> +
> + if (host->clock > CLK_TO_CHECK_DLL_LOCK) {
> + st_mmcss_set_dll(pdata->top_ioaddr);
> + ret = st_mmcss_lock_dll(host->ioaddr);
> + }
> +
> + return ret;
> +}
> +
> +static void sdhci_st_set_uhs_signaling(struct sdhci_host *host,
> + unsigned int uhs)
> +{
> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct st_mmc_platform_data *pdata = pltfm_host->priv;
> + u16 ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> + int ret = 0;
> +
> + /* Select Bus Speed Mode for host */
> + ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
> + switch (uhs) {
> + /*
> + * Set V18_EN -- UHS modes do not work without this.
> + * does not change signaling voltage
> + */
> +
> + case MMC_TIMING_UHS_SDR12:
> + st_mmcss_set_static_delay(pdata->top_ioaddr);
> + ctrl_2 |= SDHCI_CTRL_UHS_SDR12 | SDHCI_CTRL_VDD_180;
> + break;
> + case MMC_TIMING_UHS_SDR25:
> + st_mmcss_set_static_delay(pdata->top_ioaddr);
> + ctrl_2 |= SDHCI_CTRL_UHS_SDR25 | SDHCI_CTRL_VDD_180;
> + break;
> + case MMC_TIMING_UHS_SDR50:
> + st_mmcss_set_static_delay(pdata->top_ioaddr);
> + ctrl_2 |= SDHCI_CTRL_UHS_SDR50 | SDHCI_CTRL_VDD_180;
> + ret = sdhci_st_set_dll_for_clock(host);
> + break;
> + case MMC_TIMING_UHS_SDR104:
> + case MMC_TIMING_MMC_HS200:
> + st_mmcss_set_static_delay(pdata->top_ioaddr);
> + ctrl_2 |= SDHCI_CTRL_UHS_SDR104 | SDHCI_CTRL_VDD_180;
> + ret = sdhci_st_set_dll_for_clock(host);
> + break;
> + case MMC_TIMING_UHS_DDR50:
> + case MMC_TIMING_MMC_DDR52:
> + st_mmcss_set_static_delay(pdata->top_ioaddr);
> + ctrl_2 |= SDHCI_CTRL_UHS_DDR50 | SDHCI_CTRL_VDD_180;
> + break;
> + }
> +
> + if (ret)
> + dev_warn(mmc_dev(host->mmc), "Error setting dll for clock\n");
> +
> + dev_dbg(mmc_dev(host->mmc), "uhs %d, ctrl_2 %04X\n", uhs, ctrl_2);
> +
> + sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
> +}
> +
> static u32 sdhci_st_readl(struct sdhci_host *host, int reg)
> {
> u32 ret;
> @@ -48,22 +332,32 @@ static const struct sdhci_ops sdhci_st_ops = {
> .set_bus_width = sdhci_set_bus_width,
> .read_l = sdhci_st_readl,
> .reset = sdhci_reset,
> + .set_uhs_signaling = sdhci_st_set_uhs_signaling,
> };
>
> static const struct sdhci_pltfm_data sdhci_st_pdata = {
> .ops = &sdhci_st_ops,
> .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC |
> - SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
> + SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
> + SDHCI_QUIRK_NO_HISPD_BIT,
> + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
> + SDHCI_QUIRK2_STOP_WITH_TC,
> };
>
> -
> static int sdhci_st_probe(struct platform_device *pdev)
> {
> + struct device_node *np = pdev->dev.of_node;
> struct sdhci_host *host;
> + struct st_mmc_platform_data *pdata;
> struct sdhci_pltfm_host *pltfm_host;
> struct clk *clk;
> int ret = 0;
> u16 host_version;
> + struct resource *res;
> +
> + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
> + if (!pdata)
> + return -ENOMEM;
>
> clk = devm_clk_get(&pdev->dev, "mmc");
> if (IS_ERR(clk)) {
> @@ -71,6 +365,12 @@ static int sdhci_st_probe(struct platform_device *pdev)
> return PTR_ERR(clk);
> }
>
> + pdata->rstc = devm_reset_control_get(&pdev->dev, NULL);
> + if (IS_ERR(pdata->rstc))
> + pdata->rstc = NULL;
> + else
> + reset_control_deassert(pdata->rstc);
> +
> host = sdhci_pltfm_init(pdev, &sdhci_st_pdata, 0);
> if (IS_ERR(host)) {
> dev_err(&pdev->dev, "Failed sdhci_pltfm_init\n");
> @@ -78,7 +378,6 @@ static int sdhci_st_probe(struct platform_device *pdev)
> }
>
> ret = mmc_of_parse(host->mmc);
> -
> if (ret) {
> dev_err(&pdev->dev, "Failed mmc_of_parse\n");
> return ret;
> @@ -86,9 +385,22 @@ static int sdhci_st_probe(struct platform_device *pdev)
>
> clk_prepare_enable(clk);
>
> + /* Configure the FlashSS Top registers for setting eMMC TX/RX delay */
> + res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> + "top-mmc-delay");
> + pdata->top_ioaddr = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(pdata->top_ioaddr)) {
> + dev_warn(&pdev->dev, "FlashSS Top Dly registers not available");
> + pdata->top_ioaddr = NULL;
> + }
> +
> pltfm_host = sdhci_priv(host);
> + pltfm_host->priv = pdata;
> pltfm_host->clk = clk;
>
> + /* Configure the Arasan HC inside the flashSS */
> + st_mmcss_cconfig(np, host);
> +
> ret = sdhci_add_host(host);
> if (ret) {
> dev_err(&pdev->dev, "Failed sdhci_add_host\n");
> @@ -117,10 +429,17 @@ static int sdhci_st_remove(struct platform_device *pdev)
> {
> struct sdhci_host *host = platform_get_drvdata(pdev);
> struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct st_mmc_platform_data *pdata = pltfm_host->priv;
> + int ret;
>
> clk_disable_unprepare(pltfm_host->clk);
>
> - return sdhci_pltfm_unregister(pdev);
> + ret = sdhci_pltfm_unregister(pdev);
> +
> + if (pdata->rstc)
> + reset_control_assert(pdata->rstc);
> +
> + return ret;
> }
>
> #ifdef CONFIG_PM_SLEEP
> @@ -128,12 +447,18 @@ static int sdhci_st_suspend(struct device *dev)
> {
> struct sdhci_host *host = dev_get_drvdata(dev);
> struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> - int ret = sdhci_suspend_host(host);
> + struct st_mmc_platform_data *pdata = pltfm_host->priv;
> + int ret;
>
> + ret = sdhci_suspend_host(host);
> if (ret)
> goto out;
>
> + if (pdata->rstc)
> + reset_control_assert(pdata->rstc);
> +
> clk_disable_unprepare(pltfm_host->clk);
> +
> out:
> return ret;
> }
> @@ -142,10 +467,20 @@ static int sdhci_st_resume(struct device *dev)
> {
> struct sdhci_host *host = dev_get_drvdata(dev);
> struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> + struct st_mmc_platform_data *pdata = pltfm_host->priv;
> + struct device_node *np = dev->of_node;
> + int ret;
>
> clk_prepare_enable(pltfm_host->clk);
>
> - return sdhci_resume_host(host);
> + if (pdata->rstc)
> + reset_control_deassert(pdata->rstc);
> +
> + st_mmcss_cconfig(np, host);
> +
> + ret = sdhci_resume_host(host);
> +
> + return ret;
> }
> #endif
>
> --
> 1.9.1
>
Kind regards
Uffe
More information about the linux-arm-kernel
mailing list