]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/usb/host/ohci-at91.c
usb host: fix platform driver hotplug/coldplug
[linux-2.6-omap-h63xx.git] / drivers / usb / host / ohci-at91.c
index 930346487278e7598618cc16c62ebf8b62eccb0c..d72dc07dda017de12a51eaedc68b9c0626d26cb3 100644 (file)
 
 #include <asm/mach-types.h>
 #include <asm/hardware.h>
+#include <asm/gpio.h>
+
 #include <asm/arch/board.h>
+#include <asm/arch/cpu.h>
 
 #ifndef CONFIG_ARCH_AT91
 #error "CONFIG_ARCH_AT91 must be defined."
 #endif
 
-/* interface and function clocks */
-static struct clk *iclk, *fclk;
+/* interface and function clocks; sometimes also an AHB clock */
+static struct clk *iclk, *fclk, *hclk;
 static int clocked;
 
 extern int usb_disabled(void);
 
 /*-------------------------------------------------------------------------*/
 
+static void at91_start_clock(void)
+{
+       if (cpu_is_at91sam9261())
+               clk_enable(hclk);
+       clk_enable(iclk);
+       clk_enable(fclk);
+       clocked = 1;
+}
+
+static void at91_stop_clock(void)
+{
+       clk_disable(fclk);
+       clk_disable(iclk);
+       if (cpu_is_at91sam9261())
+               clk_disable(hclk);
+       clocked = 0;
+}
+
 static void at91_start_hc(struct platform_device *pdev)
 {
        struct usb_hcd *hcd = platform_get_drvdata(pdev);
@@ -41,9 +62,7 @@ static void at91_start_hc(struct platform_device *pdev)
        /*
         * Start the USB clocks.
         */
-       clk_enable(iclk);
-       clk_enable(fclk);
-       clocked = 1;
+       at91_start_clock();
 
        /*
         * The USB host controller must remain in reset.
@@ -66,9 +85,7 @@ static void at91_stop_hc(struct platform_device *pdev)
        /*
         * Stop the USB clocks.
         */
-       clk_disable(fclk);
-       clk_disable(iclk);
-       clocked = 0;
+       at91_stop_clock();
 }
 
 
@@ -126,6 +143,8 @@ static int usb_hcd_at91_probe(const struct hc_driver *driver,
 
        iclk = clk_get(&pdev->dev, "ohci_clk");
        fclk = clk_get(&pdev->dev, "uhpck");
+       if (cpu_is_at91sam9261())
+               hclk = clk_get(&pdev->dev, "hck0");
 
        at91_start_hc(pdev);
        ohci_hcd_init(hcd_to_ohci(hcd));
@@ -137,6 +156,8 @@ static int usb_hcd_at91_probe(const struct hc_driver *driver,
        /* Error handling */
        at91_stop_hc(pdev);
 
+       if (cpu_is_at91sam9261())
+               clk_put(hclk);
        clk_put(fclk);
        clk_put(iclk);
 
@@ -171,9 +192,11 @@ static int usb_hcd_at91_remove(struct usb_hcd *hcd,
        iounmap(hcd->regs);
        release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
 
+       if (cpu_is_at91sam9261())
+               clk_put(hclk);
        clk_put(fclk);
        clk_put(iclk);
-       fclk = iclk = NULL;
+       fclk = iclk = hclk = NULL;
 
        dev_set_drvdata(&pdev->dev, NULL);
        return 0;
@@ -250,12 +273,41 @@ static const struct hc_driver ohci_at91_hc_driver = {
 
 static int ohci_hcd_at91_drv_probe(struct platform_device *pdev)
 {
+       struct at91_usbh_data   *pdata = pdev->dev.platform_data;
+       int                     i;
+
+       if (pdata) {
+               /* REVISIT make the driver support per-port power switching,
+                * and also overcurrent detection.  Here we assume the ports
+                * are always powered while this driver is active, and use
+                * active-low power switches.
+                */
+               for (i = 0; i < pdata->ports; i++) {
+                       if (pdata->vbus_pin[i] <= 0)
+                               continue;
+                       gpio_request(pdata->vbus_pin[i], "ohci_vbus");
+                       gpio_direction_output(pdata->vbus_pin[i], 0);
+               }
+       }
+
        device_init_wakeup(&pdev->dev, 1);
        return usb_hcd_at91_probe(&ohci_at91_hc_driver, pdev);
 }
 
 static int ohci_hcd_at91_drv_remove(struct platform_device *pdev)
 {
+       struct at91_usbh_data   *pdata = pdev->dev.platform_data;
+       int                     i;
+
+       if (pdata) {
+               for (i = 0; i < pdata->ports; i++) {
+                       if (pdata->vbus_pin[i] <= 0)
+                               continue;
+                       gpio_direction_output(pdata->vbus_pin[i], 1);
+                       gpio_free(pdata->vbus_pin[i]);
+               }
+       }
+
        device_init_wakeup(&pdev->dev, 0);
        return usb_hcd_at91_remove(platform_get_drvdata(pdev), pdev);
 }
@@ -280,9 +332,7 @@ ohci_hcd_at91_drv_suspend(struct platform_device *pdev, pm_message_t mesg)
         */
        if (at91_suspend_entering_slow_clock()) {
                ohci_usb_reset (ohci);
-               clk_disable(fclk);
-               clk_disable(iclk);
-               clocked = 0;
+               at91_stop_clock();
        }
 
        return 0;
@@ -295,11 +345,8 @@ static int ohci_hcd_at91_drv_resume(struct platform_device *pdev)
        if (device_may_wakeup(&pdev->dev))
                disable_irq_wake(hcd->irq);
 
-       if (!clocked) {
-               clk_enable(iclk);
-               clk_enable(fclk);
-               clocked = 1;
-       }
+       if (!clocked)
+               at91_start_clock();
 
        return 0;
 }
@@ -308,7 +355,7 @@ static int ohci_hcd_at91_drv_resume(struct platform_device *pdev)
 #define ohci_hcd_at91_drv_resume  NULL
 #endif
 
-MODULE_ALIAS("at91_ohci");
+MODULE_ALIAS("platform:at91_ohci");
 
 static struct platform_driver ohci_hcd_at91_driver = {
        .probe          = ohci_hcd_at91_drv_probe,
@@ -321,4 +368,3 @@ static struct platform_driver ohci_hcd_at91_driver = {
                .owner  = THIS_MODULE,
        },
 };
-