diff options
author | Roger Quadros <rogerq@ti.com> | 2013-04-09 04:39:18 -0400 |
---|---|---|
committer | Samuel Ortiz <sameo@linux.intel.com> | 2013-04-09 04:59:55 -0400 |
commit | 03a8f438f55c7abaaa1ddf5422a6c4abd1bdc1f6 (patch) | |
tree | 9153bf39676f306b125457baa646b38a012b8dc3 /drivers | |
parent | 40b0d68a8c39c6d6cb7e975c9b180e2203864556 (diff) |
mfd: omap-usb-host: Add device tree support and binding information
Allows the OMAP HS USB host controller to be specified
via device tree.
Signed-off-by: Roger Quadros <rogerq@ti.com>
Reviewed-by: Mark Rutland <mark.rutland@arm.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mfd/omap-usb-host.c | 161 |
1 files changed, 155 insertions, 6 deletions
diff --git a/drivers/mfd/omap-usb-host.c b/drivers/mfd/omap-usb-host.c index 138ee982996c..d3b6e9491b62 100644 --- a/drivers/mfd/omap-usb-host.c +++ b/drivers/mfd/omap-usb-host.c | |||
@@ -1,8 +1,9 @@ | |||
1 | /** | 1 | /** |
2 | * omap-usb-host.c - The USBHS core driver for OMAP EHCI & OHCI | 2 | * omap-usb-host.c - The USBHS core driver for OMAP EHCI & OHCI |
3 | * | 3 | * |
4 | * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com | 4 | * Copyright (C) 2011-2013 Texas Instruments Incorporated - http://www.ti.com |
5 | * Author: Keshava Munegowda <keshava_mgowda@ti.com> | 5 | * Author: Keshava Munegowda <keshava_mgowda@ti.com> |
6 | * Author: Roger Quadros <rogerq@ti.com> | ||
6 | * | 7 | * |
7 | * This program is free software: you can redistribute it and/or modify | 8 | * This program is free software: you can redistribute it and/or modify |
8 | * it under the terms of the GNU General Public License version 2 of | 9 | * it under the terms of the GNU General Public License version 2 of |
@@ -27,6 +28,8 @@ | |||
27 | #include <linux/platform_device.h> | 28 | #include <linux/platform_device.h> |
28 | #include <linux/platform_data/usb-omap.h> | 29 | #include <linux/platform_data/usb-omap.h> |
29 | #include <linux/pm_runtime.h> | 30 | #include <linux/pm_runtime.h> |
31 | #include <linux/of.h> | ||
32 | #include <linux/of_platform.h> | ||
30 | 33 | ||
31 | #include "omap-usb.h" | 34 | #include "omap-usb.h" |
32 | 35 | ||
@@ -137,6 +140,49 @@ static inline u8 usbhs_readb(void __iomem *base, u8 reg) | |||
137 | 140 | ||
138 | /*-------------------------------------------------------------------------*/ | 141 | /*-------------------------------------------------------------------------*/ |
139 | 142 | ||
143 | /** | ||
144 | * Map 'enum usbhs_omap_port_mode' found in <linux/platform_data/usb-omap.h> | ||
145 | * to the device tree binding portN-mode found in | ||
146 | * 'Documentation/devicetree/bindings/mfd/omap-usb-host.txt' | ||
147 | */ | ||
148 | static const char * const port_modes[] = { | ||
149 | [OMAP_USBHS_PORT_MODE_UNUSED] = "", | ||
150 | [OMAP_EHCI_PORT_MODE_PHY] = "ehci-phy", | ||
151 | [OMAP_EHCI_PORT_MODE_TLL] = "ehci-tll", | ||
152 | [OMAP_EHCI_PORT_MODE_HSIC] = "ehci-hsic", | ||
153 | [OMAP_OHCI_PORT_MODE_PHY_6PIN_DATSE0] = "ohci-phy-6pin-datse0", | ||
154 | [OMAP_OHCI_PORT_MODE_PHY_6PIN_DPDM] = "ohci-phy-6pin-dpdm", | ||
155 | [OMAP_OHCI_PORT_MODE_PHY_3PIN_DATSE0] = "ohci-phy-3pin-datse0", | ||
156 | [OMAP_OHCI_PORT_MODE_PHY_4PIN_DPDM] = "ohci-phy-4pin-dpdm", | ||
157 | [OMAP_OHCI_PORT_MODE_TLL_6PIN_DATSE0] = "ohci-tll-6pin-datse0", | ||
158 | [OMAP_OHCI_PORT_MODE_TLL_6PIN_DPDM] = "ohci-tll-6pin-dpdm", | ||
159 | [OMAP_OHCI_PORT_MODE_TLL_3PIN_DATSE0] = "ohci-tll-3pin-datse0", | ||
160 | [OMAP_OHCI_PORT_MODE_TLL_4PIN_DPDM] = "ohci-tll-4pin-dpdm", | ||
161 | [OMAP_OHCI_PORT_MODE_TLL_2PIN_DATSE0] = "ohci-tll-2pin-datse0", | ||
162 | [OMAP_OHCI_PORT_MODE_TLL_2PIN_DPDM] = "ohci-tll-2pin-dpdm", | ||
163 | }; | ||
164 | |||
165 | /** | ||
166 | * omap_usbhs_get_dt_port_mode - Get the 'enum usbhs_omap_port_mode' | ||
167 | * from the port mode string. | ||
168 | * @mode: The port mode string, usually obtained from device tree. | ||
169 | * | ||
170 | * The function returns the 'enum usbhs_omap_port_mode' that matches the | ||
171 | * provided port mode string as per the port_modes table. | ||
172 | * If no match is found it returns -ENODEV | ||
173 | */ | ||
174 | static const int omap_usbhs_get_dt_port_mode(const char *mode) | ||
175 | { | ||
176 | int i; | ||
177 | |||
178 | for (i = 0; i < ARRAY_SIZE(port_modes); i++) { | ||
179 | if (!strcmp(mode, port_modes[i])) | ||
180 | return i; | ||
181 | } | ||
182 | |||
183 | return -ENODEV; | ||
184 | } | ||
185 | |||
140 | static struct platform_device *omap_usbhs_alloc_child(const char *name, | 186 | static struct platform_device *omap_usbhs_alloc_child(const char *name, |
141 | struct resource *res, int num_resources, void *pdata, | 187 | struct resource *res, int num_resources, void *pdata, |
142 | size_t pdata_size, struct device *dev) | 188 | size_t pdata_size, struct device *dev) |
@@ -464,6 +510,58 @@ static void omap_usbhs_init(struct device *dev) | |||
464 | pm_runtime_put_sync(dev); | 510 | pm_runtime_put_sync(dev); |
465 | } | 511 | } |
466 | 512 | ||
513 | static int usbhs_omap_get_dt_pdata(struct device *dev, | ||
514 | struct usbhs_omap_platform_data *pdata) | ||
515 | { | ||
516 | int ret, i; | ||
517 | struct device_node *node = dev->of_node; | ||
518 | |||
519 | ret = of_property_read_u32(node, "num-ports", &pdata->nports); | ||
520 | if (ret) | ||
521 | pdata->nports = 0; | ||
522 | |||
523 | if (pdata->nports > OMAP3_HS_USB_PORTS) { | ||
524 | dev_warn(dev, "Too many num_ports <%d> in device tree. Max %d\n", | ||
525 | pdata->nports, OMAP3_HS_USB_PORTS); | ||
526 | return -ENODEV; | ||
527 | } | ||
528 | |||
529 | /* get port modes */ | ||
530 | for (i = 0; i < OMAP3_HS_USB_PORTS; i++) { | ||
531 | char prop[11]; | ||
532 | const char *mode; | ||
533 | |||
534 | pdata->port_mode[i] = OMAP_USBHS_PORT_MODE_UNUSED; | ||
535 | |||
536 | snprintf(prop, sizeof(prop), "port%d-mode", i + 1); | ||
537 | ret = of_property_read_string(node, prop, &mode); | ||
538 | if (ret < 0) | ||
539 | continue; | ||
540 | |||
541 | ret = omap_usbhs_get_dt_port_mode(mode); | ||
542 | if (ret < 0) { | ||
543 | dev_warn(dev, "Invalid port%d-mode \"%s\" in device tree\n", | ||
544 | i, mode); | ||
545 | return -ENODEV; | ||
546 | } | ||
547 | |||
548 | dev_dbg(dev, "port%d-mode: %s -> %d\n", i, mode, ret); | ||
549 | pdata->port_mode[i] = ret; | ||
550 | } | ||
551 | |||
552 | /* get flags */ | ||
553 | pdata->single_ulpi_bypass = of_property_read_bool(node, | ||
554 | "single-ulpi-bypass"); | ||
555 | |||
556 | return 0; | ||
557 | } | ||
558 | |||
559 | static struct of_device_id usbhs_child_match_table[] = { | ||
560 | { .compatible = "ti,omap-ehci", }, | ||
561 | { .compatible = "ti,omap-ohci", }, | ||
562 | { } | ||
563 | }; | ||
564 | |||
467 | /** | 565 | /** |
468 | * usbhs_omap_probe - initialize TI-based HCDs | 566 | * usbhs_omap_probe - initialize TI-based HCDs |
469 | * | 567 | * |
@@ -479,18 +577,37 @@ static int usbhs_omap_probe(struct platform_device *pdev) | |||
479 | int i; | 577 | int i; |
480 | bool need_logic_fck; | 578 | bool need_logic_fck; |
481 | 579 | ||
580 | if (dev->of_node) { | ||
581 | /* For DT boot we populate platform data from OF node */ | ||
582 | pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); | ||
583 | if (!pdata) | ||
584 | return -ENOMEM; | ||
585 | |||
586 | ret = usbhs_omap_get_dt_pdata(dev, pdata); | ||
587 | if (ret) | ||
588 | return ret; | ||
589 | |||
590 | dev->platform_data = pdata; | ||
591 | } | ||
592 | |||
482 | if (!pdata) { | 593 | if (!pdata) { |
483 | dev_err(dev, "Missing platform data\n"); | 594 | dev_err(dev, "Missing platform data\n"); |
484 | return -ENODEV; | 595 | return -ENODEV; |
485 | } | 596 | } |
486 | 597 | ||
598 | if (pdata->nports > OMAP3_HS_USB_PORTS) { | ||
599 | dev_info(dev, "Too many num_ports <%d> in platform_data. Max %d\n", | ||
600 | pdata->nports, OMAP3_HS_USB_PORTS); | ||
601 | return -ENODEV; | ||
602 | } | ||
603 | |||
487 | omap = devm_kzalloc(dev, sizeof(*omap), GFP_KERNEL); | 604 | omap = devm_kzalloc(dev, sizeof(*omap), GFP_KERNEL); |
488 | if (!omap) { | 605 | if (!omap) { |
489 | dev_err(dev, "Memory allocation failed\n"); | 606 | dev_err(dev, "Memory allocation failed\n"); |
490 | return -ENOMEM; | 607 | return -ENOMEM; |
491 | } | 608 | } |
492 | 609 | ||
493 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "uhh"); | 610 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
494 | omap->uhh_base = devm_request_and_ioremap(dev, res); | 611 | omap->uhh_base = devm_request_and_ioremap(dev, res); |
495 | if (!omap->uhh_base) { | 612 | if (!omap->uhh_base) { |
496 | dev_err(dev, "Resource request/ioremap failed\n"); | 613 | dev_err(dev, "Resource request/ioremap failed\n"); |
@@ -661,10 +778,23 @@ static int usbhs_omap_probe(struct platform_device *pdev) | |||
661 | } | 778 | } |
662 | 779 | ||
663 | omap_usbhs_init(dev); | 780 | omap_usbhs_init(dev); |
664 | ret = omap_usbhs_alloc_children(pdev); | 781 | |
665 | if (ret) { | 782 | if (dev->of_node) { |
666 | dev_err(dev, "omap_usbhs_alloc_children failed\n"); | 783 | ret = of_platform_populate(dev->of_node, |
667 | goto err_alloc; | 784 | usbhs_child_match_table, NULL, dev); |
785 | |||
786 | if (ret) { | ||
787 | dev_err(dev, "Failed to create DT children: %d\n", ret); | ||
788 | goto err_alloc; | ||
789 | } | ||
790 | |||
791 | } else { | ||
792 | ret = omap_usbhs_alloc_children(pdev); | ||
793 | if (ret) { | ||
794 | dev_err(dev, "omap_usbhs_alloc_children failed: %d\n", | ||
795 | ret); | ||
796 | goto err_alloc; | ||
797 | } | ||
668 | } | 798 | } |
669 | 799 | ||
670 | return 0; | 800 | return 0; |
@@ -703,6 +833,13 @@ err_mem: | |||
703 | return ret; | 833 | return ret; |
704 | } | 834 | } |
705 | 835 | ||
836 | static int usbhs_omap_remove_child(struct device *dev, void *data) | ||
837 | { | ||
838 | dev_info(dev, "unregistering\n"); | ||
839 | platform_device_unregister(to_platform_device(dev)); | ||
840 | return 0; | ||
841 | } | ||
842 | |||
706 | /** | 843 | /** |
707 | * usbhs_omap_remove - shutdown processing for UHH & TLL HCDs | 844 | * usbhs_omap_remove - shutdown processing for UHH & TLL HCDs |
708 | * @pdev: USB Host Controller being removed | 845 | * @pdev: USB Host Controller being removed |
@@ -734,6 +871,8 @@ static int usbhs_omap_remove(struct platform_device *pdev) | |||
734 | 871 | ||
735 | pm_runtime_disable(&pdev->dev); | 872 | pm_runtime_disable(&pdev->dev); |
736 | 873 | ||
874 | /* remove children */ | ||
875 | device_for_each_child(&pdev->dev, NULL, usbhs_omap_remove_child); | ||
737 | return 0; | 876 | return 0; |
738 | } | 877 | } |
739 | 878 | ||
@@ -742,16 +881,26 @@ static const struct dev_pm_ops usbhsomap_dev_pm_ops = { | |||
742 | .runtime_resume = usbhs_runtime_resume, | 881 | .runtime_resume = usbhs_runtime_resume, |
743 | }; | 882 | }; |
744 | 883 | ||
884 | static const struct of_device_id usbhs_omap_dt_ids[] = { | ||
885 | { .compatible = "ti,usbhs-host" }, | ||
886 | { } | ||
887 | }; | ||
888 | |||
889 | MODULE_DEVICE_TABLE(of, usbhs_omap_dt_ids); | ||
890 | |||
891 | |||
745 | static struct platform_driver usbhs_omap_driver = { | 892 | static struct platform_driver usbhs_omap_driver = { |
746 | .driver = { | 893 | .driver = { |
747 | .name = (char *)usbhs_driver_name, | 894 | .name = (char *)usbhs_driver_name, |
748 | .owner = THIS_MODULE, | 895 | .owner = THIS_MODULE, |
749 | .pm = &usbhsomap_dev_pm_ops, | 896 | .pm = &usbhsomap_dev_pm_ops, |
897 | .of_match_table = of_match_ptr(usbhs_omap_dt_ids), | ||
750 | }, | 898 | }, |
751 | .remove = usbhs_omap_remove, | 899 | .remove = usbhs_omap_remove, |
752 | }; | 900 | }; |
753 | 901 | ||
754 | MODULE_AUTHOR("Keshava Munegowda <keshava_mgowda@ti.com>"); | 902 | MODULE_AUTHOR("Keshava Munegowda <keshava_mgowda@ti.com>"); |
903 | MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); | ||
755 | MODULE_ALIAS("platform:" USBHS_DRIVER_NAME); | 904 | MODULE_ALIAS("platform:" USBHS_DRIVER_NAME); |
756 | MODULE_LICENSE("GPL v2"); | 905 | MODULE_LICENSE("GPL v2"); |
757 | MODULE_DESCRIPTION("usb host common core driver for omap EHCI and OHCI"); | 906 | MODULE_DESCRIPTION("usb host common core driver for omap EHCI and OHCI"); |