From: Tuomas Tynkkynen <ttynkkynen@nvidia.com>
To: Alan Stern <stern@rowland.harvard.edu>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Stephen Warren <swarren@wwwdotorg.org>,
Felipe Balbi <balbi@ti.com>,
Philipp Zabel <p.zabel@pengutronix.de>,
linux-usb@vger.kernel.org, linux-tegra@vger.kernel.org,
linux-kernel@vger.kernel.org, tuomas.tynkkynen@iki.fi,
Tuomas Tynkkynen <ttynkkynen@nvidia.com>
Subject: [PATCH 2/3] USB: EHCI: tegra: Fix probe order issue leading to broken USB
Date: Wed, 2 Jul 2014 00:08:42 +0300 [thread overview]
Message-ID: <1404248923-21086-3-git-send-email-ttynkkynen@nvidia.com> (raw)
In-Reply-To: <1404248923-21086-1-git-send-email-ttynkkynen@nvidia.com>
The Tegra USB complex has a particularly annoying misdesign: some of the
UTMI pad configuration registers are global for all the 3 USB controllers
on the chip, but those registers are located in the first controller's
register space and will be cleared when the reset to the first
controller is asserted. Currently, this means that if the 1st controller
were to finish probing after the 2nd or 3rd controller, USB would not
work at all.
Fix this situation by always resetting the 1st controller before doing
any other setup to any of the controllers, and then never ever reset the
first controller again.
The really ugly part is that the EHCI controllers don't have a reset
control phandle to the 1st controller, so we have to do some device tree
groveling to find the phandle to the 1st controller.
Signed-off-by: Tuomas Tynkkynen <ttynkkynen@nvidia.com>
---
drivers/usb/host/ehci-tegra.c | 111 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 108 insertions(+), 3 deletions(-)
diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c
index 5590567..e3f362b 100644
--- a/drivers/usb/host/ehci-tegra.c
+++ b/drivers/usb/host/ehci-tegra.c
@@ -24,6 +24,7 @@
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
@@ -46,6 +47,7 @@
#define DRV_NAME "tegra-ehci"
static struct hc_driver __read_mostly tegra_ehci_hc_driver;
+static struct device_node *usb1_ehci_node;
struct tegra_ehci_soc_config {
bool has_hostpc;
@@ -60,6 +62,108 @@ struct tegra_ehci_hcd {
enum tegra_usb_phy_port_speed port_speed;
};
+/*
+ * The 1st USB controller contains some PHY registers that are global for
+ * all the controllers on the chip. Those registers are also cleared when
+ * reset is asserted to the 1st controller. This means that the 1st controller
+ * can only be reset when no other controlled has finished probing. So we'll
+ * reset the 1st controller before doing any other setup on any of the
+ * controllers, and then never again.
+ *
+ * Sadly, the EHCI device tree nodes don't have the phandle to the first USB
+ * controller, so in order not to break the DT ABI, this hack is required to
+ * locate it.
+ */
+static struct device_node *tegra_find_usb1_node(struct platform_device *pdev)
+{
+ int err;
+ int num = 0;
+ const char *compatible;
+ struct device_node *np, *lowest_node = NULL;
+ u64 lowest_addr = U64_MAX;
+
+ /*
+ * Find the node that has the same compatible string as &pdev->dev
+ * and has the lowest register address.
+ */
+ of_property_read_string(pdev->dev.of_node, "compatible",
+ &compatible);
+
+ for_each_compatible_node(np, NULL, compatible) {
+ u64 sz, address;
+ const __be32 *addr_cells;
+
+ addr_cells = of_get_address(np, 0, &sz, NULL);
+ if (!addr_cells) {
+ dev_err(&pdev->dev, "found EHCI node with no address");
+ of_node_put(np);
+ goto failed;
+ }
+
+ address = of_read_number(addr_cells, of_n_addr_cells(np));
+ if (address < lowest_addr) {
+ of_node_put(lowest_node);
+ lowest_node = np;
+ lowest_addr = address;
+ } else {
+ of_node_put(np);
+ }
+ num++;
+ }
+
+ if (num != 3) {
+ dev_err(&pdev->dev, "couldn't locate all 3 EHCI nodes");
+ err = -ENODEV;
+ goto failed;
+ }
+
+ return lowest_node;
+
+failed:
+ of_node_put(lowest_node);
+
+ return ERR_PTR(err);
+}
+
+static int tegra_reset_usb_controller(struct platform_device *pdev)
+{
+ struct usb_hcd *hcd = platform_get_drvdata(pdev);
+ struct tegra_ehci_hcd *tegra =
+ (struct tegra_ehci_hcd *)hcd_to_ehci(hcd)->priv;
+
+ if (!usb1_ehci_node) {
+ struct device_node *np;
+ struct reset_control *usb1_reset;
+
+ np = tegra_find_usb1_node(pdev);
+ if (!np)
+ return -ENODEV;
+
+ usb1_reset = of_reset_control_get(np, "usb");
+ if (IS_ERR(usb1_reset)) {
+ dev_err(&pdev->dev,
+ "couldn't get reset for 1st USB controller");
+ of_node_put(np);
+ return PTR_ERR(usb1_reset);
+ }
+
+ reset_control_assert(usb1_reset);
+ udelay(1);
+ reset_control_deassert(usb1_reset);
+
+ reset_control_put(usb1_reset);
+ usb1_ehci_node = np;
+ }
+
+ if (pdev->dev.of_node != usb1_ehci_node) {
+ reset_control_assert(tegra->rst);
+ udelay(1);
+ reset_control_deassert(tegra->rst);
+ }
+
+ return 0;
+}
+
static int tegra_ehci_internal_port_reset(
struct ehci_hcd *ehci,
u32 __iomem *portsc_reg
@@ -389,9 +493,9 @@ static int tegra_ehci_probe(struct platform_device *pdev)
if (err)
goto cleanup_hcd_create;
- reset_control_assert(tegra->rst);
- udelay(1);
- reset_control_deassert(tegra->rst);
+ err = tegra_reset_usb_controller(pdev);
+ if (err)
+ goto cleanup_clk_en;
u_phy = devm_usb_get_phy_by_phandle(&pdev->dev, "nvidia,phy", 0);
if (IS_ERR(u_phy)) {
@@ -560,6 +664,7 @@ module_init(ehci_tegra_init);
static void __exit ehci_tegra_cleanup(void)
{
+ of_node_put(usb1_ehci_node);
platform_driver_unregister(&tegra_ehci_driver);
}
module_exit(ehci_tegra_cleanup);
--
1.8.1.5
next prev parent reply other threads:[~2014-07-01 21:08 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-07-01 21:08 [PATCH 0/3] Tegra USB probe order issue fix Tuomas Tynkkynen
2014-07-01 21:08 ` [PATCH 1/3] reset: Re-export of_reset_control_get Tuomas Tynkkynen
2014-07-01 21:08 ` Tuomas Tynkkynen [this message]
[not found] ` <1404248923-21086-1-git-send-email-ttynkkynen-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
2014-07-01 21:08 ` [PATCH 3/3] USB: PHY: tegra: Call tegra_usb_phy_close only on device removal Tuomas Tynkkynen
[not found] ` <1404248923-21086-4-git-send-email-ttynkkynen-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
2014-07-01 22:21 ` Stephen Warren
2014-07-02 14:02 ` [PATCH 0/3] Tegra USB probe order issue fix Alan Stern
2014-07-02 15:45 ` Stephen Warren
2014-07-02 16:09 ` Alan Stern
2014-07-02 16:18 ` Stephen Warren
[not found] ` <53B430CE.5040905-3lzwWm7+Weoh9ZMKESR00Q@public.gmane.org>
2014-07-02 17:45 ` Alan Stern
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1404248923-21086-3-git-send-email-ttynkkynen@nvidia.com \
--to=ttynkkynen@nvidia.com \
--cc=balbi@ti.com \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-tegra@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=p.zabel@pengutronix.de \
--cc=stern@rowland.harvard.edu \
--cc=swarren@wwwdotorg.org \
--cc=tuomas.tynkkynen@iki.fi \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox