| From 23e3439296a55affce3ef0ab78f1c2e03aec8767 Mon Sep 17 00:00:00 2001 |
| From: Arnd Bergmann <arnd@arndb.de> |
| Date: Fri, 13 May 2016 15:52:27 +0200 |
| Subject: usb: dwc2: fix regression on big-endian PowerPC/ARM systems |
| |
| From: Arnd Bergmann <arnd@arndb.de> |
| |
| commit 23e3439296a55affce3ef0ab78f1c2e03aec8767 upstream. |
| |
| A patch that went into Linux-4.4 to fix big-endian mode on a Lantiq |
| MIPS system unfortunately broke big-endian operation on PowerPC |
| APM82181 as reported by Christian Lamparter, and likely other |
| systems. |
| |
| It actually introduced multiple issues: |
| |
| - it broke big-endian ARM kernels: any machine that was working |
| correctly with a little-endian kernel is no longer using byteswaps |
| on big-endian kernels, which clearly breaks them. |
| - On PowerPC the same thing must be true: if it was working before, |
| using big-endian kernels is now broken. Unlike ARM, 32-bit PowerPC |
| usually uses big-endian kernels, so they are likely all broken. |
| - The barrier for dwc2_writel is on the wrong side of the __raw_writel(), |
| so the MMIO no longer synchronizes with DMA operations. |
| - On architectures that require specific CPU instructions for MMIO |
| access, using the __raw_ variant may turn this into a pointer |
| dereference that does not have the same effect as the readl/writel. |
| |
| This patch is a simple revert for all architectures other than MIPS, |
| in the hope that we can more easily backport it to fix the regression |
| on PowerPC and ARM systems without breaking the Lantiq system again. |
| |
| We should follow this up with a more elaborate change to add runtime |
| detection of endianness, to make sure it also works on all other |
| combinations of architectures and implementations of the usb-dwc2 |
| device. That patch however will be fairly large and not appropriate |
| for backports to stable kernels. |
| |
| Felipe suggested a different approach, using an endianness switching |
| register to always put the device into LE mode, but unfortunately |
| the dwc2 hardware does not provide a generic way to do that. Also, |
| I see no practical way of addressing the problem more generally by |
| patching architecture specific code on MIPS. |
| |
| Fixes: 95c8bc360944 ("usb: dwc2: Use platform endianness when accessing registers") |
| Acked-by: John Youn <johnyoun@synopsys.com> |
| Tested-by: Christian Lamparter <chunkeey@googlemail.com> |
| Signed-off-by: Arnd Bergmann <arnd@arndb.de> |
| Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/usb/dwc2/core.h | 27 +++++++++++++++++++++++++++ |
| 1 file changed, 27 insertions(+) |
| |
| --- a/drivers/usb/dwc2/core.h |
| +++ b/drivers/usb/dwc2/core.h |
| @@ -44,6 +44,17 @@ |
| #include <linux/usb/phy.h> |
| #include "hw.h" |
| |
| +#ifdef CONFIG_MIPS |
| +/* |
| + * There are some MIPS machines that can run in either big-endian |
| + * or little-endian mode and that use the dwc2 register without |
| + * a byteswap in both ways. |
| + * Unlike other architectures, MIPS apparently does not require a |
| + * barrier before the __raw_writel() to synchronize with DMA but does |
| + * require the barrier after the __raw_writel() to serialize a set of |
| + * writes. This set of operations was added specifically for MIPS and |
| + * should only be used there. |
| + */ |
| static inline u32 dwc2_readl(const void __iomem *addr) |
| { |
| u32 value = __raw_readl(addr); |
| @@ -70,6 +81,22 @@ static inline void dwc2_writel(u32 value |
| pr_info("INFO:: wrote %08x to %p\n", value, addr); |
| #endif |
| } |
| +#else |
| +/* Normal architectures just use readl/write */ |
| +static inline u32 dwc2_readl(const void __iomem *addr) |
| +{ |
| + return readl(addr); |
| +} |
| + |
| +static inline void dwc2_writel(u32 value, void __iomem *addr) |
| +{ |
| + writel(value, addr); |
| + |
| +#ifdef DWC2_LOG_WRITES |
| + pr_info("info:: wrote %08x to %p\n", value, addr); |
| +#endif |
| +} |
| +#endif |
| |
| /* Maximum number of Endpoints/HostChannels */ |
| #define MAX_EPS_CHANNELS 16 |