diff options
Diffstat (limited to 'drivers/mmc/host/sdhci-s3c.c')
-rw-r--r-- | drivers/mmc/host/sdhci-s3c.c | 159 |
1 files changed, 125 insertions, 34 deletions
diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index b19e7d435f8d..55a164fcaa15 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c | |||
@@ -20,6 +20,10 @@ | |||
20 | #include <linux/io.h> | 20 | #include <linux/io.h> |
21 | #include <linux/gpio.h> | 21 | #include <linux/gpio.h> |
22 | #include <linux/module.h> | 22 | #include <linux/module.h> |
23 | #include <linux/of.h> | ||
24 | #include <linux/of_gpio.h> | ||
25 | #include <linux/pm.h> | ||
26 | #include <linux/pm_runtime.h> | ||
23 | 27 | ||
24 | #include <linux/mmc/host.h> | 28 | #include <linux/mmc/host.h> |
25 | 29 | ||
@@ -53,6 +57,18 @@ struct sdhci_s3c { | |||
53 | struct clk *clk_bus[MAX_BUS_CLK]; | 57 | struct clk *clk_bus[MAX_BUS_CLK]; |
54 | }; | 58 | }; |
55 | 59 | ||
60 | /** | ||
61 | * struct sdhci_s3c_driver_data - S3C SDHCI platform specific driver data | ||
62 | * @sdhci_quirks: sdhci host specific quirks. | ||
63 | * | ||
64 | * Specifies platform specific configuration of sdhci controller. | ||
65 | * Note: A structure for driver specific platform data is used for future | ||
66 | * expansion of its usage. | ||
67 | */ | ||
68 | struct sdhci_s3c_drv_data { | ||
69 | unsigned int sdhci_quirks; | ||
70 | }; | ||
71 | |||
56 | static inline struct sdhci_s3c *to_s3c(struct sdhci_host *host) | 72 | static inline struct sdhci_s3c *to_s3c(struct sdhci_host *host) |
57 | { | 73 | { |
58 | return sdhci_priv(host); | 74 | return sdhci_priv(host); |
@@ -132,10 +148,10 @@ static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost, | |||
132 | return UINT_MAX; | 148 | return UINT_MAX; |
133 | 149 | ||
134 | /* | 150 | /* |
135 | * Clock divider's step is different as 1 from that of host controller | 151 | * If controller uses a non-standard clock division, find the best clock |
136 | * when 'clk_type' is S3C_SDHCI_CLK_DIV_EXTERNAL. | 152 | * speed possible with selected clock source and skip the division. |
137 | */ | 153 | */ |
138 | if (ourhost->pdata->clk_type) { | 154 | if (ourhost->host->quirks & SDHCI_QUIRK_NONSTANDARD_CLOCK) { |
139 | rate = clk_round_rate(clksrc, wanted); | 155 | rate = clk_round_rate(clksrc, wanted); |
140 | return wanted - rate; | 156 | return wanted - rate; |
141 | } | 157 | } |
@@ -272,6 +288,8 @@ static unsigned int sdhci_cmu_get_min_clock(struct sdhci_host *host) | |||
272 | static void sdhci_cmu_set_clock(struct sdhci_host *host, unsigned int clock) | 288 | static void sdhci_cmu_set_clock(struct sdhci_host *host, unsigned int clock) |
273 | { | 289 | { |
274 | struct sdhci_s3c *ourhost = to_s3c(host); | 290 | struct sdhci_s3c *ourhost = to_s3c(host); |
291 | unsigned long timeout; | ||
292 | u16 clk = 0; | ||
275 | 293 | ||
276 | /* don't bother if the clock is going off */ | 294 | /* don't bother if the clock is going off */ |
277 | if (clock == 0) | 295 | if (clock == 0) |
@@ -282,6 +300,25 @@ static void sdhci_cmu_set_clock(struct sdhci_host *host, unsigned int clock) | |||
282 | clk_set_rate(ourhost->clk_bus[ourhost->cur_clk], clock); | 300 | clk_set_rate(ourhost->clk_bus[ourhost->cur_clk], clock); |
283 | 301 | ||
284 | host->clock = clock; | 302 | host->clock = clock; |
303 | |||
304 | clk = SDHCI_CLOCK_INT_EN; | ||
305 | sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); | ||
306 | |||
307 | /* Wait max 20 ms */ | ||
308 | timeout = 20; | ||
309 | while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) | ||
310 | & SDHCI_CLOCK_INT_STABLE)) { | ||
311 | if (timeout == 0) { | ||
312 | printk(KERN_ERR "%s: Internal clock never " | ||
313 | "stabilised.\n", mmc_hostname(host->mmc)); | ||
314 | return; | ||
315 | } | ||
316 | timeout--; | ||
317 | mdelay(1); | ||
318 | } | ||
319 | |||
320 | clk |= SDHCI_CLOCK_CARD_EN; | ||
321 | sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); | ||
285 | } | 322 | } |
286 | 323 | ||
287 | /** | 324 | /** |
@@ -382,16 +419,24 @@ static void sdhci_s3c_setup_card_detect_gpio(struct sdhci_s3c *sc) | |||
382 | } | 419 | } |
383 | } | 420 | } |
384 | 421 | ||
422 | static inline struct sdhci_s3c_drv_data *sdhci_s3c_get_driver_data( | ||
423 | struct platform_device *pdev) | ||
424 | { | ||
425 | return (struct sdhci_s3c_drv_data *) | ||
426 | platform_get_device_id(pdev)->driver_data; | ||
427 | } | ||
428 | |||
385 | static int __devinit sdhci_s3c_probe(struct platform_device *pdev) | 429 | static int __devinit sdhci_s3c_probe(struct platform_device *pdev) |
386 | { | 430 | { |
387 | struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data; | 431 | struct s3c_sdhci_platdata *pdata; |
432 | struct sdhci_s3c_drv_data *drv_data; | ||
388 | struct device *dev = &pdev->dev; | 433 | struct device *dev = &pdev->dev; |
389 | struct sdhci_host *host; | 434 | struct sdhci_host *host; |
390 | struct sdhci_s3c *sc; | 435 | struct sdhci_s3c *sc; |
391 | struct resource *res; | 436 | struct resource *res; |
392 | int ret, irq, ptr, clks; | 437 | int ret, irq, ptr, clks; |
393 | 438 | ||
394 | if (!pdata) { | 439 | if (!pdev->dev.platform_data) { |
395 | dev_err(dev, "no device data specified\n"); | 440 | dev_err(dev, "no device data specified\n"); |
396 | return -ENOENT; | 441 | return -ENOENT; |
397 | } | 442 | } |
@@ -402,18 +447,20 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) | |||
402 | return irq; | 447 | return irq; |
403 | } | 448 | } |
404 | 449 | ||
405 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
406 | if (!res) { | ||
407 | dev_err(dev, "no memory specified\n"); | ||
408 | return -ENOENT; | ||
409 | } | ||
410 | |||
411 | host = sdhci_alloc_host(dev, sizeof(struct sdhci_s3c)); | 450 | host = sdhci_alloc_host(dev, sizeof(struct sdhci_s3c)); |
412 | if (IS_ERR(host)) { | 451 | if (IS_ERR(host)) { |
413 | dev_err(dev, "sdhci_alloc_host() failed\n"); | 452 | dev_err(dev, "sdhci_alloc_host() failed\n"); |
414 | return PTR_ERR(host); | 453 | return PTR_ERR(host); |
415 | } | 454 | } |
416 | 455 | ||
456 | pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); | ||
457 | if (!pdata) { | ||
458 | ret = -ENOMEM; | ||
459 | goto err_io_clk; | ||
460 | } | ||
461 | memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata)); | ||
462 | |||
463 | drv_data = sdhci_s3c_get_driver_data(pdev); | ||
417 | sc = sdhci_priv(host); | 464 | sc = sdhci_priv(host); |
418 | 465 | ||
419 | sc->host = host; | 466 | sc->host = host; |
@@ -464,15 +511,8 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) | |||
464 | goto err_no_busclks; | 511 | goto err_no_busclks; |
465 | } | 512 | } |
466 | 513 | ||
467 | sc->ioarea = request_mem_region(res->start, resource_size(res), | 514 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
468 | mmc_hostname(host->mmc)); | 515 | host->ioaddr = devm_request_and_ioremap(&pdev->dev, res); |
469 | if (!sc->ioarea) { | ||
470 | dev_err(dev, "failed to reserve register area\n"); | ||
471 | ret = -ENXIO; | ||
472 | goto err_req_regs; | ||
473 | } | ||
474 | |||
475 | host->ioaddr = ioremap_nocache(res->start, resource_size(res)); | ||
476 | if (!host->ioaddr) { | 516 | if (!host->ioaddr) { |
477 | dev_err(dev, "failed to map registers\n"); | 517 | dev_err(dev, "failed to map registers\n"); |
478 | ret = -ENXIO; | 518 | ret = -ENXIO; |
@@ -491,6 +531,8 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) | |||
491 | /* Setup quirks for the controller */ | 531 | /* Setup quirks for the controller */ |
492 | host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC; | 532 | host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC; |
493 | host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT; | 533 | host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT; |
534 | if (drv_data) | ||
535 | host->quirks |= drv_data->sdhci_quirks; | ||
494 | 536 | ||
495 | #ifndef CONFIG_MMC_SDHCI_S3C_DMA | 537 | #ifndef CONFIG_MMC_SDHCI_S3C_DMA |
496 | 538 | ||
@@ -518,6 +560,14 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) | |||
518 | if (pdata->cd_type == S3C_SDHCI_CD_PERMANENT) | 560 | if (pdata->cd_type == S3C_SDHCI_CD_PERMANENT) |
519 | host->mmc->caps = MMC_CAP_NONREMOVABLE; | 561 | host->mmc->caps = MMC_CAP_NONREMOVABLE; |
520 | 562 | ||
563 | switch (pdata->max_width) { | ||
564 | case 8: | ||
565 | host->mmc->caps |= MMC_CAP_8_BIT_DATA; | ||
566 | case 4: | ||
567 | host->mmc->caps |= MMC_CAP_4_BIT_DATA; | ||
568 | break; | ||
569 | } | ||
570 | |||
521 | if (pdata->pm_caps) | 571 | if (pdata->pm_caps) |
522 | host->mmc->pm_caps |= pdata->pm_caps; | 572 | host->mmc->pm_caps |= pdata->pm_caps; |
523 | 573 | ||
@@ -531,7 +581,7 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) | |||
531 | * If controller does not have internal clock divider, | 581 | * If controller does not have internal clock divider, |
532 | * we can use overriding functions instead of default. | 582 | * we can use overriding functions instead of default. |
533 | */ | 583 | */ |
534 | if (pdata->clk_type) { | 584 | if (host->quirks & SDHCI_QUIRK_NONSTANDARD_CLOCK) { |
535 | sdhci_s3c_ops.set_clock = sdhci_cmu_set_clock; | 585 | sdhci_s3c_ops.set_clock = sdhci_cmu_set_clock; |
536 | sdhci_s3c_ops.get_min_clock = sdhci_cmu_get_min_clock; | 586 | sdhci_s3c_ops.get_min_clock = sdhci_cmu_get_min_clock; |
537 | sdhci_s3c_ops.get_max_clock = sdhci_cmu_get_max_clock; | 587 | sdhci_s3c_ops.get_max_clock = sdhci_cmu_get_max_clock; |
@@ -544,10 +594,17 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) | |||
544 | if (pdata->host_caps2) | 594 | if (pdata->host_caps2) |
545 | host->mmc->caps2 |= pdata->host_caps2; | 595 | host->mmc->caps2 |= pdata->host_caps2; |
546 | 596 | ||
597 | pm_runtime_enable(&pdev->dev); | ||
598 | pm_runtime_set_autosuspend_delay(&pdev->dev, 50); | ||
599 | pm_runtime_use_autosuspend(&pdev->dev); | ||
600 | pm_suspend_ignore_children(&pdev->dev, 1); | ||
601 | |||
547 | ret = sdhci_add_host(host); | 602 | ret = sdhci_add_host(host); |
548 | if (ret) { | 603 | if (ret) { |
549 | dev_err(dev, "sdhci_add_host() failed\n"); | 604 | dev_err(dev, "sdhci_add_host() failed\n"); |
550 | goto err_add_host; | 605 | pm_runtime_forbid(&pdev->dev); |
606 | pm_runtime_get_noresume(&pdev->dev); | ||
607 | goto err_req_regs; | ||
551 | } | 608 | } |
552 | 609 | ||
553 | /* The following two methods of card detection might call | 610 | /* The following two methods of card detection might call |
@@ -561,10 +618,6 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) | |||
561 | 618 | ||
562 | return 0; | 619 | return 0; |
563 | 620 | ||
564 | err_add_host: | ||
565 | release_resource(sc->ioarea); | ||
566 | kfree(sc->ioarea); | ||
567 | |||
568 | err_req_regs: | 621 | err_req_regs: |
569 | for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) { | 622 | for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) { |
570 | if (sc->clk_bus[ptr]) { | 623 | if (sc->clk_bus[ptr]) { |
@@ -601,6 +654,8 @@ static int __devexit sdhci_s3c_remove(struct platform_device *pdev) | |||
601 | 654 | ||
602 | sdhci_remove_host(host, 1); | 655 | sdhci_remove_host(host, 1); |
603 | 656 | ||
657 | pm_runtime_disable(&pdev->dev); | ||
658 | |||
604 | for (ptr = 0; ptr < 3; ptr++) { | 659 | for (ptr = 0; ptr < 3; ptr++) { |
605 | if (sc->clk_bus[ptr]) { | 660 | if (sc->clk_bus[ptr]) { |
606 | clk_disable(sc->clk_bus[ptr]); | 661 | clk_disable(sc->clk_bus[ptr]); |
@@ -610,18 +665,13 @@ static int __devexit sdhci_s3c_remove(struct platform_device *pdev) | |||
610 | clk_disable(sc->clk_io); | 665 | clk_disable(sc->clk_io); |
611 | clk_put(sc->clk_io); | 666 | clk_put(sc->clk_io); |
612 | 667 | ||
613 | iounmap(host->ioaddr); | ||
614 | release_resource(sc->ioarea); | ||
615 | kfree(sc->ioarea); | ||
616 | |||
617 | sdhci_free_host(host); | 668 | sdhci_free_host(host); |
618 | platform_set_drvdata(pdev, NULL); | 669 | platform_set_drvdata(pdev, NULL); |
619 | 670 | ||
620 | return 0; | 671 | return 0; |
621 | } | 672 | } |
622 | 673 | ||
623 | #ifdef CONFIG_PM | 674 | #ifdef CONFIG_PM_SLEEP |
624 | |||
625 | static int sdhci_s3c_suspend(struct device *dev) | 675 | static int sdhci_s3c_suspend(struct device *dev) |
626 | { | 676 | { |
627 | struct sdhci_host *host = dev_get_drvdata(dev); | 677 | struct sdhci_host *host = dev_get_drvdata(dev); |
@@ -635,10 +685,29 @@ static int sdhci_s3c_resume(struct device *dev) | |||
635 | 685 | ||
636 | return sdhci_resume_host(host); | 686 | return sdhci_resume_host(host); |
637 | } | 687 | } |
688 | #endif | ||
689 | |||
690 | #ifdef CONFIG_PM_RUNTIME | ||
691 | static int sdhci_s3c_runtime_suspend(struct device *dev) | ||
692 | { | ||
693 | struct sdhci_host *host = dev_get_drvdata(dev); | ||
694 | |||
695 | return sdhci_runtime_suspend_host(host); | ||
696 | } | ||
638 | 697 | ||
698 | static int sdhci_s3c_runtime_resume(struct device *dev) | ||
699 | { | ||
700 | struct sdhci_host *host = dev_get_drvdata(dev); | ||
701 | |||
702 | return sdhci_runtime_resume_host(host); | ||
703 | } | ||
704 | #endif | ||
705 | |||
706 | #ifdef CONFIG_PM | ||
639 | static const struct dev_pm_ops sdhci_s3c_pmops = { | 707 | static const struct dev_pm_ops sdhci_s3c_pmops = { |
640 | .suspend = sdhci_s3c_suspend, | 708 | SET_SYSTEM_SLEEP_PM_OPS(sdhci_s3c_suspend, sdhci_s3c_resume) |
641 | .resume = sdhci_s3c_resume, | 709 | SET_RUNTIME_PM_OPS(sdhci_s3c_runtime_suspend, sdhci_s3c_runtime_resume, |
710 | NULL) | ||
642 | }; | 711 | }; |
643 | 712 | ||
644 | #define SDHCI_S3C_PMOPS (&sdhci_s3c_pmops) | 713 | #define SDHCI_S3C_PMOPS (&sdhci_s3c_pmops) |
@@ -647,9 +716,31 @@ static const struct dev_pm_ops sdhci_s3c_pmops = { | |||
647 | #define SDHCI_S3C_PMOPS NULL | 716 | #define SDHCI_S3C_PMOPS NULL |
648 | #endif | 717 | #endif |
649 | 718 | ||
719 | #if defined(CONFIG_CPU_EXYNOS4210) || defined(CONFIG_SOC_EXYNOS4212) | ||
720 | static struct sdhci_s3c_drv_data exynos4_sdhci_drv_data = { | ||
721 | .sdhci_quirks = SDHCI_QUIRK_NONSTANDARD_CLOCK, | ||
722 | }; | ||
723 | #define EXYNOS4_SDHCI_DRV_DATA ((kernel_ulong_t)&exynos4_sdhci_drv_data) | ||
724 | #else | ||
725 | #define EXYNOS4_SDHCI_DRV_DATA ((kernel_ulong_t)NULL) | ||
726 | #endif | ||
727 | |||
728 | static struct platform_device_id sdhci_s3c_driver_ids[] = { | ||
729 | { | ||
730 | .name = "s3c-sdhci", | ||
731 | .driver_data = (kernel_ulong_t)NULL, | ||
732 | }, { | ||
733 | .name = "exynos4-sdhci", | ||
734 | .driver_data = EXYNOS4_SDHCI_DRV_DATA, | ||
735 | }, | ||
736 | { } | ||
737 | }; | ||
738 | MODULE_DEVICE_TABLE(platform, sdhci_s3c_driver_ids); | ||
739 | |||
650 | static struct platform_driver sdhci_s3c_driver = { | 740 | static struct platform_driver sdhci_s3c_driver = { |
651 | .probe = sdhci_s3c_probe, | 741 | .probe = sdhci_s3c_probe, |
652 | .remove = __devexit_p(sdhci_s3c_remove), | 742 | .remove = __devexit_p(sdhci_s3c_remove), |
743 | .id_table = sdhci_s3c_driver_ids, | ||
653 | .driver = { | 744 | .driver = { |
654 | .owner = THIS_MODULE, | 745 | .owner = THIS_MODULE, |
655 | .name = "s3c-sdhci", | 746 | .name = "s3c-sdhci", |