0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014 #include <linux/acpi.h>
0015 #include <linux/delay.h>
0016 #include <linux/ktime.h>
0017 #include <linux/module.h>
0018 #include <linux/of.h>
0019 #include <linux/pm.h>
0020 #include <linux/pm_runtime.h>
0021
0022 #include "sdhci-pltfm.h"
0023 #include "sdhci-xenon.h"
0024
0025 static int xenon_enable_internal_clk(struct sdhci_host *host)
0026 {
0027 u32 reg;
0028 ktime_t timeout;
0029
0030 reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
0031 reg |= SDHCI_CLOCK_INT_EN;
0032 sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
0033
0034 timeout = ktime_add_ms(ktime_get(), 20);
0035 while (1) {
0036 bool timedout = ktime_after(ktime_get(), timeout);
0037
0038 reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
0039 if (reg & SDHCI_CLOCK_INT_STABLE)
0040 break;
0041 if (timedout) {
0042 dev_err(mmc_dev(host->mmc), "Internal clock never stabilised.\n");
0043 return -ETIMEDOUT;
0044 }
0045 usleep_range(900, 1100);
0046 }
0047
0048 return 0;
0049 }
0050
0051
0052 static void xenon_set_sdclk_off_idle(struct sdhci_host *host,
0053 unsigned char sdhc_id, bool enable)
0054 {
0055 u32 reg;
0056 u32 mask;
0057
0058 reg = sdhci_readl(host, XENON_SYS_OP_CTRL);
0059
0060 mask = (0x1 << (XENON_SDCLK_IDLEOFF_ENABLE_SHIFT + sdhc_id));
0061 if (enable)
0062 reg |= mask;
0063 else
0064 reg &= ~mask;
0065
0066 sdhci_writel(host, reg, XENON_SYS_OP_CTRL);
0067 }
0068
0069
0070 static void xenon_set_acg(struct sdhci_host *host, bool enable)
0071 {
0072 u32 reg;
0073
0074 reg = sdhci_readl(host, XENON_SYS_OP_CTRL);
0075 if (enable)
0076 reg &= ~XENON_AUTO_CLKGATE_DISABLE_MASK;
0077 else
0078 reg |= XENON_AUTO_CLKGATE_DISABLE_MASK;
0079 sdhci_writel(host, reg, XENON_SYS_OP_CTRL);
0080 }
0081
0082
0083 static void xenon_enable_sdhc(struct sdhci_host *host,
0084 unsigned char sdhc_id)
0085 {
0086 u32 reg;
0087
0088 reg = sdhci_readl(host, XENON_SYS_OP_CTRL);
0089 reg |= (BIT(sdhc_id) << XENON_SLOT_ENABLE_SHIFT);
0090 sdhci_writel(host, reg, XENON_SYS_OP_CTRL);
0091
0092 host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
0093
0094
0095
0096
0097 host->mmc->caps &= ~MMC_CAP_BUS_WIDTH_TEST;
0098 }
0099
0100
0101 static void xenon_disable_sdhc(struct sdhci_host *host,
0102 unsigned char sdhc_id)
0103 {
0104 u32 reg;
0105
0106 reg = sdhci_readl(host, XENON_SYS_OP_CTRL);
0107 reg &= ~(BIT(sdhc_id) << XENON_SLOT_ENABLE_SHIFT);
0108 sdhci_writel(host, reg, XENON_SYS_OP_CTRL);
0109 }
0110
0111
0112 static void xenon_enable_sdhc_parallel_tran(struct sdhci_host *host,
0113 unsigned char sdhc_id)
0114 {
0115 u32 reg;
0116
0117 reg = sdhci_readl(host, XENON_SYS_EXT_OP_CTRL);
0118 reg |= BIT(sdhc_id);
0119 sdhci_writel(host, reg, XENON_SYS_EXT_OP_CTRL);
0120 }
0121
0122
0123 static void xenon_mask_cmd_conflict_err(struct sdhci_host *host)
0124 {
0125 u32 reg;
0126
0127 reg = sdhci_readl(host, XENON_SYS_EXT_OP_CTRL);
0128 reg |= XENON_MASK_CMD_CONFLICT_ERR;
0129 sdhci_writel(host, reg, XENON_SYS_EXT_OP_CTRL);
0130 }
0131
0132 static void xenon_retune_setup(struct sdhci_host *host)
0133 {
0134 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
0135 struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
0136 u32 reg;
0137
0138
0139 reg = sdhci_readl(host, XENON_SLOT_RETUNING_REQ_CTRL);
0140 reg &= ~XENON_RETUNING_COMPATIBLE;
0141 sdhci_writel(host, reg, XENON_SLOT_RETUNING_REQ_CTRL);
0142
0143
0144 reg = sdhci_readl(host, SDHCI_SIGNAL_ENABLE);
0145 reg &= ~SDHCI_INT_RETUNE;
0146 sdhci_writel(host, reg, SDHCI_SIGNAL_ENABLE);
0147 reg = sdhci_readl(host, SDHCI_INT_ENABLE);
0148 reg &= ~SDHCI_INT_RETUNE;
0149 sdhci_writel(host, reg, SDHCI_INT_ENABLE);
0150
0151
0152 host->tuning_mode = SDHCI_TUNING_MODE_1;
0153
0154 host->tuning_count = 1 << (priv->tuning_count - 1);
0155 }
0156
0157
0158
0159
0160
0161 static void xenon_reset_exit(struct sdhci_host *host,
0162 unsigned char sdhc_id, u8 mask)
0163 {
0164
0165 if (!(mask & SDHCI_RESET_ALL))
0166 return;
0167
0168
0169 xenon_retune_setup(host);
0170
0171
0172
0173
0174
0175
0176 xenon_set_acg(host, false);
0177
0178 xenon_set_sdclk_off_idle(host, sdhc_id, false);
0179
0180 xenon_mask_cmd_conflict_err(host);
0181 }
0182
0183 static void xenon_reset(struct sdhci_host *host, u8 mask)
0184 {
0185 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
0186 struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
0187
0188 sdhci_reset(host, mask);
0189 xenon_reset_exit(host, priv->sdhc_id, mask);
0190 }
0191
0192
0193
0194
0195
0196 static void xenon_set_uhs_signaling(struct sdhci_host *host,
0197 unsigned int timing)
0198 {
0199 u16 ctrl_2;
0200
0201 ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
0202
0203 ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
0204 if (timing == MMC_TIMING_MMC_HS200)
0205 ctrl_2 |= XENON_CTRL_HS200;
0206 else if (timing == MMC_TIMING_UHS_SDR104)
0207 ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
0208 else if (timing == MMC_TIMING_UHS_SDR12)
0209 ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
0210 else if (timing == MMC_TIMING_UHS_SDR25)
0211 ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
0212 else if (timing == MMC_TIMING_UHS_SDR50)
0213 ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
0214 else if ((timing == MMC_TIMING_UHS_DDR50) ||
0215 (timing == MMC_TIMING_MMC_DDR52))
0216 ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
0217 else if (timing == MMC_TIMING_MMC_HS400)
0218 ctrl_2 |= XENON_CTRL_HS400;
0219 sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
0220 }
0221
0222 static void xenon_set_power(struct sdhci_host *host, unsigned char mode,
0223 unsigned short vdd)
0224 {
0225 struct mmc_host *mmc = host->mmc;
0226 u8 pwr = host->pwr;
0227
0228 sdhci_set_power_noreg(host, mode, vdd);
0229
0230 if (host->pwr == pwr)
0231 return;
0232
0233 if (host->pwr == 0)
0234 vdd = 0;
0235
0236 if (!IS_ERR(mmc->supply.vmmc))
0237 mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd);
0238 }
0239
0240 static void xenon_voltage_switch(struct sdhci_host *host)
0241 {
0242
0243 usleep_range(5000, 5500);
0244 }
0245
0246 static unsigned int xenon_get_max_clock(struct sdhci_host *host)
0247 {
0248 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
0249
0250 if (pltfm_host->clk)
0251 return sdhci_pltfm_clk_get_max_clock(host);
0252 else
0253 return pltfm_host->clock;
0254 }
0255
0256 static const struct sdhci_ops sdhci_xenon_ops = {
0257 .voltage_switch = xenon_voltage_switch,
0258 .set_clock = sdhci_set_clock,
0259 .set_power = xenon_set_power,
0260 .set_bus_width = sdhci_set_bus_width,
0261 .reset = xenon_reset,
0262 .set_uhs_signaling = xenon_set_uhs_signaling,
0263 .get_max_clock = xenon_get_max_clock,
0264 };
0265
0266 static const struct sdhci_pltfm_data sdhci_xenon_pdata = {
0267 .ops = &sdhci_xenon_ops,
0268 .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC |
0269 SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER |
0270 SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
0271 };
0272
0273
0274
0275
0276 static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
0277 {
0278 struct sdhci_host *host = mmc_priv(mmc);
0279 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
0280 struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
0281 u32 reg;
0282
0283
0284
0285
0286
0287
0288
0289
0290 if ((ios->timing == MMC_TIMING_MMC_HS400) ||
0291 (ios->timing == MMC_TIMING_MMC_HS200) ||
0292 (ios->timing == MMC_TIMING_MMC_HS)) {
0293 host->preset_enabled = false;
0294 host->quirks2 |= SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
0295 host->flags &= ~SDHCI_PV_ENABLED;
0296
0297 reg = sdhci_readw(host, SDHCI_HOST_CONTROL2);
0298 reg &= ~SDHCI_CTRL_PRESET_VAL_ENABLE;
0299 sdhci_writew(host, reg, SDHCI_HOST_CONTROL2);
0300 } else {
0301 host->quirks2 &= ~SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
0302 }
0303
0304 sdhci_set_ios(mmc, ios);
0305 xenon_phy_adj(host, ios);
0306
0307 if (host->clock > XENON_DEFAULT_SDCLK_FREQ)
0308 xenon_set_sdclk_off_idle(host, priv->sdhc_id, true);
0309 }
0310
0311 static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
0312 struct mmc_ios *ios)
0313 {
0314 struct sdhci_host *host = mmc_priv(mmc);
0315
0316
0317
0318
0319
0320
0321
0322
0323
0324
0325
0326 xenon_enable_internal_clk(host);
0327
0328 xenon_soc_pad_ctrl(host, ios->signal_voltage);
0329
0330
0331
0332
0333
0334
0335 if (PTR_ERR(mmc->supply.vqmmc) == -ENODEV)
0336 return 0;
0337
0338 return sdhci_start_signal_voltage_switch(mmc, ios);
0339 }
0340
0341
0342
0343
0344
0345 static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
0346 {
0347 struct sdhci_host *host = mmc_priv(mmc);
0348 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
0349 struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
0350
0351
0352 priv->init_card_type = card->type;
0353 }
0354
0355 static int xenon_execute_tuning(struct mmc_host *mmc, u32 opcode)
0356 {
0357 struct sdhci_host *host = mmc_priv(mmc);
0358
0359 if (host->timing == MMC_TIMING_UHS_DDR50 ||
0360 host->timing == MMC_TIMING_MMC_DDR52)
0361 return 0;
0362
0363
0364
0365
0366
0367
0368 if (host->tuning_mode != SDHCI_TUNING_MODE_1)
0369 xenon_retune_setup(host);
0370
0371 return sdhci_execute_tuning(mmc, opcode);
0372 }
0373
0374 static void xenon_enable_sdio_irq(struct mmc_host *mmc, int enable)
0375 {
0376 struct sdhci_host *host = mmc_priv(mmc);
0377 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
0378 struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
0379 u32 reg;
0380 u8 sdhc_id = priv->sdhc_id;
0381
0382 sdhci_enable_sdio_irq(mmc, enable);
0383
0384 if (enable) {
0385
0386
0387
0388
0389 reg = sdhci_readl(host, XENON_SYS_CFG_INFO);
0390 reg |= (1 << (sdhc_id + XENON_SLOT_TYPE_SDIO_SHIFT));
0391 sdhci_writel(host, reg, XENON_SYS_CFG_INFO);
0392 } else {
0393
0394 reg = sdhci_readl(host, XENON_SYS_CFG_INFO);
0395 reg &= ~(1 << (sdhc_id + XENON_SLOT_TYPE_SDIO_SHIFT));
0396 sdhci_writel(host, reg, XENON_SYS_CFG_INFO);
0397 }
0398 }
0399
0400 static void xenon_replace_mmc_host_ops(struct sdhci_host *host)
0401 {
0402 host->mmc_host_ops.set_ios = xenon_set_ios;
0403 host->mmc_host_ops.start_signal_voltage_switch =
0404 xenon_start_signal_voltage_switch;
0405 host->mmc_host_ops.init_card = xenon_init_card;
0406 host->mmc_host_ops.execute_tuning = xenon_execute_tuning;
0407 host->mmc_host_ops.enable_sdio_irq = xenon_enable_sdio_irq;
0408 }
0409
0410
0411
0412
0413
0414
0415
0416 static int xenon_probe_params(struct platform_device *pdev)
0417 {
0418 struct device *dev = &pdev->dev;
0419 struct sdhci_host *host = platform_get_drvdata(pdev);
0420 struct mmc_host *mmc = host->mmc;
0421 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
0422 struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
0423 u32 sdhc_id, nr_sdhc;
0424 u32 tuning_count;
0425
0426
0427 if (priv->hw_version == XENON_AP806)
0428 host->quirks2 |= SDHCI_QUIRK2_BROKEN_HS200;
0429
0430 sdhc_id = 0x0;
0431 if (!device_property_read_u32(dev, "marvell,xenon-sdhc-id", &sdhc_id)) {
0432 nr_sdhc = sdhci_readl(host, XENON_SYS_CFG_INFO);
0433 nr_sdhc &= XENON_NR_SUPPORTED_SLOT_MASK;
0434 if (unlikely(sdhc_id > nr_sdhc)) {
0435 dev_err(mmc_dev(mmc), "SDHC Index %d exceeds Number of SDHCs %d\n",
0436 sdhc_id, nr_sdhc);
0437 return -EINVAL;
0438 }
0439 }
0440 priv->sdhc_id = sdhc_id;
0441
0442 tuning_count = XENON_DEF_TUNING_COUNT;
0443 if (!device_property_read_u32(dev, "marvell,xenon-tun-count",
0444 &tuning_count)) {
0445 if (unlikely(tuning_count >= XENON_TMR_RETUN_NO_PRESENT)) {
0446 dev_err(mmc_dev(mmc), "Wrong Re-tuning Count. Set default value %d\n",
0447 XENON_DEF_TUNING_COUNT);
0448 tuning_count = XENON_DEF_TUNING_COUNT;
0449 }
0450 }
0451 priv->tuning_count = tuning_count;
0452
0453 return xenon_phy_parse_params(dev, host);
0454 }
0455
0456 static int xenon_sdhc_prepare(struct sdhci_host *host)
0457 {
0458 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
0459 struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
0460 u8 sdhc_id = priv->sdhc_id;
0461
0462
0463 xenon_enable_sdhc(host, sdhc_id);
0464
0465
0466 xenon_set_acg(host, true);
0467
0468
0469 xenon_enable_sdhc_parallel_tran(host, sdhc_id);
0470
0471
0472 xenon_set_sdclk_off_idle(host, sdhc_id, false);
0473
0474 xenon_mask_cmd_conflict_err(host);
0475
0476 return 0;
0477 }
0478
0479 static void xenon_sdhc_unprepare(struct sdhci_host *host)
0480 {
0481 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
0482 struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
0483 u8 sdhc_id = priv->sdhc_id;
0484
0485
0486 xenon_disable_sdhc(host, sdhc_id);
0487 }
0488
0489 static int xenon_probe(struct platform_device *pdev)
0490 {
0491 struct sdhci_pltfm_host *pltfm_host;
0492 struct device *dev = &pdev->dev;
0493 struct sdhci_host *host;
0494 struct xenon_priv *priv;
0495 int err;
0496
0497 host = sdhci_pltfm_init(pdev, &sdhci_xenon_pdata,
0498 sizeof(struct xenon_priv));
0499 if (IS_ERR(host))
0500 return PTR_ERR(host);
0501
0502 pltfm_host = sdhci_priv(host);
0503 priv = sdhci_pltfm_priv(pltfm_host);
0504
0505 priv->hw_version = (unsigned long)device_get_match_data(&pdev->dev);
0506
0507
0508
0509
0510
0511 xenon_replace_mmc_host_ops(host);
0512
0513 if (dev->of_node) {
0514 pltfm_host->clk = devm_clk_get(&pdev->dev, "core");
0515 if (IS_ERR(pltfm_host->clk)) {
0516 err = PTR_ERR(pltfm_host->clk);
0517 dev_err(&pdev->dev, "Failed to setup input clk: %d\n", err);
0518 goto free_pltfm;
0519 }
0520 err = clk_prepare_enable(pltfm_host->clk);
0521 if (err)
0522 goto free_pltfm;
0523
0524 priv->axi_clk = devm_clk_get(&pdev->dev, "axi");
0525 if (IS_ERR(priv->axi_clk)) {
0526 err = PTR_ERR(priv->axi_clk);
0527 if (err == -EPROBE_DEFER)
0528 goto err_clk;
0529 } else {
0530 err = clk_prepare_enable(priv->axi_clk);
0531 if (err)
0532 goto err_clk;
0533 }
0534 }
0535
0536 err = mmc_of_parse(host->mmc);
0537 if (err)
0538 goto err_clk_axi;
0539
0540 sdhci_get_property(pdev);
0541
0542 xenon_set_acg(host, false);
0543
0544
0545 err = xenon_probe_params(pdev);
0546 if (err)
0547 goto err_clk_axi;
0548
0549 err = xenon_sdhc_prepare(host);
0550 if (err)
0551 goto err_clk_axi;
0552
0553 pm_runtime_get_noresume(&pdev->dev);
0554 pm_runtime_set_active(&pdev->dev);
0555 pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
0556 pm_runtime_use_autosuspend(&pdev->dev);
0557 pm_runtime_enable(&pdev->dev);
0558 pm_suspend_ignore_children(&pdev->dev, 1);
0559
0560 err = sdhci_add_host(host);
0561 if (err)
0562 goto remove_sdhc;
0563
0564 pm_runtime_put_autosuspend(&pdev->dev);
0565
0566 return 0;
0567
0568 remove_sdhc:
0569 pm_runtime_disable(&pdev->dev);
0570 pm_runtime_put_noidle(&pdev->dev);
0571 xenon_sdhc_unprepare(host);
0572 err_clk_axi:
0573 clk_disable_unprepare(priv->axi_clk);
0574 err_clk:
0575 clk_disable_unprepare(pltfm_host->clk);
0576 free_pltfm:
0577 sdhci_pltfm_free(pdev);
0578 return err;
0579 }
0580
0581 static int xenon_remove(struct platform_device *pdev)
0582 {
0583 struct sdhci_host *host = platform_get_drvdata(pdev);
0584 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
0585 struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
0586
0587 pm_runtime_get_sync(&pdev->dev);
0588 pm_runtime_disable(&pdev->dev);
0589 pm_runtime_put_noidle(&pdev->dev);
0590
0591 sdhci_remove_host(host, 0);
0592
0593 xenon_sdhc_unprepare(host);
0594 clk_disable_unprepare(priv->axi_clk);
0595 clk_disable_unprepare(pltfm_host->clk);
0596
0597 sdhci_pltfm_free(pdev);
0598
0599 return 0;
0600 }
0601
0602 #ifdef CONFIG_PM_SLEEP
0603 static int xenon_suspend(struct device *dev)
0604 {
0605 struct sdhci_host *host = dev_get_drvdata(dev);
0606 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
0607 struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
0608 int ret;
0609
0610 ret = pm_runtime_force_suspend(dev);
0611
0612 priv->restore_needed = true;
0613 return ret;
0614 }
0615 #endif
0616
0617 #ifdef CONFIG_PM
0618 static int xenon_runtime_suspend(struct device *dev)
0619 {
0620 struct sdhci_host *host = dev_get_drvdata(dev);
0621 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
0622 struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
0623 int ret;
0624
0625 ret = sdhci_runtime_suspend_host(host);
0626 if (ret)
0627 return ret;
0628
0629 if (host->tuning_mode != SDHCI_TUNING_MODE_3)
0630 mmc_retune_needed(host->mmc);
0631
0632 clk_disable_unprepare(pltfm_host->clk);
0633
0634
0635
0636
0637
0638 priv->clock = 0;
0639 return 0;
0640 }
0641
0642 static int xenon_runtime_resume(struct device *dev)
0643 {
0644 struct sdhci_host *host = dev_get_drvdata(dev);
0645 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
0646 struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
0647 int ret;
0648
0649 ret = clk_prepare_enable(pltfm_host->clk);
0650 if (ret) {
0651 dev_err(dev, "can't enable mainck\n");
0652 return ret;
0653 }
0654
0655 if (priv->restore_needed) {
0656 ret = xenon_sdhc_prepare(host);
0657 if (ret)
0658 goto out;
0659 priv->restore_needed = false;
0660 }
0661
0662 ret = sdhci_runtime_resume_host(host, 0);
0663 if (ret)
0664 goto out;
0665 return 0;
0666 out:
0667 clk_disable_unprepare(pltfm_host->clk);
0668 return ret;
0669 }
0670 #endif
0671
0672 static const struct dev_pm_ops sdhci_xenon_dev_pm_ops = {
0673 SET_SYSTEM_SLEEP_PM_OPS(xenon_suspend,
0674 pm_runtime_force_resume)
0675 SET_RUNTIME_PM_OPS(xenon_runtime_suspend,
0676 xenon_runtime_resume,
0677 NULL)
0678 };
0679
0680 static const struct of_device_id sdhci_xenon_dt_ids[] = {
0681 { .compatible = "marvell,armada-ap806-sdhci", .data = (void *)XENON_AP806},
0682 { .compatible = "marvell,armada-ap807-sdhci", .data = (void *)XENON_AP807},
0683 { .compatible = "marvell,armada-cp110-sdhci", .data = (void *)XENON_CP110},
0684 { .compatible = "marvell,armada-3700-sdhci", .data = (void *)XENON_A3700},
0685 {}
0686 };
0687 MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
0688
0689 #ifdef CONFIG_ACPI
0690 static const struct acpi_device_id sdhci_xenon_acpi_ids[] = {
0691 { .id = "MRVL0002", XENON_AP806},
0692 { .id = "MRVL0003", XENON_AP807},
0693 { .id = "MRVL0004", XENON_CP110},
0694 {}
0695 };
0696 MODULE_DEVICE_TABLE(acpi, sdhci_xenon_acpi_ids);
0697 #endif
0698
0699 static struct platform_driver sdhci_xenon_driver = {
0700 .driver = {
0701 .name = "xenon-sdhci",
0702 .probe_type = PROBE_PREFER_ASYNCHRONOUS,
0703 .of_match_table = sdhci_xenon_dt_ids,
0704 .acpi_match_table = ACPI_PTR(sdhci_xenon_acpi_ids),
0705 .pm = &sdhci_xenon_dev_pm_ops,
0706 },
0707 .probe = xenon_probe,
0708 .remove = xenon_remove,
0709 };
0710
0711 module_platform_driver(sdhci_xenon_driver);
0712
0713 MODULE_DESCRIPTION("SDHCI platform driver for Marvell Xenon SDHC");
0714 MODULE_AUTHOR("Hu Ziji <huziji@marvell.com>");
0715 MODULE_LICENSE("GPL v2");