mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-29 23:53:55 +08:00
ce15bda101
Here's the big USB Gadget pull request. This time not as large as usual with only 57 non-merge commits. The most important part here is, again, all the work on dwc3. This time around we're treating all endpoints (except for control endpoint) exactly the same. They all have the same amount of TRBs on the ring, they all treat the ring as an actual ring with a link TRB pointing to the head, etc. We're also helping the host side burst (on SuperSpeed GEN1 or GEN2 at least) for as long as possible until the endpoint returns NRDY. Other than this big TRB ring rework on dwc3, we also have a dwc3-omap DMA initialization fix, some extra debugfs files to aid in some odd debug sessions and a complete removal of our FIFO resizing logic. We have a new quirk for some dwc3 P3 quirk in some implementations. The rest is basically non-critical fixes and the usual cleanups. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJXIek2AAoJEIaOsuA1yqRE2+cP/iZzzRshn6atj4b0BaRzyk6k acK6YNs8pBgJMt9FaVWhDOlCfa/8hg5W+6Z5Ovpx4WPyJdZbYkoxJ0ZJGKYjYZ/y U8hUxpTJ2s2wZKXRKchzT90hQvNlHdg3u2okP2GK0Oa17/idcQf2w1+0Cscm/dqN Ur5HgukLe31/MrcPr49OuLkahSPfHUQlOn8i4AZyBTh9L8ouzKDCvzUt2ABoCRLE wPcMegJhks7FQ5fksXtLsfqhqAoHpYHlsZQcH35iV8wPdtSueAtZ8tS6t445XbX9 vEsFq1ovpBMYfl/dwV6RBZiQTjZiGWaIdjOZMUcpbD03yXE2IC6l+mtE/wuhlnHu J0Rr4YmfS4g++J1+380NQGoreOBZ2u7pujQ4TAy399zsLD8LR3zOQK5IErILSfpO m6p3ElkGuAyHJKmo4CysSspXnnNGBp/fuTkYdPM9IJRJCe0YwnF/zsQd0OrNZ/Pm 39f6woE2aBQaEzdn+3Nya9B2IWYi6SIheQXPg0HETX/hujZHJv1x758VO6+c7aeq nlhRlxSe8u8DKryBG43+F+myHaIz1p0Y2O3MzIlvRv0yw87QevJ4pAWptdMITaMh YpgSjDPw5y2z91AhK/Fv+AXswKaoWBc6EEzBirW6hQWllSp+7qWloA6vYXkdHDr7 QajUBeXzNgXF8JfIos5H =7szP -----END PGP SIGNATURE----- Merge tag 'usb-for-v4.7' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb into usb-next Felipe writes: usb: changes for v4.7 merge window Here's the big USB Gadget pull request. This time not as large as usual with only 57 non-merge commits. The most important part here is, again, all the work on dwc3. This time around we're treating all endpoints (except for control endpoint) exactly the same. They all have the same amount of TRBs on the ring, they all treat the ring as an actual ring with a link TRB pointing to the head, etc. We're also helping the host side burst (on SuperSpeed GEN1 or GEN2 at least) for as long as possible until the endpoint returns NRDY. Other than this big TRB ring rework on dwc3, we also have a dwc3-omap DMA initialization fix, some extra debugfs files to aid in some odd debug sessions and a complete removal of our FIFO resizing logic. We have a new quirk for some dwc3 P3 quirk in some implementations. The rest is basically non-critical fixes and the usual cleanups.
1263 lines
31 KiB
C
1263 lines
31 KiB
C
/**
|
|
* core.c - DesignWare USB3 DRD Controller Core file
|
|
*
|
|
* Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com
|
|
*
|
|
* Authors: Felipe Balbi <balbi@ti.com>,
|
|
* Sebastian Andrzej Siewior <bigeasy@linutronix.de>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 of
|
|
* the License as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <linux/version.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/io.h>
|
|
#include <linux/list.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/of.h>
|
|
#include <linux/acpi.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
|
|
#include <linux/usb/ch9.h>
|
|
#include <linux/usb/gadget.h>
|
|
#include <linux/usb/of.h>
|
|
#include <linux/usb/otg.h>
|
|
|
|
#include "platform_data.h"
|
|
#include "core.h"
|
|
#include "gadget.h"
|
|
#include "io.h"
|
|
|
|
#include "debug.h"
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
|
|
{
|
|
u32 reg;
|
|
|
|
reg = dwc3_readl(dwc->regs, DWC3_GCTL);
|
|
reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG));
|
|
reg |= DWC3_GCTL_PRTCAPDIR(mode);
|
|
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
|
|
}
|
|
|
|
u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type)
|
|
{
|
|
struct dwc3 *dwc = dep->dwc;
|
|
u32 reg;
|
|
|
|
dwc3_writel(dwc->regs, DWC3_GDBGFIFOSPACE,
|
|
DWC3_GDBGFIFOSPACE_NUM(dep->number) |
|
|
DWC3_GDBGFIFOSPACE_TYPE(type));
|
|
|
|
reg = dwc3_readl(dwc->regs, DWC3_GDBGFIFOSPACE);
|
|
|
|
return DWC3_GDBGFIFOSPACE_SPACE_AVAILABLE(reg);
|
|
}
|
|
|
|
/**
|
|
* dwc3_core_soft_reset - Issues core soft reset and PHY reset
|
|
* @dwc: pointer to our context structure
|
|
*/
|
|
static int dwc3_core_soft_reset(struct dwc3 *dwc)
|
|
{
|
|
u32 reg;
|
|
int retries = 1000;
|
|
int ret;
|
|
|
|
usb_phy_init(dwc->usb2_phy);
|
|
usb_phy_init(dwc->usb3_phy);
|
|
ret = phy_init(dwc->usb2_generic_phy);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = phy_init(dwc->usb3_generic_phy);
|
|
if (ret < 0) {
|
|
phy_exit(dwc->usb2_generic_phy);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* We're resetting only the device side because, if we're in host mode,
|
|
* XHCI driver will reset the host block. If dwc3 was configured for
|
|
* host-only mode, then we can return early.
|
|
*/
|
|
if (dwc->dr_mode == USB_DR_MODE_HOST)
|
|
return 0;
|
|
|
|
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
|
|
reg |= DWC3_DCTL_CSFTRST;
|
|
dwc3_writel(dwc->regs, DWC3_DCTL, reg);
|
|
|
|
do {
|
|
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
|
|
if (!(reg & DWC3_DCTL_CSFTRST))
|
|
return 0;
|
|
|
|
udelay(1);
|
|
} while (--retries);
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/**
|
|
* dwc3_soft_reset - Issue soft reset
|
|
* @dwc: Pointer to our controller context structure
|
|
*/
|
|
static int dwc3_soft_reset(struct dwc3 *dwc)
|
|
{
|
|
unsigned long timeout;
|
|
u32 reg;
|
|
|
|
timeout = jiffies + msecs_to_jiffies(500);
|
|
dwc3_writel(dwc->regs, DWC3_DCTL, DWC3_DCTL_CSFTRST);
|
|
do {
|
|
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
|
|
if (!(reg & DWC3_DCTL_CSFTRST))
|
|
break;
|
|
|
|
if (time_after(jiffies, timeout)) {
|
|
dev_err(dwc->dev, "Reset Timed Out\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
cpu_relax();
|
|
} while (true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* dwc3_frame_length_adjustment - Adjusts frame length if required
|
|
* @dwc3: Pointer to our controller context structure
|
|
* @fladj: Value of GFLADJ_30MHZ to adjust frame length
|
|
*/
|
|
static void dwc3_frame_length_adjustment(struct dwc3 *dwc, u32 fladj)
|
|
{
|
|
u32 reg;
|
|
u32 dft;
|
|
|
|
if (dwc->revision < DWC3_REVISION_250A)
|
|
return;
|
|
|
|
if (fladj == 0)
|
|
return;
|
|
|
|
reg = dwc3_readl(dwc->regs, DWC3_GFLADJ);
|
|
dft = reg & DWC3_GFLADJ_30MHZ_MASK;
|
|
if (!dev_WARN_ONCE(dwc->dev, dft == fladj,
|
|
"request value same as default, ignoring\n")) {
|
|
reg &= ~DWC3_GFLADJ_30MHZ_MASK;
|
|
reg |= DWC3_GFLADJ_30MHZ_SDBND_SEL | fladj;
|
|
dwc3_writel(dwc->regs, DWC3_GFLADJ, reg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* dwc3_free_one_event_buffer - Frees one event buffer
|
|
* @dwc: Pointer to our controller context structure
|
|
* @evt: Pointer to event buffer to be freed
|
|
*/
|
|
static void dwc3_free_one_event_buffer(struct dwc3 *dwc,
|
|
struct dwc3_event_buffer *evt)
|
|
{
|
|
dma_free_coherent(dwc->dev, evt->length, evt->buf, evt->dma);
|
|
}
|
|
|
|
/**
|
|
* dwc3_alloc_one_event_buffer - Allocates one event buffer structure
|
|
* @dwc: Pointer to our controller context structure
|
|
* @length: size of the event buffer
|
|
*
|
|
* Returns a pointer to the allocated event buffer structure on success
|
|
* otherwise ERR_PTR(errno).
|
|
*/
|
|
static struct dwc3_event_buffer *dwc3_alloc_one_event_buffer(struct dwc3 *dwc,
|
|
unsigned length)
|
|
{
|
|
struct dwc3_event_buffer *evt;
|
|
|
|
evt = devm_kzalloc(dwc->dev, sizeof(*evt), GFP_KERNEL);
|
|
if (!evt)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
evt->dwc = dwc;
|
|
evt->length = length;
|
|
evt->buf = dma_alloc_coherent(dwc->dev, length,
|
|
&evt->dma, GFP_KERNEL);
|
|
if (!evt->buf)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
return evt;
|
|
}
|
|
|
|
/**
|
|
* dwc3_free_event_buffers - frees all allocated event buffers
|
|
* @dwc: Pointer to our controller context structure
|
|
*/
|
|
static void dwc3_free_event_buffers(struct dwc3 *dwc)
|
|
{
|
|
struct dwc3_event_buffer *evt;
|
|
|
|
evt = dwc->ev_buf;
|
|
if (evt)
|
|
dwc3_free_one_event_buffer(dwc, evt);
|
|
}
|
|
|
|
/**
|
|
* dwc3_alloc_event_buffers - Allocates @num event buffers of size @length
|
|
* @dwc: pointer to our controller context structure
|
|
* @length: size of event buffer
|
|
*
|
|
* Returns 0 on success otherwise negative errno. In the error case, dwc
|
|
* may contain some buffers allocated but not all which were requested.
|
|
*/
|
|
static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length)
|
|
{
|
|
struct dwc3_event_buffer *evt;
|
|
|
|
evt = dwc3_alloc_one_event_buffer(dwc, length);
|
|
if (IS_ERR(evt)) {
|
|
dev_err(dwc->dev, "can't allocate event buffer\n");
|
|
return PTR_ERR(evt);
|
|
}
|
|
dwc->ev_buf = evt;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* dwc3_event_buffers_setup - setup our allocated event buffers
|
|
* @dwc: pointer to our controller context structure
|
|
*
|
|
* Returns 0 on success otherwise negative errno.
|
|
*/
|
|
static int dwc3_event_buffers_setup(struct dwc3 *dwc)
|
|
{
|
|
struct dwc3_event_buffer *evt;
|
|
|
|
evt = dwc->ev_buf;
|
|
dwc3_trace(trace_dwc3_core,
|
|
"Event buf %p dma %08llx length %d\n",
|
|
evt->buf, (unsigned long long) evt->dma,
|
|
evt->length);
|
|
|
|
evt->lpos = 0;
|
|
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(0),
|
|
lower_32_bits(evt->dma));
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(0),
|
|
upper_32_bits(evt->dma));
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(0),
|
|
DWC3_GEVNTSIZ_SIZE(evt->length));
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dwc3_event_buffers_cleanup(struct dwc3 *dwc)
|
|
{
|
|
struct dwc3_event_buffer *evt;
|
|
|
|
evt = dwc->ev_buf;
|
|
|
|
evt->lpos = 0;
|
|
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(0), 0);
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(0), 0);
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(0), DWC3_GEVNTSIZ_INTMASK
|
|
| DWC3_GEVNTSIZ_SIZE(0));
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), 0);
|
|
}
|
|
|
|
static int dwc3_alloc_scratch_buffers(struct dwc3 *dwc)
|
|
{
|
|
if (!dwc->has_hibernation)
|
|
return 0;
|
|
|
|
if (!dwc->nr_scratch)
|
|
return 0;
|
|
|
|
dwc->scratchbuf = kmalloc_array(dwc->nr_scratch,
|
|
DWC3_SCRATCHBUF_SIZE, GFP_KERNEL);
|
|
if (!dwc->scratchbuf)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwc3_setup_scratch_buffers(struct dwc3 *dwc)
|
|
{
|
|
dma_addr_t scratch_addr;
|
|
u32 param;
|
|
int ret;
|
|
|
|
if (!dwc->has_hibernation)
|
|
return 0;
|
|
|
|
if (!dwc->nr_scratch)
|
|
return 0;
|
|
|
|
/* should never fall here */
|
|
if (!WARN_ON(dwc->scratchbuf))
|
|
return 0;
|
|
|
|
scratch_addr = dma_map_single(dwc->dev, dwc->scratchbuf,
|
|
dwc->nr_scratch * DWC3_SCRATCHBUF_SIZE,
|
|
DMA_BIDIRECTIONAL);
|
|
if (dma_mapping_error(dwc->dev, scratch_addr)) {
|
|
dev_err(dwc->dev, "failed to map scratch buffer\n");
|
|
ret = -EFAULT;
|
|
goto err0;
|
|
}
|
|
|
|
dwc->scratch_addr = scratch_addr;
|
|
|
|
param = lower_32_bits(scratch_addr);
|
|
|
|
ret = dwc3_send_gadget_generic_command(dwc,
|
|
DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO, param);
|
|
if (ret < 0)
|
|
goto err1;
|
|
|
|
param = upper_32_bits(scratch_addr);
|
|
|
|
ret = dwc3_send_gadget_generic_command(dwc,
|
|
DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI, param);
|
|
if (ret < 0)
|
|
goto err1;
|
|
|
|
return 0;
|
|
|
|
err1:
|
|
dma_unmap_single(dwc->dev, dwc->scratch_addr, dwc->nr_scratch *
|
|
DWC3_SCRATCHBUF_SIZE, DMA_BIDIRECTIONAL);
|
|
|
|
err0:
|
|
return ret;
|
|
}
|
|
|
|
static void dwc3_free_scratch_buffers(struct dwc3 *dwc)
|
|
{
|
|
if (!dwc->has_hibernation)
|
|
return;
|
|
|
|
if (!dwc->nr_scratch)
|
|
return;
|
|
|
|
/* should never fall here */
|
|
if (!WARN_ON(dwc->scratchbuf))
|
|
return;
|
|
|
|
dma_unmap_single(dwc->dev, dwc->scratch_addr, dwc->nr_scratch *
|
|
DWC3_SCRATCHBUF_SIZE, DMA_BIDIRECTIONAL);
|
|
kfree(dwc->scratchbuf);
|
|
}
|
|
|
|
static void dwc3_core_num_eps(struct dwc3 *dwc)
|
|
{
|
|
struct dwc3_hwparams *parms = &dwc->hwparams;
|
|
|
|
dwc->num_in_eps = DWC3_NUM_IN_EPS(parms);
|
|
dwc->num_out_eps = DWC3_NUM_EPS(parms) - dwc->num_in_eps;
|
|
|
|
dwc3_trace(trace_dwc3_core, "found %d IN and %d OUT endpoints",
|
|
dwc->num_in_eps, dwc->num_out_eps);
|
|
}
|
|
|
|
static void dwc3_cache_hwparams(struct dwc3 *dwc)
|
|
{
|
|
struct dwc3_hwparams *parms = &dwc->hwparams;
|
|
|
|
parms->hwparams0 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS0);
|
|
parms->hwparams1 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS1);
|
|
parms->hwparams2 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS2);
|
|
parms->hwparams3 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS3);
|
|
parms->hwparams4 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS4);
|
|
parms->hwparams5 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS5);
|
|
parms->hwparams6 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS6);
|
|
parms->hwparams7 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS7);
|
|
parms->hwparams8 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS8);
|
|
}
|
|
|
|
/**
|
|
* dwc3_phy_setup - Configure USB PHY Interface of DWC3 Core
|
|
* @dwc: Pointer to our controller context structure
|
|
*
|
|
* Returns 0 on success. The USB PHY interfaces are configured but not
|
|
* initialized. The PHY interfaces and the PHYs get initialized together with
|
|
* the core in dwc3_core_init.
|
|
*/
|
|
static int dwc3_phy_setup(struct dwc3 *dwc)
|
|
{
|
|
u32 reg;
|
|
int ret;
|
|
|
|
reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
|
|
|
|
/*
|
|
* Above 1.94a, it is recommended to set DWC3_GUSB3PIPECTL_SUSPHY
|
|
* to '0' during coreConsultant configuration. So default value
|
|
* will be '0' when the core is reset. Application needs to set it
|
|
* to '1' after the core initialization is completed.
|
|
*/
|
|
if (dwc->revision > DWC3_REVISION_194A)
|
|
reg |= DWC3_GUSB3PIPECTL_SUSPHY;
|
|
|
|
if (dwc->u2ss_inp3_quirk)
|
|
reg |= DWC3_GUSB3PIPECTL_U2SSINP3OK;
|
|
|
|
if (dwc->dis_rxdet_inp3_quirk)
|
|
reg |= DWC3_GUSB3PIPECTL_DISRXDETINP3;
|
|
|
|
if (dwc->req_p1p2p3_quirk)
|
|
reg |= DWC3_GUSB3PIPECTL_REQP1P2P3;
|
|
|
|
if (dwc->del_p1p2p3_quirk)
|
|
reg |= DWC3_GUSB3PIPECTL_DEP1P2P3_EN;
|
|
|
|
if (dwc->del_phy_power_chg_quirk)
|
|
reg |= DWC3_GUSB3PIPECTL_DEPOCHANGE;
|
|
|
|
if (dwc->lfps_filter_quirk)
|
|
reg |= DWC3_GUSB3PIPECTL_LFPSFILT;
|
|
|
|
if (dwc->rx_detect_poll_quirk)
|
|
reg |= DWC3_GUSB3PIPECTL_RX_DETOPOLL;
|
|
|
|
if (dwc->tx_de_emphasis_quirk)
|
|
reg |= DWC3_GUSB3PIPECTL_TX_DEEPH(dwc->tx_de_emphasis);
|
|
|
|
if (dwc->dis_u3_susphy_quirk)
|
|
reg &= ~DWC3_GUSB3PIPECTL_SUSPHY;
|
|
|
|
dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
|
|
|
|
reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
|
|
|
|
/* Select the HS PHY interface */
|
|
switch (DWC3_GHWPARAMS3_HSPHY_IFC(dwc->hwparams.hwparams3)) {
|
|
case DWC3_GHWPARAMS3_HSPHY_IFC_UTMI_ULPI:
|
|
if (dwc->hsphy_interface &&
|
|
!strncmp(dwc->hsphy_interface, "utmi", 4)) {
|
|
reg &= ~DWC3_GUSB2PHYCFG_ULPI_UTMI;
|
|
break;
|
|
} else if (dwc->hsphy_interface &&
|
|
!strncmp(dwc->hsphy_interface, "ulpi", 4)) {
|
|
reg |= DWC3_GUSB2PHYCFG_ULPI_UTMI;
|
|
dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
|
|
} else {
|
|
/* Relying on default value. */
|
|
if (!(reg & DWC3_GUSB2PHYCFG_ULPI_UTMI))
|
|
break;
|
|
}
|
|
/* FALLTHROUGH */
|
|
case DWC3_GHWPARAMS3_HSPHY_IFC_ULPI:
|
|
/* Making sure the interface and PHY are operational */
|
|
ret = dwc3_soft_reset(dwc);
|
|
if (ret)
|
|
return ret;
|
|
|
|
udelay(1);
|
|
|
|
ret = dwc3_ulpi_init(dwc);
|
|
if (ret)
|
|
return ret;
|
|
/* FALLTHROUGH */
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Above 1.94a, it is recommended to set DWC3_GUSB2PHYCFG_SUSPHY to
|
|
* '0' during coreConsultant configuration. So default value will
|
|
* be '0' when the core is reset. Application needs to set it to
|
|
* '1' after the core initialization is completed.
|
|
*/
|
|
if (dwc->revision > DWC3_REVISION_194A)
|
|
reg |= DWC3_GUSB2PHYCFG_SUSPHY;
|
|
|
|
if (dwc->dis_u2_susphy_quirk)
|
|
reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
|
|
|
|
if (dwc->dis_enblslpm_quirk)
|
|
reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM;
|
|
|
|
dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* dwc3_core_init - Low-level initialization of DWC3 Core
|
|
* @dwc: Pointer to our controller context structure
|
|
*
|
|
* Returns 0 on success otherwise negative errno.
|
|
*/
|
|
static int dwc3_core_init(struct dwc3 *dwc)
|
|
{
|
|
u32 hwparams4 = dwc->hwparams.hwparams4;
|
|
u32 reg;
|
|
int ret;
|
|
|
|
reg = dwc3_readl(dwc->regs, DWC3_GSNPSID);
|
|
/* This should read as U3 followed by revision number */
|
|
if ((reg & DWC3_GSNPSID_MASK) == 0x55330000) {
|
|
/* Detected DWC_usb3 IP */
|
|
dwc->revision = reg;
|
|
} else if ((reg & DWC3_GSNPSID_MASK) == 0x33310000) {
|
|
/* Detected DWC_usb31 IP */
|
|
dwc->revision = dwc3_readl(dwc->regs, DWC3_VER_NUMBER);
|
|
dwc->revision |= DWC3_REVISION_IS_DWC31;
|
|
} else {
|
|
dev_err(dwc->dev, "this is not a DesignWare USB3 DRD Core\n");
|
|
ret = -ENODEV;
|
|
goto err0;
|
|
}
|
|
|
|
/*
|
|
* Write Linux Version Code to our GUID register so it's easy to figure
|
|
* out which kernel version a bug was found.
|
|
*/
|
|
dwc3_writel(dwc->regs, DWC3_GUID, LINUX_VERSION_CODE);
|
|
|
|
/* Handle USB2.0-only core configuration */
|
|
if (DWC3_GHWPARAMS3_SSPHY_IFC(dwc->hwparams.hwparams3) ==
|
|
DWC3_GHWPARAMS3_SSPHY_IFC_DIS) {
|
|
if (dwc->maximum_speed == USB_SPEED_SUPER)
|
|
dwc->maximum_speed = USB_SPEED_HIGH;
|
|
}
|
|
|
|
/* issue device SoftReset too */
|
|
ret = dwc3_soft_reset(dwc);
|
|
if (ret)
|
|
goto err0;
|
|
|
|
ret = dwc3_core_soft_reset(dwc);
|
|
if (ret)
|
|
goto err0;
|
|
|
|
reg = dwc3_readl(dwc->regs, DWC3_GCTL);
|
|
reg &= ~DWC3_GCTL_SCALEDOWN_MASK;
|
|
|
|
switch (DWC3_GHWPARAMS1_EN_PWROPT(dwc->hwparams.hwparams1)) {
|
|
case DWC3_GHWPARAMS1_EN_PWROPT_CLK:
|
|
/**
|
|
* WORKAROUND: DWC3 revisions between 2.10a and 2.50a have an
|
|
* issue which would cause xHCI compliance tests to fail.
|
|
*
|
|
* Because of that we cannot enable clock gating on such
|
|
* configurations.
|
|
*
|
|
* Refers to:
|
|
*
|
|
* STAR#9000588375: Clock Gating, SOF Issues when ref_clk-Based
|
|
* SOF/ITP Mode Used
|
|
*/
|
|
if ((dwc->dr_mode == USB_DR_MODE_HOST ||
|
|
dwc->dr_mode == USB_DR_MODE_OTG) &&
|
|
(dwc->revision >= DWC3_REVISION_210A &&
|
|
dwc->revision <= DWC3_REVISION_250A))
|
|
reg |= DWC3_GCTL_DSBLCLKGTNG | DWC3_GCTL_SOFITPSYNC;
|
|
else
|
|
reg &= ~DWC3_GCTL_DSBLCLKGTNG;
|
|
break;
|
|
case DWC3_GHWPARAMS1_EN_PWROPT_HIB:
|
|
/* enable hibernation here */
|
|
dwc->nr_scratch = DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(hwparams4);
|
|
|
|
/*
|
|
* REVISIT Enabling this bit so that host-mode hibernation
|
|
* will work. Device-mode hibernation is not yet implemented.
|
|
*/
|
|
reg |= DWC3_GCTL_GBLHIBERNATIONEN;
|
|
break;
|
|
default:
|
|
dwc3_trace(trace_dwc3_core, "No power optimization available\n");
|
|
}
|
|
|
|
/* check if current dwc3 is on simulation board */
|
|
if (dwc->hwparams.hwparams6 & DWC3_GHWPARAMS6_EN_FPGA) {
|
|
dwc3_trace(trace_dwc3_core,
|
|
"running on FPGA platform\n");
|
|
dwc->is_fpga = true;
|
|
}
|
|
|
|
WARN_ONCE(dwc->disable_scramble_quirk && !dwc->is_fpga,
|
|
"disable_scramble cannot be used on non-FPGA builds\n");
|
|
|
|
if (dwc->disable_scramble_quirk && dwc->is_fpga)
|
|
reg |= DWC3_GCTL_DISSCRAMBLE;
|
|
else
|
|
reg &= ~DWC3_GCTL_DISSCRAMBLE;
|
|
|
|
if (dwc->u2exit_lfps_quirk)
|
|
reg |= DWC3_GCTL_U2EXIT_LFPS;
|
|
|
|
/*
|
|
* WORKAROUND: DWC3 revisions <1.90a have a bug
|
|
* where the device can fail to connect at SuperSpeed
|
|
* and falls back to high-speed mode which causes
|
|
* the device to enter a Connect/Disconnect loop
|
|
*/
|
|
if (dwc->revision < DWC3_REVISION_190A)
|
|
reg |= DWC3_GCTL_U2RSTECN;
|
|
|
|
dwc3_core_num_eps(dwc);
|
|
|
|
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
|
|
|
|
ret = dwc3_alloc_scratch_buffers(dwc);
|
|
if (ret)
|
|
goto err1;
|
|
|
|
ret = dwc3_setup_scratch_buffers(dwc);
|
|
if (ret)
|
|
goto err2;
|
|
|
|
return 0;
|
|
|
|
err2:
|
|
dwc3_free_scratch_buffers(dwc);
|
|
|
|
err1:
|
|
usb_phy_shutdown(dwc->usb2_phy);
|
|
usb_phy_shutdown(dwc->usb3_phy);
|
|
phy_exit(dwc->usb2_generic_phy);
|
|
phy_exit(dwc->usb3_generic_phy);
|
|
|
|
err0:
|
|
return ret;
|
|
}
|
|
|
|
static void dwc3_core_exit(struct dwc3 *dwc)
|
|
{
|
|
dwc3_free_scratch_buffers(dwc);
|
|
usb_phy_shutdown(dwc->usb2_phy);
|
|
usb_phy_shutdown(dwc->usb3_phy);
|
|
phy_exit(dwc->usb2_generic_phy);
|
|
phy_exit(dwc->usb3_generic_phy);
|
|
}
|
|
|
|
static int dwc3_core_get_phy(struct dwc3 *dwc)
|
|
{
|
|
struct device *dev = dwc->dev;
|
|
struct device_node *node = dev->of_node;
|
|
int ret;
|
|
|
|
if (node) {
|
|
dwc->usb2_phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 0);
|
|
dwc->usb3_phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 1);
|
|
} else {
|
|
dwc->usb2_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
|
|
dwc->usb3_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB3);
|
|
}
|
|
|
|
if (IS_ERR(dwc->usb2_phy)) {
|
|
ret = PTR_ERR(dwc->usb2_phy);
|
|
if (ret == -ENXIO || ret == -ENODEV) {
|
|
dwc->usb2_phy = NULL;
|
|
} else if (ret == -EPROBE_DEFER) {
|
|
return ret;
|
|
} else {
|
|
dev_err(dev, "no usb2 phy configured\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (IS_ERR(dwc->usb3_phy)) {
|
|
ret = PTR_ERR(dwc->usb3_phy);
|
|
if (ret == -ENXIO || ret == -ENODEV) {
|
|
dwc->usb3_phy = NULL;
|
|
} else if (ret == -EPROBE_DEFER) {
|
|
return ret;
|
|
} else {
|
|
dev_err(dev, "no usb3 phy configured\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
dwc->usb2_generic_phy = devm_phy_get(dev, "usb2-phy");
|
|
if (IS_ERR(dwc->usb2_generic_phy)) {
|
|
ret = PTR_ERR(dwc->usb2_generic_phy);
|
|
if (ret == -ENOSYS || ret == -ENODEV) {
|
|
dwc->usb2_generic_phy = NULL;
|
|
} else if (ret == -EPROBE_DEFER) {
|
|
return ret;
|
|
} else {
|
|
dev_err(dev, "no usb2 phy configured\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
dwc->usb3_generic_phy = devm_phy_get(dev, "usb3-phy");
|
|
if (IS_ERR(dwc->usb3_generic_phy)) {
|
|
ret = PTR_ERR(dwc->usb3_generic_phy);
|
|
if (ret == -ENOSYS || ret == -ENODEV) {
|
|
dwc->usb3_generic_phy = NULL;
|
|
} else if (ret == -EPROBE_DEFER) {
|
|
return ret;
|
|
} else {
|
|
dev_err(dev, "no usb3 phy configured\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwc3_core_init_mode(struct dwc3 *dwc)
|
|
{
|
|
struct device *dev = dwc->dev;
|
|
int ret;
|
|
|
|
switch (dwc->dr_mode) {
|
|
case USB_DR_MODE_PERIPHERAL:
|
|
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
|
|
ret = dwc3_gadget_init(dwc);
|
|
if (ret) {
|
|
dev_err(dev, "failed to initialize gadget\n");
|
|
return ret;
|
|
}
|
|
break;
|
|
case USB_DR_MODE_HOST:
|
|
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
|
|
ret = dwc3_host_init(dwc);
|
|
if (ret) {
|
|
dev_err(dev, "failed to initialize host\n");
|
|
return ret;
|
|
}
|
|
break;
|
|
case USB_DR_MODE_OTG:
|
|
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
|
|
ret = dwc3_host_init(dwc);
|
|
if (ret) {
|
|
dev_err(dev, "failed to initialize host\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = dwc3_gadget_init(dwc);
|
|
if (ret) {
|
|
dev_err(dev, "failed to initialize gadget\n");
|
|
return ret;
|
|
}
|
|
break;
|
|
default:
|
|
dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dwc3_core_exit_mode(struct dwc3 *dwc)
|
|
{
|
|
switch (dwc->dr_mode) {
|
|
case USB_DR_MODE_PERIPHERAL:
|
|
dwc3_gadget_exit(dwc);
|
|
break;
|
|
case USB_DR_MODE_HOST:
|
|
dwc3_host_exit(dwc);
|
|
break;
|
|
case USB_DR_MODE_OTG:
|
|
dwc3_host_exit(dwc);
|
|
dwc3_gadget_exit(dwc);
|
|
break;
|
|
default:
|
|
/* do nothing */
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define DWC3_ALIGN_MASK (16 - 1)
|
|
|
|
static int dwc3_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct dwc3_platform_data *pdata = dev_get_platdata(dev);
|
|
struct resource *res;
|
|
struct dwc3 *dwc;
|
|
u8 lpm_nyet_threshold;
|
|
u8 tx_de_emphasis;
|
|
u8 hird_threshold;
|
|
u32 fladj = 0;
|
|
|
|
int ret;
|
|
|
|
void __iomem *regs;
|
|
void *mem;
|
|
|
|
mem = devm_kzalloc(dev, sizeof(*dwc) + DWC3_ALIGN_MASK, GFP_KERNEL);
|
|
if (!mem)
|
|
return -ENOMEM;
|
|
|
|
dwc = PTR_ALIGN(mem, DWC3_ALIGN_MASK + 1);
|
|
dwc->mem = mem;
|
|
dwc->dev = dev;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
if (!res) {
|
|
dev_err(dev, "missing IRQ\n");
|
|
return -ENODEV;
|
|
}
|
|
dwc->xhci_resources[1].start = res->start;
|
|
dwc->xhci_resources[1].end = res->end;
|
|
dwc->xhci_resources[1].flags = res->flags;
|
|
dwc->xhci_resources[1].name = res->name;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!res) {
|
|
dev_err(dev, "missing memory resource\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
dwc->xhci_resources[0].start = res->start;
|
|
dwc->xhci_resources[0].end = dwc->xhci_resources[0].start +
|
|
DWC3_XHCI_REGS_END;
|
|
dwc->xhci_resources[0].flags = res->flags;
|
|
dwc->xhci_resources[0].name = res->name;
|
|
|
|
res->start += DWC3_GLOBALS_REGS_START;
|
|
|
|
/*
|
|
* Request memory region but exclude xHCI regs,
|
|
* since it will be requested by the xhci-plat driver.
|
|
*/
|
|
regs = devm_ioremap_resource(dev, res);
|
|
if (IS_ERR(regs)) {
|
|
ret = PTR_ERR(regs);
|
|
goto err0;
|
|
}
|
|
|
|
dwc->regs = regs;
|
|
dwc->regs_size = resource_size(res);
|
|
|
|
/* default to highest possible threshold */
|
|
lpm_nyet_threshold = 0xff;
|
|
|
|
/* default to -3.5dB de-emphasis */
|
|
tx_de_emphasis = 1;
|
|
|
|
/*
|
|
* default to assert utmi_sleep_n and use maximum allowed HIRD
|
|
* threshold value of 0b1100
|
|
*/
|
|
hird_threshold = 12;
|
|
|
|
dwc->maximum_speed = usb_get_maximum_speed(dev);
|
|
dwc->dr_mode = usb_get_dr_mode(dev);
|
|
|
|
dwc->has_lpm_erratum = device_property_read_bool(dev,
|
|
"snps,has-lpm-erratum");
|
|
device_property_read_u8(dev, "snps,lpm-nyet-threshold",
|
|
&lpm_nyet_threshold);
|
|
dwc->is_utmi_l1_suspend = device_property_read_bool(dev,
|
|
"snps,is-utmi-l1-suspend");
|
|
device_property_read_u8(dev, "snps,hird-threshold",
|
|
&hird_threshold);
|
|
dwc->usb3_lpm_capable = device_property_read_bool(dev,
|
|
"snps,usb3_lpm_capable");
|
|
|
|
dwc->disable_scramble_quirk = device_property_read_bool(dev,
|
|
"snps,disable_scramble_quirk");
|
|
dwc->u2exit_lfps_quirk = device_property_read_bool(dev,
|
|
"snps,u2exit_lfps_quirk");
|
|
dwc->u2ss_inp3_quirk = device_property_read_bool(dev,
|
|
"snps,u2ss_inp3_quirk");
|
|
dwc->req_p1p2p3_quirk = device_property_read_bool(dev,
|
|
"snps,req_p1p2p3_quirk");
|
|
dwc->del_p1p2p3_quirk = device_property_read_bool(dev,
|
|
"snps,del_p1p2p3_quirk");
|
|
dwc->del_phy_power_chg_quirk = device_property_read_bool(dev,
|
|
"snps,del_phy_power_chg_quirk");
|
|
dwc->lfps_filter_quirk = device_property_read_bool(dev,
|
|
"snps,lfps_filter_quirk");
|
|
dwc->rx_detect_poll_quirk = device_property_read_bool(dev,
|
|
"snps,rx_detect_poll_quirk");
|
|
dwc->dis_u3_susphy_quirk = device_property_read_bool(dev,
|
|
"snps,dis_u3_susphy_quirk");
|
|
dwc->dis_u2_susphy_quirk = device_property_read_bool(dev,
|
|
"snps,dis_u2_susphy_quirk");
|
|
dwc->dis_enblslpm_quirk = device_property_read_bool(dev,
|
|
"snps,dis_enblslpm_quirk");
|
|
dwc->dis_rxdet_inp3_quirk = device_property_read_bool(dev,
|
|
"snps,dis_rxdet_inp3_quirk");
|
|
|
|
dwc->tx_de_emphasis_quirk = device_property_read_bool(dev,
|
|
"snps,tx_de_emphasis_quirk");
|
|
device_property_read_u8(dev, "snps,tx_de_emphasis",
|
|
&tx_de_emphasis);
|
|
device_property_read_string(dev, "snps,hsphy_interface",
|
|
&dwc->hsphy_interface);
|
|
device_property_read_u32(dev, "snps,quirk-frame-length-adjustment",
|
|
&fladj);
|
|
|
|
if (pdata) {
|
|
dwc->maximum_speed = pdata->maximum_speed;
|
|
dwc->has_lpm_erratum = pdata->has_lpm_erratum;
|
|
if (pdata->lpm_nyet_threshold)
|
|
lpm_nyet_threshold = pdata->lpm_nyet_threshold;
|
|
dwc->is_utmi_l1_suspend = pdata->is_utmi_l1_suspend;
|
|
if (pdata->hird_threshold)
|
|
hird_threshold = pdata->hird_threshold;
|
|
|
|
dwc->usb3_lpm_capable = pdata->usb3_lpm_capable;
|
|
dwc->dr_mode = pdata->dr_mode;
|
|
|
|
dwc->disable_scramble_quirk = pdata->disable_scramble_quirk;
|
|
dwc->u2exit_lfps_quirk = pdata->u2exit_lfps_quirk;
|
|
dwc->u2ss_inp3_quirk = pdata->u2ss_inp3_quirk;
|
|
dwc->req_p1p2p3_quirk = pdata->req_p1p2p3_quirk;
|
|
dwc->del_p1p2p3_quirk = pdata->del_p1p2p3_quirk;
|
|
dwc->del_phy_power_chg_quirk = pdata->del_phy_power_chg_quirk;
|
|
dwc->lfps_filter_quirk = pdata->lfps_filter_quirk;
|
|
dwc->rx_detect_poll_quirk = pdata->rx_detect_poll_quirk;
|
|
dwc->dis_u3_susphy_quirk = pdata->dis_u3_susphy_quirk;
|
|
dwc->dis_u2_susphy_quirk = pdata->dis_u2_susphy_quirk;
|
|
dwc->dis_enblslpm_quirk = pdata->dis_enblslpm_quirk;
|
|
dwc->dis_rxdet_inp3_quirk = pdata->dis_rxdet_inp3_quirk;
|
|
|
|
dwc->tx_de_emphasis_quirk = pdata->tx_de_emphasis_quirk;
|
|
if (pdata->tx_de_emphasis)
|
|
tx_de_emphasis = pdata->tx_de_emphasis;
|
|
|
|
dwc->hsphy_interface = pdata->hsphy_interface;
|
|
fladj = pdata->fladj_value;
|
|
}
|
|
|
|
dwc->lpm_nyet_threshold = lpm_nyet_threshold;
|
|
dwc->tx_de_emphasis = tx_de_emphasis;
|
|
|
|
dwc->hird_threshold = hird_threshold
|
|
| (dwc->is_utmi_l1_suspend << 4);
|
|
|
|
platform_set_drvdata(pdev, dwc);
|
|
dwc3_cache_hwparams(dwc);
|
|
|
|
ret = dwc3_phy_setup(dwc);
|
|
if (ret)
|
|
goto err0;
|
|
|
|
ret = dwc3_core_get_phy(dwc);
|
|
if (ret)
|
|
goto err0;
|
|
|
|
spin_lock_init(&dwc->lock);
|
|
|
|
if (!dev->dma_mask) {
|
|
dev->dma_mask = dev->parent->dma_mask;
|
|
dev->dma_parms = dev->parent->dma_parms;
|
|
dma_set_coherent_mask(dev, dev->parent->coherent_dma_mask);
|
|
}
|
|
|
|
pm_runtime_enable(dev);
|
|
pm_runtime_get_sync(dev);
|
|
pm_runtime_forbid(dev);
|
|
|
|
ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE);
|
|
if (ret) {
|
|
dev_err(dwc->dev, "failed to allocate event buffers\n");
|
|
ret = -ENOMEM;
|
|
goto err1;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_USB_DWC3_HOST))
|
|
dwc->dr_mode = USB_DR_MODE_HOST;
|
|
else if (IS_ENABLED(CONFIG_USB_DWC3_GADGET))
|
|
dwc->dr_mode = USB_DR_MODE_PERIPHERAL;
|
|
|
|
if (dwc->dr_mode == USB_DR_MODE_UNKNOWN)
|
|
dwc->dr_mode = USB_DR_MODE_OTG;
|
|
|
|
ret = dwc3_core_init(dwc);
|
|
if (ret) {
|
|
dev_err(dev, "failed to initialize core\n");
|
|
goto err1;
|
|
}
|
|
|
|
/* Check the maximum_speed parameter */
|
|
switch (dwc->maximum_speed) {
|
|
case USB_SPEED_LOW:
|
|
case USB_SPEED_FULL:
|
|
case USB_SPEED_HIGH:
|
|
case USB_SPEED_SUPER:
|
|
case USB_SPEED_SUPER_PLUS:
|
|
break;
|
|
default:
|
|
dev_err(dev, "invalid maximum_speed parameter %d\n",
|
|
dwc->maximum_speed);
|
|
/* fall through */
|
|
case USB_SPEED_UNKNOWN:
|
|
/* default to superspeed */
|
|
dwc->maximum_speed = USB_SPEED_SUPER;
|
|
|
|
/*
|
|
* default to superspeed plus if we are capable.
|
|
*/
|
|
if (dwc3_is_usb31(dwc) &&
|
|
(DWC3_GHWPARAMS3_SSPHY_IFC(dwc->hwparams.hwparams3) ==
|
|
DWC3_GHWPARAMS3_SSPHY_IFC_GEN2))
|
|
dwc->maximum_speed = USB_SPEED_SUPER_PLUS;
|
|
|
|
break;
|
|
}
|
|
|
|
/* Adjust Frame Length */
|
|
dwc3_frame_length_adjustment(dwc, fladj);
|
|
|
|
usb_phy_set_suspend(dwc->usb2_phy, 0);
|
|
usb_phy_set_suspend(dwc->usb3_phy, 0);
|
|
ret = phy_power_on(dwc->usb2_generic_phy);
|
|
if (ret < 0)
|
|
goto err2;
|
|
|
|
ret = phy_power_on(dwc->usb3_generic_phy);
|
|
if (ret < 0)
|
|
goto err3;
|
|
|
|
ret = dwc3_event_buffers_setup(dwc);
|
|
if (ret) {
|
|
dev_err(dwc->dev, "failed to setup event buffers\n");
|
|
goto err4;
|
|
}
|
|
|
|
ret = dwc3_core_init_mode(dwc);
|
|
if (ret)
|
|
goto err5;
|
|
|
|
dwc3_debugfs_init(dwc);
|
|
pm_runtime_allow(dev);
|
|
|
|
return 0;
|
|
|
|
err5:
|
|
dwc3_event_buffers_cleanup(dwc);
|
|
|
|
err4:
|
|
phy_power_off(dwc->usb3_generic_phy);
|
|
|
|
err3:
|
|
phy_power_off(dwc->usb2_generic_phy);
|
|
|
|
err2:
|
|
usb_phy_set_suspend(dwc->usb2_phy, 1);
|
|
usb_phy_set_suspend(dwc->usb3_phy, 1);
|
|
dwc3_core_exit(dwc);
|
|
|
|
err1:
|
|
dwc3_free_event_buffers(dwc);
|
|
dwc3_ulpi_exit(dwc);
|
|
|
|
err0:
|
|
/*
|
|
* restore res->start back to its original value so that, in case the
|
|
* probe is deferred, we don't end up getting error in request the
|
|
* memory region the next time probe is called.
|
|
*/
|
|
res->start -= DWC3_GLOBALS_REGS_START;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int dwc3_remove(struct platform_device *pdev)
|
|
{
|
|
struct dwc3 *dwc = platform_get_drvdata(pdev);
|
|
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
/*
|
|
* restore res->start back to its original value so that, in case the
|
|
* probe is deferred, we don't end up getting error in request the
|
|
* memory region the next time probe is called.
|
|
*/
|
|
res->start -= DWC3_GLOBALS_REGS_START;
|
|
|
|
dwc3_debugfs_exit(dwc);
|
|
dwc3_core_exit_mode(dwc);
|
|
dwc3_event_buffers_cleanup(dwc);
|
|
dwc3_free_event_buffers(dwc);
|
|
|
|
usb_phy_set_suspend(dwc->usb2_phy, 1);
|
|
usb_phy_set_suspend(dwc->usb3_phy, 1);
|
|
phy_power_off(dwc->usb2_generic_phy);
|
|
phy_power_off(dwc->usb3_generic_phy);
|
|
|
|
dwc3_core_exit(dwc);
|
|
dwc3_ulpi_exit(dwc);
|
|
|
|
pm_runtime_put_sync(&pdev->dev);
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int dwc3_suspend(struct device *dev)
|
|
{
|
|
struct dwc3 *dwc = dev_get_drvdata(dev);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dwc->lock, flags);
|
|
|
|
switch (dwc->dr_mode) {
|
|
case USB_DR_MODE_PERIPHERAL:
|
|
case USB_DR_MODE_OTG:
|
|
dwc3_gadget_suspend(dwc);
|
|
/* FALLTHROUGH */
|
|
case USB_DR_MODE_HOST:
|
|
default:
|
|
dwc3_event_buffers_cleanup(dwc);
|
|
break;
|
|
}
|
|
|
|
dwc->gctl = dwc3_readl(dwc->regs, DWC3_GCTL);
|
|
spin_unlock_irqrestore(&dwc->lock, flags);
|
|
|
|
usb_phy_shutdown(dwc->usb3_phy);
|
|
usb_phy_shutdown(dwc->usb2_phy);
|
|
phy_exit(dwc->usb2_generic_phy);
|
|
phy_exit(dwc->usb3_generic_phy);
|
|
|
|
usb_phy_set_suspend(dwc->usb2_phy, 1);
|
|
usb_phy_set_suspend(dwc->usb3_phy, 1);
|
|
WARN_ON(phy_power_off(dwc->usb2_generic_phy) < 0);
|
|
WARN_ON(phy_power_off(dwc->usb3_generic_phy) < 0);
|
|
|
|
pinctrl_pm_select_sleep_state(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwc3_resume(struct device *dev)
|
|
{
|
|
struct dwc3 *dwc = dev_get_drvdata(dev);
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
pinctrl_pm_select_default_state(dev);
|
|
|
|
usb_phy_set_suspend(dwc->usb2_phy, 0);
|
|
usb_phy_set_suspend(dwc->usb3_phy, 0);
|
|
ret = phy_power_on(dwc->usb2_generic_phy);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = phy_power_on(dwc->usb3_generic_phy);
|
|
if (ret < 0)
|
|
goto err_usb2phy_power;
|
|
|
|
usb_phy_init(dwc->usb3_phy);
|
|
usb_phy_init(dwc->usb2_phy);
|
|
ret = phy_init(dwc->usb2_generic_phy);
|
|
if (ret < 0)
|
|
goto err_usb3phy_power;
|
|
|
|
ret = phy_init(dwc->usb3_generic_phy);
|
|
if (ret < 0)
|
|
goto err_usb2phy_init;
|
|
|
|
spin_lock_irqsave(&dwc->lock, flags);
|
|
|
|
dwc3_event_buffers_setup(dwc);
|
|
dwc3_writel(dwc->regs, DWC3_GCTL, dwc->gctl);
|
|
|
|
switch (dwc->dr_mode) {
|
|
case USB_DR_MODE_PERIPHERAL:
|
|
case USB_DR_MODE_OTG:
|
|
dwc3_gadget_resume(dwc);
|
|
/* FALLTHROUGH */
|
|
case USB_DR_MODE_HOST:
|
|
default:
|
|
/* do nothing */
|
|
break;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&dwc->lock, flags);
|
|
|
|
pm_runtime_disable(dev);
|
|
pm_runtime_set_active(dev);
|
|
pm_runtime_enable(dev);
|
|
|
|
return 0;
|
|
|
|
err_usb2phy_init:
|
|
phy_exit(dwc->usb2_generic_phy);
|
|
|
|
err_usb3phy_power:
|
|
phy_power_off(dwc->usb3_generic_phy);
|
|
|
|
err_usb2phy_power:
|
|
phy_power_off(dwc->usb2_generic_phy);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct dev_pm_ops dwc3_dev_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(dwc3_suspend, dwc3_resume)
|
|
};
|
|
|
|
#define DWC3_PM_OPS &(dwc3_dev_pm_ops)
|
|
#else
|
|
#define DWC3_PM_OPS NULL
|
|
#endif
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id of_dwc3_match[] = {
|
|
{
|
|
.compatible = "snps,dwc3"
|
|
},
|
|
{
|
|
.compatible = "synopsys,dwc3"
|
|
},
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, of_dwc3_match);
|
|
#endif
|
|
|
|
#ifdef CONFIG_ACPI
|
|
|
|
#define ACPI_ID_INTEL_BSW "808622B7"
|
|
|
|
static const struct acpi_device_id dwc3_acpi_match[] = {
|
|
{ ACPI_ID_INTEL_BSW, 0 },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(acpi, dwc3_acpi_match);
|
|
#endif
|
|
|
|
static struct platform_driver dwc3_driver = {
|
|
.probe = dwc3_probe,
|
|
.remove = dwc3_remove,
|
|
.driver = {
|
|
.name = "dwc3",
|
|
.of_match_table = of_match_ptr(of_dwc3_match),
|
|
.acpi_match_table = ACPI_PTR(dwc3_acpi_match),
|
|
.pm = DWC3_PM_OPS,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(dwc3_driver);
|
|
|
|
MODULE_ALIAS("platform:dwc3");
|
|
MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("DesignWare USB3 DRD Controller Driver");
|