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 *
 180 * This ensures that device will be in runtime active state in
 181 * dw_mci_exynos_resume_noirq after calling pm_runtime_force_resume()
 182 */
 183static int dw_mci_exynos_suspend_noirq(struct device *dev)
 184{
 185        pm_runtime_get_noresume(dev);
 186        return pm_runtime_force_suspend(dev);
 187}
 188
 189/**
 190 * dw_mci_exynos_resume_noirq - Exynos-specific resume code
 191 *
 192 * On exynos5420 there is a silicon errata that will sometimes leave the
 193 * WAKEUP_INT bit in the CLKSEL register asserted.  This bit is 1 to indicate
 194 * that it fired and we can clear it by writing a 1 back.  Clear it to prevent
 195 * interrupts from going off constantly.
 196 *
 197 * We run this code on all exynos variants because it doesn't hurt.
 198 */
 199static int dw_mci_exynos_resume_noirq(struct device *dev)
 200{
 201        struct dw_mci *host = dev_get_drvdata(dev);
 202        struct dw_mci_exynos_priv_data *priv = host->priv;
 203        u32 clksel;
 204        int ret;
 205
 206        ret = pm_runtime_force_resume(dev);
 207        if (ret)
 208                return ret;
 209
 210        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 211                priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 212                clksel = mci_readl(host, CLKSEL64);
 213        else
 214                clksel = mci_readl(host, CLKSEL);
 215
 216        if (clksel & SDMMC_CLKSEL_WAKEUP_INT) {
 217                if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 218                        priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 219                        mci_writel(host, CLKSEL64, clksel);
 220                else
 221                        mci_writel(host, CLKSEL, clksel);
 222        }
 223
 224        pm_runtime_put(dev);
 225
 226        return 0;
 227}
 228#endif /* CONFIG_PM_SLEEP */
 229
 230static void dw_mci_exynos_config_hs400(struct dw_mci *host, u32 timing)
 231{
 232        struct dw_mci_exynos_priv_data *priv = host->priv;
 233        u32 dqs, strobe;
 234
 235        /*
 236         * Not supported to configure register
 237         * related to HS400
 238         */
 239        if (priv->ctrl_type < DW_MCI_TYPE_EXYNOS5420) {
 240                if (timing == MMC_TIMING_MMC_HS400)
 241                        dev_warn(host->dev,
 242                                 "cannot configure HS400, unsupported chipset\n");
 243                return;
 244        }
 245
 246        dqs = priv->saved_dqs_en;
 247        strobe = priv->saved_strobe_ctrl;
 248
 249        if (timing == MMC_TIMING_MMC_HS400) {
 250                dqs |= DATA_STROBE_EN;
 251                strobe = DQS_CTRL_RD_DELAY(strobe, priv->dqs_delay);
 252        } else if (timing == MMC_TIMING_UHS_SDR104) {
 253                dqs &= 0xffffff00;
 254        } else {
 255                dqs &= ~DATA_STROBE_EN;
 256        }
 257
 258        mci_writel(host, HS400_DQS_EN, dqs);
 259        mci_writel(host, HS400_DLINE_CTRL, strobe);
 260}
 261
 262static void dw_mci_exynos_adjust_clock(struct dw_mci *host, unsigned int wanted)
 263{
 264        struct dw_mci_exynos_priv_data *priv = host->priv;
 265        unsigned long actual;
 266        u8 div;
 267        int ret;
 268        /*
 269         * Don't care if wanted clock is zero or
 270         * ciu clock is unavailable
 271         */
 272        if (!wanted || IS_ERR(host->ciu_clk))
 273                return;
 274
 275        /* Guaranteed minimum frequency for cclkin */
 276        if (wanted < EXYNOS_CCLKIN_MIN)
 277                wanted = EXYNOS_CCLKIN_MIN;
 278
 279        if (wanted == priv->cur_speed)
 280                return;
 281
 282        div = dw_mci_exynos_get_ciu_div(host);
 283        ret = clk_set_rate(host->ciu_clk, wanted * div);
 284        if (ret)
 285                dev_warn(host->dev,
 286                        "failed to set clk-rate %u error: %d\n",
 287                        wanted * div, ret);
 288        actual = clk_get_rate(host->ciu_clk);
 289        host->bus_hz = actual / div;
 290        priv->cur_speed = wanted;
 291        host->current_speed = 0;
 292}
 293
 294static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios)
 295{
 296        struct dw_mci_exynos_priv_data *priv = host->priv;
 297        unsigned int wanted = ios->clock;
 298        u32 timing = ios->timing, clksel;
 299
 300        switch (timing) {
 301        case MMC_TIMING_MMC_HS400:
 302                /* Update tuned sample timing */
 303                clksel = SDMMC_CLKSEL_UP_SAMPLE(
 304                                priv->hs400_timing, priv->tuned_sample);
 305                wanted <<= 1;
 306                break;
 307        case MMC_TIMING_MMC_DDR52:
 308                clksel = priv->ddr_timing;
 309                /* Should be double rate for DDR mode */
 310                if (ios->bus_width == MMC_BUS_WIDTH_8)
 311                        wanted <<= 1;
 312                break;
 313        case MMC_TIMING_UHS_SDR104:
 314        case MMC_TIMING_UHS_SDR50:
 315                clksel = (priv->sdr_timing & 0xfff8ffff) |
 316                        (priv->ciu_div << 16);
 317                break;
 318        case MMC_TIMING_UHS_DDR50:
 319                clksel = (priv->ddr_timing & 0xfff8ffff) |
 320                        (priv->ciu_div << 16);
 321                break;
 322        default:
 323                clksel = priv->sdr_timing;
 324        }
 325
 326        /* Set clock timing for the requested speed mode*/
 327        dw_mci_exynos_set_clksel_timing(host, clksel);
 328
 329        /* Configure setting for HS400 */
 330        dw_mci_exynos_config_hs400(host, timing);
 331
 332        /* Configure clock rate */
 333        dw_mci_exynos_adjust_clock(host, wanted);
 334}
 335
 336static int dw_mci_exynos_parse_dt(struct dw_mci *host)
 337{
 338        struct dw_mci_exynos_priv_data *priv;
 339        struct device_node *np = host->dev->of_node;
 340        u32 timing[2];
 341        u32 div = 0;
 342        int idx;
 343        int ret;
 344
 345        priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
 346        if (!priv)
 347                return -ENOMEM;
 348
 349        for (idx = 0; idx < ARRAY_SIZE(exynos_compat); idx++) {
 350                if (of_device_is_compatible(np, exynos_compat[idx].compatible))
 351                        priv->ctrl_type = exynos_compat[idx].ctrl_type;
 352        }
 353
 354        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412)
 355                priv->ciu_div = EXYNOS4412_FIXED_CIU_CLK_DIV - 1;
 356        else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210)
 357                priv->ciu_div = EXYNOS4210_FIXED_CIU_CLK_DIV - 1;
 358        else {
 359                of_property_read_u32(np, "samsung,dw-mshc-ciu-div", &div);
 360                priv->ciu_div = div;
 361        }
 362
 363        ret = of_property_read_u32_array(np,
 364                        "samsung,dw-mshc-sdr-timing", timing, 2);
 365        if (ret)
 366                return ret;
 367
 368        priv->sdr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
 369
 370        ret = of_property_read_u32_array(np,
 371                        "samsung,dw-mshc-ddr-timing", timing, 2);
 372        if (ret)
 373                return ret;
 374
 375        priv->ddr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
 376
 377        ret = of_property_read_u32_array(np,
 378                        "samsung,dw-mshc-hs400-timing", timing, 2);
 379        if (!ret && of_property_read_u32(np,
 380                                "samsung,read-strobe-delay", &priv->dqs_delay))
 381                dev_dbg(host->dev,
 382                        "read-strobe-delay is not found, assuming usage of default value\n");
 383
 384        priv->hs400_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1],
 385                                                HS400_FIXED_CIU_CLK_DIV);
 386        host->priv = priv;
 387        return 0;
 388}
 389
 390static inline u8 dw_mci_exynos_get_clksmpl(struct dw_mci *host)
 391{
 392        struct dw_mci_exynos_priv_data *priv = host->priv;
 393
 394        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 395                priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 396                return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL64));
 397        else
 398                return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL));
 399}
 400
 401static inline void dw_mci_exynos_set_clksmpl(struct dw_mci *host, u8 sample)
 402{
 403        u32 clksel;
 404        struct dw_mci_exynos_priv_data *priv = host->priv;
 405
 406        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 407                priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 408                clksel = mci_readl(host, CLKSEL64);
 409        else
 410                clksel = mci_readl(host, CLKSEL);
 411        clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample);
 412        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 413                priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 414                mci_writel(host, CLKSEL64, clksel);
 415        else
 416                mci_writel(host, CLKSEL, clksel);
 417}
 418
 419static inline u8 dw_mci_exynos_move_next_clksmpl(struct dw_mci *host)
 420{
 421        struct dw_mci_exynos_priv_data *priv = host->priv;
 422        u32 clksel;
 423        u8 sample;
 424
 425        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 426                priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 427                clksel = mci_readl(host, CLKSEL64);
 428        else
 429                clksel = mci_readl(host, CLKSEL);
 430
 431        sample = (clksel + 1) & 0x7;
 432        clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample);
 433
 434        if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
 435                priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
 436                mci_writel(host, CLKSEL64, clksel);
 437        else
 438                mci_writel(host, CLKSEL, clksel);
 439
 440        return sample;
 441}
 442
 443static s8 dw_mci_exynos_get_best_clksmpl(u8 candiates)
 444{
 445        const u8 iter = 8;
 446        u8 __c;
 447        s8 i, loc = -1;
 448
 449        for (i = 0; i < iter; i++) {
 450                __c = ror8(candiates, i);
 451                if ((__c & 0xc7) == 0xc7) {
 452                        loc = i;
 453                        goto out;
 454                }
 455        }
 456
 457        for (i = 0; i < iter; i++) {
 458                __c = ror8(candiates, i);
 459                if ((__c & 0x83) == 0x83) {
 460                        loc = i;
 461                        goto out;
 462                }
 463        }
 464
 465out:
 466        return loc;
 467}
 468
 469static int dw_mci_exynos_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
 470{
 471        struct dw_mci *host = slot->host;
 472        struct dw_mci_exynos_priv_data *priv = host->priv;
 473        struct mmc_host *mmc = slot->mmc;
 474        u8 start_smpl, smpl, candiates = 0;
 475        s8 found = -1;
 476        int ret = 0;
 477
 478        start_smpl = dw_mci_exynos_get_clksmpl(host);
 479
 480        do {
 481                mci_writel(host, TMOUT, ~0);
 482                smpl = dw_mci_exynos_move_next_clksmpl(host);
 483
 484                if (!mmc_send_tuning(mmc, opcode, NULL))
 485                        candiates |= (1 << smpl);
 486
 487        } while (start_smpl != smpl);
 488
 489        found = dw_mci_exynos_get_best_clksmpl(candiates);
 490        if (found >= 0) {
 491                dw_mci_exynos_set_clksmpl(host, found);
 492                priv->tuned_sample = found;
 493        } else {
 494                ret = -EIO;
 495        }
 496
 497        return ret;
 498}
 499
 500static int dw_mci_exynos_prepare_hs400_tuning(struct dw_mci *host,
 501                                        struct mmc_ios *ios)
 502{
 503        struct dw_mci_exynos_priv_data *priv = host->priv;
 504
 505        dw_mci_exynos_set_clksel_timing(host, priv->hs400_timing);
 506        dw_mci_exynos_adjust_clock(host, (ios->clock) << 1);
 507
 508        return 0;
 509}
 510
 511/* Common capabilities of Exynos4/Exynos5 SoC */
 512static unsigned long exynos_dwmmc_caps[4] = {
 513        MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23,
 514        MMC_CAP_CMD23,
 515        MMC_CAP_CMD23,
 516        MMC_CAP_CMD23,
 517};
 518
 519static const struct dw_mci_drv_data exynos_drv_data = {
 520        .caps                   = exynos_dwmmc_caps,
 521        .num_caps               = ARRAY_SIZE(exynos_dwmmc_caps),
 522        .init                   = dw_mci_exynos_priv_init,
 523        .set_ios                = dw_mci_exynos_set_ios,
 524        .parse_dt               = dw_mci_exynos_parse_dt,
 525        .execute_tuning         = dw_mci_exynos_execute_tuning,
 526        .prepare_hs400_tuning   = dw_mci_exynos_prepare_hs400_tuning,
 527};
 528
 529static const struct of_device_id dw_mci_exynos_match[] = {
 530        { .compatible = "samsung,exynos4412-dw-mshc",
 531                        .data = &exynos_drv_data, },
 532        { .compatible = "samsung,exynos5250-dw-mshc",
 533                        .data = &exynos_drv_data, },
 534        { .compatible = "samsung,exynos5420-dw-mshc",
 535                        .data = &exynos_drv_data, },
 536        { .compatible = "samsung,exynos5420-dw-mshc-smu",
 537                        .data = &exynos_drv_data, },
 538        { .compatible = "samsung,exynos7-dw-mshc",
 539                        .data = &exynos_drv_data, },
 540        { .compatible = "samsung,exynos7-dw-mshc-smu",
 541                        .data = &exynos_drv_data, },
 542        {},
 543};
 544MODULE_DEVICE_TABLE(of, dw_mci_exynos_match);
 545
 546static int dw_mci_exynos_probe(struct platform_device *pdev)
 547{
 548        const struct dw_mci_drv_data *drv_data;
 549        const struct of_device_id *match;
 550        int ret;
 551
 552        match = of_match_node(dw_mci_exynos_match, pdev->dev.of_node);
 553        drv_data = match->data;
 554
 555        pm_runtime_get_noresume(&pdev->dev);
 556        pm_runtime_set_active(&pdev->dev);
 557        pm_runtime_enable(&pdev->dev);
 558
 559        ret = dw_mci_pltfm_register(pdev, drv_data);
 560        if (ret) {
 561                pm_runtime_disable(&pdev->dev);
 562                pm_runtime_set_suspended(&pdev->dev);
 563                pm_runtime_put_noidle(&pdev->dev);
 564
 565                return ret;
 566        }
 567
 568        return 0;
 569}
 570
 571static int dw_mci_exynos_remove(struct platform_device *pdev)
 572{
 573        pm_runtime_disable(&pdev->dev);
 574        pm_runtime_set_suspended(&pdev->dev);
 575        pm_runtime_put_noidle(&pdev->dev);
 576
 577        return dw_mci_pltfm_remove(pdev);
 578}
 579
 580static const struct dev_pm_ops dw_mci_exynos_pmops = {
 581        SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(dw_mci_exynos_suspend_noirq,
 582                                      dw_mci_exynos_resume_noirq)
 583        SET_RUNTIME_PM_OPS(dw_mci_runtime_suspend,
 584                           dw_mci_exynos_runtime_resume,
 585                           NULL)
 586};
 587
 588static struct platform_driver dw_mci_exynos_pltfm_driver = {
 589        .probe          = dw_mci_exynos_probe,
 590        .remove         = dw_mci_exynos_remove,
 591        .driver         = {
 592                .name           = "dwmmc_exynos",
 593                .of_match_table = dw_mci_exynos_match,
 594                .pm             = &dw_mci_exynos_pmops,
 595        },
 596};
 597
 598module_platform_driver(dw_mci_exynos_pltfm_driver);
 599
 600MODULE_DESCRIPTION("Samsung Specific DW-MSHC Driver Extension");
 601MODULE_AUTHOR("Thomas Abraham <thomas.ab@samsung.com");
 602MODULE_LICENSE("GPL v2");
 603MODULE_ALIAS("platform:dwmmc_exynos");
 604