162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Exynos Specific Extensions for Synopsys DW Multimedia Card Interface driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2012, Samsung Electronics Co., Ltd.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/platform_device.h>
1062306a36Sopenharmony_ci#include <linux/clk.h>
1162306a36Sopenharmony_ci#include <linux/mmc/host.h>
1262306a36Sopenharmony_ci#include <linux/mmc/mmc.h>
1362306a36Sopenharmony_ci#include <linux/of.h>
1462306a36Sopenharmony_ci#include <linux/of_gpio.h>
1562306a36Sopenharmony_ci#include <linux/pm_runtime.h>
1662306a36Sopenharmony_ci#include <linux/slab.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include "dw_mmc.h"
1962306a36Sopenharmony_ci#include "dw_mmc-pltfm.h"
2062306a36Sopenharmony_ci#include "dw_mmc-exynos.h"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci/* Variations in Exynos specific dw-mshc controller */
2362306a36Sopenharmony_cienum dw_mci_exynos_type {
2462306a36Sopenharmony_ci	DW_MCI_TYPE_EXYNOS4210,
2562306a36Sopenharmony_ci	DW_MCI_TYPE_EXYNOS4412,
2662306a36Sopenharmony_ci	DW_MCI_TYPE_EXYNOS5250,
2762306a36Sopenharmony_ci	DW_MCI_TYPE_EXYNOS5420,
2862306a36Sopenharmony_ci	DW_MCI_TYPE_EXYNOS5420_SMU,
2962306a36Sopenharmony_ci	DW_MCI_TYPE_EXYNOS7,
3062306a36Sopenharmony_ci	DW_MCI_TYPE_EXYNOS7_SMU,
3162306a36Sopenharmony_ci	DW_MCI_TYPE_ARTPEC8,
3262306a36Sopenharmony_ci};
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci/* Exynos implementation specific driver private data */
3562306a36Sopenharmony_cistruct dw_mci_exynos_priv_data {
3662306a36Sopenharmony_ci	enum dw_mci_exynos_type		ctrl_type;
3762306a36Sopenharmony_ci	u8				ciu_div;
3862306a36Sopenharmony_ci	u32				sdr_timing;
3962306a36Sopenharmony_ci	u32				ddr_timing;
4062306a36Sopenharmony_ci	u32				hs400_timing;
4162306a36Sopenharmony_ci	u32				tuned_sample;
4262306a36Sopenharmony_ci	u32				cur_speed;
4362306a36Sopenharmony_ci	u32				dqs_delay;
4462306a36Sopenharmony_ci	u32				saved_dqs_en;
4562306a36Sopenharmony_ci	u32				saved_strobe_ctrl;
4662306a36Sopenharmony_ci};
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic struct dw_mci_exynos_compatible {
4962306a36Sopenharmony_ci	char				*compatible;
5062306a36Sopenharmony_ci	enum dw_mci_exynos_type		ctrl_type;
5162306a36Sopenharmony_ci} exynos_compat[] = {
5262306a36Sopenharmony_ci	{
5362306a36Sopenharmony_ci		.compatible	= "samsung,exynos4210-dw-mshc",
5462306a36Sopenharmony_ci		.ctrl_type	= DW_MCI_TYPE_EXYNOS4210,
5562306a36Sopenharmony_ci	}, {
5662306a36Sopenharmony_ci		.compatible	= "samsung,exynos4412-dw-mshc",
5762306a36Sopenharmony_ci		.ctrl_type	= DW_MCI_TYPE_EXYNOS4412,
5862306a36Sopenharmony_ci	}, {
5962306a36Sopenharmony_ci		.compatible	= "samsung,exynos5250-dw-mshc",
6062306a36Sopenharmony_ci		.ctrl_type	= DW_MCI_TYPE_EXYNOS5250,
6162306a36Sopenharmony_ci	}, {
6262306a36Sopenharmony_ci		.compatible	= "samsung,exynos5420-dw-mshc",
6362306a36Sopenharmony_ci		.ctrl_type	= DW_MCI_TYPE_EXYNOS5420,
6462306a36Sopenharmony_ci	}, {
6562306a36Sopenharmony_ci		.compatible	= "samsung,exynos5420-dw-mshc-smu",
6662306a36Sopenharmony_ci		.ctrl_type	= DW_MCI_TYPE_EXYNOS5420_SMU,
6762306a36Sopenharmony_ci	}, {
6862306a36Sopenharmony_ci		.compatible	= "samsung,exynos7-dw-mshc",
6962306a36Sopenharmony_ci		.ctrl_type	= DW_MCI_TYPE_EXYNOS7,
7062306a36Sopenharmony_ci	}, {
7162306a36Sopenharmony_ci		.compatible	= "samsung,exynos7-dw-mshc-smu",
7262306a36Sopenharmony_ci		.ctrl_type	= DW_MCI_TYPE_EXYNOS7_SMU,
7362306a36Sopenharmony_ci	}, {
7462306a36Sopenharmony_ci		.compatible	= "axis,artpec8-dw-mshc",
7562306a36Sopenharmony_ci		.ctrl_type	= DW_MCI_TYPE_ARTPEC8,
7662306a36Sopenharmony_ci	},
7762306a36Sopenharmony_ci};
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic inline u8 dw_mci_exynos_get_ciu_div(struct dw_mci *host)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	struct dw_mci_exynos_priv_data *priv = host->priv;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412)
8462306a36Sopenharmony_ci		return EXYNOS4412_FIXED_CIU_CLK_DIV;
8562306a36Sopenharmony_ci	else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210)
8662306a36Sopenharmony_ci		return EXYNOS4210_FIXED_CIU_CLK_DIV;
8762306a36Sopenharmony_ci	else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
8862306a36Sopenharmony_ci			priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU ||
8962306a36Sopenharmony_ci			priv->ctrl_type == DW_MCI_TYPE_ARTPEC8)
9062306a36Sopenharmony_ci		return SDMMC_CLKSEL_GET_DIV(mci_readl(host, CLKSEL64)) + 1;
9162306a36Sopenharmony_ci	else
9262306a36Sopenharmony_ci		return SDMMC_CLKSEL_GET_DIV(mci_readl(host, CLKSEL)) + 1;
9362306a36Sopenharmony_ci}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic void dw_mci_exynos_config_smu(struct dw_mci *host)
9662306a36Sopenharmony_ci{
9762306a36Sopenharmony_ci	struct dw_mci_exynos_priv_data *priv = host->priv;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	/*
10062306a36Sopenharmony_ci	 * If Exynos is provided the Security management,
10162306a36Sopenharmony_ci	 * set for non-ecryption mode at this time.
10262306a36Sopenharmony_ci	 */
10362306a36Sopenharmony_ci	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS5420_SMU ||
10462306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU) {
10562306a36Sopenharmony_ci		mci_writel(host, MPSBEGIN0, 0);
10662306a36Sopenharmony_ci		mci_writel(host, MPSEND0, SDMMC_ENDING_SEC_NR_MAX);
10762306a36Sopenharmony_ci		mci_writel(host, MPSCTRL0, SDMMC_MPSCTRL_SECURE_WRITE_BIT |
10862306a36Sopenharmony_ci			   SDMMC_MPSCTRL_NON_SECURE_READ_BIT |
10962306a36Sopenharmony_ci			   SDMMC_MPSCTRL_VALID |
11062306a36Sopenharmony_ci			   SDMMC_MPSCTRL_NON_SECURE_WRITE_BIT);
11162306a36Sopenharmony_ci	}
11262306a36Sopenharmony_ci}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_cistatic int dw_mci_exynos_priv_init(struct dw_mci *host)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	struct dw_mci_exynos_priv_data *priv = host->priv;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	dw_mci_exynos_config_smu(host);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	if (priv->ctrl_type >= DW_MCI_TYPE_EXYNOS5420) {
12162306a36Sopenharmony_ci		priv->saved_strobe_ctrl = mci_readl(host, HS400_DLINE_CTRL);
12262306a36Sopenharmony_ci		priv->saved_dqs_en = mci_readl(host, HS400_DQS_EN);
12362306a36Sopenharmony_ci		priv->saved_dqs_en |= AXI_NON_BLOCKING_WR;
12462306a36Sopenharmony_ci		mci_writel(host, HS400_DQS_EN, priv->saved_dqs_en);
12562306a36Sopenharmony_ci		if (!priv->dqs_delay)
12662306a36Sopenharmony_ci			priv->dqs_delay =
12762306a36Sopenharmony_ci				DQS_CTRL_GET_RD_DELAY(priv->saved_strobe_ctrl);
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	if (priv->ctrl_type == DW_MCI_TYPE_ARTPEC8) {
13162306a36Sopenharmony_ci		/* Quirk needed for the ARTPEC-8 SoC */
13262306a36Sopenharmony_ci		host->quirks |= DW_MMC_QUIRK_EXTENDED_TMOUT;
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	host->bus_hz /= (priv->ciu_div + 1);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	return 0;
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cistatic void dw_mci_exynos_set_clksel_timing(struct dw_mci *host, u32 timing)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	struct dw_mci_exynos_priv_data *priv = host->priv;
14362306a36Sopenharmony_ci	u32 clksel;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
14662306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU ||
14762306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_ARTPEC8)
14862306a36Sopenharmony_ci		clksel = mci_readl(host, CLKSEL64);
14962306a36Sopenharmony_ci	else
15062306a36Sopenharmony_ci		clksel = mci_readl(host, CLKSEL);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	clksel = (clksel & ~SDMMC_CLKSEL_TIMING_MASK) | timing;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
15562306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU ||
15662306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_ARTPEC8)
15762306a36Sopenharmony_ci		mci_writel(host, CLKSEL64, clksel);
15862306a36Sopenharmony_ci	else
15962306a36Sopenharmony_ci		mci_writel(host, CLKSEL, clksel);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	/*
16262306a36Sopenharmony_ci	 * Exynos4412 and Exynos5250 extends the use of CMD register with the
16362306a36Sopenharmony_ci	 * use of bit 29 (which is reserved on standard MSHC controllers) for
16462306a36Sopenharmony_ci	 * optionally bypassing the HOLD register for command and data. The
16562306a36Sopenharmony_ci	 * HOLD register should be bypassed in case there is no phase shift
16662306a36Sopenharmony_ci	 * applied on CMD/DATA that is sent to the card.
16762306a36Sopenharmony_ci	 */
16862306a36Sopenharmony_ci	if (!SDMMC_CLKSEL_GET_DRV_WD3(clksel) && host->slot)
16962306a36Sopenharmony_ci		set_bit(DW_MMC_CARD_NO_USE_HOLD, &host->slot->flags);
17062306a36Sopenharmony_ci}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci#ifdef CONFIG_PM
17362306a36Sopenharmony_cistatic int dw_mci_exynos_runtime_resume(struct device *dev)
17462306a36Sopenharmony_ci{
17562306a36Sopenharmony_ci	struct dw_mci *host = dev_get_drvdata(dev);
17662306a36Sopenharmony_ci	int ret;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	ret = dw_mci_runtime_resume(dev);
17962306a36Sopenharmony_ci	if (ret)
18062306a36Sopenharmony_ci		return ret;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	dw_mci_exynos_config_smu(host);
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	return ret;
18562306a36Sopenharmony_ci}
18662306a36Sopenharmony_ci#endif /* CONFIG_PM */
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
18962306a36Sopenharmony_ci/**
19062306a36Sopenharmony_ci * dw_mci_exynos_suspend_noirq - Exynos-specific suspend code
19162306a36Sopenharmony_ci * @dev: Device to suspend (this device)
19262306a36Sopenharmony_ci *
19362306a36Sopenharmony_ci * This ensures that device will be in runtime active state in
19462306a36Sopenharmony_ci * dw_mci_exynos_resume_noirq after calling pm_runtime_force_resume()
19562306a36Sopenharmony_ci */
19662306a36Sopenharmony_cistatic int dw_mci_exynos_suspend_noirq(struct device *dev)
19762306a36Sopenharmony_ci{
19862306a36Sopenharmony_ci	pm_runtime_get_noresume(dev);
19962306a36Sopenharmony_ci	return pm_runtime_force_suspend(dev);
20062306a36Sopenharmony_ci}
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci/**
20362306a36Sopenharmony_ci * dw_mci_exynos_resume_noirq - Exynos-specific resume code
20462306a36Sopenharmony_ci * @dev: Device to resume (this device)
20562306a36Sopenharmony_ci *
20662306a36Sopenharmony_ci * On exynos5420 there is a silicon errata that will sometimes leave the
20762306a36Sopenharmony_ci * WAKEUP_INT bit in the CLKSEL register asserted.  This bit is 1 to indicate
20862306a36Sopenharmony_ci * that it fired and we can clear it by writing a 1 back.  Clear it to prevent
20962306a36Sopenharmony_ci * interrupts from going off constantly.
21062306a36Sopenharmony_ci *
21162306a36Sopenharmony_ci * We run this code on all exynos variants because it doesn't hurt.
21262306a36Sopenharmony_ci */
21362306a36Sopenharmony_cistatic int dw_mci_exynos_resume_noirq(struct device *dev)
21462306a36Sopenharmony_ci{
21562306a36Sopenharmony_ci	struct dw_mci *host = dev_get_drvdata(dev);
21662306a36Sopenharmony_ci	struct dw_mci_exynos_priv_data *priv = host->priv;
21762306a36Sopenharmony_ci	u32 clksel;
21862306a36Sopenharmony_ci	int ret;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	ret = pm_runtime_force_resume(dev);
22162306a36Sopenharmony_ci	if (ret)
22262306a36Sopenharmony_ci		return ret;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
22562306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU ||
22662306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_ARTPEC8)
22762306a36Sopenharmony_ci		clksel = mci_readl(host, CLKSEL64);
22862306a36Sopenharmony_ci	else
22962306a36Sopenharmony_ci		clksel = mci_readl(host, CLKSEL);
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	if (clksel & SDMMC_CLKSEL_WAKEUP_INT) {
23262306a36Sopenharmony_ci		if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
23362306a36Sopenharmony_ci			priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU ||
23462306a36Sopenharmony_ci			priv->ctrl_type == DW_MCI_TYPE_ARTPEC8)
23562306a36Sopenharmony_ci			mci_writel(host, CLKSEL64, clksel);
23662306a36Sopenharmony_ci		else
23762306a36Sopenharmony_ci			mci_writel(host, CLKSEL, clksel);
23862306a36Sopenharmony_ci	}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	pm_runtime_put(dev);
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	return 0;
24362306a36Sopenharmony_ci}
24462306a36Sopenharmony_ci#endif /* CONFIG_PM_SLEEP */
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_cistatic void dw_mci_exynos_config_hs400(struct dw_mci *host, u32 timing)
24762306a36Sopenharmony_ci{
24862306a36Sopenharmony_ci	struct dw_mci_exynos_priv_data *priv = host->priv;
24962306a36Sopenharmony_ci	u32 dqs, strobe;
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	/*
25262306a36Sopenharmony_ci	 * Not supported to configure register
25362306a36Sopenharmony_ci	 * related to HS400
25462306a36Sopenharmony_ci	 */
25562306a36Sopenharmony_ci	if ((priv->ctrl_type < DW_MCI_TYPE_EXYNOS5420) ||
25662306a36Sopenharmony_ci		(priv->ctrl_type == DW_MCI_TYPE_ARTPEC8)) {
25762306a36Sopenharmony_ci		if (timing == MMC_TIMING_MMC_HS400)
25862306a36Sopenharmony_ci			dev_warn(host->dev,
25962306a36Sopenharmony_ci				 "cannot configure HS400, unsupported chipset\n");
26062306a36Sopenharmony_ci		return;
26162306a36Sopenharmony_ci	}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	dqs = priv->saved_dqs_en;
26462306a36Sopenharmony_ci	strobe = priv->saved_strobe_ctrl;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	if (timing == MMC_TIMING_MMC_HS400) {
26762306a36Sopenharmony_ci		dqs |= DATA_STROBE_EN;
26862306a36Sopenharmony_ci		strobe = DQS_CTRL_RD_DELAY(strobe, priv->dqs_delay);
26962306a36Sopenharmony_ci	} else if (timing == MMC_TIMING_UHS_SDR104) {
27062306a36Sopenharmony_ci		dqs &= 0xffffff00;
27162306a36Sopenharmony_ci	} else {
27262306a36Sopenharmony_ci		dqs &= ~DATA_STROBE_EN;
27362306a36Sopenharmony_ci	}
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	mci_writel(host, HS400_DQS_EN, dqs);
27662306a36Sopenharmony_ci	mci_writel(host, HS400_DLINE_CTRL, strobe);
27762306a36Sopenharmony_ci}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_cistatic void dw_mci_exynos_adjust_clock(struct dw_mci *host, unsigned int wanted)
28062306a36Sopenharmony_ci{
28162306a36Sopenharmony_ci	struct dw_mci_exynos_priv_data *priv = host->priv;
28262306a36Sopenharmony_ci	unsigned long actual;
28362306a36Sopenharmony_ci	u8 div;
28462306a36Sopenharmony_ci	int ret;
28562306a36Sopenharmony_ci	/*
28662306a36Sopenharmony_ci	 * Don't care if wanted clock is zero or
28762306a36Sopenharmony_ci	 * ciu clock is unavailable
28862306a36Sopenharmony_ci	 */
28962306a36Sopenharmony_ci	if (!wanted || IS_ERR(host->ciu_clk))
29062306a36Sopenharmony_ci		return;
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	/* Guaranteed minimum frequency for cclkin */
29362306a36Sopenharmony_ci	if (wanted < EXYNOS_CCLKIN_MIN)
29462306a36Sopenharmony_ci		wanted = EXYNOS_CCLKIN_MIN;
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	if (wanted == priv->cur_speed)
29762306a36Sopenharmony_ci		return;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	div = dw_mci_exynos_get_ciu_div(host);
30062306a36Sopenharmony_ci	ret = clk_set_rate(host->ciu_clk, wanted * div);
30162306a36Sopenharmony_ci	if (ret)
30262306a36Sopenharmony_ci		dev_warn(host->dev,
30362306a36Sopenharmony_ci			"failed to set clk-rate %u error: %d\n",
30462306a36Sopenharmony_ci			wanted * div, ret);
30562306a36Sopenharmony_ci	actual = clk_get_rate(host->ciu_clk);
30662306a36Sopenharmony_ci	host->bus_hz = actual / div;
30762306a36Sopenharmony_ci	priv->cur_speed = wanted;
30862306a36Sopenharmony_ci	host->current_speed = 0;
30962306a36Sopenharmony_ci}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_cistatic void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios)
31262306a36Sopenharmony_ci{
31362306a36Sopenharmony_ci	struct dw_mci_exynos_priv_data *priv = host->priv;
31462306a36Sopenharmony_ci	unsigned int wanted = ios->clock;
31562306a36Sopenharmony_ci	u32 timing = ios->timing, clksel;
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	switch (timing) {
31862306a36Sopenharmony_ci	case MMC_TIMING_MMC_HS400:
31962306a36Sopenharmony_ci		/* Update tuned sample timing */
32062306a36Sopenharmony_ci		clksel = SDMMC_CLKSEL_UP_SAMPLE(
32162306a36Sopenharmony_ci				priv->hs400_timing, priv->tuned_sample);
32262306a36Sopenharmony_ci		wanted <<= 1;
32362306a36Sopenharmony_ci		break;
32462306a36Sopenharmony_ci	case MMC_TIMING_MMC_DDR52:
32562306a36Sopenharmony_ci		clksel = priv->ddr_timing;
32662306a36Sopenharmony_ci		/* Should be double rate for DDR mode */
32762306a36Sopenharmony_ci		if (ios->bus_width == MMC_BUS_WIDTH_8)
32862306a36Sopenharmony_ci			wanted <<= 1;
32962306a36Sopenharmony_ci		break;
33062306a36Sopenharmony_ci	case MMC_TIMING_UHS_SDR104:
33162306a36Sopenharmony_ci	case MMC_TIMING_UHS_SDR50:
33262306a36Sopenharmony_ci		clksel = (priv->sdr_timing & 0xfff8ffff) |
33362306a36Sopenharmony_ci			(priv->ciu_div << 16);
33462306a36Sopenharmony_ci		break;
33562306a36Sopenharmony_ci	case MMC_TIMING_UHS_DDR50:
33662306a36Sopenharmony_ci		clksel = (priv->ddr_timing & 0xfff8ffff) |
33762306a36Sopenharmony_ci			(priv->ciu_div << 16);
33862306a36Sopenharmony_ci		break;
33962306a36Sopenharmony_ci	default:
34062306a36Sopenharmony_ci		clksel = priv->sdr_timing;
34162306a36Sopenharmony_ci	}
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	/* Set clock timing for the requested speed mode*/
34462306a36Sopenharmony_ci	dw_mci_exynos_set_clksel_timing(host, clksel);
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci	/* Configure setting for HS400 */
34762306a36Sopenharmony_ci	dw_mci_exynos_config_hs400(host, timing);
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci	/* Configure clock rate */
35062306a36Sopenharmony_ci	dw_mci_exynos_adjust_clock(host, wanted);
35162306a36Sopenharmony_ci}
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_cistatic int dw_mci_exynos_parse_dt(struct dw_mci *host)
35462306a36Sopenharmony_ci{
35562306a36Sopenharmony_ci	struct dw_mci_exynos_priv_data *priv;
35662306a36Sopenharmony_ci	struct device_node *np = host->dev->of_node;
35762306a36Sopenharmony_ci	u32 timing[2];
35862306a36Sopenharmony_ci	u32 div = 0;
35962306a36Sopenharmony_ci	int idx;
36062306a36Sopenharmony_ci	int ret;
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
36362306a36Sopenharmony_ci	if (!priv)
36462306a36Sopenharmony_ci		return -ENOMEM;
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci	for (idx = 0; idx < ARRAY_SIZE(exynos_compat); idx++) {
36762306a36Sopenharmony_ci		if (of_device_is_compatible(np, exynos_compat[idx].compatible))
36862306a36Sopenharmony_ci			priv->ctrl_type = exynos_compat[idx].ctrl_type;
36962306a36Sopenharmony_ci	}
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412)
37262306a36Sopenharmony_ci		priv->ciu_div = EXYNOS4412_FIXED_CIU_CLK_DIV - 1;
37362306a36Sopenharmony_ci	else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210)
37462306a36Sopenharmony_ci		priv->ciu_div = EXYNOS4210_FIXED_CIU_CLK_DIV - 1;
37562306a36Sopenharmony_ci	else {
37662306a36Sopenharmony_ci		of_property_read_u32(np, "samsung,dw-mshc-ciu-div", &div);
37762306a36Sopenharmony_ci		priv->ciu_div = div;
37862306a36Sopenharmony_ci	}
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	ret = of_property_read_u32_array(np,
38162306a36Sopenharmony_ci			"samsung,dw-mshc-sdr-timing", timing, 2);
38262306a36Sopenharmony_ci	if (ret)
38362306a36Sopenharmony_ci		return ret;
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	priv->sdr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	ret = of_property_read_u32_array(np,
38862306a36Sopenharmony_ci			"samsung,dw-mshc-ddr-timing", timing, 2);
38962306a36Sopenharmony_ci	if (ret)
39062306a36Sopenharmony_ci		return ret;
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	priv->ddr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ci	ret = of_property_read_u32_array(np,
39562306a36Sopenharmony_ci			"samsung,dw-mshc-hs400-timing", timing, 2);
39662306a36Sopenharmony_ci	if (!ret && of_property_read_u32(np,
39762306a36Sopenharmony_ci				"samsung,read-strobe-delay", &priv->dqs_delay))
39862306a36Sopenharmony_ci		dev_dbg(host->dev,
39962306a36Sopenharmony_ci			"read-strobe-delay is not found, assuming usage of default value\n");
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	priv->hs400_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1],
40262306a36Sopenharmony_ci						HS400_FIXED_CIU_CLK_DIV);
40362306a36Sopenharmony_ci	host->priv = priv;
40462306a36Sopenharmony_ci	return 0;
40562306a36Sopenharmony_ci}
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_cistatic inline u8 dw_mci_exynos_get_clksmpl(struct dw_mci *host)
40862306a36Sopenharmony_ci{
40962306a36Sopenharmony_ci	struct dw_mci_exynos_priv_data *priv = host->priv;
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
41262306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU ||
41362306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_ARTPEC8)
41462306a36Sopenharmony_ci		return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL64));
41562306a36Sopenharmony_ci	else
41662306a36Sopenharmony_ci		return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL));
41762306a36Sopenharmony_ci}
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_cistatic inline void dw_mci_exynos_set_clksmpl(struct dw_mci *host, u8 sample)
42062306a36Sopenharmony_ci{
42162306a36Sopenharmony_ci	u32 clksel;
42262306a36Sopenharmony_ci	struct dw_mci_exynos_priv_data *priv = host->priv;
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
42562306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU ||
42662306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_ARTPEC8)
42762306a36Sopenharmony_ci		clksel = mci_readl(host, CLKSEL64);
42862306a36Sopenharmony_ci	else
42962306a36Sopenharmony_ci		clksel = mci_readl(host, CLKSEL);
43062306a36Sopenharmony_ci	clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample);
43162306a36Sopenharmony_ci	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
43262306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU ||
43362306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_ARTPEC8)
43462306a36Sopenharmony_ci		mci_writel(host, CLKSEL64, clksel);
43562306a36Sopenharmony_ci	else
43662306a36Sopenharmony_ci		mci_writel(host, CLKSEL, clksel);
43762306a36Sopenharmony_ci}
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_cistatic inline u8 dw_mci_exynos_move_next_clksmpl(struct dw_mci *host)
44062306a36Sopenharmony_ci{
44162306a36Sopenharmony_ci	struct dw_mci_exynos_priv_data *priv = host->priv;
44262306a36Sopenharmony_ci	u32 clksel;
44362306a36Sopenharmony_ci	u8 sample;
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
44662306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU ||
44762306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_ARTPEC8)
44862306a36Sopenharmony_ci		clksel = mci_readl(host, CLKSEL64);
44962306a36Sopenharmony_ci	else
45062306a36Sopenharmony_ci		clksel = mci_readl(host, CLKSEL);
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci	sample = (clksel + 1) & 0x7;
45362306a36Sopenharmony_ci	clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample);
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
45662306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU ||
45762306a36Sopenharmony_ci		priv->ctrl_type == DW_MCI_TYPE_ARTPEC8)
45862306a36Sopenharmony_ci		mci_writel(host, CLKSEL64, clksel);
45962306a36Sopenharmony_ci	else
46062306a36Sopenharmony_ci		mci_writel(host, CLKSEL, clksel);
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci	return sample;
46362306a36Sopenharmony_ci}
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_cistatic s8 dw_mci_exynos_get_best_clksmpl(u8 candidates)
46662306a36Sopenharmony_ci{
46762306a36Sopenharmony_ci	const u8 iter = 8;
46862306a36Sopenharmony_ci	u8 __c;
46962306a36Sopenharmony_ci	s8 i, loc = -1;
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci	for (i = 0; i < iter; i++) {
47262306a36Sopenharmony_ci		__c = ror8(candidates, i);
47362306a36Sopenharmony_ci		if ((__c & 0xc7) == 0xc7) {
47462306a36Sopenharmony_ci			loc = i;
47562306a36Sopenharmony_ci			goto out;
47662306a36Sopenharmony_ci		}
47762306a36Sopenharmony_ci	}
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci	for (i = 0; i < iter; i++) {
48062306a36Sopenharmony_ci		__c = ror8(candidates, i);
48162306a36Sopenharmony_ci		if ((__c & 0x83) == 0x83) {
48262306a36Sopenharmony_ci			loc = i;
48362306a36Sopenharmony_ci			goto out;
48462306a36Sopenharmony_ci		}
48562306a36Sopenharmony_ci	}
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci	/*
48862306a36Sopenharmony_ci	 * If there is no cadiates value, then it needs to return -EIO.
48962306a36Sopenharmony_ci	 * If there are candidates values and don't find bset clk sample value,
49062306a36Sopenharmony_ci	 * then use a first candidates clock sample value.
49162306a36Sopenharmony_ci	 */
49262306a36Sopenharmony_ci	for (i = 0; i < iter; i++) {
49362306a36Sopenharmony_ci		__c = ror8(candidates, i);
49462306a36Sopenharmony_ci		if ((__c & 0x1) == 0x1) {
49562306a36Sopenharmony_ci			loc = i;
49662306a36Sopenharmony_ci			goto out;
49762306a36Sopenharmony_ci		}
49862306a36Sopenharmony_ci	}
49962306a36Sopenharmony_ciout:
50062306a36Sopenharmony_ci	return loc;
50162306a36Sopenharmony_ci}
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_cistatic int dw_mci_exynos_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
50462306a36Sopenharmony_ci{
50562306a36Sopenharmony_ci	struct dw_mci *host = slot->host;
50662306a36Sopenharmony_ci	struct dw_mci_exynos_priv_data *priv = host->priv;
50762306a36Sopenharmony_ci	struct mmc_host *mmc = slot->mmc;
50862306a36Sopenharmony_ci	u8 start_smpl, smpl, candidates = 0;
50962306a36Sopenharmony_ci	s8 found;
51062306a36Sopenharmony_ci	int ret = 0;
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci	start_smpl = dw_mci_exynos_get_clksmpl(host);
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci	do {
51562306a36Sopenharmony_ci		mci_writel(host, TMOUT, ~0);
51662306a36Sopenharmony_ci		smpl = dw_mci_exynos_move_next_clksmpl(host);
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci		if (!mmc_send_tuning(mmc, opcode, NULL))
51962306a36Sopenharmony_ci			candidates |= (1 << smpl);
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	} while (start_smpl != smpl);
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	found = dw_mci_exynos_get_best_clksmpl(candidates);
52462306a36Sopenharmony_ci	if (found >= 0) {
52562306a36Sopenharmony_ci		dw_mci_exynos_set_clksmpl(host, found);
52662306a36Sopenharmony_ci		priv->tuned_sample = found;
52762306a36Sopenharmony_ci	} else {
52862306a36Sopenharmony_ci		ret = -EIO;
52962306a36Sopenharmony_ci		dev_warn(&mmc->class_dev,
53062306a36Sopenharmony_ci			"There is no candidates value about clksmpl!\n");
53162306a36Sopenharmony_ci	}
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	return ret;
53462306a36Sopenharmony_ci}
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_cistatic int dw_mci_exynos_prepare_hs400_tuning(struct dw_mci *host,
53762306a36Sopenharmony_ci					struct mmc_ios *ios)
53862306a36Sopenharmony_ci{
53962306a36Sopenharmony_ci	struct dw_mci_exynos_priv_data *priv = host->priv;
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	dw_mci_exynos_set_clksel_timing(host, priv->hs400_timing);
54262306a36Sopenharmony_ci	dw_mci_exynos_adjust_clock(host, (ios->clock) << 1);
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_ci	return 0;
54562306a36Sopenharmony_ci}
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_cistatic void dw_mci_exynos_set_data_timeout(struct dw_mci *host,
54862306a36Sopenharmony_ci					   unsigned int timeout_ns)
54962306a36Sopenharmony_ci{
55062306a36Sopenharmony_ci	u32 clk_div, tmout;
55162306a36Sopenharmony_ci	u64 tmp;
55262306a36Sopenharmony_ci	unsigned int tmp2;
55362306a36Sopenharmony_ci
55462306a36Sopenharmony_ci	clk_div = (mci_readl(host, CLKDIV) & 0xFF) * 2;
55562306a36Sopenharmony_ci	if (clk_div == 0)
55662306a36Sopenharmony_ci		clk_div = 1;
55762306a36Sopenharmony_ci
55862306a36Sopenharmony_ci	tmp = DIV_ROUND_UP_ULL((u64)timeout_ns * host->bus_hz, NSEC_PER_SEC);
55962306a36Sopenharmony_ci	tmp = DIV_ROUND_UP_ULL(tmp, clk_div);
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	/* TMOUT[7:0] (RESPONSE_TIMEOUT) */
56262306a36Sopenharmony_ci	tmout = 0xFF; /* Set maximum */
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	/*
56562306a36Sopenharmony_ci	 * Extended HW timer (max = 0x6FFFFF2):
56662306a36Sopenharmony_ci	 * ((TMOUT[10:8] - 1) * 0xFFFFFF + TMOUT[31:11] * 8)
56762306a36Sopenharmony_ci	 */
56862306a36Sopenharmony_ci	if (!tmp || tmp > 0x6FFFFF2)
56962306a36Sopenharmony_ci		tmout |= (0xFFFFFF << 8);
57062306a36Sopenharmony_ci	else {
57162306a36Sopenharmony_ci		/* TMOUT[10:8] */
57262306a36Sopenharmony_ci		tmp2 = (((unsigned int)tmp / 0xFFFFFF) + 1) & 0x7;
57362306a36Sopenharmony_ci		tmout |= tmp2 << 8;
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_ci		/* TMOUT[31:11] */
57662306a36Sopenharmony_ci		tmp = tmp - ((tmp2 - 1) * 0xFFFFFF);
57762306a36Sopenharmony_ci		tmout |= (tmp & 0xFFFFF8) << 8;
57862306a36Sopenharmony_ci	}
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	mci_writel(host, TMOUT, tmout);
58162306a36Sopenharmony_ci	dev_dbg(host->dev, "timeout_ns: %u => TMOUT[31:8]: %#08x",
58262306a36Sopenharmony_ci		timeout_ns, tmout >> 8);
58362306a36Sopenharmony_ci}
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_cistatic u32 dw_mci_exynos_get_drto_clks(struct dw_mci *host)
58662306a36Sopenharmony_ci{
58762306a36Sopenharmony_ci	u32 drto_clks;
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci	drto_clks = mci_readl(host, TMOUT) >> 8;
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_ci	return (((drto_clks & 0x7) - 1) * 0xFFFFFF) + ((drto_clks & 0xFFFFF8));
59262306a36Sopenharmony_ci}
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ci/* Common capabilities of Exynos4/Exynos5 SoC */
59562306a36Sopenharmony_cistatic unsigned long exynos_dwmmc_caps[4] = {
59662306a36Sopenharmony_ci	MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA,
59762306a36Sopenharmony_ci	0,
59862306a36Sopenharmony_ci	0,
59962306a36Sopenharmony_ci	0,
60062306a36Sopenharmony_ci};
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_cistatic const struct dw_mci_drv_data exynos_drv_data = {
60362306a36Sopenharmony_ci	.caps			= exynos_dwmmc_caps,
60462306a36Sopenharmony_ci	.num_caps		= ARRAY_SIZE(exynos_dwmmc_caps),
60562306a36Sopenharmony_ci	.common_caps		= MMC_CAP_CMD23,
60662306a36Sopenharmony_ci	.init			= dw_mci_exynos_priv_init,
60762306a36Sopenharmony_ci	.set_ios		= dw_mci_exynos_set_ios,
60862306a36Sopenharmony_ci	.parse_dt		= dw_mci_exynos_parse_dt,
60962306a36Sopenharmony_ci	.execute_tuning		= dw_mci_exynos_execute_tuning,
61062306a36Sopenharmony_ci	.prepare_hs400_tuning	= dw_mci_exynos_prepare_hs400_tuning,
61162306a36Sopenharmony_ci};
61262306a36Sopenharmony_ci
61362306a36Sopenharmony_cistatic const struct dw_mci_drv_data artpec_drv_data = {
61462306a36Sopenharmony_ci	.common_caps		= MMC_CAP_CMD23,
61562306a36Sopenharmony_ci	.init			= dw_mci_exynos_priv_init,
61662306a36Sopenharmony_ci	.set_ios		= dw_mci_exynos_set_ios,
61762306a36Sopenharmony_ci	.parse_dt		= dw_mci_exynos_parse_dt,
61862306a36Sopenharmony_ci	.execute_tuning		= dw_mci_exynos_execute_tuning,
61962306a36Sopenharmony_ci	.set_data_timeout		= dw_mci_exynos_set_data_timeout,
62062306a36Sopenharmony_ci	.get_drto_clks		= dw_mci_exynos_get_drto_clks,
62162306a36Sopenharmony_ci};
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_cistatic const struct of_device_id dw_mci_exynos_match[] = {
62462306a36Sopenharmony_ci	{ .compatible = "samsung,exynos4412-dw-mshc",
62562306a36Sopenharmony_ci			.data = &exynos_drv_data, },
62662306a36Sopenharmony_ci	{ .compatible = "samsung,exynos5250-dw-mshc",
62762306a36Sopenharmony_ci			.data = &exynos_drv_data, },
62862306a36Sopenharmony_ci	{ .compatible = "samsung,exynos5420-dw-mshc",
62962306a36Sopenharmony_ci			.data = &exynos_drv_data, },
63062306a36Sopenharmony_ci	{ .compatible = "samsung,exynos5420-dw-mshc-smu",
63162306a36Sopenharmony_ci			.data = &exynos_drv_data, },
63262306a36Sopenharmony_ci	{ .compatible = "samsung,exynos7-dw-mshc",
63362306a36Sopenharmony_ci			.data = &exynos_drv_data, },
63462306a36Sopenharmony_ci	{ .compatible = "samsung,exynos7-dw-mshc-smu",
63562306a36Sopenharmony_ci			.data = &exynos_drv_data, },
63662306a36Sopenharmony_ci	{ .compatible = "axis,artpec8-dw-mshc",
63762306a36Sopenharmony_ci			.data = &artpec_drv_data, },
63862306a36Sopenharmony_ci	{},
63962306a36Sopenharmony_ci};
64062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, dw_mci_exynos_match);
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_cistatic int dw_mci_exynos_probe(struct platform_device *pdev)
64362306a36Sopenharmony_ci{
64462306a36Sopenharmony_ci	const struct dw_mci_drv_data *drv_data;
64562306a36Sopenharmony_ci	const struct of_device_id *match;
64662306a36Sopenharmony_ci	int ret;
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	match = of_match_node(dw_mci_exynos_match, pdev->dev.of_node);
64962306a36Sopenharmony_ci	drv_data = match->data;
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_ci	pm_runtime_get_noresume(&pdev->dev);
65262306a36Sopenharmony_ci	pm_runtime_set_active(&pdev->dev);
65362306a36Sopenharmony_ci	pm_runtime_enable(&pdev->dev);
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	ret = dw_mci_pltfm_register(pdev, drv_data);
65662306a36Sopenharmony_ci	if (ret) {
65762306a36Sopenharmony_ci		pm_runtime_disable(&pdev->dev);
65862306a36Sopenharmony_ci		pm_runtime_set_suspended(&pdev->dev);
65962306a36Sopenharmony_ci		pm_runtime_put_noidle(&pdev->dev);
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci		return ret;
66262306a36Sopenharmony_ci	}
66362306a36Sopenharmony_ci
66462306a36Sopenharmony_ci	return 0;
66562306a36Sopenharmony_ci}
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_cistatic void dw_mci_exynos_remove(struct platform_device *pdev)
66862306a36Sopenharmony_ci{
66962306a36Sopenharmony_ci	pm_runtime_disable(&pdev->dev);
67062306a36Sopenharmony_ci	pm_runtime_set_suspended(&pdev->dev);
67162306a36Sopenharmony_ci	pm_runtime_put_noidle(&pdev->dev);
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_ci	dw_mci_pltfm_remove(pdev);
67462306a36Sopenharmony_ci}
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_cistatic const struct dev_pm_ops dw_mci_exynos_pmops = {
67762306a36Sopenharmony_ci	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(dw_mci_exynos_suspend_noirq,
67862306a36Sopenharmony_ci				      dw_mci_exynos_resume_noirq)
67962306a36Sopenharmony_ci	SET_RUNTIME_PM_OPS(dw_mci_runtime_suspend,
68062306a36Sopenharmony_ci			   dw_mci_exynos_runtime_resume,
68162306a36Sopenharmony_ci			   NULL)
68262306a36Sopenharmony_ci};
68362306a36Sopenharmony_ci
68462306a36Sopenharmony_cistatic struct platform_driver dw_mci_exynos_pltfm_driver = {
68562306a36Sopenharmony_ci	.probe		= dw_mci_exynos_probe,
68662306a36Sopenharmony_ci	.remove_new	= dw_mci_exynos_remove,
68762306a36Sopenharmony_ci	.driver		= {
68862306a36Sopenharmony_ci		.name		= "dwmmc_exynos",
68962306a36Sopenharmony_ci		.probe_type	= PROBE_PREFER_ASYNCHRONOUS,
69062306a36Sopenharmony_ci		.of_match_table	= dw_mci_exynos_match,
69162306a36Sopenharmony_ci		.pm		= &dw_mci_exynos_pmops,
69262306a36Sopenharmony_ci	},
69362306a36Sopenharmony_ci};
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_cimodule_platform_driver(dw_mci_exynos_pltfm_driver);
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_ciMODULE_DESCRIPTION("Samsung Specific DW-MSHC Driver Extension");
69862306a36Sopenharmony_ciMODULE_AUTHOR("Thomas Abraham <thomas.ab@samsung.com");
69962306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
70062306a36Sopenharmony_ciMODULE_ALIAS("platform:dwmmc_exynos");
701