linux/drivers/mmc/host/dw_mmc-exynos.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Exynos Specific Extensions for Synopsys DW Multimedia Card Interface driver
   4 *
   5 * Copyright (C) 2012, Samsung Electronics Co., Ltd.
   6 */
   7
   8#include <linux/module.h>
   9#include <linux/platform_device.h>
  10#include <linux/clk.h>
  11#include <linux/mmc/host.h>
  12#include <linux/mmc/mmc.h>
  13#include <linux/of.h>
  14#include <linux/of_gpio.h>
  15#include <linux/pm_runtime.h>
  16#include <linux/slab.h>
  17
  18#include "dw_mmc.h"
  19#include "dw_mmc-pltfm.h"
  20#include "dw_mmc-exynos.h"
  21
  22/* Variations in Exynos specific dw-mshc controller */
  23enum dw_mci_exynos_type {
  24        DW_MCI_TYPE_EXYNOS4210,
  25        DW_MCI_TYPE_EXYNOS4412,
  26        DW_MCI_TYPE_EXYNOS5250,
  27        DW_MCI_TYPE_EXYNOS5420,
  28        DW_MCI_TYPE_EXYNOS5420_SMU,
  29        DW_MCI_TYPE_EXYNOS7,
  30        DW_MCI_TYPE_EXYNOS7_SMU,
  31};
  32
  33/* Exynos implementation specific driver private data */
  34struct dw_mci_exynos_priv_data {
  35        enum dw_mci_exynos_type         ctrl_type;
  36        u8                              ciu_div;
  37        u32                             sdr_timing;
  38        u32                             ddr_timing;
  39        u32                             hs400_timing;
  40        u32                             tuned_sample;
  41        u32                             cur_speed;
  42        u32                             dqs_delay;
  43        u32                             saved_dqs_en;
  44        u32                             saved_strobe_ctrl;
  45};
  46
  47static struct dw_mci_exynos_compatible {
  48        char                            *compatible;
  49        enum dw_mci_exynos_type         ctrl_type;
  50} exynos_compat[] = {
  51        {
  52                .compatible     = "samsung,exynos4210-dw-mshc",
  53                .ctrl_type      = DW_MCI_TYPE_EXYNOS4210,
  54        }, {
  55                .compatible     = "samsung,exynos4412-dw-mshc",
  56                .ctrl_type      = DW_MCI_TYPE_EXYNOS4412,
  57        }, {
  58                .compatible     = "samsung,exynos5250-dw-mshc",
  59                .ctrl_type      = DW_MCI_TYPE_EXYNOS5250,
  60        }, {
  61                .compatible     = "samsung,exynos5420-dw-mshc",
  62                .ctrl_type      = DW_MCI_TYPE_EXYNOS5420,
  63        }, {
  64                .compatible     = "samsung,exynos5420-dw-mshc-smu",
  65                .ctrl_type      = DW_MCI_TYPE_EXYNOS5420_SMU,
  66        }, {
  67                .compatible     = "samsung,exynos7-dw-mshc",
  68                .ctrl_type      = DW_MCI_TYPE_EXYNOS7,
  69        }, {
  70                .compatible     = "samsung,exynos7-dw-mshc-smu",
  71                .ctrl_type      = DW_MCI_TYPE_EXYNOS7_SMU,
  72        },
  73};
  74
  75static inline u8 dw_mci_exynos_get_ciu_div(struct dw_mci *host)
  76{
  77        struct dw_mci_exynos_priv_data *priv = host->priv;
  78
  79        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412)
  80                return EXYNOS4412_FIXED_CIU_CLK_DIV;
  81        else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210)
  82                return EXYNOS4210_FIXED_CIU_CLK_DIV;
  83        else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
  84                        priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
  85                return SDMMC_CLKSEL_GET_DIV(mci_readl(host, CLKSEL64)) + 1;
  86        else
  87                return SDMMC_CLKSEL_GET_DIV(mci_readl(host, CLKSEL)) + 1;
  88}
  89
  90static void dw_mci_exynos_config_smu(struct dw_mci *host)
  91{
  92        struct dw_mci_exynos_priv_data *priv = host->priv;
  93
  94        /*
  95         * If Exynos is provided the Security management,
  96         * set for non-ecryption mode at this time.
  97         */
  98        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS5420_SMU ||
  99                priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU) {
 100                mci_writel(host, MPSBEGIN0, 0);
 101                mci_writel(host, MPSEND0, SDMMC_ENDING_SEC_NR_MAX);
 102                mci_writel(host, MPSCTRL0, SDMMC_MPSCTRL_SECURE_WRITE_BIT |
 103                           SDMMC_MPSCTRL_NON_SECURE_READ_BIT |
 104                           SDMMC_MPSCTRL_VALID |
 105                           SDMMC_MPSCTRL_NON_SECURE_WRITE_BIT);
 106        }
 107}
 108
 109static int dw_mci_exynos_priv_init(struct dw_mci *host)
 110{
 111        struct dw_mci_exynos_priv_data *priv = host->priv;
 112
 113        dw_mci_exynos_config_smu(host);
 114
 115        if (priv->ctrl_type >= DW_MCI_TYPE_EXYNOS5420) {
 116                priv->saved_strobe_ctrl = mci_readl(host, HS400_DLINE_CTRL);
 117                priv->saved_dqs_en = mci_readl(host, HS400_DQS_EN);
 118                priv->saved_dqs_en |= AXI_NON_BLOCKING_WR;
 119                mci_writel(host, HS400_DQS_EN, priv->saved_dqs_en);
 120                if (!priv->dqs_delay)
 121                        priv->dqs_delay =
 122                                DQS_CTRL_GET_RD_DELAY(priv->saved_strobe_ctrl);
 123        }
 124
 125        host->bus_hz /= (priv->ciu_div + 1);
 126
 127        return 0;
 128}
 129
 130static void dw_mci_exynos_set_clksel_timing(struct dw_mci *host, u32 timing)
 131{
 132        struct dw_mci_exynos_priv_data *priv = host->priv;
 133        u32 clksel;
 134
 135        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 136                priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 137                clksel = mci_readl(host, CLKSEL64);
 138        else
 139                clksel = mci_readl(host, CLKSEL);
 140
 141        clksel = (clksel & ~SDMMC_CLKSEL_TIMING_MASK) | timing;
 142
 143        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 144                priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 145                mci_writel(host, CLKSEL64, clksel);
 146        else
 147                mci_writel(host, CLKSEL, clksel);
 148
 149        /*
 150         * Exynos4412 and Exynos5250 extends the use of CMD register with the
 151         * use of bit 29 (which is reserved on standard MSHC controllers) for
 152         * optionally bypassing the HOLD register for command and data. The
 153         * HOLD register should be bypassed in case there is no phase shift
 154         * applied on CMD/DATA that is sent to the card.
 155         */
 156        if (!SDMMC_CLKSEL_GET_DRV_WD3(clksel) && host->slot)
 157                set_bit(DW_MMC_CARD_NO_USE_HOLD, &host->slot->flags);
 158}
 159
 160#ifdef CONFIG_PM
 161static int dw_mci_exynos_runtime_resume(struct device *dev)
 162{
 163        struct dw_mci *host = dev_get_drvdata(dev);
 164        int ret;
 165
 166        ret = dw_mci_runtime_resume(dev);
 167        if (ret)
 168                return ret;
 169
 170        dw_mci_exynos_config_smu(host);
 171
 172        return ret;
 173}
 174#endif /* CONFIG_PM */
 175
 176#ifdef CONFIG_PM_SLEEP
 177/**
 178 * dw_mci_exynos_suspend_noirq - Exynos-specific suspend code
 179 * @dev: Device to suspend (this device)
 180 *
 181 * This ensures that device will be in runtime active state in
 182 * dw_mci_exynos_resume_noirq after calling pm_runtime_force_resume()
 183 */
 184static int dw_mci_exynos_suspend_noirq(struct device *dev)
 185{
 186        pm_runtime_get_noresume(dev);
 187        return pm_runtime_force_suspend(dev);
 188}
 189
 190/**
 191 * dw_mci_exynos_resume_noirq - Exynos-specific resume code
 192 * @dev: Device to resume (this device)
 193 *
 194 * On exynos5420 there is a silicon errata that will sometimes leave the
 195 * WAKEUP_INT bit in the CLKSEL register asserted.  This bit is 1 to indicate
 196 * that it fired and we can clear it by writing a 1 back.  Clear it to prevent
 197 * interrupts from going off constantly.
 198 *
 199 * We run this code on all exynos variants because it doesn't hurt.
 200 */
 201static int dw_mci_exynos_resume_noirq(struct device *dev)
 202{
 203        struct dw_mci *host = dev_get_drvdata(dev);
 204        struct dw_mci_exynos_priv_data *priv = host->priv;
 205        u32 clksel;
 206        int ret;
 207
 208        ret = pm_runtime_force_resume(dev);
 209        if (ret)
 210                return ret;
 211
 212        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 213                priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 214                clksel = mci_readl(host, CLKSEL64);
 215        else
 216                clksel = mci_readl(host, CLKSEL);
 217
 218        if (clksel & SDMMC_CLKSEL_WAKEUP_INT) {
 219                if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 220                        priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 221                        mci_writel(host, CLKSEL64, clksel);
 222                else
 223                        mci_writel(host, CLKSEL, clksel);
 224        }
 225
 226        pm_runtime_put(dev);
 227
 228        return 0;
 229}
 230#endif /* CONFIG_PM_SLEEP */
 231
 232static void dw_mci_exynos_config_hs400(struct dw_mci *host, u32 timing)
 233{
 234        struct dw_mci_exynos_priv_data *priv = host->priv;
 235        u32 dqs, strobe;
 236
 237        /*
 238         * Not supported to configure register
 239         * related to HS400
 240         */
 241        if (priv->ctrl_type < DW_MCI_TYPE_EXYNOS5420) {
 242                if (timing == MMC_TIMING_MMC_HS400)
 243                        dev_warn(host->dev,
 244                                 "cannot configure HS400, unsupported chipset\n");
 245                return;
 246        }
 247
 248        dqs = priv->saved_dqs_en;
 249        strobe = priv->saved_strobe_ctrl;
 250
 251        if (timing == MMC_TIMING_MMC_HS400) {
 252                dqs |= DATA_STROBE_EN;
 253                strobe = DQS_CTRL_RD_DELAY(strobe, priv->dqs_delay);
 254        } else if (timing == MMC_TIMING_UHS_SDR104) {
 255                dqs &= 0xffffff00;
 256        } else {
 257                dqs &= ~DATA_STROBE_EN;
 258        }
 259
 260        mci_writel(host, HS400_DQS_EN, dqs);
 261        mci_writel(host, HS400_DLINE_CTRL, strobe);
 262}
 263
 264static void dw_mci_exynos_adjust_clock(struct dw_mci *host, unsigned int wanted)
 265{
 266        struct dw_mci_exynos_priv_data *priv = host->priv;
 267        unsigned long actual;
 268        u8 div;
 269        int ret;
 270        /*
 271         * Don't care if wanted clock is zero or
 272         * ciu clock is unavailable
 273         */
 274        if (!wanted || IS_ERR(host->ciu_clk))
 275                return;
 276
 277        /* Guaranteed minimum frequency for cclkin */
 278        if (wanted < EXYNOS_CCLKIN_MIN)
 279                wanted = EXYNOS_CCLKIN_MIN;
 280
 281        if (wanted == priv->cur_speed)
 282                return;
 283
 284        div = dw_mci_exynos_get_ciu_div(host);
 285        ret = clk_set_rate(host->ciu_clk, wanted * div);
 286        if (ret)
 287                dev_warn(host->dev,
 288                        "failed to set clk-rate %u error: %d\n",
 289                        wanted * div, ret);
 290        actual = clk_get_rate(host->ciu_clk);
 291        host->bus_hz = actual / div;
 292        priv->cur_speed = wanted;
 293        host->current_speed = 0;
 294}
 295
 296static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios)
 297{
 298        struct dw_mci_exynos_priv_data *priv = host->priv;
 299        unsigned int wanted = ios->clock;
 300        u32 timing = ios->timing, clksel;
 301
 302        switch (timing) {
 303        case MMC_TIMING_MMC_HS400:
 304                /* Update tuned sample timing */
 305                clksel = SDMMC_CLKSEL_UP_SAMPLE(
 306                                priv->hs400_timing, priv->tuned_sample);
 307                wanted <<= 1;
 308                break;
 309        case MMC_TIMING_MMC_DDR52:
 310                clksel = priv->ddr_timing;
 311                /* Should be double rate for DDR mode */
 312                if (ios->bus_width == MMC_BUS_WIDTH_8)
 313                        wanted <<= 1;
 314                break;
 315        case MMC_TIMING_UHS_SDR104:
 316        case MMC_TIMING_UHS_SDR50:
 317                clksel = (priv->sdr_timing & 0xfff8ffff) |
 318                        (priv->ciu_div << 16);
 319                break;
 320        case MMC_TIMING_UHS_DDR50:
 321                clksel = (priv->ddr_timing & 0xfff8ffff) |
 322                        (priv->ciu_div << 16);
 323                break;
 324        default:
 325                clksel = priv->sdr_timing;
 326        }
 327
 328        /* Set clock timing for the requested speed mode*/
 329        dw_mci_exynos_set_clksel_timing(host, clksel);
 330
 331        /* Configure setting for HS400 */
 332        dw_mci_exynos_config_hs400(host, timing);
 333
 334        /* Configure clock rate */
 335        dw_mci_exynos_adjust_clock(host, wanted);
 336}
 337
 338static int dw_mci_exynos_parse_dt(struct dw_mci *host)
 339{
 340        struct dw_mci_exynos_priv_data *priv;
 341        struct device_node *np = host->dev->of_node;
 342        u32 timing[2];
 343        u32 div = 0;
 344        int idx;
 345        int ret;
 346
 347        priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
 348        if (!priv)
 349                return -ENOMEM;
 350
 351        for (idx = 0; idx < ARRAY_SIZE(exynos_compat); idx++) {
 352                if (of_device_is_compatible(np, exynos_compat[idx].compatible))
 353                        priv->ctrl_type = exynos_compat[idx].ctrl_type;
 354        }
 355
 356        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412)
 357                priv->ciu_div = EXYNOS4412_FIXED_CIU_CLK_DIV - 1;
 358        else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210)
 359                priv->ciu_div = EXYNOS4210_FIXED_CIU_CLK_DIV - 1;
 360        else {
 361                of_property_read_u32(np, "samsung,dw-mshc-ciu-div", &div);
 362                priv->ciu_div = div;
 363        }
 364
 365        ret = of_property_read_u32_array(np,
 366                        "samsung,dw-mshc-sdr-timing", timing, 2);
 367        if (ret)
 368                return ret;
 369
 370        priv->sdr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
 371
 372        ret = of_property_read_u32_array(np,
 373                        "samsung,dw-mshc-ddr-timing", timing, 2);
 374        if (ret)
 375                return ret;
 376
 377        priv->ddr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
 378
 379        ret = of_property_read_u32_array(np,
 380                        "samsung,dw-mshc-hs400-timing", timing, 2);
 381        if (!ret && of_property_read_u32(np,
 382                                "samsung,read-strobe-delay", &priv->dqs_delay))
 383                dev_dbg(host->dev,
 384                        "read-strobe-delay is not found, assuming usage of default value\n");
 385
 386        priv->hs400_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1],
 387                                                HS400_FIXED_CIU_CLK_DIV);
 388        host->priv = priv;
 389        return 0;
 390}
 391
 392static inline u8 dw_mci_exynos_get_clksmpl(struct dw_mci *host)
 393{
 394        struct dw_mci_exynos_priv_data *priv = host->priv;
 395
 396        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 397                priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 398                return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL64));
 399        else
 400                return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL));
 401}
 402
 403static inline void dw_mci_exynos_set_clksmpl(struct dw_mci *host, u8 sample)
 404{
 405        u32 clksel;
 406        struct dw_mci_exynos_priv_data *priv = host->priv;
 407
 408        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 409                priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 410                clksel = mci_readl(host, CLKSEL64);
 411        else
 412                clksel = mci_readl(host, CLKSEL);
 413        clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample);
 414        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 415                priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 416                mci_writel(host, CLKSEL64, clksel);
 417        else
 418                mci_writel(host, CLKSEL, clksel);
 419}
 420
 421static inline u8 dw_mci_exynos_move_next_clksmpl(struct dw_mci *host)
 422{
 423        struct dw_mci_exynos_priv_data *priv = host->priv;
 424        u32 clksel;
 425        u8 sample;
 426
 427        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 428                priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 429                clksel = mci_readl(host, CLKSEL64);
 430        else
 431                clksel = mci_readl(host, CLKSEL);
 432
 433        sample = (clksel + 1) & 0x7;
 434        clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample);
 435
 436        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 437                priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 438                mci_writel(host, CLKSEL64, clksel);
 439        else
 440                mci_writel(host, CLKSEL, clksel);
 441
 442        return sample;
 443}
 444
 445static s8 dw_mci_exynos_get_best_clksmpl(u8 candiates)
 446{
 447        const u8 iter = 8;
 448        u8 __c;
 449        s8 i, loc = -1;
 450
 451        for (i = 0; i < iter; i++) {
 452                __c = ror8(candiates, i);
 453                if ((__c & 0xc7) == 0xc7) {
 454                        loc = i;
 455                        goto out;
 456                }
 457        }
 458
 459        for (i = 0; i < iter; i++) {
 460                __c = ror8(candiates, i);
 461                if ((__c & 0x83) == 0x83) {
 462                        loc = i;
 463                        goto out;
 464                }
 465        }
 466
 467        /*
 468         * If there is no cadiates value, then it needs to return -EIO.
 469         * If there are candiates values and don't find bset clk sample value,
 470         * then use a first candiates clock sample value.
 471         */
 472        for (i = 0; i < iter; i++) {
 473                __c = ror8(candiates, i);
 474                if ((__c & 0x1) == 0x1) {
 475                        loc = i;
 476                        goto out;
 477                }
 478        }
 479out:
 480        return loc;
 481}
 482
 483static int dw_mci_exynos_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
 484{
 485        struct dw_mci *host = slot->host;
 486        struct dw_mci_exynos_priv_data *priv = host->priv;
 487        struct mmc_host *mmc = slot->mmc;
 488        u8 start_smpl, smpl, candiates = 0;
 489        s8 found;
 490        int ret = 0;
 491
 492        start_smpl = dw_mci_exynos_get_clksmpl(host);
 493
 494        do {
 495                mci_writel(host, TMOUT, ~0);
 496                smpl = dw_mci_exynos_move_next_clksmpl(host);
 497
 498                if (!mmc_send_tuning(mmc, opcode, NULL))
 499                        candiates |= (1 << smpl);
 500
 501        } while (start_smpl != smpl);
 502
 503        found = dw_mci_exynos_get_best_clksmpl(candiates);
 504        if (found >= 0) {
 505                dw_mci_exynos_set_clksmpl(host, found);
 506                priv->tuned_sample = found;
 507        } else {
 508                ret = -EIO;
 509                dev_warn(&mmc->class_dev,
 510                        "There is no candiates value about clksmpl!\n");
 511        }
 512
 513        return ret;
 514}
 515
 516static int dw_mci_exynos_prepare_hs400_tuning(struct dw_mci *host,
 517                                        struct mmc_ios *ios)
 518{
 519        struct dw_mci_exynos_priv_data *priv = host->priv;
 520
 521        dw_mci_exynos_set_clksel_timing(host, priv->hs400_timing);
 522        dw_mci_exynos_adjust_clock(host, (ios->clock) << 1);
 523
 524        return 0;
 525}
 526
 527/* Common capabilities of Exynos4/Exynos5 SoC */
 528static unsigned long exynos_dwmmc_caps[4] = {
 529        MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23,
 530        MMC_CAP_CMD23,
 531        MMC_CAP_CMD23,
 532        MMC_CAP_CMD23,
 533};
 534
 535static const struct dw_mci_drv_data exynos_drv_data = {
 536        .caps                   = exynos_dwmmc_caps,
 537        .num_caps               = ARRAY_SIZE(exynos_dwmmc_caps),
 538        .init                   = dw_mci_exynos_priv_init,
 539        .set_ios                = dw_mci_exynos_set_ios,
 540        .parse_dt               = dw_mci_exynos_parse_dt,
 541        .execute_tuning         = dw_mci_exynos_execute_tuning,
 542        .prepare_hs400_tuning   = dw_mci_exynos_prepare_hs400_tuning,
 543};
 544
 545static const struct of_device_id dw_mci_exynos_match[] = {
 546        { .compatible = "samsung,exynos4412-dw-mshc",
 547                        .data = &exynos_drv_data, },
 548        { .compatible = "samsung,exynos5250-dw-mshc",
 549                        .data = &exynos_drv_data, },
 550        { .compatible = "samsung,exynos5420-dw-mshc",
 551                        .data = &exynos_drv_data, },
 552        { .compatible = "samsung,exynos5420-dw-mshc-smu",
 553                        .data = &exynos_drv_data, },
 554        { .compatible = "samsung,exynos7-dw-mshc",
 555                        .data = &exynos_drv_data, },
 556        { .compatible = "samsung,exynos7-dw-mshc-smu",
 557                        .data = &exynos_drv_data, },
 558        {},
 559};
 560MODULE_DEVICE_TABLE(of, dw_mci_exynos_match);
 561
 562static int dw_mci_exynos_probe(struct platform_device *pdev)
 563{
 564        const struct dw_mci_drv_data *drv_data;
 565        const struct of_device_id *match;
 566        int ret;
 567
 568        match = of_match_node(dw_mci_exynos_match, pdev->dev.of_node);
 569        drv_data = match->data;
 570
 571        pm_runtime_get_noresume(&pdev->dev);
 572        pm_runtime_set_active(&pdev->dev);
 573        pm_runtime_enable(&pdev->dev);
 574
 575        ret = dw_mci_pltfm_register(pdev, drv_data);
 576        if (ret) {
 577                pm_runtime_disable(&pdev->dev);
 578                pm_runtime_set_suspended(&pdev->dev);
 579                pm_runtime_put_noidle(&pdev->dev);
 580
 581                return ret;
 582        }
 583
 584        return 0;
 585}
 586
 587static int dw_mci_exynos_remove(struct platform_device *pdev)
 588{
 589        pm_runtime_disable(&pdev->dev);
 590        pm_runtime_set_suspended(&pdev->dev);
 591        pm_runtime_put_noidle(&pdev->dev);
 592
 593        return dw_mci_pltfm_remove(pdev);
 594}
 595
 596static const struct dev_pm_ops dw_mci_exynos_pmops = {
 597        SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(dw_mci_exynos_suspend_noirq,
 598                                      dw_mci_exynos_resume_noirq)
 599        SET_RUNTIME_PM_OPS(dw_mci_runtime_suspend,
 600                           dw_mci_exynos_runtime_resume,
 601                           NULL)
 602};
 603
 604static struct platform_driver dw_mci_exynos_pltfm_driver = {
 605        .probe          = dw_mci_exynos_probe,
 606        .remove         = dw_mci_exynos_remove,
 607        .driver         = {
 608                .name           = "dwmmc_exynos",
 609                .probe_type     = PROBE_PREFER_ASYNCHRONOUS,
 610                .of_match_table = dw_mci_exynos_match,
 611                .pm             = &dw_mci_exynos_pmops,
 612        },
 613};
 614
 615module_platform_driver(dw_mci_exynos_pltfm_driver);
 616
 617MODULE_DESCRIPTION("Samsung Specific DW-MSHC Driver Extension");
 618MODULE_AUTHOR("Thomas Abraham <thomas.ab@samsung.com");
 619MODULE_LICENSE("GPL v2");
 620MODULE_ALIAS("platform:dwmmc_exynos");
 621