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