diff options
-rw-r--r-- | Documentation/devicetree/bindings/mmc/samsung-sdhci.txt | 53 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci-s3c.c | 158 |
2 files changed, 205 insertions, 6 deletions
diff --git a/Documentation/devicetree/bindings/mmc/samsung-sdhci.txt b/Documentation/devicetree/bindings/mmc/samsung-sdhci.txt new file mode 100644 index 000000000000..630a7d7f4718 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/samsung-sdhci.txt | |||
@@ -0,0 +1,53 @@ | |||
1 | * Samsung's SDHCI Controller device tree bindings | ||
2 | |||
3 | Samsung's SDHCI controller is used as a connectivity interface with external | ||
4 | MMC, SD and eMMC storage mediums. This file documents differences between the | ||
5 | core mmc properties described by mmc.txt and the properties used by the | ||
6 | Samsung implmentation of the SDHCI controller. | ||
7 | |||
8 | Note: The mmc core bindings documentation states that if none of the core | ||
9 | card-detect bindings are used, then the standard sdhci card detect mechanism | ||
10 | is used. The Samsung's SDHCI controller bindings extends this as listed below. | ||
11 | |||
12 | [A] The property "samsung,cd-pinmux-gpio" can be used as stated in the | ||
13 | "Optional Board Specific Properties" section below. | ||
14 | |||
15 | [B] If core card-detect bindings and "samsung,cd-pinmux-gpio" property | ||
16 | is not specified, it is assumed that there is no card detection | ||
17 | mechanism used. | ||
18 | |||
19 | Required SoC Specific Properties: | ||
20 | - compatible: should be one of the following | ||
21 | - "samsung,s3c6410-sdhci": For controllers compatible with s3c6410 sdhci | ||
22 | controller. | ||
23 | - "samsung,exynos4210-sdhci": For controllers compatible with Exynos4 sdhci | ||
24 | controller. | ||
25 | |||
26 | Required Board Specific Properties: | ||
27 | - gpios: Should specify the gpios used for clock, command and data lines. The | ||
28 | gpio specifier format depends on the gpio controller. | ||
29 | |||
30 | Optional Board Specific Properties: | ||
31 | - samsung,cd-pinmux-gpio: Specifies the card detect line that is routed | ||
32 | through a pinmux to the card-detect pin of the card slot. This property | ||
33 | should be used only if none of the mmc core card-detect properties are | ||
34 | used. | ||
35 | |||
36 | Example: | ||
37 | sdhci@12530000 { | ||
38 | compatible = "samsung,exynos4210-sdhci"; | ||
39 | reg = <0x12530000 0x100>; | ||
40 | interrupts = <0 75 0>; | ||
41 | bus-width = <4>; | ||
42 | cd-gpios = <&gpk2 2 2 3 3>; | ||
43 | gpios = <&gpk2 0 2 0 3>, /* clock line */ | ||
44 | <&gpk2 1 2 0 3>, /* command line */ | ||
45 | <&gpk2 3 2 3 3>, /* data line 0 */ | ||
46 | <&gpk2 4 2 3 3>, /* data line 1 */ | ||
47 | <&gpk2 5 2 3 3>, /* data line 2 */ | ||
48 | <&gpk2 6 2 3 3>; /* data line 3 */ | ||
49 | }; | ||
50 | |||
51 | Note: This example shows both SoC specific and board specific properties | ||
52 | in a single device node. The properties can be actually be seperated | ||
53 | into SoC specific node and board specific node. | ||
diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index 00969ba03277..0c77b10d2619 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c | |||
@@ -34,6 +34,9 @@ | |||
34 | 34 | ||
35 | #define MAX_BUS_CLK (4) | 35 | #define MAX_BUS_CLK (4) |
36 | 36 | ||
37 | /* Number of gpio's used is max data bus width + command and clock lines */ | ||
38 | #define NUM_GPIOS(x) (x + 2) | ||
39 | |||
37 | /** | 40 | /** |
38 | * struct sdhci_s3c - S3C SDHCI instance | 41 | * struct sdhci_s3c - S3C SDHCI instance |
39 | * @host: The SDHCI host created | 42 | * @host: The SDHCI host created |
@@ -41,6 +44,7 @@ | |||
41 | * @ioarea: The resource created when we claimed the IO area. | 44 | * @ioarea: The resource created when we claimed the IO area. |
42 | * @pdata: The platform data for this controller. | 45 | * @pdata: The platform data for this controller. |
43 | * @cur_clk: The index of the current bus clock. | 46 | * @cur_clk: The index of the current bus clock. |
47 | * @gpios: List of gpio numbers parsed from device tree. | ||
44 | * @clk_io: The clock for the internal bus interface. | 48 | * @clk_io: The clock for the internal bus interface. |
45 | * @clk_bus: The clocks that are available for the SD/MMC bus clock. | 49 | * @clk_bus: The clocks that are available for the SD/MMC bus clock. |
46 | */ | 50 | */ |
@@ -52,6 +56,7 @@ struct sdhci_s3c { | |||
52 | unsigned int cur_clk; | 56 | unsigned int cur_clk; |
53 | int ext_cd_irq; | 57 | int ext_cd_irq; |
54 | int ext_cd_gpio; | 58 | int ext_cd_gpio; |
59 | int *gpios; | ||
55 | 60 | ||
56 | struct clk *clk_io; | 61 | struct clk *clk_io; |
57 | struct clk *clk_bus[MAX_BUS_CLK]; | 62 | struct clk *clk_bus[MAX_BUS_CLK]; |
@@ -422,9 +427,121 @@ static void sdhci_s3c_setup_card_detect_gpio(struct sdhci_s3c *sc) | |||
422 | } | 427 | } |
423 | } | 428 | } |
424 | 429 | ||
430 | #ifdef CONFIG_OF | ||
431 | static int __devinit sdhci_s3c_parse_dt(struct device *dev, | ||
432 | struct sdhci_host *host, struct s3c_sdhci_platdata *pdata) | ||
433 | { | ||
434 | struct device_node *node = dev->of_node; | ||
435 | struct sdhci_s3c *ourhost = to_s3c(host); | ||
436 | u32 max_width; | ||
437 | int gpio, cnt, ret; | ||
438 | |||
439 | /* if the bus-width property is not specified, assume width as 1 */ | ||
440 | if (of_property_read_u32(node, "bus-width", &max_width)) | ||
441 | max_width = 1; | ||
442 | pdata->max_width = max_width; | ||
443 | |||
444 | ourhost->gpios = devm_kzalloc(dev, NUM_GPIOS(pdata->max_width) * | ||
445 | sizeof(int), GFP_KERNEL); | ||
446 | if (!ourhost->gpios) | ||
447 | return -ENOMEM; | ||
448 | |||
449 | /* get the card detection method */ | ||
450 | if (of_get_property(node, "broken-cd", 0)) { | ||
451 | pdata->cd_type = S3C_SDHCI_CD_NONE; | ||
452 | goto setup_bus; | ||
453 | } | ||
454 | |||
455 | if (of_get_property(node, "non-removable", 0)) { | ||
456 | pdata->cd_type = S3C_SDHCI_CD_PERMANENT; | ||
457 | goto setup_bus; | ||
458 | } | ||
459 | |||
460 | gpio = of_get_named_gpio(node, "cd-gpios", 0); | ||
461 | if (gpio_is_valid(gpio)) { | ||
462 | pdata->cd_type = S3C_SDHCI_CD_GPIO; | ||
463 | goto found_cd; | ||
464 | } else if (gpio != -ENOENT) { | ||
465 | dev_err(dev, "invalid card detect gpio specified\n"); | ||
466 | return -EINVAL; | ||
467 | } | ||
468 | |||
469 | gpio = of_get_named_gpio(node, "samsung,cd-pinmux-gpio", 0); | ||
470 | if (gpio_is_valid(gpio)) { | ||
471 | pdata->cd_type = S3C_SDHCI_CD_INTERNAL; | ||
472 | goto found_cd; | ||
473 | } else if (gpio != -ENOENT) { | ||
474 | dev_err(dev, "invalid card detect gpio specified\n"); | ||
475 | return -EINVAL; | ||
476 | } | ||
477 | |||
478 | dev_info(dev, "assuming no card detect line available\n"); | ||
479 | pdata->cd_type = S3C_SDHCI_CD_NONE; | ||
480 | |||
481 | found_cd: | ||
482 | if (pdata->cd_type == S3C_SDHCI_CD_GPIO) { | ||
483 | pdata->ext_cd_gpio = gpio; | ||
484 | ourhost->ext_cd_gpio = -1; | ||
485 | if (of_get_property(node, "cd-inverted", NULL)) | ||
486 | pdata->ext_cd_gpio_invert = 1; | ||
487 | } else if (pdata->cd_type == S3C_SDHCI_CD_INTERNAL) { | ||
488 | ret = gpio_request(gpio, "sdhci-cd"); | ||
489 | if (ret) { | ||
490 | dev_err(dev, "card detect gpio request failed\n"); | ||
491 | return -EINVAL; | ||
492 | } | ||
493 | ourhost->ext_cd_gpio = gpio; | ||
494 | } | ||
495 | |||
496 | setup_bus: | ||
497 | /* get the gpios for command, clock and data lines */ | ||
498 | for (cnt = 0; cnt < NUM_GPIOS(pdata->max_width); cnt++) { | ||
499 | gpio = of_get_gpio(node, cnt); | ||
500 | if (!gpio_is_valid(gpio)) { | ||
501 | dev_err(dev, "invalid gpio[%d]\n", cnt); | ||
502 | goto err_free_dt_cd_gpio; | ||
503 | } | ||
504 | ourhost->gpios[cnt] = gpio; | ||
505 | } | ||
506 | |||
507 | for (cnt = 0; cnt < NUM_GPIOS(pdata->max_width); cnt++) { | ||
508 | ret = gpio_request(ourhost->gpios[cnt], "sdhci-gpio"); | ||
509 | if (ret) { | ||
510 | dev_err(dev, "gpio[%d] request failed\n", cnt); | ||
511 | goto err_free_dt_gpios; | ||
512 | } | ||
513 | } | ||
514 | |||
515 | return 0; | ||
516 | |||
517 | err_free_dt_gpios: | ||
518 | while (--cnt >= 0) | ||
519 | gpio_free(ourhost->gpios[cnt]); | ||
520 | err_free_dt_cd_gpio: | ||
521 | if (pdata->cd_type == S3C_SDHCI_CD_INTERNAL) | ||
522 | gpio_free(ourhost->ext_cd_gpio); | ||
523 | return -EINVAL; | ||
524 | } | ||
525 | #else | ||
526 | static int __devinit sdhci_s3c_parse_dt(struct device *dev, | ||
527 | struct sdhci_host *host, struct s3c_sdhci_platdata *pdata) | ||
528 | { | ||
529 | return -EINVAL; | ||
530 | } | ||
531 | #endif | ||
532 | |||
533 | static const struct of_device_id sdhci_s3c_dt_match[]; | ||
534 | |||
425 | static inline struct sdhci_s3c_drv_data *sdhci_s3c_get_driver_data( | 535 | static inline struct sdhci_s3c_drv_data *sdhci_s3c_get_driver_data( |
426 | struct platform_device *pdev) | 536 | struct platform_device *pdev) |
427 | { | 537 | { |
538 | #ifdef CONFIG_OF | ||
539 | if (pdev->dev.of_node) { | ||
540 | const struct of_device_id *match; | ||
541 | match = of_match_node(sdhci_s3c_dt_match, pdev->dev.of_node); | ||
542 | return (struct sdhci_s3c_drv_data *)match->data; | ||
543 | } | ||
544 | #endif | ||
428 | return (struct sdhci_s3c_drv_data *) | 545 | return (struct sdhci_s3c_drv_data *) |
429 | platform_get_device_id(pdev)->driver_data; | 546 | platform_get_device_id(pdev)->driver_data; |
430 | } | 547 | } |
@@ -439,7 +556,7 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) | |||
439 | struct resource *res; | 556 | struct resource *res; |
440 | int ret, irq, ptr, clks; | 557 | int ret, irq, ptr, clks; |
441 | 558 | ||
442 | if (!pdev->dev.platform_data) { | 559 | if (!pdev->dev.platform_data && !pdev->dev.of_node) { |
443 | dev_err(dev, "no device data specified\n"); | 560 | dev_err(dev, "no device data specified\n"); |
444 | return -ENOENT; | 561 | return -ENOENT; |
445 | } | 562 | } |
@@ -455,21 +572,28 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) | |||
455 | dev_err(dev, "sdhci_alloc_host() failed\n"); | 572 | dev_err(dev, "sdhci_alloc_host() failed\n"); |
456 | return PTR_ERR(host); | 573 | return PTR_ERR(host); |
457 | } | 574 | } |
575 | sc = sdhci_priv(host); | ||
458 | 576 | ||
459 | pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); | 577 | pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); |
460 | if (!pdata) { | 578 | if (!pdata) { |
461 | ret = -ENOMEM; | 579 | ret = -ENOMEM; |
462 | goto err_io_clk; | 580 | goto err_pdata; |
581 | } | ||
582 | |||
583 | if (pdev->dev.of_node) { | ||
584 | ret = sdhci_s3c_parse_dt(&pdev->dev, host, pdata); | ||
585 | if (ret) | ||
586 | goto err_pdata; | ||
587 | } else { | ||
588 | memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata)); | ||
589 | sc->ext_cd_gpio = -1; /* invalid gpio number */ | ||
463 | } | 590 | } |
464 | memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata)); | ||
465 | 591 | ||
466 | drv_data = sdhci_s3c_get_driver_data(pdev); | 592 | drv_data = sdhci_s3c_get_driver_data(pdev); |
467 | sc = sdhci_priv(host); | ||
468 | 593 | ||
469 | sc->host = host; | 594 | sc->host = host; |
470 | sc->pdev = pdev; | 595 | sc->pdev = pdev; |
471 | sc->pdata = pdata; | 596 | sc->pdata = pdata; |
472 | sc->ext_cd_gpio = -1; /* invalid gpio number */ | ||
473 | 597 | ||
474 | platform_set_drvdata(pdev, host); | 598 | platform_set_drvdata(pdev, host); |
475 | 599 | ||
@@ -633,6 +757,12 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) | |||
633 | clk_put(sc->clk_io); | 757 | clk_put(sc->clk_io); |
634 | 758 | ||
635 | err_io_clk: | 759 | err_io_clk: |
760 | for (ptr = 0; ptr < NUM_GPIOS(sc->pdata->max_width); ptr++) | ||
761 | gpio_free(sc->gpios[ptr]); | ||
762 | if (pdata->cd_type == S3C_SDHCI_CD_INTERNAL) | ||
763 | gpio_free(sc->ext_cd_gpio); | ||
764 | |||
765 | err_pdata: | ||
636 | sdhci_free_host(host); | 766 | sdhci_free_host(host); |
637 | 767 | ||
638 | return ret; | 768 | return ret; |
@@ -640,9 +770,9 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) | |||
640 | 770 | ||
641 | static int __devexit sdhci_s3c_remove(struct platform_device *pdev) | 771 | static int __devexit sdhci_s3c_remove(struct platform_device *pdev) |
642 | { | 772 | { |
643 | struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data; | ||
644 | struct sdhci_host *host = platform_get_drvdata(pdev); | 773 | struct sdhci_host *host = platform_get_drvdata(pdev); |
645 | struct sdhci_s3c *sc = sdhci_priv(host); | 774 | struct sdhci_s3c *sc = sdhci_priv(host); |
775 | struct s3c_sdhci_platdata *pdata = sc->pdata; | ||
646 | int ptr; | 776 | int ptr; |
647 | 777 | ||
648 | if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_cleanup) | 778 | if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_cleanup) |
@@ -667,6 +797,11 @@ static int __devexit sdhci_s3c_remove(struct platform_device *pdev) | |||
667 | clk_disable(sc->clk_io); | 797 | clk_disable(sc->clk_io); |
668 | clk_put(sc->clk_io); | 798 | clk_put(sc->clk_io); |
669 | 799 | ||
800 | if (pdev->dev.of_node) { | ||
801 | for (ptr = 0; ptr < NUM_GPIOS(sc->pdata->max_width); ptr++) | ||
802 | gpio_free(sc->gpios[ptr]); | ||
803 | } | ||
804 | |||
670 | sdhci_free_host(host); | 805 | sdhci_free_host(host); |
671 | platform_set_drvdata(pdev, NULL); | 806 | platform_set_drvdata(pdev, NULL); |
672 | 807 | ||
@@ -739,6 +874,16 @@ static struct platform_device_id sdhci_s3c_driver_ids[] = { | |||
739 | }; | 874 | }; |
740 | MODULE_DEVICE_TABLE(platform, sdhci_s3c_driver_ids); | 875 | MODULE_DEVICE_TABLE(platform, sdhci_s3c_driver_ids); |
741 | 876 | ||
877 | #ifdef CONFIG_OF | ||
878 | static const struct of_device_id sdhci_s3c_dt_match[] = { | ||
879 | { .compatible = "samsung,s3c6410-sdhci", }, | ||
880 | { .compatible = "samsung,exynos4210-sdhci", | ||
881 | .data = (void *)EXYNOS4_SDHCI_DRV_DATA }, | ||
882 | {}, | ||
883 | }; | ||
884 | MODULE_DEVICE_TABLE(of, sdhci_s3c_dt_match); | ||
885 | #endif | ||
886 | |||
742 | static struct platform_driver sdhci_s3c_driver = { | 887 | static struct platform_driver sdhci_s3c_driver = { |
743 | .probe = sdhci_s3c_probe, | 888 | .probe = sdhci_s3c_probe, |
744 | .remove = __devexit_p(sdhci_s3c_remove), | 889 | .remove = __devexit_p(sdhci_s3c_remove), |
@@ -746,6 +891,7 @@ static struct platform_driver sdhci_s3c_driver = { | |||
746 | .driver = { | 891 | .driver = { |
747 | .owner = THIS_MODULE, | 892 | .owner = THIS_MODULE, |
748 | .name = "s3c-sdhci", | 893 | .name = "s3c-sdhci", |
894 | .of_match_table = of_match_ptr(sdhci_s3c_dt_match), | ||
749 | .pm = SDHCI_S3C_PMOPS, | 895 | .pm = SDHCI_S3C_PMOPS, |
750 | }, | 896 | }, |
751 | }; | 897 | }; |