UPSTREAM: usb: dwc2: Add support for Lantiq ARX and XRX SoCs
[firefly-linux-kernel-4.4.55.git] / drivers / usb / dwc2 / platform.c
index 39c1cbf0e75d9a5957b1567def5d23fb3631a015..da2824581b08493785ea99da14214f1a08533324 100644 (file)
 
 static const char dwc2_driver_name[] = "dwc2";
 
+static const struct dwc2_core_params params_hi6220 = {
+       .otg_cap                        = 2,    /* No HNP/SRP capable */
+       .otg_ver                        = 0,    /* 1.3 */
+       .dma_enable                     = 1,
+       .dma_desc_enable                = 0,
+       .dma_desc_fs_enable             = 0,
+       .speed                          = 0,    /* High Speed */
+       .enable_dynamic_fifo            = 1,
+       .en_multiple_tx_fifo            = 1,
+       .host_rx_fifo_size              = 512,
+       .host_nperio_tx_fifo_size       = 512,
+       .host_perio_tx_fifo_size        = 512,
+       .max_transfer_size              = 65535,
+       .max_packet_count               = 511,
+       .host_channels                  = 16,
+       .phy_type                       = 1,    /* UTMI */
+       .phy_utmi_width                 = 8,
+       .phy_ulpi_ddr                   = 0,    /* Single */
+       .phy_ulpi_ext_vbus              = 0,
+       .i2c_enable                     = 0,
+       .ulpi_fs_ls                     = 0,
+       .host_support_fs_ls_low_power   = 0,
+       .host_ls_low_power_phy_clk      = 0,    /* 48 MHz */
+       .ts_dline                       = 0,
+       .reload_ctl                     = 0,
+       .ahbcfg                         = GAHBCFG_HBSTLEN_INCR16 <<
+                                         GAHBCFG_HBSTLEN_SHIFT,
+       .uframe_sched                   = 0,
+       .external_id_pin_ctl            = -1,
+       .hibernation                    = -1,
+};
+
 static const struct dwc2_core_params params_bcm2835 = {
        .otg_cap                        = 0,    /* HNP/SRP capable */
        .otg_ver                        = 0,    /* 1.3 */
        .dma_enable                     = 1,
        .dma_desc_enable                = 0,
+       .dma_desc_fs_enable             = 0,
        .speed                          = 0,    /* High Speed */
        .enable_dynamic_fifo            = 1,
        .en_multiple_tx_fifo            = 1,
@@ -89,13 +122,14 @@ static const struct dwc2_core_params params_rk3066 = {
        .otg_ver                        = -1,
        .dma_enable                     = -1,
        .dma_desc_enable                = 0,
+       .dma_desc_fs_enable             = 0,
        .speed                          = -1,
        .enable_dynamic_fifo            = 1,
        .en_multiple_tx_fifo            = -1,
-       .host_rx_fifo_size              = 520,  /* 520 DWORDs */
+       .host_rx_fifo_size              = 525,  /* 525 DWORDs */
        .host_nperio_tx_fifo_size       = 128,  /* 128 DWORDs */
        .host_perio_tx_fifo_size        = 256,  /* 256 DWORDs */
-       .max_transfer_size              = 65535,
+       .max_transfer_size              = -1,
        .max_packet_count               = -1,
        .host_channels                  = -1,
        .phy_type                       = -1,
@@ -115,6 +149,103 @@ static const struct dwc2_core_params params_rk3066 = {
        .hibernation                    = -1,
 };
 
+static const struct dwc2_core_params params_ltq = {
+       .otg_cap                        = 2,    /* non-HNP/non-SRP */
+       .otg_ver                        = -1,
+       .dma_enable                     = -1,
+       .dma_desc_enable                = -1,
+       .dma_desc_fs_enable             = -1,
+       .speed                          = -1,
+       .enable_dynamic_fifo            = -1,
+       .en_multiple_tx_fifo            = -1,
+       .host_rx_fifo_size              = 288,  /* 288 DWORDs */
+       .host_nperio_tx_fifo_size       = 128,  /* 128 DWORDs */
+       .host_perio_tx_fifo_size        = 96,   /* 96 DWORDs */
+       .max_transfer_size              = 65535,
+       .max_packet_count               = 511,
+       .host_channels                  = -1,
+       .phy_type                       = -1,
+       .phy_utmi_width                 = -1,
+       .phy_ulpi_ddr                   = -1,
+       .phy_ulpi_ext_vbus              = -1,
+       .i2c_enable                     = -1,
+       .ulpi_fs_ls                     = -1,
+       .host_support_fs_ls_low_power   = -1,
+       .host_ls_low_power_phy_clk      = -1,
+       .ts_dline                       = -1,
+       .reload_ctl                     = -1,
+       .ahbcfg                         = GAHBCFG_HBSTLEN_INCR16 <<
+                                         GAHBCFG_HBSTLEN_SHIFT,
+       .uframe_sched                   = -1,
+       .external_id_pin_ctl            = -1,
+       .hibernation                    = -1,
+};
+
+/*
+ * Check the dr_mode against the module configuration and hardware
+ * capabilities.
+ *
+ * The hardware, module, and dr_mode, can each be set to host, device,
+ * or otg. Check that all these values are compatible and adjust the
+ * value of dr_mode if possible.
+ *
+ *                      actual
+ *    HW  MOD dr_mode   dr_mode
+ *  ------------------------------
+ *   HST  HST  any    :  HST
+ *   HST  DEV  any    :  ---
+ *   HST  OTG  any    :  HST
+ *
+ *   DEV  HST  any    :  ---
+ *   DEV  DEV  any    :  DEV
+ *   DEV  OTG  any    :  DEV
+ *
+ *   OTG  HST  any    :  HST
+ *   OTG  DEV  any    :  DEV
+ *   OTG  OTG  any    :  dr_mode
+ */
+static int dwc2_get_dr_mode(struct dwc2_hsotg *hsotg)
+{
+       enum usb_dr_mode mode;
+
+       hsotg->dr_mode = usb_get_dr_mode(hsotg->dev);
+       if (hsotg->dr_mode == USB_DR_MODE_UNKNOWN)
+               hsotg->dr_mode = USB_DR_MODE_OTG;
+
+       mode = hsotg->dr_mode;
+
+       if (dwc2_hw_is_device(hsotg)) {
+               if (IS_ENABLED(CONFIG_USB_DWC2_HOST)) {
+                       dev_err(hsotg->dev,
+                               "Controller does not support host mode.\n");
+                       return -EINVAL;
+               }
+               mode = USB_DR_MODE_PERIPHERAL;
+       } else if (dwc2_hw_is_host(hsotg)) {
+               if (IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL)) {
+                       dev_err(hsotg->dev,
+                               "Controller does not support device mode.\n");
+                       return -EINVAL;
+               }
+               mode = USB_DR_MODE_HOST;
+       } else {
+               if (IS_ENABLED(CONFIG_USB_DWC2_HOST))
+                       mode = USB_DR_MODE_HOST;
+               else if (IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL))
+                       mode = USB_DR_MODE_PERIPHERAL;
+       }
+
+       if (mode != hsotg->dr_mode) {
+               dev_warn(hsotg->dev,
+                       "Configuration mismatch. dr_mode forced to %s\n",
+                       mode == USB_DR_MODE_HOST ? "host" : "device");
+
+               hsotg->dr_mode = mode;
+       }
+
+       return 0;
+}
+
 static int __dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg)
 {
        struct platform_device *pdev = to_platform_device(hsotg->dev);
@@ -202,6 +333,14 @@ int dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg)
        return ret;
 }
 
+/* Only used to reset usb phy at interrupter runtime */
+static void dwc2_reset_phy_work(struct work_struct *data)
+{
+       struct dwc2_hsotg *hsotg = container_of(data, struct dwc2_hsotg,
+                       phy_rst_work);
+       phy_reset(hsotg->phy);
+}
+
 static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg)
 {
        int i, ret;
@@ -228,6 +367,7 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg)
                        return ret;
                }
        }
+       INIT_WORK(&hsotg->phy_rst_work, dwc2_reset_phy_work);
 
        if (!hsotg->phy) {
                hsotg->uphy = devm_usb_get_phy(hsotg->dev, USB_PHY_TYPE_USB2);
@@ -306,9 +446,31 @@ static int dwc2_driver_remove(struct platform_device *dev)
        return 0;
 }
 
+/**
+ * dwc2_driver_shutdown() - Called on device shutdown
+ *
+ * @dev: Platform device
+ *
+ * In specific conditions (involving usb hubs) dwc2 devices can create a
+ * lot of interrupts, even to the point of overwhelming devices running
+ * at low frequencies. Some devices need to do special clock handling
+ * at shutdown-time which may bring the system clock below the threshold
+ * of being able to handle the dwc2 interrupts. Disabling dwc2-irqs
+ * prevents reboots/poweroffs from getting stuck in such cases.
+ */
+static void dwc2_driver_shutdown(struct platform_device *dev)
+{
+       struct dwc2_hsotg *hsotg = platform_get_drvdata(dev);
+
+       disable_irq(hsotg->irq);
+}
+
 static const struct of_device_id dwc2_of_match_table[] = {
        { .compatible = "brcm,bcm2835-usb", .data = &params_bcm2835 },
+       { .compatible = "hisilicon,hi6220-usb", .data = &params_hi6220 },
        { .compatible = "rockchip,rk3066-usb", .data = &params_rk3066 },
+       { .compatible = "lantiq,arx100-usb", .data = &params_ltq },
+       { .compatible = "lantiq,xrx200-usb", .data = &params_ltq },
        { .compatible = "snps,dwc2", .data = NULL },
        { .compatible = "samsung,s3c6400-hsotg", .data = NULL},
        {},
@@ -335,7 +497,6 @@ static int dwc2_driver_probe(struct platform_device *dev)
        struct dwc2_hsotg *hsotg;
        struct resource *res;
        int retval;
-       int irq;
 
        match = of_match_device(dwc2_of_match_table, &dev->dev);
        if (match && match->data) {
@@ -348,8 +509,10 @@ static int dwc2_driver_probe(struct platform_device *dev)
                /*
                 * Disable descriptor dma mode by default as the HW can support
                 * it, but does not support it for SPLIT transactions.
+                * Disable it for FS devices as well.
                 */
                defparams.dma_desc_enable = 0;
+               defparams.dma_desc_fs_enable = 0;
        }
 
        hsotg = devm_kzalloc(&dev->dev, sizeof(*hsotg), GFP_KERNEL);
@@ -375,19 +538,6 @@ static int dwc2_driver_probe(struct platform_device *dev)
        dev_dbg(&dev->dev, "mapped PA %08lx to VA %p\n",
                (unsigned long)res->start, hsotg->regs);
 
-       hsotg->dr_mode = usb_get_dr_mode(&dev->dev);
-       if (IS_ENABLED(CONFIG_USB_DWC2_HOST) &&
-                       hsotg->dr_mode != USB_DR_MODE_HOST) {
-               hsotg->dr_mode = USB_DR_MODE_HOST;
-               dev_warn(hsotg->dev,
-                       "Configuration mismatch. Forcing host mode\n");
-       } else if (IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) &&
-                       hsotg->dr_mode != USB_DR_MODE_PERIPHERAL) {
-               hsotg->dr_mode = USB_DR_MODE_PERIPHERAL;
-               dev_warn(hsotg->dev,
-                       "Configuration mismatch. Forcing peripheral mode\n");
-       }
-
        retval = dwc2_lowlevel_hw_init(hsotg);
        if (retval)
                return retval;
@@ -401,15 +551,15 @@ static int dwc2_driver_probe(struct platform_device *dev)
 
        dwc2_set_all_params(hsotg->core_params, -1);
 
-       irq = platform_get_irq(dev, 0);
-       if (irq < 0) {
+       hsotg->irq = platform_get_irq(dev, 0);
+       if (hsotg->irq < 0) {
                dev_err(&dev->dev, "missing IRQ resource\n");
-               return irq;
+               return hsotg->irq;
        }
 
        dev_dbg(hsotg->dev, "registering common handler for irq%d\n",
-               irq);
-       retval = devm_request_irq(hsotg->dev, irq,
+               hsotg->irq);
+       retval = devm_request_irq(hsotg->dev, hsotg->irq,
                                  dwc2_handle_common_intr, IRQF_SHARED,
                                  dev_name(hsotg->dev), hsotg);
        if (retval)
@@ -419,6 +569,16 @@ static int dwc2_driver_probe(struct platform_device *dev)
        if (retval)
                return retval;
 
+       retval = dwc2_get_dr_mode(hsotg);
+       if (retval)
+               return retval;
+
+       /*
+        * Reset before dwc2_get_hwparams() then it could get power-on real
+        * reset value form registers.
+        */
+       dwc2_core_reset_and_force_dr_mode(hsotg);
+
        /* Detect config values from hardware */
        retval = dwc2_get_hwparams(hsotg);
        if (retval)
@@ -427,15 +587,17 @@ static int dwc2_driver_probe(struct platform_device *dev)
        /* Validate parameter values */
        dwc2_set_parameters(hsotg, params);
 
+       dwc2_force_dr_mode(hsotg);
+
        if (hsotg->dr_mode != USB_DR_MODE_HOST) {
-               retval = dwc2_gadget_init(hsotg, irq);
+               retval = dwc2_gadget_init(hsotg, hsotg->irq);
                if (retval)
                        goto error;
                hsotg->gadget_enabled = 1;
        }
 
        if (hsotg->dr_mode != USB_DR_MODE_PERIPHERAL) {
-               retval = dwc2_hcd_init(hsotg, irq);
+               retval = dwc2_hcd_init(hsotg, hsotg->irq);
                if (retval) {
                        if (hsotg->gadget_enabled)
                                dwc2_hsotg_remove(hsotg);
@@ -502,6 +664,7 @@ static struct platform_driver dwc2_platform_driver = {
        },
        .probe = dwc2_driver_probe,
        .remove = dwc2_driver_remove,
+       .shutdown = dwc2_driver_shutdown,
 };
 
 module_platform_driver(dwc2_platform_driver);