usb: chipidea: Fix Internal error: : 808 [#1] ARM related to STS flag
[firefly-linux-kernel-4.4.55.git] / drivers / usb / chipidea / core.c
index 23763dcec069b2e8d1ba6dd140fc9cf644d2ddec..edeec363868497cfa4b06ca4bcf2edf0ac9df972 100644 (file)
 #include "otg.h"
 
 /* Controller register map */
-static uintptr_t ci_regs_nolpm[] = {
-       [CAP_CAPLENGTH]         = 0x000UL,
-       [CAP_HCCPARAMS]         = 0x008UL,
-       [CAP_DCCPARAMS]         = 0x024UL,
-       [CAP_TESTMODE]          = 0x038UL,
-       [OP_USBCMD]             = 0x000UL,
-       [OP_USBSTS]             = 0x004UL,
-       [OP_USBINTR]            = 0x008UL,
-       [OP_DEVICEADDR]         = 0x014UL,
-       [OP_ENDPTLISTADDR]      = 0x018UL,
-       [OP_PORTSC]             = 0x044UL,
-       [OP_DEVLC]              = 0x084UL,
-       [OP_OTGSC]              = 0x064UL,
-       [OP_USBMODE]            = 0x068UL,
-       [OP_ENDPTSETUPSTAT]     = 0x06CUL,
-       [OP_ENDPTPRIME]         = 0x070UL,
-       [OP_ENDPTFLUSH]         = 0x074UL,
-       [OP_ENDPTSTAT]          = 0x078UL,
-       [OP_ENDPTCOMPLETE]      = 0x07CUL,
-       [OP_ENDPTCTRL]          = 0x080UL,
+static const u8 ci_regs_nolpm[] = {
+       [CAP_CAPLENGTH]         = 0x00U,
+       [CAP_HCCPARAMS]         = 0x08U,
+       [CAP_DCCPARAMS]         = 0x24U,
+       [CAP_TESTMODE]          = 0x38U,
+       [OP_USBCMD]             = 0x00U,
+       [OP_USBSTS]             = 0x04U,
+       [OP_USBINTR]            = 0x08U,
+       [OP_DEVICEADDR]         = 0x14U,
+       [OP_ENDPTLISTADDR]      = 0x18U,
+       [OP_PORTSC]             = 0x44U,
+       [OP_DEVLC]              = 0x84U,
+       [OP_OTGSC]              = 0x64U,
+       [OP_USBMODE]            = 0x68U,
+       [OP_ENDPTSETUPSTAT]     = 0x6CU,
+       [OP_ENDPTPRIME]         = 0x70U,
+       [OP_ENDPTFLUSH]         = 0x74U,
+       [OP_ENDPTSTAT]          = 0x78U,
+       [OP_ENDPTCOMPLETE]      = 0x7CU,
+       [OP_ENDPTCTRL]          = 0x80U,
 };
 
-static uintptr_t ci_regs_lpm[] = {
-       [CAP_CAPLENGTH]         = 0x000UL,
-       [CAP_HCCPARAMS]         = 0x008UL,
-       [CAP_DCCPARAMS]         = 0x024UL,
-       [CAP_TESTMODE]          = 0x0FCUL,
-       [OP_USBCMD]             = 0x000UL,
-       [OP_USBSTS]             = 0x004UL,
-       [OP_USBINTR]            = 0x008UL,
-       [OP_DEVICEADDR]         = 0x014UL,
-       [OP_ENDPTLISTADDR]      = 0x018UL,
-       [OP_PORTSC]             = 0x044UL,
-       [OP_DEVLC]              = 0x084UL,
-       [OP_OTGSC]              = 0x0C4UL,
-       [OP_USBMODE]            = 0x0C8UL,
-       [OP_ENDPTSETUPSTAT]     = 0x0D8UL,
-       [OP_ENDPTPRIME]         = 0x0DCUL,
-       [OP_ENDPTFLUSH]         = 0x0E0UL,
-       [OP_ENDPTSTAT]          = 0x0E4UL,
-       [OP_ENDPTCOMPLETE]      = 0x0E8UL,
-       [OP_ENDPTCTRL]          = 0x0ECUL,
+static const u8 ci_regs_lpm[] = {
+       [CAP_CAPLENGTH]         = 0x00U,
+       [CAP_HCCPARAMS]         = 0x08U,
+       [CAP_DCCPARAMS]         = 0x24U,
+       [CAP_TESTMODE]          = 0xFCU,
+       [OP_USBCMD]             = 0x00U,
+       [OP_USBSTS]             = 0x04U,
+       [OP_USBINTR]            = 0x08U,
+       [OP_DEVICEADDR]         = 0x14U,
+       [OP_ENDPTLISTADDR]      = 0x18U,
+       [OP_PORTSC]             = 0x44U,
+       [OP_DEVLC]              = 0x84U,
+       [OP_OTGSC]              = 0xC4U,
+       [OP_USBMODE]            = 0xC8U,
+       [OP_ENDPTSETUPSTAT]     = 0xD8U,
+       [OP_ENDPTPRIME]         = 0xDCU,
+       [OP_ENDPTFLUSH]         = 0xE0U,
+       [OP_ENDPTSTAT]          = 0xE4U,
+       [OP_ENDPTCOMPLETE]      = 0xE8U,
+       [OP_ENDPTCTRL]          = 0xECU,
 };
 
 static int hw_alloc_regmap(struct ci_hdrc *ci, bool is_lpm)
 {
        int i;
 
-       kfree(ci->hw_bank.regmap);
-
-       ci->hw_bank.regmap = kzalloc((OP_LAST + 1) * sizeof(void *),
-                                    GFP_KERNEL);
-       if (!ci->hw_bank.regmap)
-               return -ENOMEM;
-
        for (i = 0; i < OP_ENDPTCTRL; i++)
                ci->hw_bank.regmap[i] =
                        (i <= CAP_LAST ? ci->hw_bank.cap : ci->hw_bank.op) +
@@ -172,6 +165,27 @@ u8 hw_port_test_get(struct ci_hdrc *ci)
        return hw_read(ci, OP_PORTSC, PORTSC_PTC) >> __ffs(PORTSC_PTC);
 }
 
+/* The PHY enters/leaves low power mode */
+static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable)
+{
+       enum ci_hw_regs reg = ci->hw_bank.lpm ? OP_DEVLC : OP_PORTSC;
+       bool lpm = !!(hw_read(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm)));
+
+       if (enable && !lpm) {
+               hw_write(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm),
+                               PORTSC_PHCD(ci->hw_bank.lpm));
+       } else  if (!enable && lpm) {
+               hw_write(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm),
+                               0);
+               /* 
+                * The controller needs at least 1ms to reflect
+                * PHY's status, the PHY also needs some time (less
+                * than 1ms) to leave low power mode.
+                */
+               usleep_range(1500, 2000);
+       }
+}
+
 static int hw_device_init(struct ci_hdrc *ci, void __iomem *base)
 {
        u32 reg;
@@ -187,7 +201,8 @@ static int hw_device_init(struct ci_hdrc *ci, void __iomem *base)
        reg = hw_read(ci, CAP_HCCPARAMS, HCCPARAMS_LEN) >>
                __ffs(HCCPARAMS_LEN);
        ci->hw_bank.lpm  = reg;
-       hw_alloc_regmap(ci, !!reg);
+       if (reg)
+               hw_alloc_regmap(ci, !!reg);
        ci->hw_bank.size = ci->hw_bank.op - ci->hw_bank.abs;
        ci->hw_bank.size += OP_LAST;
        ci->hw_bank.size /= sizeof(u32);
@@ -199,6 +214,8 @@ static int hw_device_init(struct ci_hdrc *ci, void __iomem *base)
        if (ci->hw_ep_max > ENDPT_MAX)
                return -ENODEV;
 
+       ci_hdrc_enter_lpm(ci, false);
+
        /* Disable all interrupts bits */
        hw_write(ci, OP_USBINTR, 0xffffffff, 0);
 
@@ -219,7 +236,7 @@ static int hw_device_init(struct ci_hdrc *ci, void __iomem *base)
 
 static void hw_phymode_configure(struct ci_hdrc *ci)
 {
-       u32 portsc, lpm, sts;
+       u32 portsc, lpm, sts = 0;
 
        switch (ci->platdata->phy_mode) {
        case USBPHY_INTERFACE_MODE_UTMI:
@@ -249,10 +266,12 @@ static void hw_phymode_configure(struct ci_hdrc *ci)
 
        if (ci->hw_bank.lpm) {
                hw_write(ci, OP_DEVLC, DEVLC_PTS(7) | DEVLC_PTW, lpm);
-               hw_write(ci, OP_DEVLC, DEVLC_STS, sts);
+               if (sts)
+                       hw_write(ci, OP_DEVLC, DEVLC_STS, DEVLC_STS);
        } else {
                hw_write(ci, OP_PORTSC, PORTSC_PTS(7) | PORTSC_PTW, portsc);
-               hw_write(ci, OP_PORTSC, PORTSC_STS, sts);
+               if (sts)
+                       hw_write(ci, OP_PORTSC, PORTSC_STS, PORTSC_STS);
        }
 }
 
@@ -369,16 +388,28 @@ static irqreturn_t ci_irq(int irq, void *data)
 static int ci_get_platdata(struct device *dev,
                struct ci_hdrc_platform_data *platdata)
 {
-       /* Get the vbus regulator */
-       platdata->reg_vbus = devm_regulator_get(dev, "vbus");
-       if (PTR_ERR(platdata->reg_vbus) == -EPROBE_DEFER) {
-               return -EPROBE_DEFER;
-       } else if (PTR_ERR(platdata->reg_vbus) == -ENODEV) {
-               platdata->reg_vbus = NULL; /* no vbus regualator is needed */
-       } else if (IS_ERR(platdata->reg_vbus)) {
-               dev_err(dev, "Getting regulator error: %ld\n",
-                       PTR_ERR(platdata->reg_vbus));
-               return PTR_ERR(platdata->reg_vbus);
+       if (!platdata->phy_mode)
+               platdata->phy_mode = of_usb_get_phy_mode(dev->of_node);
+
+       if (!platdata->dr_mode)
+               platdata->dr_mode = of_usb_get_dr_mode(dev->of_node);
+
+       if (platdata->dr_mode == USB_DR_MODE_UNKNOWN)
+               platdata->dr_mode = USB_DR_MODE_OTG;
+
+       if (platdata->dr_mode != USB_DR_MODE_PERIPHERAL) {
+               /* Get the vbus regulator */
+               platdata->reg_vbus = devm_regulator_get(dev, "vbus");
+               if (PTR_ERR(platdata->reg_vbus) == -EPROBE_DEFER) {
+                       return -EPROBE_DEFER;
+               } else if (PTR_ERR(platdata->reg_vbus) == -ENODEV) {
+                       /* no vbus regualator is needed */
+                       platdata->reg_vbus = NULL;
+               } else if (IS_ERR(platdata->reg_vbus)) {
+                       dev_err(dev, "Getting regulator error: %ld\n",
+                               PTR_ERR(platdata->reg_vbus));
+                       return PTR_ERR(platdata->reg_vbus);
+               }
        }
 
        return 0;
@@ -465,6 +496,33 @@ static void ci_get_otg_capable(struct ci_hdrc *ci)
        }
 }
 
+static int ci_usb_phy_init(struct ci_hdrc *ci)
+{
+       if (ci->platdata->phy) {
+               ci->transceiver = ci->platdata->phy;
+               return usb_phy_init(ci->transceiver);
+       } else {
+               ci->global_phy = true;
+               ci->transceiver = usb_get_phy(USB_PHY_TYPE_USB2);
+               if (IS_ERR(ci->transceiver))
+                       ci->transceiver = NULL;
+
+               return 0;
+       }
+}
+
+static void ci_usb_phy_destroy(struct ci_hdrc *ci)
+{
+       if (!ci->transceiver)
+               return;
+
+       otg_set_peripheral(ci->transceiver->otg, NULL);
+       if (ci->global_phy)
+               usb_put_phy(ci->transceiver);
+       else
+               usb_phy_shutdown(ci->transceiver);
+}
+
 static int ci_hdrc_probe(struct platform_device *pdev)
 {
        struct device   *dev = &pdev->dev;
@@ -473,7 +531,6 @@ static int ci_hdrc_probe(struct platform_device *pdev)
        void __iomem    *base;
        int             ret;
        enum usb_dr_mode dr_mode;
-       struct device_node *of_node = dev->of_node ?: dev->parent->of_node;
 
        if (!dev->platform_data) {
                dev_err(dev, "platform data missing\n");
@@ -493,10 +550,8 @@ static int ci_hdrc_probe(struct platform_device *pdev)
 
        ci->dev = dev;
        ci->platdata = dev->platform_data;
-       if (ci->platdata->phy)
-               ci->transceiver = ci->platdata->phy;
-       else
-               ci->global_phy = true;
+       ci->imx28_write_fix = !!(ci->platdata->flags &
+               CI_HDRC_IMX28_WRITE_FIX);
 
        ret = hw_device_init(ci, base);
        if (ret < 0) {
@@ -504,27 +559,25 @@ static int ci_hdrc_probe(struct platform_device *pdev)
                return -ENODEV;
        }
 
+       ret = ci_usb_phy_init(ci);
+       if (ret) {
+               dev_err(dev, "unable to init phy: %d\n", ret);
+               return ret;
+       }
+
        ci->hw_bank.phys = res->start;
 
        ci->irq = platform_get_irq(pdev, 0);
        if (ci->irq < 0) {
                dev_err(dev, "missing IRQ\n");
-               return -ENODEV;
+               ret = -ENODEV;
+               goto destroy_phy;
        }
 
        ci_get_otg_capable(ci);
 
-       if (!ci->platdata->phy_mode)
-               ci->platdata->phy_mode = of_usb_get_phy_mode(of_node);
-
        hw_phymode_configure(ci);
 
-       if (!ci->platdata->dr_mode)
-               ci->platdata->dr_mode = of_usb_get_dr_mode(of_node);
-
-       if (ci->platdata->dr_mode == USB_DR_MODE_UNKNOWN)
-               ci->platdata->dr_mode = USB_DR_MODE_OTG;
-
        dr_mode = ci->platdata->dr_mode;
        /* initialize role(s) before the interrupt is requested */
        if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
@@ -537,11 +590,23 @@ static int ci_hdrc_probe(struct platform_device *pdev)
                ret = ci_hdrc_gadget_init(ci);
                if (ret)
                        dev_info(dev, "doesn't support gadget\n");
+               if (!ret && ci->transceiver) {
+                       ret = otg_set_peripheral(ci->transceiver->otg,
+                                                       &ci->gadget);
+                       /*
+                        * If we implement all USB functions using chipidea drivers,
+                        * it doesn't need to call above API, meanwhile, if we only
+                        * use gadget function, calling above API is useless.
+                        */
+                       if (ret && ret != -ENOTSUPP)
+                               goto destroy_phy;
+               }
        }
 
        if (!ci->roles[CI_ROLE_HOST] && !ci->roles[CI_ROLE_GADGET]) {
                dev_err(dev, "no supported roles\n");
-               return -ENODEV;
+               ret = -ENODEV;
+               goto destroy_phy;
        }
 
        if (ci->is_otg) {
@@ -575,6 +640,10 @@ static int ci_hdrc_probe(struct platform_device *pdev)
                        : CI_ROLE_GADGET;
        }
 
+       /* only update vbus status for peripheral */
+       if (ci->role == CI_ROLE_GADGET)
+               ci_handle_vbus_change(ci);
+
        ret = ci_role_start(ci, ci->role);
        if (ret) {
                dev_err(dev, "can't start %s role\n", ci_role(ci)->name);
@@ -594,6 +663,8 @@ static int ci_hdrc_probe(struct platform_device *pdev)
        free_irq(ci->irq, ci);
 stop:
        ci_role_destroy(ci);
+destroy_phy:
+       ci_usb_phy_destroy(ci);
 
        return ret;
 }
@@ -605,7 +676,8 @@ static int ci_hdrc_remove(struct platform_device *pdev)
        dbg_remove_files(ci);
        free_irq(ci->irq, ci);
        ci_role_destroy(ci);
-       kfree(ci->hw_bank.regmap);
+       ci_hdrc_enter_lpm(ci, true);
+       ci_usb_phy_destroy(ci);
 
        return 0;
 }