}
EXPORT_SYMBOL_GPL(usb_get_dr_mode);
+/**
+ * of_usb_get_suspend_clk_freq - Get suspend clock frequency
+ *
+ * USB3 core needs 16KHz clock for a small part that operates
+ * when the SS PHY is in its lowest power (P3) state.
+ * USB3 core receives suspend clock and divides it to make 16KHz clock.
+ */
+unsigned int of_usb_get_suspend_clk_freq(struct device *dev)
+{
+ unsigned int freq;
+ int err;
+
+ err = device_property_read_u32(dev, "suspend_clk_freq", &freq);
+ if (err < 0)
+ return 0;
+
+ return freq;
+}
+EXPORT_SYMBOL_GPL(of_usb_get_suspend_clk_freq);
+
#ifdef CONFIG_OF
/**
* of_usb_get_dr_mode_by_phy - Get dual role mode for the controller device
buffer += d->bLength;
size -= d->bLength;
+ if (( d->bmAttributes & 0x3 ) == 0x1) {
+ if (d->bEndpointAddress & USB_ENDPOINT_DIR_MASK) {
+ to_usb_device(ddev)->hwinfo.in_ep = d->bEndpointAddress;
+ dev_info(ddev, " This is IN ISO endpoint #0%x \n", d->bEndpointAddress);
+ } else {
+ to_usb_device(ddev)->hwinfo.out_ep = d->bEndpointAddress;
+ dev_info(ddev, " This is OUT ISO endpoint #0%x \n", d->bEndpointAddress);
+ }
+ }
+
if (d->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE)
n = USB_DT_ENDPOINT_AUDIO_SIZE;
else if (d->bLength >= USB_DT_ENDPOINT_SIZE)
return buffer - buffer0 + i;
}
-static int usb_parse_configuration(struct usb_device *dev, int cfgidx,
+int usb_parse_configuration(struct usb_device *dev, int cfgidx,
struct usb_host_config *config, unsigned char *buffer, int size)
{
struct device *ddev = &dev->dev;
if (dev->quirks & USB_QUIRK_DELAY_INIT)
msleep(200);
+ dev->do_remote_wakeup =
+ (desc->bmAttributes & USB_CONFIG_ATT_WAKEUP) ? 1 : 0;
+ if (dev->do_remote_wakeup == 1) {
+ device_init_wakeup(ddev, 1);
+ }
+
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
bigbuffer, length);
if (result < 0) {
}
dev->rawdescriptors[cfgno] = bigbuffer;
+ dev->rawdesc_length = length;
result = usb_parse_configuration(dev, cfgno,
&dev->config[cfgno], bigbuffer, length);
int status;
int old_state = hcd->state;
- dev_dbg(&rhdev->dev, "bus %ssuspend, wakeup %d\n",
+ dev_info(&rhdev->dev, "bus %ssuspend, wakeup %d\n",
(PMSG_IS_AUTO(msg) ? "auto-" : ""),
rhdev->do_remote_wakeup);
if (HCD_DEAD(hcd)) {
dev_dbg(&rhdev->dev, "bus %s fail, err %d\n",
"suspend", status);
}
+
+ /* L2 suspend only support USB2.0port */
+ if ((hcd->state == HC_STATE_SUSPENDED) && !hcd->driver->hub_check_speed(hcd->shared_hcd)) {
+ dev_info(&rhdev->dev, "HCD STATE IS SUSPENDED, NEED WAKE UNLOCK\n");
+ hcd->driver->wake_lock(hcd, 0);
+ }
+
return status;
}
int status;
int old_state = hcd->state;
- dev_dbg(&rhdev->dev, "usb %sresume\n",
+ dev_info(&rhdev->dev, "usb %sresume\n",
(PMSG_IS_AUTO(msg) ? "auto-" : ""));
if (HCD_DEAD(hcd)) {
dev_dbg(&rhdev->dev, "skipped %s of dead bus\n", "resume");
struct usb_device *udev;
int port1;
+ if (hcd->state == HC_STATE_RESUMING) {
+ dev_info(&rhdev->dev, "HCD STATE IS RESUMING, NEED WAKE LOCK\n");
+ hcd->driver->wake_lock(hcd, 1);
+ }
+
spin_lock_irq(&hcd_root_hub_lock);
if (!HCD_DEAD(hcd)) {
usb_set_device_state(rhdev, rhdev->actconfig
struct usb_host_interface *desc;
struct usb_device *hdev;
struct usb_hub *hub;
+ struct usb_hcd *hcd;
desc = intf->cur_altsetting;
hdev = interface_to_usbdev(intf);
* usbcore.autosuspend = -1 then keep autosuspend disabled.
*/
#ifdef CONFIG_PM
- if (hdev->dev.power.autosuspend_delay >= 0)
+ /*
+ * bus suspend is executed before USB3.0 hub and devices are detected,
+ * which causes USB3.0 hub and devices aren't recognized when l2 sleep is enabled.
+ * Restoring suspend delay will resolve USB3.0 hub detect issue.
+ */
+ hcd = bus_to_hcd(hdev->bus);
+ if ((hdev->dev.power.autosuspend_delay >= 0) && !hcd->driver->usb_l2_check(hcd))
pm_runtime_set_autosuspend_delay(&hdev->dev, 0);
#endif
struct usb_interface *intf;
struct usb_hub *hub;
struct device *hub_dev;
+ struct usb_hcd *hcd;
u16 hubstatus;
u16 hubchange;
int i, ret;
hub = container_of(work, struct usb_hub, events);
hdev = hub->hdev;
hub_dev = hub->intfdev;
+ hcd = bus_to_hcd(hdev->bus);
intf = to_usb_interface(hub_dev);
dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
/* Lock the device, then check to see if we were
* disconnected while waiting for the lock to succeed. */
usb_lock_device(hdev);
+ hcd->is_in_hub_event = true;
if (unlikely(hub->disconnected))
goto out_hdev_lock;
/* Balance the usb_autopm_get_interface() above */
usb_autopm_put_interface_no_suspend(intf);
out_hdev_lock:
+ hcd->is_in_hub_event = false;
usb_unlock_device(hdev);
/* Balance the stuff in kick_hub_wq() and allow autosuspend */
config USB_DWC3_DUAL_ROLE
bool "Dual Role mode"
depends on ((USB=y || USB=USB_DWC3) && (USB_GADGET=y || USB_GADGET=USB_DWC3))
- depends on (EXTCON=y || EXTCON=USB_DWC3)
help
This is the default mode of working of DWC3 controller where
both host and gadget features are enabled.
dwc3-y += trace.o
endif
+dwc3-y += otg.o
+
ifneq ($(filter y,$(CONFIG_USB_DWC3_HOST) $(CONFIG_USB_DWC3_DUAL_ROLE)),)
dwc3-y += host.o
endif
#include <linux/usb/otg.h>
#include "core.h"
+#include "otg.h"
#include "gadget.h"
#include "io.h"
* dwc3_get_dr_mode - Validates and sets dr_mode
* @dwc: pointer to our context structure
*/
+/* Remove warning - Check later!
static int dwc3_get_dr_mode(struct dwc3 *dwc)
{
enum usb_dr_mode mode;
return 0;
}
+*/
-static void dwc3_event_buffers_cleanup(struct dwc3 *dwc);
-static int dwc3_event_buffers_setup(struct dwc3 *dwc);
+void dwc3_event_buffers_cleanup(struct dwc3 *dwc);
+int dwc3_event_buffers_setup(struct dwc3 *dwc);
static void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
{
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
}
+/*
static void __dwc3_set_mode(struct work_struct *work)
{
struct dwc3 *dwc = work_to_dwc(work);
queue_work(system_freezable_wq, &dwc->drd_work);
}
+*/
+
+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);
+}
+
+void dwc3_core_config(struct dwc3 *dwc)
+{
+ u32 reg;
+
+ /* AHB bus configuration */
+ reg = dwc3_readl(dwc->regs, DWC3_GSBUSCFG0);
+ reg |= (DWC3_GSBUSCFG0_INCRBRSTEN | DWC3_GSBUSCFG0_INCR16BRSTEN);
+ /**
+ * AXI Bus' cache type configuration for DMA transfer.
+ * By below setting, cache type was set to Cacheable/Modifiable.
+ * From DWC USB3.0 Link version 2.20A, this cache type could be set.
+ */
+ if (dwc->revision >= DWC3_REVISION_220A)
+ reg |= (DWC3_GSBUSCFG0_DESWRREQINFO |
+ DWC3_GSBUSCFG0_DATWRREQINFO |
+ DWC3_GSBUSCFG0_DESRDREQINFO |
+ DWC3_GSBUSCFG0_DATRDREQINFO);
+ dwc3_writel(dwc->regs, DWC3_GSBUSCFG0, reg);
+
+ reg = dwc3_readl(dwc->regs, DWC3_GSBUSCFG1);
+ reg |= (DWC3_GSBUSCFG1_BREQLIMIT(3));
+ dwc3_writel(dwc->regs, DWC3_GSBUSCFG1, reg);
+
+ /*
+ * WORKAROUND:
+ * For ss bulk-in data packet, when the host detects
+ * a DPP error or the internal buffer becomes full,
+ * it retries with an ACK TP Retry=1. Under the following
+ * conditions, the Retry=1 is falsely carried over to the next
+ * DWC3_GUCTL_USBHSTINAUTORETRYEN should be set to a one
+ * regardless of revision
+ * - There is only single active asynchronous SS EP at the time.
+ * - The active asynchronous EP is a Bulk IN EP.
+ * - The burst with the correctly Retry=1 ACK TP and
+ * the next burst belong to the same transfer.
+ */
+ reg = dwc3_readl(dwc->regs, DWC3_GUCTL);
+ reg |= (DWC3_GUCTL_USBHSTINAUTORETRYEN);
+ if (dwc->adj_sof_accuracy) {
+ reg &= ~DWC3_GUCTL_REFCLKPER_MASK;
+ /* fix ITP interval time to 125us */
+ reg |= DWC3_GUCTL_REFCLKPER(0x14);
+ }
+ if (dwc->dis_u2_freeclk_exists_quirk) {
+ reg &= ~DWC3_GUCTL_REFCLKPER_MASK;
+ /* fix ITP interval time to 125us */
+ reg |= DWC3_GUCTL_REFCLKPER(0x14);
+ }
+ if (dwc->sparse_transfer_control)
+ reg |= DWC3_GUCTL_SPRSCTRLTRANSEN;
+ dwc3_writel(dwc->regs, DWC3_GUCTL, reg);
+ if (dwc->revision >= DWC3_REVISION_190A &&
+ dwc->revision <= DWC3_REVISION_210A) {
+ reg = dwc3_readl(dwc->regs, DWC3_GRXTHRCFG);
+ reg &= ~(DWC3_GRXTHRCFG_USBRXPKTCNT_MASK |
+ DWC3_GRXTHRCFG_USBMAXRXBURSTSIZE_MASK);
+ reg |= (DWC3_GRXTHRCFG_USBRXPKTCNTSEL |
+ DWC3_GRXTHRCFG_USBRXPKTCNT(3) |
+ DWC3_GRXTHRCFG_USBMAXRXBURSTSIZE(3));
+ dwc3_writel(dwc->regs, DWC3_GRXTHRCFG, reg);
+ }
+
+ /*
+ * WORKAROUND: DWC3 revisions 2.10a and earlier have a bug
+ * The delay of the entry to a low power state such that
+ * for applications where the link stays in a non-U0 state
+ * for a short duration(< 1 microsecond),
+ * the local PHY does not enter the low power state prior
+ * to receiving a potential LFPS wakeup.
+ * This causes the PHY CDR (Clock and Data Recovery) operation
+ * to be unstable for some Synopsys PHYs.
+ * The proposal now is to change the default and the recommended value
+ * for GUSB3PIPECTL[21:19] in the RTL from 3'b100 to a minimum of 3'b001
+ */
+ if (dwc->revision <= DWC3_REVISION_210A) {
+ reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
+ reg &= ~(DWC3_GUSB3PIPECTL_DEP1P2P3_MASK);
+ reg |= (DWC3_GUSB3PIPECTL_DEP1P2P3_EN);
+ dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
+ }
+
+ if (dwc->revision >= DWC3_REVISION_250A) {
+ reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
+ reg |= DWC3_GUSB3PIPECTL_DISRXDETINP3;
+ dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
+ }
+
+ if (dwc->revision >= DWC3_REVISION_250A) {
+ reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
+ reg |= DWC3_GUSB3PIPECTL_DISRXDETINP3;
+ dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
+ }
+
+ if (dwc->revision >= DWC3_REVISION_250A) {
+ reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
+ reg |= DWC3_GUSB3PIPECTL_DISRXDETINP3;
+ dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
+ }
+
+ if (dwc->revision >= DWC3_REVISION_250A) {
+ reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
+ reg |= DWC3_GUSB3PIPECTL_DISRXDETINP3;
+ dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
+ }
+
+ /*
+ * WORKAROUND: DWC3 revisions 2.10a and earlier have a bug
+ * Race Condition in PORTSC Write Followed by Read
+ * If the software quickly does a read to the PORTSC,
+ * some fields (port status change related fields
+ * like OCC, etc.) may not have correct value
+ * due to the current way of handling these bits.
+ * After clearing the status register (for example, OCC) bit
+ * by writing PORTSC tregister, software can insert some delay
+ * (for example, 5 mac2_clk -> UTMI clock = 60 MHz ->
+ * (16.66 ns x 5 = 84ns)) before reading the PORTSC to check status.
+ */
+ if (dwc->revision <= DWC3_REVISION_210A) {
+ reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
+ reg |= (DWC3_GUSB2PHYCFG_PHYIF(UTMI_PHYIF_16_BIT));
+ dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
+ }
+}
u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type)
{
int retries = 1000;
int ret;
+ ret = phy_power_on(dwc->usb3_generic_phy);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_power_on(dwc->usb2_generic_phy);
+ if (ret < 0)
+ goto err_usb3phy_power;
+
usb_phy_init(dwc->usb2_phy);
usb_phy_init(dwc->usb3_phy);
ret = phy_init(dwc->usb2_generic_phy);
if (ret < 0)
- return ret;
+ goto err_usb2phy_power;
ret = phy_init(dwc->usb3_generic_phy);
- if (ret < 0) {
- phy_exit(dwc->usb2_generic_phy);
- return ret;
- }
+ if (ret < 0)
+ goto err_usb2phy_init;
/*
* We're resetting only the device side because, if we're in host mode,
} while (--retries);
return -ETIMEDOUT;
+
+err_usb2phy_init:
+ phy_exit(dwc->usb2_generic_phy);
+
+err_usb2phy_power:
+ phy_power_off(dwc->usb2_generic_phy);
+
+err_usb3phy_power:
+ phy_power_off(dwc->usb3_generic_phy);
+
+ return ret;
+}
+
+/**
+ * dwc3_soft_reset - Issue soft reset
+ * @dwc: Pointer to our controller context structure
+ */
+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;
}
/*
reg = dwc3_readl(dwc->regs, DWC3_GFLADJ);
dft = reg & DWC3_GFLADJ_30MHZ_MASK;
- if (!dev_WARN_ONCE(dwc->dev, dft == dwc->fladj,
- "request value same as default, ignoring\n")) {
+ /*if (!dev_WARN_ONCE(dwc->dev, dft == dwc->fladj,
+ * "request value same as default, ignoring\n")) {
+ */
+ if(dft != dwc->fladj) {
reg &= ~DWC3_GFLADJ_30MHZ_MASK;
reg |= DWC3_GFLADJ_30MHZ_SDBND_SEL | dwc->fladj;
dwc3_writel(dwc->regs, DWC3_GFLADJ, reg);
*
* Returns 0 on success otherwise negative errno.
*/
-static int dwc3_event_buffers_setup(struct dwc3 *dwc)
+int dwc3_event_buffers_setup(struct dwc3 *dwc)
{
struct dwc3_event_buffer *evt;
return 0;
}
-static void dwc3_event_buffers_cleanup(struct dwc3 *dwc)
+void dwc3_event_buffers_cleanup(struct dwc3 *dwc)
{
struct dwc3_event_buffer *evt;
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);
* 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)
+int dwc3_phy_setup(struct dwc3 *dwc)
{
u32 reg;
int ret;
if (dwc->dis_rxdet_inp3_quirk)
reg |= DWC3_GUSB3PIPECTL_DISRXDETINP3;
+ if (dwc->u1u2_exitfail_to_recov_quirk)
+ reg |= DWC3_GUSB3PIPECTL_U1U2EXITFAIL_TO_RECOV;
+
if (dwc->req_p1p2p3_quirk)
reg |= DWC3_GUSB3PIPECTL_REQP1P2P3;
if (dwc->dis_u2_freeclk_exists_quirk)
reg &= ~DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS;
+ if (dwc->adj_sof_accuracy)
+ reg &= ~DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS;
+
+ reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM;
+
dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
return 0;
}
-static void dwc3_core_exit(struct dwc3 *dwc)
+void dwc3_core_exit(struct dwc3 *dwc)
{
dwc3_event_buffers_cleanup(dwc);
usb_phy_set_suspend(dwc->usb3_phy, 1);
phy_power_off(dwc->usb2_generic_phy);
phy_power_off(dwc->usb3_generic_phy);
+
+ dwc->link_state = DWC3_LINK_STATE_SS_DIS;
}
static bool dwc3_core_is_valid(struct dwc3 *dwc)
if (dwc->revision < DWC3_REVISION_190A)
reg |= DWC3_GCTL_U2RSTECN;
+ if (dwc->adj_sof_accuracy) {
+ reg &= ~DWC3_GCTL_SOFITPSYNC;
+ reg |= DWC3_GCTL_DSBLCLKGTNG;
+ }
+
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
}
*
* Returns 0 on success otherwise negative errno.
*/
-static int dwc3_core_init(struct dwc3 *dwc)
+int dwc3_core_init(struct dwc3 *dwc)
{
u32 reg;
+ struct dwc3_otg *dotg = dwc->dotg;
int ret;
if (!dwc3_core_is_valid(dwc)) {
if (ret)
goto err0;
+ /* Adjust SOF accuracy only for revisions >= 2.50a */
+ if (dwc->revision < DWC3_REVISION_250A)
+ dwc->adj_sof_accuracy = 0;
+
ret = dwc3_core_soft_reset(dwc);
if (ret)
goto err0;
if (ret)
goto err0;
+ if (dotg) {
+ phy_tune(dwc->usb2_generic_phy, dotg->otg.state);
+ phy_tune(dwc->usb3_generic_phy, dotg->otg.state);
+ } else {
+ phy_tune(dwc->usb2_generic_phy, 0);
+ phy_tune(dwc->usb3_generic_phy, 0);
+ }
+
dwc3_core_setup_global_control(dwc);
dwc3_core_num_eps(dwc);
+ dwc->suspend_clk_freq = 50000000;
+ if (dwc->suspend_clk_freq) {
+ reg &= ~DWC3_GCTL_PWRDNSCALE_MASK;
+ reg |= DWC3_GCTL_PWRDNSCALE(dwc->suspend_clk_freq/(16*1000));
+ }
+
+ dev_info(dwc->dev, "%s: max speed:%d, hibernation:%d, nr_scratch:%d\n",
+ __func__, dwc->maximum_speed, dwc->has_hibernation,
+ dwc->nr_scratch);
+
ret = dwc3_setup_scratch_buffers(dwc);
if (ret)
goto err1;
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;
+ goto err2;
}
/*
dwc3_writel(dwc->regs, DWC3_GUCTL1, reg);
}
- return 0;
+ dwc3_core_config(dwc);
-err4:
- phy_power_off(dwc->usb3_generic_phy);
+ if (dwc->adj_sof_accuracy) {
+ reg = dwc3_readl(dwc->regs, DWC3_GFLADJ);
+ reg &= ~DWC3_GFLADJ_REFCLK_240MHZDECR_PLS1;
+ reg &= ~DWC3_GFLADJ_REFCLK_240MHZ_DECR_MASK;
+ reg |= DWC3_GFLADJ_REFCLK_240MHZ_DECR(0xA);
+ reg |= DWC3_GFLADJ_REFCLK_LPM_SEL;
+ reg &= ~DWC3_GFLADJ_REFCLK_FLADJ_MASK;
+ reg |= DWC3_GFLADJ_REFCLK_FLADJ(0x7F0);
+ dwc3_writel(dwc->regs, DWC3_GFLADJ, reg);
+ }
-err3:
- phy_power_off(dwc->usb2_generic_phy);
+ dwc->link_state = DWC3_LINK_STATE_U0;
+ return 0;
err2:
+ phy_power_off(dwc->usb3_generic_phy);
+ phy_power_off(dwc->usb2_generic_phy);
usb_phy_set_suspend(dwc->usb2_phy, 1);
usb_phy_set_suspend(dwc->usb3_phy, 1);
phy_exit(dwc->usb2_generic_phy);
phy_exit(dwc->usb3_generic_phy);
+ phy_power_off(dwc->usb2_generic_phy);
+ phy_power_off(dwc->usb3_generic_phy);
+
+ dwc->link_state = DWC3_LINK_STATE_SS_DIS;
err0:
return ret;
}
}
break;
case USB_DR_MODE_OTG:
+ ret = dwc3_otg_init(dwc);
+ if (ret) {
+ dev_err(dev, "failed to initialize otg\n");
+ return ret;
+ }
+
+ /* turn off PHYs to save power */
+ dwc3_core_exit(dwc);
+
+ ret = dwc3_host_init(dwc);
+ if (ret) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to initialize host\n");
+ dwc3_otg_exit(dwc);
+ return ret;
+ }
+ ret = dwc3_gadget_init(dwc);
+ if (ret) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to initialize gadget\n");
+ dwc3_host_exit(dwc);
+ dwc3_otg_exit(dwc);
+ return ret;
+ }
+
+ /* Now we are ready to start OTG */
+ ret = dwc3_otg_start(dwc);
+ if (ret) {
+ dev_err(dev, "failed to start otg\n");
+ dwc3_host_exit(dwc);
+ dwc3_gadget_exit(dwc);
+ dwc3_otg_exit(dwc);
+ return ret;
+ }
+
+ /* Unblock runtime PM for OTG */
+ pm_runtime_put_sync(dev);
+
+ /* New drd routine in Kernel 4.14 */
+ /*
INIT_WORK(&dwc->drd_work, __dwc3_set_mode);
ret = dwc3_drd_init(dwc);
if (ret) {
dev_err(dev, "failed to initialize dual-role\n");
return ret;
}
+ */
break;
default:
dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
}
}
+static int dwc3_get_option(struct dwc3 *dwc)
+{
+ struct device *dev = dwc->dev;
+ struct device_node *node = dev->of_node;
+ int value;
+
+ if (!of_property_read_u32(node, "adj-sof-accuracy", &value)) {
+ dwc->adj_sof_accuracy = value ? true : false;
+ } else {
+ dev_err(dev, "can't get adj-sof-accuracy from %s node", node->name);
+ return -EINVAL;
+ }
+ if (!of_property_read_u32(node, "is_not_vbus_pad", &value)) {
+ dwc->is_not_vbus_pad = value ? true : false;
+ } else {
+ dev_err(dev, "can't get is_not_vbus_pad from %s node", node->name);
+ return -EINVAL;
+ }
+ if (!of_property_read_u32(node, "enable_sprs_transfer", &value)) {
+ dwc->sparse_transfer_control = value ? true : false;
+ } else {
+ dev_err(dev, "can't get sprs-xfer-ctrl from %s node", node->name);
+ return -EINVAL;
+ }
+
+ return 0;
+
+}
+
static void dwc3_get_properties(struct dwc3 *dwc)
{
struct device *dev = dwc->dev;
u8 lpm_nyet_threshold;
u8 tx_de_emphasis;
u8 hird_threshold;
+ int ret;
/* default to highest possible threshold */
lpm_nyet_threshold = 0xff;
dwc->maximum_speed = usb_get_maximum_speed(dev);
dwc->dr_mode = usb_get_dr_mode(dev);
+ dwc->suspend_clk_freq = of_usb_get_suspend_clk_freq(dev);
dwc->hsphy_mode = of_usb_get_phy_mode(dev->of_node);
+ ret = dwc3_get_option(dwc);
+ if (ret)
+ dev_err(dev, "dwc3_get_option error occurred\n");
+
dwc->sysdev_is_parent = device_property_read_bool(dev,
"linux,sysdev_is_parent");
if (dwc->sysdev_is_parent)
"snps,req_p1p2p3_quirk");
dwc->del_p1p2p3_quirk = device_property_read_bool(dev,
"snps,del_p1p2p3_quirk");
+ dwc->u1u2_exitfail_to_recov_quirk = device_property_read_bool(dev,
+ "snps,u1u2_exitfail_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,
device_property_read_u32(dev, "snps,quirk-frame-length-adjustment",
&dwc->fladj);
+ dev_info(dwc->dev, "%s: dr_mode:%d, suspend clock:%dMHz\n", __func__,
+ dwc->dr_mode , dwc->suspend_clk_freq/1000000);
+
dwc->lpm_nyet_threshold = lpm_nyet_threshold;
dwc->tx_de_emphasis = tx_de_emphasis;
void __iomem *regs;
+ pr_info("%s: +++\n", __func__);
dwc = devm_kzalloc(dev, sizeof(*dwc), GFP_KERNEL);
if (!dwc)
return -ENOMEM;
dwc3_get_properties(dwc);
+ ret = dma_set_mask(dwc->sysdev, DMA_BIT_MASK(36));
+ if (ret) {
+ pr_err("dma set mask ret = %d\n", ret);
+ return ret;
+ }
+
platform_set_drvdata(pdev, dwc);
dwc3_cache_hwparams(dwc);
spin_lock_init(&dwc->lock);
-
- pm_runtime_set_active(dev);
- pm_runtime_use_autosuspend(dev);
- pm_runtime_set_autosuspend_delay(dev, DWC3_DEFAULT_AUTOSUSPEND_DELAY);
- pm_runtime_enable(dev);
- ret = pm_runtime_get_sync(dev);
- if (ret < 0)
- goto err1;
-
- pm_runtime_forbid(dev);
+ init_completion(&dwc->disconnect);
ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE);
if (ret) {
goto err2;
}
- ret = dwc3_get_dr_mode(dwc);
- if (ret)
- goto err3;
+ //ret = dwc3_get_dr_mode(dwc);
+ //if (ret)
+ // goto err3;
ret = dwc3_alloc_scratch_buffers(dwc);
if (ret)
goto err3;
+ /* pm_runtime_get_sync triggers dwc3_core_init, which causes Kernel panic
+ because dwc3_core_init is called in pm_runtime_get_sync before dwc3_alloc_event_buffers.
+ Move the location to call pm runtime api */
+ pm_runtime_set_active(dev);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_autosuspend_delay(dev, DWC3_DEFAULT_AUTOSUSPEND_DELAY);
+ pm_runtime_enable(dev);
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0)
+ goto err1;
+
+ pm_runtime_forbid(dev);
+
ret = dwc3_core_init(dwc);
if (ret) {
dev_err(dev, "failed to initialize core\n");
goto err5;
dwc3_debugfs_init(dwc);
- pm_runtime_put(dev);
+ ret = pm_runtime_put(dev);
+ pr_info("%s, pm_runtime_put = %d\n",
+ __func__, ret);
+ pr_info("%s: ---\n", __func__);
return 0;
err5:
dwc3_core_exit(dwc);
dwc3_ulpi_exit(dwc);
- pm_runtime_put_sync(&pdev->dev);
+ if (dwc->dr_mode != USB_DR_MODE_OTG)
+ pm_runtime_put_sync(&pdev->dev);
pm_runtime_allow(&pdev->dev);
pm_runtime_disable(&pdev->dev);
{
unsigned long flags;
+ /* bring to full power */
+ pm_runtime_get_sync(dwc->dev);
+
switch (dwc->dr_mode) {
case USB_DR_MODE_PERIPHERAL:
- case USB_DR_MODE_OTG:
spin_lock_irqsave(&dwc->lock, flags);
dwc3_gadget_suspend(dwc);
spin_unlock_irqrestore(&dwc->lock, flags);
break;
+ case USB_DR_MODE_OTG:
+ dwc3_event_buffers_cleanup(dwc);
+ break;
case USB_DR_MODE_HOST:
+ usb_phy_shutdown(dwc->usb3_phy);
+ usb_phy_shutdown(dwc->usb2_phy);
+ phy_exit(dwc->usb2_generic_phy);
+ phy_exit(dwc->usb3_generic_phy);
+
+ phy_power_off(dwc->usb2_generic_phy);
+ phy_power_off(dwc->usb3_generic_phy);
default:
/* do nothing */
break;
}
- dwc3_core_exit(dwc);
-
return 0;
}
static int dwc3_resume_common(struct dwc3 *dwc)
{
unsigned long flags;
- int ret;
- ret = dwc3_core_init(dwc);
- if (ret)
- return ret;
+ /* executing phy_init() twice caused 'ep0out' enable failure in resume.
+ * don't call dwc3_core_init
+ * ret = dwc3_core_init(dwc);
+ */
+ dwc3_event_buffers_setup(dwc);
switch (dwc->dr_mode) {
case USB_DR_MODE_PERIPHERAL:
- case USB_DR_MODE_OTG:
spin_lock_irqsave(&dwc->lock, flags);
dwc3_gadget_resume(dwc);
spin_unlock_irqrestore(&dwc->lock, flags);
- /* FALLTHROUGH */
- case USB_DR_MODE_HOST:
- default:
- /* do nothing */
- break;
- }
-
- return 0;
-}
-
-static int dwc3_runtime_checks(struct dwc3 *dwc)
-{
- switch (dwc->dr_mode) {
- case USB_DR_MODE_PERIPHERAL:
- case USB_DR_MODE_OTG:
- if (dwc->connected)
- return -EBUSY;
- break;
- case USB_DR_MODE_HOST:
- default:
- /* do nothing */
- break;
- }
-
- return 0;
-}
-
-static int dwc3_runtime_suspend(struct device *dev)
-{
- struct dwc3 *dwc = dev_get_drvdata(dev);
- int ret;
-
- if (dwc3_runtime_checks(dwc))
- return -EBUSY;
-
- ret = dwc3_suspend_common(dwc);
- if (ret)
- return ret;
-
- device_init_wakeup(dev, true);
-
- return 0;
-}
-
-static int dwc3_runtime_resume(struct device *dev)
-{
- struct dwc3 *dwc = dev_get_drvdata(dev);
- int ret;
-
- device_init_wakeup(dev, false);
-
- ret = dwc3_resume_common(dwc);
- if (ret)
- return ret;
-
- switch (dwc->dr_mode) {
- case USB_DR_MODE_PERIPHERAL:
- case USB_DR_MODE_OTG:
- dwc3_gadget_process_pending_events(dwc);
- break;
- case USB_DR_MODE_HOST:
- default:
- /* do nothing */
break;
- }
-
- pm_runtime_mark_last_busy(dev);
- pm_runtime_put(dev);
-
- return 0;
-}
-
-static int dwc3_runtime_idle(struct device *dev)
-{
- struct dwc3 *dwc = dev_get_drvdata(dev);
-
- switch (dwc->dr_mode) {
- case USB_DR_MODE_PERIPHERAL:
+ /* Applying KC OTG and Role switch driver patch - e40c2ae,
+ it's difficult to apply the patch because dwc3_resume func was changed in Kernel4.9 */
case USB_DR_MODE_OTG:
- if (dwc3_runtime_checks(dwc))
- return -EBUSY;
break;
case USB_DR_MODE_HOST:
default:
break;
}
- pm_runtime_mark_last_busy(dev);
- pm_runtime_autosuspend(dev);
-
return 0;
}
#endif /* CONFIG_PM */
struct dwc3 *dwc = dev_get_drvdata(dev);
int ret;
+ pr_info("[%s]\n", __func__);
ret = dwc3_suspend_common(dwc);
if (ret)
return ret;
struct dwc3 *dwc = dev_get_drvdata(dev);
int ret;
+ pr_info("[%s]\n", __func__);
pinctrl_pm_select_default_state(dev);
ret = dwc3_resume_common(dwc);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
+ /* Compensate usage count incremented during prepare */
+ pm_runtime_put_sync(dev);
+
return 0;
}
#endif /* CONFIG_PM_SLEEP */
static const struct dev_pm_ops dwc3_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(dwc3_suspend, dwc3_resume)
- SET_RUNTIME_PM_OPS(dwc3_runtime_suspend, dwc3_runtime_resume,
- dwc3_runtime_idle)
+ /*SET_RUNTIME_PM_OPS(dwc3_runtime_suspend, dwc3_runtime_resume,
+ dwc3_runtime_idle) */
};
#ifdef CONFIG_OF
#include <linux/dma-mapping.h>
#include <linux/mm.h>
#include <linux/debugfs.h>
+#include <linux/completion.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
/* Bit fields */
/* Global Debug Queue/FIFO Space Available Register */
+#define DWC3_GSBUSCFG0_INCRBRSTEN (1 << 0)
+#define DWC3_GSBUSCFG0_INCR4BRSTEN (1 << 1)
+#define DWC3_GSBUSCFG0_INCR8BRSTEN (1 << 2)
+#define DWC3_GSBUSCFG0_INCR16BRSTEN (1 << 3)
+#define DWC3_GSBUSCFG0_INCR32BRSTEN (1 << 4)
+#define DWC3_GSBUSCFG0_INCR64BRSTEN (1 << 5)
+#define DWC3_GSBUSCFG0_INCR128BRSTEN (1 << 6)
+#define DWC3_GSBUSCFG0_INCR256BRSTEN (1 << 7)
+#define DWC3_GSBUSCFG0_DESWRREQINFO (2 << 16)
+#define DWC3_GSBUSCFG0_DATWRREQINFO (2 << 20)
+#define DWC3_GSBUSCFG0_DESRDREQINFO (2 << 24)
+#define DWC3_GSBUSCFG0_DATRDREQINFO (2 << 28)
#define DWC3_GDBGFIFOSPACE_NUM(n) ((n) & 0x1f)
#define DWC3_GDBGFIFOSPACE_TYPE(n) (((n) << 5) & 0x1e0)
#define DWC3_GDBGFIFOSPACE_SPACE_AVAILABLE(n) (((n) >> 16) & 0xffff)
+#define DWC3_GSBUSCFG1_BREQLIMIT(n) ((n) << 8)
+#define DWC3_GSBUSCFG1_BREQLIMIT_SHIFT 8
+#define DWC3_GSBUSCFG1_BREQLIMIT_MASK (0xf << 8)
+#define DWC3_GSBUSCFG1_EN1KPAGE (1 << 12)
+
+#define DWC3_GRXTHRCFG_USBRXPKTCNTSEL (1 << 29)
+#define DWC3_GRXTHRCFG_USBRXPKTCNT_MASK (0xf << 24)
+#define DWC3_GRXTHRCFG_USBRXPKTCNT_SHIFT 24
+#define DWC3_GRXTHRCFG_USBRXPKTCNT(n) ((n) << 24)
+#define DWC3_GRXTHRCFG_USBMAXRXBURSTSIZE_MASK (0x1f << 19)
+#define DWC3_GRXTHRCFG_USBMAXRXBURSTSIZE_SHIFT 19
+#define DWC3_GRXTHRCFG_USBMAXRXBURSTSIZE(n) ((n) << 19)
+
#define DWC3_TXFIFOQ 0
#define DWC3_RXFIFOQ 1
#define DWC3_TXREQQ 2
/* Global Configuration Register */
#define DWC3_GCTL_PWRDNSCALE(n) ((n) << 19)
+#define DWC3_GCTL_PWRDNSCALE_MASK DWC3_GCTL_PWRDNSCALE(0x1fff)
#define DWC3_GCTL_U2RSTECN BIT(16)
#define DWC3_GCTL_RAMCLKSEL(x) (((x) & DWC3_GCTL_CLK_MASK) << 6)
#define DWC3_GCTL_CLK_BUS (0)
#define DWC3_GCTL_GBLHIBERNATIONEN BIT(1)
#define DWC3_GCTL_DSBLCLKGTNG BIT(0)
+#define DWC3_GUCTL_REFCLKPER(n) ((n) << 22)
+#define DWC3_GUCTL_REFCLKPER_MASK DWC3_GUCTL_REFCLKPER(0x3FF)
+#define DWC3_GUCTL_USBHSTINAUTORETRYEN (1 << 14)
+#define DWC3_GUCTL_SPRSCTRLTRANSEN (1 << 17)
+
/* Global User Control 1 Register */
#define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS BIT(28)
#define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24)
#define DWC3_GUSB3PIPECTL_U2SSINP3OK BIT(29)
#define DWC3_GUSB3PIPECTL_DISRXDETINP3 BIT(28)
#define DWC3_GUSB3PIPECTL_UX_EXIT_PX BIT(27)
+#define DWC3_GUSB3PIPECTL_U1U2EXITFAIL_TO_RECOV BIT(25)
#define DWC3_GUSB3PIPECTL_REQP1P2P3 BIT(24)
#define DWC3_GUSB3PIPECTL_DEP1P2P3(n) ((n) << 19)
#define DWC3_GUSB3PIPECTL_DEP1P2P3_MASK DWC3_GUSB3PIPECTL_DEP1P2P3(7)
#define DWC3_GHWPARAMS7_RAM2_DEPTH(n) (((n) >> 16) & 0xffff)
/* Global Frame Length Adjustment Register */
+#define DWC3_GFLADJ_REFCLK_240MHZDECR_PLS1 BIT(31)
+#define DWC3_GFLADJ_REFCLK_240MHZ_DECR(n) ((n) << 24)
+#define DWC3_GFLADJ_REFCLK_240MHZ_DECR_MASK DWC3_GFLADJ_REFCLK_240MHZ_DECR(0x7F)
+#define DWC3_GFLADJ_REFCLK_LPM_SEL BIT(23)
+#define DWC3_GFLADJ_REFCLK_FLADJ(n) ((n) << 8)
+#define DWC3_GFLADJ_REFCLK_FLADJ_MASK DWC3_GFLADJ_REFCLK_FLADJ(0x3FFF)
#define DWC3_GFLADJ_30MHZ_SDBND_SEL BIT(7)
#define DWC3_GFLADJ_30MHZ_MASK 0x3f
#define DWC3_DCFG_SUPERSPEED_PLUS (5 << 0) /* DWC_usb31 only */
#define DWC3_DCFG_SUPERSPEED (4 << 0)
#define DWC3_DCFG_HIGHSPEED (0 << 0)
-#define DWC3_DCFG_FULLSPEED BIT(0)
+#define DWC3_DCFG_FULLSPEED (1 << 0) // Check later!
#define DWC3_DCFG_LOWSPEED (2 << 0)
+#define DWC3_DCFG_FULLSPEED1 (3 << 0) // Check later!
#define DWC3_DCFG_NUMP_SHIFT 17
#define DWC3_DCFG_NUMP(n) (((n) >> DWC3_DCFG_NUMP_SHIFT) & 0x1f)
#define DWC3_DEVTEN_ERRTICERREN BIT(9)
#define DWC3_DEVTEN_SOFEN BIT(7)
#define DWC3_DEVTEN_EOPFEN BIT(6)
+#define DWC3_DEVTEN_U3L2_SUSPEN BIT(6)
#define DWC3_DEVTEN_HIBERNATIONREQEVTEN BIT(5)
#define DWC3_DEVTEN_WKUPEVTEN BIT(4)
#define DWC3_DEVTEN_ULSTCNGEN BIT(3)
#define DWC3_DSTS_SUPERSPEED_PLUS (5 << 0) /* DWC_usb31 only */
#define DWC3_DSTS_SUPERSPEED (4 << 0)
#define DWC3_DSTS_HIGHSPEED (0 << 0)
-#define DWC3_DSTS_FULLSPEED BIT(0)
+#define DWC3_DSTS_FULLSPEED (1 << 0) // Check later!
#define DWC3_DSTS_LOWSPEED (2 << 0)
+#define DWC3_DSTS_FULLSPEED1 (3 << 0)
+
/* Device Generic Command Register */
#define DWC3_DGCMD_SET_LMP 0x01
#define DWC3_DEPCMD_TYPE_BULK 2
#define DWC3_DEPCMD_TYPE_INTR 3
+/* OTG Control Register */
+#define DWC3_OTG_OCTL_PERIMODE (1 << 6)
+
+/* OTG Events Register */
+#define DWC3_OEVT_DEVICEMODE (1 << 31)
+#define DWC3_OEVT_CLEAR_ALL (~DWC3_OEVT_DEVICEMODE)
+#define DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT (1 << 24)
+#define DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT (1 << 8)
+
+/* OTG Status Register */
+#define DWC3_OTG_OSTS_BSESVALID (1 << 2)
+#define DWC3_OTG_OSTS_CONIDSTS (1 << 0)
+
#define DWC3_DEV_IMOD_COUNT_SHIFT 16
#define DWC3_DEV_IMOD_COUNT_MASK (0xffff << 16)
#define DWC3_DEV_IMOD_INTERVAL_SHIFT 0
#define DWC3_EP_DIRECTION_RX false
#define DWC3_TRB_NUM 256
+#define DWC3_TRB_MASK (DWC3_TRB_NUM - 1)
/**
* struct dwc3_ep - device side endpoint representation
struct dwc3_trb *trb_pool;
dma_addr_t trb_pool_dma;
+ u32 free_slot;
+ u32 busy_slot;
struct dwc3 *dwc;
u32 saved_state;
* @xhci_resources: struct resources for our @xhci child
* @ev_buf: struct dwc3_event_buffer pointer
* @eps: endpoint array
+ * @dotg: pointer to OTG
* @gadget: device side representation of the peripheral controller
* @gadget_driver: pointer to the gadget driver
* @regs: base address for our registers
* @lpm_nyet_threshold: LPM NYET response threshold
* @hird_threshold: HIRD threshold
* @hsphy_interface: "utmi" or "ulpi"
+ * @vbus_session: Indicates if the gadget was powered by the otg driver
+ * @softconnect: Indicates if pullup was issued by the usb_gadget_driver
+ * @disconnect: signals that Disconnection interrupt happend
+ * @suspend_clk_freq: frequency of suspend clock
* @connected: true when we're connected to a host, false otherwise
* @delayed_status: true when gadget driver asks for delayed status
* @ep0_bounced: true when we used bounce buffer
* @u2ss_inp3_quirk: set if we enable P3 OK for U2/SS Inactive quirk
* @req_p1p2p3_quirk: set if we enable request p1p2p3 quirk
* @del_p1p2p3_quirk: set if we enable delay p1p2p3 quirk
+ * @u1u2_exitfail_to_recov_quirk: set if we enable u1u2 exitfail to recov quirk
* @del_phy_power_chg_quirk: set if we enable delay phy power change quirk
* @lfps_filter_quirk: set if we enable LFPS filter quirk
* @rx_detect_poll_quirk: set if we enable rx_detect to polling lfps quirk
* 3 - Reserved
* @imod_interval: set the interrupt moderation interval in 250ns
* increments or 0 to disable.
+ * @adj_sof_accuracy: set to adjust sof accuracy
*/
struct dwc3 {
struct work_struct drd_work;
struct dwc3_event_buffer *ev_buf;
struct dwc3_ep *eps[DWC3_ENDPOINTS_NUM];
+ struct dwc3_otg *dotg;
struct usb_gadget gadget;
struct usb_gadget_driver *gadget_driver;
const char *hsphy_interface;
+ bool vbus_session;
+ bool softconnect;
+
+ struct completion disconnect;
+
+ /**
+ * Frequency of suspend clock.
+ * Suspend clock is a clock source of 16KHz clock for a small part
+ * of the USB3 core that operates when the SS PHY is in its lowest
+ * power (P3) state.
+ */
+ u32 suspend_clk_freq;
+
unsigned connected:1;
unsigned delayed_status:1;
unsigned ep0_bounced:1;
unsigned u2exit_lfps_quirk:1;
unsigned u2ss_inp3_quirk:1;
unsigned req_p1p2p3_quirk:1;
- unsigned del_p1p2p3_quirk:1;
+ unsigned del_p1p2p3_quirk:1;
+ unsigned u1u2_exitfail_to_recov_quirk:1;
unsigned del_phy_power_chg_quirk:1;
unsigned lfps_filter_quirk:1;
unsigned rx_detect_poll_quirk:1;
unsigned tx_de_emphasis:2;
u16 imod_interval;
+
+ unsigned adj_sof_accuracy:1;
+
+ unsigned sparse_transfer_control:1;
+ unsigned is_not_vbus_pad:1;
+ unsigned start_config_issued:1;
};
#define work_to_dwc(w) (container_of((w), struct dwc3, drd_work))
/* prototypes */
void dwc3_set_mode(struct dwc3 *dwc, u32 mode);
+int dwc3_soft_reset(struct dwc3 *dwc);
+int dwc3_event_buffers_setup(struct dwc3 *dwc);
+void dwc3_event_buffers_cleanup(struct dwc3 *dwc);
+int dwc3_phy_setup(struct dwc3 *dwc);
+int dwc3_core_init(struct dwc3 *dwc);
+void dwc3_core_exit(struct dwc3 *dwc);
+int dwc3_otg_start(struct dwc3 *dwc);
+void dwc3_otg_stop(struct dwc3 *dwc);
+int dwc3_otg_init(struct dwc3 *dwc);
+void dwc3_otg_exit(struct dwc3 *dwc);
+
u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type);
/* check whether we are on the DWC_usb3 core */
int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd,
struct dwc3_gadget_ep_cmd_params *params);
int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param);
+void dwc3_gadget_disconnect_proc(struct dwc3 *dwc);
#else
static inline int dwc3_gadget_init(struct dwc3 *dwc)
{ return 0; }
static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc,
int cmd, u32 param)
{ return 0; }
+static inline void dwc3_gadget_disconnect_proc(struct dwc3 *dwc)
+{ }
#endif
#if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
if (id < 0)
id = 0;
- dwc3_set_mode(dwc, id ?
- DWC3_GCTL_PRTCAP_HOST :
- DWC3_GCTL_PRTCAP_DEVICE);
+ /* Force set OTG mode, modify this after device bring-up */
+ dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
+
+ /* Force set OTG mode, modify this after device bring-up
+ *dwc3_set_mode(dwc, id ?
+ * DWC3_GCTL_PRTCAP_HOST :
+ * DWC3_GCTL_PRTCAP_DEVICE);
+ */
}
static int dwc3_drd_notifier(struct notifier_block *nb,
extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST,
&dwc->edev_nb);
- dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+ /*dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);*/
+ dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
flush_work(&dwc->drd_work);
dwc3_gadget_exit(dwc);
}
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
+#include <linux/mutex.h>
#include <linux/clk.h>
#include <linux/usb/otg.h>
#include <linux/usb/usb_phy_generic.h>
+#include <linux/usb/samsung_usb.h>
+#include <linux/dma-mapping.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+
+#include <linux/io.h>
+#include <linux/usb/otg-fsm.h>
+
+#ifdef CONFIG_CPU_IDLE
+#include <soc/samsung/exynos-cpupm.h>
+#endif
+
+/* -------------------------------------------------------------------------- */
+
+struct dwc3_exynos_rsw {
+ struct otg_fsm *fsm;
+ struct work_struct work;
+};
struct dwc3_exynos {
struct platform_device *usb2_phy;
struct platform_device *usb3_phy;
struct device *dev;
- struct clk *clk;
- struct clk *susp_clk;
- struct clk *axius_clk;
+ struct clk **clocks;
struct regulator *vdd33;
struct regulator *vdd10;
+
+ int idle_ip_index;
+
+ struct dwc3_exynos_rsw rsw;
+};
+
+void dwc3_otg_run_sm(struct otg_fsm *fsm);
+
+static const struct of_device_id exynos_dwc3_match[] = {
+ {
+ .compatible = "samsung,exynos-dwusb",
+ },
+ {},
};
+MODULE_DEVICE_TABLE(of, exynos_dwc3_match);
+
+/* -------------------------------------------------------------------------- */
+
+static int dwc3_exynos_clk_get(struct dwc3_exynos *exynos)
+{
+ struct device *dev = exynos->dev;
+ const char **clk_ids;
+ struct clk *clk;
+ int clk_count;
+ int ret, i;
+
+ clk_count = of_property_count_strings(dev->of_node, "clock-names");
+ if (IS_ERR_VALUE((unsigned long)clk_count)) {
+ dev_err(dev, "invalid clk list in %s node", dev->of_node->name);
+ return -EINVAL;
+ }
+
+ clk_ids = (const char **)devm_kmalloc(dev,
+ (clk_count + 1) * sizeof(const char *),
+ GFP_KERNEL);
+ if (!clk_ids) {
+ dev_err(dev, "failed to alloc for clock ids");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < clk_count; i++) {
+ ret = of_property_read_string_index(dev->of_node, "clock-names",
+ i, &clk_ids[i]);
+ if (ret) {
+ dev_err(dev, "failed to read clocks name %d from %s node\n",
+ i, dev->of_node->name);
+ return ret;
+ }
+ }
+ clk_ids[clk_count] = NULL;
+
+ exynos->clocks = (struct clk **) devm_kmalloc(exynos->dev,
+ clk_count * sizeof(struct clk *), GFP_KERNEL);
+ if (!exynos->clocks) {
+ dev_err(exynos->dev, "%s: couldn't alloc\n", __func__);
+ return -ENOMEM;
+ }
+
+ for (i = 0; clk_ids[i] != NULL; i++) {
+ clk = devm_clk_get(exynos->dev, clk_ids[i]);
+ if (IS_ERR_OR_NULL(clk))
+ goto err;
+
+ exynos->clocks[i] = clk;
+ }
+ exynos->clocks[i] = NULL;
+
+ return 0;
+
+err:
+ dev_err(exynos->dev, "couldn't get %s clock\n", clk_ids[i]);
+ return -EINVAL;
+}
+
+static int dwc3_exynos_clk_prepare(struct dwc3_exynos *exynos)
+{
+ int i;
+ int ret;
+
+ for (i = 0; exynos->clocks[i] != NULL; i++) {
+ ret = clk_prepare(exynos->clocks[i]);
+ if (ret)
+ goto err;
+ }
+
+ return 0;
+
+err:
+ dev_err(exynos->dev, "couldn't prepare clock[%d]\n", i);
+
+ /* roll back */
+ for (i = i - 1; i >= 0; i--)
+ clk_unprepare(exynos->clocks[i]);
+
+ return ret;
+}
+
+static int dwc3_exynos_clk_enable(struct dwc3_exynos *exynos)
+{
+ int i;
+ int ret;
+
+ for (i = 0; exynos->clocks[i] != NULL; i++) {
+ ret = clk_enable(exynos->clocks[i]);
+ if (ret)
+ goto err;
+ }
+
+ return 0;
+
+err:
+ dev_err(exynos->dev, "couldn't enable clock[%d]\n", i);
+
+ /* roll back */
+ for (i = i - 1; i >= 0; i--)
+ clk_disable(exynos->clocks[i]);
+
+ return ret;
+}
+
+static void dwc3_exynos_clk_unprepare(struct dwc3_exynos *exynos)
+{
+ int i;
+
+ for (i = 0; exynos->clocks[i] != NULL; i++)
+ clk_unprepare(exynos->clocks[i]);
+}
+
+static void dwc3_exynos_clk_disable(struct dwc3_exynos *exynos)
+{
+ int i;
+
+ for (i = 0; exynos->clocks[i] != NULL; i++)
+ clk_disable(exynos->clocks[i]);
+}
+
+/* -------------------------------------------------------------------------- */
+
+static struct dwc3_exynos *dwc3_exynos_match(struct device *dev)
+{
+ const struct of_device_id *matches = NULL;
+ struct dwc3_exynos *exynos = NULL;
+
+ if (!dev)
+ return NULL;
+
+ matches = exynos_dwc3_match;
+
+ if (of_match_device(matches, dev))
+ exynos = dev_get_drvdata(dev);
+
+ return exynos;
+}
+
+bool dwc3_exynos_rsw_available(struct device *dev)
+{
+ struct dwc3_exynos *exynos;
+
+ exynos = dwc3_exynos_match(dev);
+ if (!exynos)
+ return false;
+
+ return true;
+}
+
+int dwc3_exynos_rsw_start(struct device *dev)
+{
+ struct dwc3_exynos *exynos = dev_get_drvdata(dev);
+ struct dwc3_exynos_rsw *rsw = &exynos->rsw;
+
+ dev_info(dev, "%s\n", __func__);
+
+ /* B-device by default */
+ rsw->fsm->id = 1;
+ rsw->fsm->b_sess_vld = 0;
+
+ return 0;
+}
+
+void dwc3_exynos_rsw_stop(struct device *dev)
+{
+ dev_info(dev, "%s\n", __func__);
+}
+
+static void dwc3_exynos_rsw_work(struct work_struct *w)
+{
+ struct dwc3_exynos_rsw *rsw = container_of(w,
+ struct dwc3_exynos_rsw, work);
+ struct dwc3_exynos *exynos = container_of(rsw,
+ struct dwc3_exynos, rsw);
+
+ dev_info(exynos->dev, "%s\n", __func__);
+
+ dwc3_otg_run_sm(rsw->fsm);
+}
+
+int dwc3_exynos_rsw_setup(struct device *dev, struct otg_fsm *fsm)
+{
+ struct dwc3_exynos *exynos = dev_get_drvdata(dev);
+ struct dwc3_exynos_rsw *rsw = &exynos->rsw;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ INIT_WORK(&rsw->work, dwc3_exynos_rsw_work);
+
+ rsw->fsm = fsm;
+
+ return 0;
+}
+
+void dwc3_exynos_rsw_exit(struct device *dev)
+{
+ struct dwc3_exynos *exynos = dev_get_drvdata(dev);
+ struct dwc3_exynos_rsw *rsw = &exynos->rsw;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ cancel_work_sync(&rsw->work);
+
+ rsw->fsm = NULL;
+}
+
+/**
+ * dwc3_exynos_id_event - receive ID pin state change event.
+ * @state : New ID pin state.
+ */
+int dwc3_exynos_id_event(struct device *dev, int state)
+{
+ struct dwc3_exynos *exynos;
+ struct dwc3_exynos_rsw *rsw;
+ struct otg_fsm *fsm;
+
+ dev_dbg(dev, "EVENT: ID: %d\n", state);
+
+ exynos = dev_get_drvdata(dev);
+ if (!exynos)
+ return -ENOENT;
+
+ rsw = &exynos->rsw;
+
+ fsm = rsw->fsm;
+ if (!fsm)
+ return -ENOENT;
+
+ if (fsm->id != state) {
+ fsm->id = state;
+ schedule_work(&rsw->work);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dwc3_exynos_id_event);
+
+/**
+ * dwc3_exynos_vbus_event - receive VBus change event.
+ * vbus_active : New VBus state, true if active, false otherwise.
+ */
+int dwc3_exynos_vbus_event(struct device *dev, bool vbus_active)
+{
+ struct dwc3_exynos *exynos;
+ struct dwc3_exynos_rsw *rsw;
+ struct otg_fsm *fsm;
+
+ dev_dbg(dev, "EVENT: VBUS: %sactive\n", vbus_active ? "" : "in");
+
+ exynos = dev_get_drvdata(dev);
+ if (!exynos)
+ return -ENOENT;
+
+ rsw = &exynos->rsw;
+
+ fsm = rsw->fsm;
+ if (!fsm)
+ return -ENOENT;
+
+ if (fsm->b_sess_vld != vbus_active) {
+ fsm->b_sess_vld = vbus_active;
+ schedule_work(&rsw->work);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dwc3_exynos_vbus_event);
static int dwc3_exynos_register_phys(struct dwc3_exynos *exynos)
{
int ret;
+ pr_info("%s: +++\n", __func__);
exynos = devm_kzalloc(dev, sizeof(*exynos), GFP_KERNEL);
if (!exynos)
return -ENOMEM;
+ ret = dma_set_mask(dev, DMA_BIT_MASK(36));
+ if (ret) {
+ pr_err("dma set mask ret = %d\n", ret);
+ return ret;
+ }
+
platform_set_drvdata(pdev, exynos);
- exynos->dev = dev;
+ exynos->dev = dev;
- exynos->clk = devm_clk_get(dev, "usbdrd30");
- if (IS_ERR(exynos->clk)) {
- dev_err(dev, "couldn't get clock\n");
- return -EINVAL;
- }
- ret = clk_prepare_enable(exynos->clk);
+#ifdef CONFIG_CPU_IDLE
+ exynos->idle_ip_index = exynos_get_idle_ip_index(dev_name(dev));
+ exynos_update_ip_idle_status(exynos->idle_ip_index, 0);
+#endif
+
+ ret = dwc3_exynos_clk_get(exynos);
if (ret)
return ret;
- exynos->susp_clk = devm_clk_get(dev, "usbdrd30_susp_clk");
- if (IS_ERR(exynos->susp_clk))
- exynos->susp_clk = NULL;
- ret = clk_prepare_enable(exynos->susp_clk);
+ ret = dwc3_exynos_clk_prepare(exynos);
if (ret)
- goto susp_clk_err;
-
- if (of_device_is_compatible(node, "samsung,exynos7-dwusb3")) {
- exynos->axius_clk = devm_clk_get(dev, "usbdrd30_axius_clk");
- if (IS_ERR(exynos->axius_clk)) {
- dev_err(dev, "no AXI UpScaler clk specified\n");
- ret = -ENODEV;
- goto axius_clk_err;
- }
- ret = clk_prepare_enable(exynos->axius_clk);
- if (ret)
- goto axius_clk_err;
- } else {
- exynos->axius_clk = NULL;
+ return ret;
+
+ ret = dwc3_exynos_clk_enable(exynos);
+ if (ret) {
+ dwc3_exynos_clk_unprepare(exynos);
+ return ret;
}
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+
exynos->vdd33 = devm_regulator_get(dev, "vdd33");
if (IS_ERR(exynos->vdd33)) {
- ret = PTR_ERR(exynos->vdd33);
- goto vdd33_err;
+ dev_dbg(dev, "couldn't get regulator vdd33\n");
+ exynos->vdd33 = NULL;
}
- ret = regulator_enable(exynos->vdd33);
- if (ret) {
- dev_err(dev, "Failed to enable VDD33 supply\n");
- goto vdd33_err;
+ if (exynos->vdd33) {
+ ret = regulator_enable(exynos->vdd33);
+ if (ret) {
+ dev_err(dev, "Failed to enable VDD33 supply\n");
+ goto vdd33_err;
+ }
}
exynos->vdd10 = devm_regulator_get(dev, "vdd10");
if (IS_ERR(exynos->vdd10)) {
- ret = PTR_ERR(exynos->vdd10);
- goto vdd10_err;
- }
- ret = regulator_enable(exynos->vdd10);
- if (ret) {
- dev_err(dev, "Failed to enable VDD10 supply\n");
- goto vdd10_err;
+ dev_dbg(dev, "couldn't get regulator vdd10\n");
+ exynos->vdd10 = NULL;
}
+ if (exynos->vdd10) {
+ ret = regulator_enable(exynos->vdd10);
+ if (ret) {
+ dev_err(dev, "Failed to enable VDD10 supply\n");
+ goto vdd10_err;
+ }
+ }
ret = dwc3_exynos_register_phys(exynos);
if (ret) {
goto populate_err;
}
+ pr_info("%s: ---\n", __func__);
return 0;
populate_err:
platform_device_unregister(exynos->usb2_phy);
platform_device_unregister(exynos->usb3_phy);
phys_err:
- regulator_disable(exynos->vdd10);
+ if (exynos->vdd10)
+ regulator_disable(exynos->vdd10);
vdd10_err:
- regulator_disable(exynos->vdd33);
+ if (exynos->vdd33)
+ regulator_disable(exynos->vdd33);
vdd33_err:
- clk_disable_unprepare(exynos->axius_clk);
-axius_clk_err:
- clk_disable_unprepare(exynos->susp_clk);
-susp_clk_err:
- clk_disable_unprepare(exynos->clk);
+ pm_runtime_disable(&pdev->dev);
+ dwc3_exynos_clk_disable(exynos);
+ dwc3_exynos_clk_unprepare(exynos);
+ pm_runtime_set_suspended(&pdev->dev);
return ret;
}
platform_device_unregister(exynos->usb2_phy);
platform_device_unregister(exynos->usb3_phy);
- clk_disable_unprepare(exynos->axius_clk);
- clk_disable_unprepare(exynos->susp_clk);
- clk_disable_unprepare(exynos->clk);
+ pm_runtime_disable(&pdev->dev);
+ if (!pm_runtime_status_suspended(&pdev->dev)) {
+ dwc3_exynos_clk_disable(exynos);
+ pm_runtime_set_suspended(&pdev->dev);
+ }
+
+ dwc3_exynos_clk_unprepare(exynos);
- regulator_disable(exynos->vdd33);
- regulator_disable(exynos->vdd10);
+ if (exynos->vdd33)
+ regulator_disable(exynos->vdd33);
+ if (exynos->vdd10)
+ regulator_disable(exynos->vdd10);
return 0;
}
-static const struct of_device_id exynos_dwc3_match[] = {
- { .compatible = "samsung,exynos5250-dwusb3" },
- { .compatible = "samsung,exynos7-dwusb3" },
- {},
-};
-MODULE_DEVICE_TABLE(of, exynos_dwc3_match);
+#ifdef CONFIG_PM
+static int dwc3_exynos_runtime_suspend(struct device *dev)
+{
+ struct dwc3_exynos *exynos = dev_get_drvdata(dev);
+
+ dev_info(dev, "%s\n", __func__);
+
+ dwc3_exynos_clk_disable(exynos);
+
+#ifdef CONFIG_CPU_IDLE
+ /* inform what USB state is idle to IDLE_IP */
+ exynos_update_ip_idle_status(exynos->idle_ip_index, 1);
+#endif
+
+ return 0;
+}
+
+static int dwc3_exynos_runtime_resume(struct device *dev)
+{
+ struct dwc3_exynos *exynos = dev_get_drvdata(dev);
+ int ret = 0;
+
+ dev_info(dev, "%s\n", __func__);
+
+#ifdef CONFIG_CPU_IDLE
+ /* inform what USB state is not idle to IDLE_IP */
+ exynos_update_ip_idle_status(exynos->idle_ip_index, 0);
+#endif
+
+ ret = dwc3_exynos_clk_enable(exynos);
+ if (ret) {
+ dev_err(dev, "%s: clk_enable failed\n", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+#endif
#ifdef CONFIG_PM_SLEEP
static int dwc3_exynos_suspend(struct device *dev)
{
struct dwc3_exynos *exynos = dev_get_drvdata(dev);
- clk_disable(exynos->axius_clk);
- clk_disable(exynos->clk);
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (pm_runtime_suspended(dev))
+ return 0;
+
+ dwc3_exynos_clk_disable(exynos);
- regulator_disable(exynos->vdd33);
- regulator_disable(exynos->vdd10);
+ if (exynos->vdd33)
+ regulator_disable(exynos->vdd33);
+ if (exynos->vdd10)
+ regulator_disable(exynos->vdd10);
+
+ /* inform what USB state is idle to IDLE_IP */
+ //exynos_update_ip_idle_status(exynos->idle_ip_index, 1);
return 0;
}
struct dwc3_exynos *exynos = dev_get_drvdata(dev);
int ret;
- ret = regulator_enable(exynos->vdd33);
- if (ret) {
- dev_err(dev, "Failed to enable VDD33 supply\n");
- return ret;
+ dev_dbg(dev, "%s\n", __func__);
+
+ /* inform what USB state is not idle to IDLE_IP */
+ //exynos_update_ip_idle_status(exynos->idle_ip_index, 0);
+
+ if (exynos->vdd33) {
+ ret = regulator_enable(exynos->vdd33);
+ if (ret) {
+ dev_err(dev, "Failed to enable VDD33 supply\n");
+ return ret;
+ }
+ }
+ if (exynos->vdd10) {
+ ret = regulator_enable(exynos->vdd10);
+ if (ret) {
+ if (exynos->vdd33)
+ regulator_disable(exynos->vdd33);
+ dev_err(dev, "Failed to enable VDD10 supply\n");
+ return ret;
+ }
}
- ret = regulator_enable(exynos->vdd10);
+
+ ret = dwc3_exynos_clk_enable(exynos);
if (ret) {
- dev_err(dev, "Failed to enable VDD10 supply\n");
+ dev_err(dev, "%s: clk_enable failed\n", __func__);
return ret;
}
- clk_enable(exynos->clk);
- clk_enable(exynos->axius_clk);
-
/* runtime set active to reflect active state. */
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
static const struct dev_pm_ops dwc3_exynos_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(dwc3_exynos_suspend, dwc3_exynos_resume)
+ SET_RUNTIME_PM_OPS(dwc3_exynos_runtime_suspend,
+ dwc3_exynos_runtime_resume, NULL)
};
#define DEV_PM_OPS (&dwc3_exynos_dev_pm_ops)
{
struct dwc3_ep *dep;
+ if (dwc->eps[1]->endpoint.desc == NULL) {
+ dev_err(dwc->dev, "EP1 was disabled: DESC NULL\n");
+ return;
+ }
+ if (dwc->eps[0]->endpoint.desc == NULL) {
+ dev_err(dwc->dev, "EP0 was disabled: DESC NULL\n");
+ return;
+ }
+
/* reinitialize physical ep1 */
dep = dwc->eps[1];
dep->flags = DWC3_EP_ENABLED;
(dwc->speed != DWC3_DSTS_SUPERSPEED_PLUS))
return -EINVAL;
+ /* see NEGATIVE RX DETECTION comment */
+ if (set && dwc->revision < DWC3_REVISION_230A)
+ return 0;
+
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
if (set)
reg |= DWC3_DCTL_INITU1ENA;
(dwc->speed != DWC3_DSTS_SUPERSPEED_PLUS))
return -EINVAL;
+ /* see NEGATIVE RX DETECTION comment */
+ if (set && dwc->revision < DWC3_REVISION_230A)
+ return 0;
+
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
if (set)
reg |= DWC3_DCTL_INITU2ENA;
USB_STATE_CONFIGURED);
/*
- * Enable transition to U1/U2 state when
- * nothing is pending from application.
+ * NEGATIVE RX DETECTION
+ * Some host controllers (e.g. Intel) perform far-end
+ * receiver termination _negative_ detection while link
+ * is in U2 state. Synopsys PIPE PHY considers this
+ * signalling as U2 LFPS exit, moves to Recovery state
+ * and waits for training sequence which never comes.
+ * This finally leads to reconnection. Starting from
+ * DWC3 core 2.30a, GCTL register has bit U2EXIT_LFPS,
+ * which improves interoperability with such HCs.
*/
- reg = dwc3_readl(dwc->regs, DWC3_DCTL);
- reg |= (DWC3_DCTL_ACCEPTU1ENA | DWC3_DCTL_ACCEPTU2ENA);
- dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+ if (dwc->revision >= DWC3_REVISION_230A) {
+ /*
+ * Enable transition to U1/U2 state when
+ * nothing is pending from application.
+ */
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+ reg |= (DWC3_DCTL_ACCEPTU1ENA | DWC3_DCTL_ACCEPTU2ENA);
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+ }
}
break;
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
+#include <linux/usb/samsung_usb.h>
+#include <linux/phy/phy.h>
#include "debug.h"
#include "core.h"
+#include "otg.h"
#include "gadget.h"
#include "io.h"
int status)
{
struct dwc3 *dwc = dep->dwc;
+ unsigned int unmap_after_complete = false;
req->started = false;
- list_del(&req->list);
+ /* Only delete from the list if the item isn't poisoned. */
+ if (req->list.next != LIST_POISON1)
+ list_del(&req->list);
req->remaining = 0;
if (req->request.status == -EINPROGRESS)
req->request.status = status;
- if (req->trb)
- usb_gadget_unmap_request_by_dev(dwc->sysdev,
+ /*
+ * NOTICE we don't want to unmap before calling ->complete() if we're
+ * dealing with a bounced ep0 request. If we unmap it here, we would end
+ * up overwritting the contents of req->buf and this could confuse the
+ * gadget driver.
+ */
+ if (req->trb) {
+ if (dwc->ep0_bounced && dep->number <= 1) {
+ dwc->ep0_bounced = false;
+ unmap_after_complete = true;
+ } else {
+ usb_gadget_unmap_request_by_dev(dwc->sysdev,
&req->request, req->direction);
-
- req->trb = NULL;
+ req->trb = NULL;
+ }
+ }
trace_dwc3_gadget_giveback(req);
usb_gadget_giveback_request(&dep->endpoint, &req->request);
spin_lock(&dwc->lock);
+ if (unmap_after_complete)
+ usb_gadget_unmap_request_by_dev(dwc->sysdev,
+ &req->request, req->direction);
+ req->trb = NULL;
+
if (dep->number > 1)
pm_runtime_put(dwc->dev);
}
if (unlikely(needs_wakeup)) {
ret = __dwc3_gadget_wakeup(dwc);
- dev_WARN_ONCE(dwc->dev, ret, "wakeup failed --> %d\n",
- ret);
+ /*
+ * disable warning when muic/ccic wasn't merged
+ *dev_WARN_ONCE(dwc->dev, ret, "wakeup failed --> %d\n",
+ * ret);
+ */
}
}
list_add_tail(&req->list, &dep->pending_list);
+ /* prevent starting transfer if controller is stopped */
+ if (!dwc->pullups_connected) {
+ dev_dbg(dwc->dev, "queue request while udc is stopped");
+ return 0;
+ }
+
/*
* NOTICE: Isochronous endpoints should NEVER be prestarted. We must
* wait for a XferNotReady event so we will know what's the current
struct dwc3 *dwc = dep->dwc;
int ret;
+ if (dep->endpoint.desc == NULL)
+ return -EINVAL;
+
if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) {
dev_err(dwc->dev, "%s is of Isochronous type\n", dep->name);
return -EINVAL;
unsigned transfer_in_flight;
unsigned started;
- if (dep->flags & DWC3_EP_STALL)
- return 0;
-
if (dep->number > 1)
trb = dwc3_ep_prev_trb(dep, dep->trb_enqueue);
else
else
dep->flags |= DWC3_EP_STALL;
} else {
- if (!(dep->flags & DWC3_EP_STALL))
- return 0;
-
ret = dwc3_send_clear_stall_ep_cmd(dep);
if (ret)
dev_err(dwc->dev, "failed to clear STALL on %s\n",
return 0;
}
+static int dwc3_udc_init(struct dwc3 *dwc)
+{
+ struct dwc3_ep *dep;
+ u32 reg;
+ int ret = 0;
+
+ reg = dwc3_readl(dwc->regs, DWC3_DCFG);
+ reg &= ~(DWC3_DCFG_SPEED_MASK);
+
+ /**
+ * WORKAROUND: DWC3 revision < 2.20a have an issue
+ * which would cause metastability state on Run/Stop
+ * bit if we try to force the IP to USB2-only mode.
+ *
+ * Because of that, we cannot configure the IP to any
+ * speed other than the SuperSpeed
+ *
+ * Refers to:
+ *
+ * STAR#9000525659: Clock Domain Crossing on DCTL in
+ * USB 2.0 Mode
+ */
+ if (dwc->revision < DWC3_REVISION_220A) {
+ reg |= DWC3_DCFG_SUPERSPEED;
+ } else {
+ switch (dwc->maximum_speed) {
+ case USB_SPEED_LOW:
+ reg |= DWC3_DSTS_LOWSPEED;
+ break;
+ case USB_SPEED_FULL:
+ reg |= DWC3_DSTS_FULLSPEED1;
+ break;
+ case USB_SPEED_HIGH:
+ reg |= DWC3_DSTS_HIGHSPEED;
+ break;
+ case USB_SPEED_SUPER: /* FALLTHROUGH */
+ case USB_SPEED_UNKNOWN: /* FALTHROUGH */
+ default:
+ reg |= DWC3_DSTS_SUPERSPEED;
+ }
+ }
+ dwc3_writel(dwc->regs, DWC3_DCFG, reg);
+
+ /* Start with SuperSpeed Default */
+ dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
+
+ dep = dwc->eps[0];
+ ret = __dwc3_gadget_ep_enable(dep, false, false);
+ if (ret) {
+ dev_err(dwc->dev, "failed to enable %s\n", dep->name);
+ goto err0;
+ }
+
+ dep = dwc->eps[1];
+ ret = __dwc3_gadget_ep_enable(dep, false, false);
+ if (ret) {
+ dev_err(dwc->dev, "failed to enable %s\n", dep->name);
+ goto err1;
+ }
+
+ /* begin to receive SETUP packets */
+ dwc->ep0state = EP0_SETUP_PHASE;
+ dwc3_ep0_out_start(dwc);
+
+ return 0;
+
+err1:
+ __dwc3_gadget_ep_disable(dwc->eps[0]);
+
+err0:
+ return ret;
+}
+
+static void dwc3_gadget_enable_irq(struct dwc3 *dwc)
+{
+ u32 reg;
+
+ /* Enable all but Start and End of Frame IRQs */
+ reg = (DWC3_DEVTEN_VNDRDEVTSTRCVEDEN |
+ DWC3_DEVTEN_EVNTOVERFLOWEN |
+ DWC3_DEVTEN_CMDCMPLTEN |
+ DWC3_DEVTEN_ERRTICERREN |
+ DWC3_DEVTEN_WKUPEVTEN |
+ DWC3_DEVTEN_U3L2_SUSPEN |
+ DWC3_DEVTEN_ULSTCNGEN |
+ DWC3_DEVTEN_CONNECTDONEEN |
+ DWC3_DEVTEN_USBRSTEN |
+ DWC3_DEVTEN_DISCONNEVTEN);
+
+ dwc3_writel(dwc->regs, DWC3_DEVTEN, reg);
+}
+
+static void dwc3_gadget_disable_irq(struct dwc3 *dwc)
+{
+ /* mask all interrupts */
+ dwc3_writel(dwc->regs, DWC3_DEVTEN, 0x00);
+}
+
static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend)
{
u32 reg;
- u32 timeout = 500;
+ u32 timeout = 1000;
+ int retries = 1000;
+ int ret = 0;
if (pm_runtime_suspended(dwc->dev))
return 0;
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
if (is_on) {
+ dwc3_event_buffers_setup(dwc);
+ ret = dwc3_udc_init(dwc);
+ if (ret) {
+ dev_err(dwc->dev, "failed to reinitialize udc\n");
+ return ret;
+ }
+
+ dwc3_gadget_enable_irq(dwc);
+
if (dwc->revision <= DWC3_REVISION_187A) {
reg &= ~DWC3_DCTL_TRGTULST_MASK;
reg |= DWC3_DCTL_TRGTULST_RX_DET;
if (dwc->revision >= DWC3_REVISION_194A)
reg &= ~DWC3_DCTL_KEEP_CONNECT;
+
reg |= DWC3_DCTL_RUN_STOP;
if (dwc->has_hibernation)
dwc->pullups_connected = true;
} else {
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
reg &= ~DWC3_DCTL_RUN_STOP;
if (dwc->has_hibernation && !suspend)
do {
reg = dwc3_readl(dwc->regs, DWC3_DSTS);
- reg &= DWC3_DSTS_DEVCTRLHLT;
- } while (--timeout && !(!is_on ^ !reg));
+ if (is_on) {
+ if (!(reg & DWC3_DSTS_DEVCTRLHLT))
+ break;
+ } else {
+ if (reg & DWC3_DSTS_DEVCTRLHLT)
+ break;
+ }
+ timeout--;
+ if (!timeout) {
+ if (is_on) {
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+ reg |= DWC3_DCTL_CSFTRST;
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+ dev_err(dwc->dev,
+ "gadget run/stop timeout, DCTL : 0x%x\n",
+ reg);
+ reg = dwc3_readl(dwc->regs, DWC3_DSTS);
+ dev_err(dwc->dev,
+ "gadget run/stop timeout, DSTS : 0x%x\n",
+ reg);
+ do {
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+ if (!(reg & DWC3_DCTL_CSFTRST)) {
+ dev_info(dwc->dev,
+ "gadget run/stop DCTL softreset, DCTL : 0x%x\n",
+ reg);
+ goto good;
+ }
+ udelay(1);
+
+ } while (--retries);
+
+ return -ETIMEDOUT;
+ }
- if (!timeout)
- return -ETIMEDOUT;
+ /* Do nothing in DCTL stop timeout */
+ dev_err(dwc->dev,
+ "gadget DCTL stop timeout, DSTS: 0x%x\n",
+ reg);
+ goto good;
+ }
+ udelay(1);
+ } while (1);
+good:
+ return ret;
+}
- return 0;
+static int dwc3_gadget_run_stop_vbus(struct dwc3 *dwc, int is_on, int suspend)
+{
+ u32 reg;
+ u32 timeout = 1000;
+ int retries = 1000;
+ int ret = 0;
+
+ if (pm_runtime_suspended(dwc->dev))
+ return 0;
+
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+ if (is_on) {
+ dwc3_event_buffers_setup(dwc);
+ ret = dwc3_udc_init(dwc);
+ if (ret) {
+ dev_err(dwc->dev, "failed to reinitialize udc\n");
+ return ret;
+ }
+
+ dwc3_gadget_enable_irq(dwc);
+
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+
+ if (dwc->revision <= DWC3_REVISION_187A) {
+ reg &= ~DWC3_DCTL_TRGTULST_MASK;
+ reg |= DWC3_DCTL_TRGTULST_RX_DET;
+ }
+
+ if (dwc->revision >= DWC3_REVISION_194A)
+ reg &= ~DWC3_DCTL_KEEP_CONNECT;
+
+ reg |= DWC3_DCTL_RUN_STOP;
+
+ if (dwc->has_hibernation)
+ reg |= DWC3_DCTL_KEEP_CONNECT;
+
+ dwc->pullups_connected = true;
+ } else {
+ dwc3_gadget_disable_irq(dwc);
+ dwc3_event_buffers_cleanup(dwc);
+ __dwc3_gadget_ep_disable(dwc->eps[1]);
+ __dwc3_gadget_ep_disable(dwc->eps[0]);
+
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+ reg &= ~DWC3_DCTL_RUN_STOP;
+
+ if (dwc->has_hibernation && !suspend)
+ reg &= ~DWC3_DCTL_KEEP_CONNECT;
+
+ dwc->pullups_connected = false;
+ }
+
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+
+ do {
+ reg = dwc3_readl(dwc->regs, DWC3_DSTS);
+ if (is_on) {
+ if (!(reg & DWC3_DSTS_DEVCTRLHLT))
+ break;
+ } else {
+ if (reg & DWC3_DSTS_DEVCTRLHLT)
+ break;
+ }
+ timeout--;
+ if (!timeout) {
+ if (is_on) {
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+ reg |= DWC3_DCTL_CSFTRST;
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+ dev_err(dwc->dev,
+ "gadget run/stop timeout, DCTL : 0x%x\n",
+ reg);
+ reg = dwc3_readl(dwc->regs, DWC3_DSTS);
+ dev_err(dwc->dev,
+ "gadget run/stop timeout, DSTS : 0x%x\n",
+ reg);
+ do {
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+ if (!(reg & DWC3_DCTL_CSFTRST)) {
+ dev_info(dwc->dev,
+ "gadget run/stop DCTL softreset, DCTL : 0x%x\n",
+ reg);
+ goto good;
+ }
+ udelay(1);
+
+ } while (--retries);
+
+ return -ETIMEDOUT;
+ }
+
+ /* Do nothing in DCTL stop timeout */
+ dev_err(dwc->dev,
+ "gadget DCTL stop timeout, DSTS: 0x%x\n",
+ reg);
+ goto good;
+ }
+ udelay(1);
+ } while (1);
+good:
+ return ret;
}
-static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on)
+static int dwc3_gadget_vbus_session(struct usb_gadget *g, int is_active)
{
- struct dwc3 *dwc = gadget_to_dwc(g);
- unsigned long flags;
- int ret;
+ struct dwc3 *dwc = gadget_to_dwc(g);
+ unsigned long flags;
+ int ret = 0;
- is_on = !!is_on;
+ if (!dwc->dotg)
+ return -EPERM;
+
+ is_active = !!is_active;
+
+ spin_lock_irqsave(&dwc->lock, flags);
+
+ /* Mark that the vbus was powered */
+ dwc->vbus_session = is_active;
/*
- * Per databook, when we want to stop the gadget, if a control transfer
- * is still in process, complete it and get the core into setup phase.
+ * Check if upper level usb_gadget_driver was already registerd with
+ * this udc controller driver (if dwc3_gadget_start was called)
*/
- if (!is_on && dwc->ep0state != EP0_SETUP_PHASE) {
- reinit_completion(&dwc->ep0_in_setup);
-
- ret = wait_for_completion_timeout(&dwc->ep0_in_setup,
- msecs_to_jiffies(DWC3_PULL_UP_TIMEOUT));
- if (ret == 0) {
- dev_err(dwc->dev, "timed out waiting for SETUP phase\n");
- return -ETIMEDOUT;
+ if (dwc->gadget_driver && dwc->softconnect) {
+ if (dwc->vbus_session) {
+ /*
+ * Both vbus was activated by otg and pullup was
+ * signaled by the gadget driver.
+ */
+ ret = dwc3_gadget_run_stop_vbus(dwc, 1, false);
+ } else {
+ ret = dwc3_gadget_run_stop_vbus(dwc, 0, false);
}
}
- spin_lock_irqsave(&dwc->lock, flags);
- ret = dwc3_gadget_run_stop(dwc, is_on, false);
spin_unlock_irqrestore(&dwc->lock, flags);
+ if (ret)
+ dev_err(dwc->dev, "dwc3 gadget run/stop error:%d\n", ret);
+
return ret;
}
-static void dwc3_gadget_enable_irq(struct dwc3 *dwc)
+static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on)
{
- u32 reg;
+ struct dwc3 *dwc = gadget_to_dwc(g);
+ unsigned long flags;
+ struct usb_otg *otg = &(dwc->dotg->otg);
+ int ret;
- /* Enable all but Start and End of Frame IRQs */
- reg = (DWC3_DEVTEN_VNDRDEVTSTRCVEDEN |
- DWC3_DEVTEN_EVNTOVERFLOWEN |
- DWC3_DEVTEN_CMDCMPLTEN |
- DWC3_DEVTEN_ERRTICERREN |
- DWC3_DEVTEN_WKUPEVTEN |
- DWC3_DEVTEN_CONNECTDONEEN |
- DWC3_DEVTEN_USBRSTEN |
- DWC3_DEVTEN_DISCONNEVTEN);
+ is_on = !!is_on;
- if (dwc->revision < DWC3_REVISION_250A)
- reg |= DWC3_DEVTEN_ULSTCNGEN;
+ spin_lock_irqsave(&dwc->lock, flags);
- dwc3_writel(dwc->regs, DWC3_DEVTEN, reg);
-}
+ dev_info(dwc->dev, "%s: pullup = %d, vbus = %d\n",
+ __func__, is_on, dwc->vbus_session);
+ if (is_on == dwc->softconnect) {
+ dev_info(dwc->dev, "pullup is already %s\n",
+ is_on ? "on" : "off");
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ return 0;
+ }
-static void dwc3_gadget_disable_irq(struct dwc3 *dwc)
-{
- /* mask all interrupts */
- dwc3_writel(dwc->regs, DWC3_DEVTEN, 0x00);
+ dwc->softconnect = is_on;
+
+ if (dwc->dotg && !dwc->vbus_session) {
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ /* Need to wait for vbus_session(on) from otg driver */
+ return 0;
+ }
+
+ if (is_on) {
+ dwc3_soft_reset(dwc);
+
+ phy_tune(dwc->usb2_generic_phy, otg->state);
+ phy_tune(dwc->usb3_generic_phy, otg->state);
+
+ /**
+ * In case there is not a resistance to detect VBUS,
+ * DP/DM controls by S/W are needed at this point.
+ */
+ if (dwc->is_not_vbus_pad) {
+ phy_set(dwc->usb2_generic_phy, SET_DPPULLUP_DISABLE, NULL);
+ phy_set(dwc->usb3_generic_phy, SET_DPPULLUP_DISABLE, NULL);
+ }
+ }
+
+ ret = dwc3_gadget_run_stop(dwc, is_on, false);
+ spin_unlock_irqrestore(&dwc->lock, flags);
+
+ return ret;
}
static irqreturn_t dwc3_interrupt(int irq, void *_dwc);
+#if IS_ENABLED(DWC3_GADGET_IRQ_ORG)
static irqreturn_t dwc3_thread_interrupt(int irq, void *_dwc);
+#endif
/**
* dwc3_gadget_setup_nump - calculate and initialize NUMP field of %DWC3_DCFG
int ret = 0;
u32 reg;
+ dwc3_event_buffers_setup(dwc);
+
/*
* Use IMOD if enabled via dwc->imod_interval. Otherwise, if
* the core supports IMOD, disable it.
int irq;
irq = dwc->irq_gadget;
+#if IS_ENABLED(DWC3_GADGET_IRQ_ORG)
ret = request_threaded_irq(irq, dwc3_interrupt, dwc3_thread_interrupt,
IRQF_SHARED, "dwc3", dwc->ev_buf);
+#else
+ ret = devm_request_irq(dwc->dev, irq, dwc3_interrupt,
+ IRQF_SHARED, "dwc3", dwc->ev_buf);
+#endif
if (ret) {
dev_err(dwc->dev, "failed to request irq #%d --> %d\n",
irq, ret);
dwc->gadget_driver = driver;
- if (pm_runtime_active(dwc->dev))
- __dwc3_gadget_start(dwc);
+ /* Modifying Kernel 4.14 change -
+ * executing ep_enable here makes conflict
+ * with dwc3_udc_init in dwc3_gadget_run_stop
+ * if (pm_runtime_active(dwc->dev))
+ * __dwc3_gadget_start(dwc);
+ */
spin_unlock_irqrestore(&dwc->lock, flags);
static void __dwc3_gadget_stop(struct dwc3 *dwc)
{
dwc3_gadget_disable_irq(dwc);
- __dwc3_gadget_ep_disable(dwc->eps[0]);
__dwc3_gadget_ep_disable(dwc->eps[1]);
+ __dwc3_gadget_ep_disable(dwc->eps[0]);
}
static int dwc3_gadget_stop(struct usb_gadget *g)
{
struct dwc3 *dwc = gadget_to_dwc(g);
unsigned long flags;
- int epnum;
+ /*int epnum;*/
spin_lock_irqsave(&dwc->lock, flags);
__dwc3_gadget_stop(dwc);
+#if defined(WAIT_EP_CMD_CMPLT_ENABLED)
for (epnum = 2; epnum < DWC3_ENDPOINTS_NUM; epnum++) {
struct dwc3_ep *dep = dwc->eps[epnum];
!(dep->flags & DWC3_EP_END_TRANSFER_PENDING),
dwc->lock);
}
+#endif
out:
dwc->gadget_driver = NULL;
.get_frame = dwc3_gadget_get_frame,
.wakeup = dwc3_gadget_wakeup,
.set_selfpowered = dwc3_gadget_set_selfpowered,
+ .vbus_session = dwc3_gadget_vbus_session,
.pullup = dwc3_gadget_pullup,
.udc_start = dwc3_gadget_start,
.udc_stop = dwc3_gadget_stop,
int clean_busy;
u32 is_xfer_complete;
+ if (dep->endpoint.desc == NULL)
+ return;
+
is_xfer_complete = (event->endpoint_event == DWC3_DEPEVT_XFERCOMPLETE);
if (event->status & DEPEVT_STATUS_BUSERR)
cmd |= DWC3_DEPCMD_PARAM(dep->resource_index);
memset(¶ms, 0, sizeof(params));
ret = dwc3_send_gadget_ep_cmd(dep, cmd, ¶ms);
- WARN_ON_ONCE(ret);
+ /* WA for MTP failure, Ignore CMDACT timeout such as Kernel 4.4 */
+ /*WARN_ON_ONCE(ret);*/
dep->resource_index = 0;
dep->flags &= ~DWC3_EP_BUSY;
dwc->setup_packet_pending = false;
usb_gadget_set_state(&dwc->gadget, USB_STATE_NOTATTACHED);
+ complete(&dwc->disconnect);
dwc->connected = false;
}
reg = dwc3_readl(dwc->regs, DWC3_DCFG);
reg &= ~(DWC3_DCFG_DEVADDR_MASK);
dwc3_writel(dwc->regs, DWC3_DCFG, reg);
+
+ if (dwc->is_not_vbus_pad) {
+ phy_set(dwc->usb2_generic_phy, SET_DPPULLUP_ENABLE, NULL);
+ phy_set(dwc->usb3_generic_phy, SET_DPPULLUP_ENABLE, NULL);
+ }
}
+/* Remove Warning - Check later!
+static void dwc3_update_ram_clk_sel(struct dwc3 *dwc, u32 speed)
+{
+ u32 reg;
+
+ reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+ reg |= DWC3_GCTL_RAMCLKSEL(DWC3_GCTL_CLK_MASK);
+ dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+}
+*/
+
static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc)
{
struct dwc3_ep *dep;
*
* In both cases reset values should be sufficient.
*/
+
+ /**
+ * In case there is not a resistance to detect VBUS,
+ * DP/DM controls by S/W are needed at this point.
+ */
+ if (dwc->is_not_vbus_pad && !(speed & DWC3_DCFG_FULLSPEED1)) {
+ phy_set(dwc->usb2_generic_phy, SET_DPPULLUP_DISABLE, NULL);
+ phy_set(dwc->usb3_generic_phy, SET_DPPULLUP_DISABLE, NULL);
+ }
}
static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc)
{
+ if (dwc->is_not_vbus_pad) {
+ phy_set(dwc->usb2_generic_phy, SET_DPPULLUP_DISABLE, NULL);
+ phy_set(dwc->usb3_generic_phy, SET_DPPULLUP_DISABLE, NULL);
+ }
+
/*
* TODO take core out of low power mode when that's
* implemented.
}
switch (next) {
+ case DWC3_LINK_STATE_U0:
+ if (dwc->is_not_vbus_pad) {
+ phy_set(dwc->usb2_generic_phy, SET_DPPULLUP_ENABLE, NULL);
+ phy_set(dwc->usb3_generic_phy, SET_DPPULLUP_ENABLE, NULL);
+ }
+ break;
case DWC3_LINK_STATE_U1:
if (dwc->speed == USB_SPEED_SUPER)
dwc3_suspend_gadget(dwc);
case DWC3_LINK_STATE_U3:
dwc3_suspend_gadget(dwc);
break;
+ case DWC3_LINK_STATE_RX_DET: /* Early Suspend in HS */
+ if (dwc->is_not_vbus_pad) {
+ phy_set(dwc->usb2_generic_phy, SET_DPPULLUP_ENABLE, NULL);
+ phy_set(dwc->usb3_generic_phy, SET_DPPULLUP_ENABLE, NULL);
+ }
+ break;
case DWC3_LINK_STATE_RESUME:
+ if (dwc->is_not_vbus_pad) {
+ phy_set(dwc->usb2_generic_phy, SET_DPPULLUP_ENABLE, NULL);
+ phy_set(dwc->usb3_generic_phy, SET_DPPULLUP_ENABLE, NULL);
+ }
dwc3_resume_gadget(dwc);
break;
default:
{
enum dwc3_link_state next = evtinfo & DWC3_LINK_STATE_MASK;
+ /* WA for Lhotse U3 */
+ if (dwc->gadget.speed >= USB_SPEED_SUPER)
+ phy_ilbk(dwc->usb3_generic_phy);
+
if (dwc->link_state != next && next == DWC3_LINK_STATE_U3)
dwc3_suspend_gadget(dwc);
int left;
u32 reg;
+#if IS_ENABLED(DWC3_GADGET_IRQ_ORG)
left = evt->count;
if (!(evt->flags & DWC3_EVENT_PENDING))
return IRQ_NONE;
+#else
+ reg = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(0));
+ reg &= DWC3_GEVNTCOUNT_MASK;
+ evt->count = reg;
+ left = evt->count;
+#endif
while (left > 0) {
union dwc3_event event;
- event.raw = *(u32 *) (evt->cache + evt->lpos);
+ event.raw = *(u32 *) (evt->buf + evt->lpos);
dwc3_process_event_entry(dwc, &event);
* boundary so I worry about that once we try to handle
* that.
*/
- evt->lpos = (evt->lpos + 4) % evt->length;
+ evt->lpos = (evt->lpos + 4) % DWC3_EVENT_BUFFERS_SIZE;
left -= 4;
+
+ dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), 4);
}
evt->count = 0;
+#if IS_ENABLED(DWC3_GADGET_IRQ_ORG)
evt->flags &= ~DWC3_EVENT_PENDING;
+#endif
ret = IRQ_HANDLED;
+#if IS_ENABLED(DWC3_GADGET_IRQ_ORG)
/* Unmask interrupt */
reg = dwc3_readl(dwc->regs, DWC3_GEVNTSIZ(0));
reg &= ~DWC3_GEVNTSIZ_INTMASK;
dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(0), reg);
+#endif
if (dwc->imod_interval) {
dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), DWC3_GEVNTCOUNT_EHB);
return ret;
}
+#if IS_ENABLED(DWC3_GADGET_IRQ_ORG)
static irqreturn_t dwc3_thread_interrupt(int irq, void *_evt)
{
struct dwc3_event_buffer *evt = _evt;
return IRQ_WAKE_THREAD;
}
+#endif
static irqreturn_t dwc3_interrupt(int irq, void *_evt)
{
struct dwc3_event_buffer *evt = _evt;
+ irqreturn_t ret = IRQ_NONE;
+ struct dwc3 *dwc = evt->dwc;
+
+ spin_lock(&dwc->lock);
+
+#if IS_ENABLED(DWC3_GADGET_IRQ_ORG)
+ irqreturn_t status;
- return dwc3_check_event_buf(evt);
+ status = dwc3_check_event_buf(evt);
+ if (status == IRQ_WAKE_THREAD)
+ ret = status;
+#else
+ ret |= dwc3_process_event_buf(evt);
+#endif
+
+ spin_unlock(&dwc->lock);
+
+ return ret;
}
static int dwc3_gadget_get_irq(struct dwc3 *dwc)
goto err4;
}
+ if (dwc->dotg) {
+ ret = otg_set_peripheral(&dwc->dotg->otg, &dwc->gadget);
+ if (ret) {
+ dev_err(dwc->dev, "failed to set otg peripheral\n");
+ goto err4;
+ }
+ }
+
return 0;
err4:
+ usb_del_gadget_udc(&dwc->gadget);
dwc3_gadget_free_endpoints(dwc);
err3:
void dwc3_gadget_exit(struct dwc3 *dwc)
{
+ if (dwc->dotg)
+ otg_set_peripheral(&dwc->dotg->otg, NULL);
+
usb_del_gadget_udc(&dwc->gadget);
dwc3_gadget_free_endpoints(dwc);
dma_free_coherent(dwc->sysdev, DWC3_BOUNCE_SIZE, dwc->bounce,
return ret;
}
+void dwc3_gadget_disconnect_proc(struct dwc3 *dwc)
+{
+ int reg;
+
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+ reg &= ~DWC3_DCTL_INITU1ENA;
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+
+ reg &= ~DWC3_DCTL_INITU2ENA;
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+
+ if (dwc->gadget_driver && dwc->gadget_driver->disconnect)
+ dwc->gadget_driver->disconnect(&dwc->gadget);
+
+ dwc->start_config_issued = false;
+
+ dwc->gadget.speed = USB_SPEED_UNKNOWN;
+ dwc->setup_packet_pending = false;
+
+ complete(&dwc->disconnect);
+}
+
void dwc3_gadget_process_pending_events(struct dwc3 *dwc)
{
if (dwc->pending_events) {
return DWC3_DEPCMD_GET_RSC_IDX(res_id);
}
+/**
+ * ISR for DWC3 gadget was changed because of RNDIS performance.
+ * However, previous ISR code was not removed to track the history.
+ */
+#undef DWC3_GADGET_IRQ_ORG
+
#endif /* __DRIVERS_USB_DWC3_GADGET_H */
*/
#include <linux/platform_device.h>
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+#include <linux/usb/exynos_usb_audio.h>
+#endif
#include "core.h"
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+struct host_data xhci_data;
+#endif
+
static int dwc3_host_get_irq(struct dwc3 *dwc)
{
struct platform_device *dwc3_pdev = to_platform_device(dwc->dev);
struct resource *res;
struct platform_device *dwc3_pdev = to_platform_device(dwc->dev);
int prop_idx = 0;
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+ dma_addr_t dma;
+#endif
irq = dwc3_host_get_irq(dwc);
if (irq < 0)
}
xhci->dev.parent = dwc->dev;
+ //xhci->dev.dma_mask = dwc->dev->dma_mask;
+ //xhci->dev.dma_parms = dwc->dev->dma_parms;
+ //xhci->dev.archdata.dma_ops = dwc->dev->archdata.dma_ops;
+
+ dma_set_coherent_mask(&xhci->dev, dwc->dev->coherent_dma_mask);
dwc->xhci = xhci;
phy_create_lookup(dwc->usb3_generic_phy, "usb3-phy",
dev_name(dwc->dev));
- ret = platform_device_add(xhci);
- if (ret) {
- dev_err(dwc->dev, "failed to register xHCI device\n");
- goto err2;
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+ xhci_data.in_data_addr = dma_alloc_coherent(dwc->dev, (PAGE_SIZE * 256), &dma,
+ GFP_KERNEL);
+ xhci_data.in_data_dma = dma;
+ dev_info(dwc->dev, "// Data address = 0x%llx (DMA), %p (virt)",
+ (unsigned long long)xhci_data.in_data_dma, xhci_data.in_data_addr);
+#endif
+ if (!dwc->dotg) {
+ ret = platform_device_add(xhci);
+ if (ret) {
+ dev_err(dwc->dev, "failed to register xHCI device\n");
+ goto err2;
+ }
}
return 0;
+
err2:
phy_remove_lookup(dwc->usb2_generic_phy, "usb2-phy",
dev_name(dwc->dev));
dev_name(dwc->dev));
phy_remove_lookup(dwc->usb3_generic_phy, "usb3-phy",
dev_name(dwc->dev));
- platform_device_unregister(dwc->xhci);
+ if (!dwc->dotg)
+ platform_device_unregister(dwc->xhci);
}
config USB_F_TCM
tristate
+config USB_F_MTP
+ tristate
+
+config USB_F_PTP
+ tristate
+
config USB_F_AUDIO_SRC
tristate
implemented in kernel space (for instance Ethernet, serial or
mass storage) and other are implemented in user space.
+config USB_CONFIGFS_F_DM
+ boolean "DM gadget"
+ depends on USB_CONFIGFS
+ select USB_F_DM
+ help
+ USB gadget DM(Diagnosis Monitor) support
+
+config USB_CONFIGFS_F_ADB
+ boolean "ADB gadget"
+ depends on USB_CONFIGFS
+ select USB_F_ADB
+ help
+ USB gadget ADB support
+
+config USB_CONFIGFS_F_MTP
+ boolean "MTP gadget"
+ depends on USB_CONFIGFS
+ select USB_F_MTP
+ help
+ USB gadget MTP support
+
+config USB_CONFIGFS_F_PTP
+ boolean "PTP gadget"
+ depends on USB_CONFIGFS && USB_CONFIGFS_F_MTP
+ select USB_F_PTP
+ help
+ USB gadget PTP support
+
config USB_CONFIGFS_F_ACC
boolean "Accessory gadget"
depends on USB_CONFIGFS
#include "configfs.h"
#include "u_f.h"
#include "u_os_desc.h"
+#include <linux/soc/samsung/exynos-soc.h>
#ifdef CONFIG_USB_CONFIGFS_UEVENT
#include <linux/platform_device.h>
EXPORT_SYMBOL_GPL(create_function_device);
#endif
+#ifdef CONFIG_USB_TYPEC_MANAGER_NOTIFIER
+void set_usb_enumeration_state(bool state);
+void set_usb_enable_state(void);
+#endif
+
+#define CHIPID_SIZE (16)
+
int check_user_usb_string(const char *name,
struct usb_gadget_strings *stringtab_dev)
{
char b_vendor_code;
char qw_sign[OS_STRING_QW_SIGN_LEN];
#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ bool enabled;
bool connected;
bool sw_connected;
struct work_struct work;
struct device *dev;
+ struct list_head linked_func;
#endif
};
return 0;
}
+static int set_alt_serialnumber(struct gadget_strings *gs)
+{
+ char *str;
+ int ret = -ENOMEM;
+
+ str = kmalloc(CHIPID_SIZE + 1, GFP_KERNEL);
+ if (!str) {
+ pr_err("%s: failed to alloc for string\n", __func__);
+ return ret;
+ }
+
+ snprintf(str, CHIPID_SIZE + 1, "%016lx", (long)exynos_soc_info.unique_id);
+ if (usb_string_copy(str, &gs->serialnumber))
+ pr_err("%s: failed to copy alternative string\n", __func__);
+ else
+ ret = 0;
+
+ kfree(str);
+ return ret;
+}
+
#define GI_DEVICE_DESC_SIMPLE_R_u8(__name) \
static ssize_t gadget_dev_desc_##__name##_show(struct config_item *item, \
char *page) \
char *name;
int ret;
+ pr_info("%s: +++\n", __func__);
+
name = kstrdup(page, GFP_KERNEL);
if (!name)
return -ENOMEM;
goto out;
}
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ list_add_tail(&f->list, &gi->linked_func);
+#else
/* stash the function until we bind it to the gadget */
list_add_tail(&f->list, &cfg->func_list);
+#endif
ret = 0;
out:
mutex_unlock(&gi->lock);
list_move_tail(&f->list, &cfg->func_list);
if (f->unbind) {
- dev_dbg(&gi->cdev.gadget->dev,
+ dev_err(&gi->cdev.gadget->dev,
"unbind function '%s'/%p\n",
f->name, f);
f->unbind(c, f);
gs->strings[USB_GADGET_MANUFACTURER_IDX].s =
gs->manufacturer;
gs->strings[USB_GADGET_PRODUCT_IDX].s = gs->product;
+ if (gs->serialnumber && !set_alt_serialnumber(gs))
+ pr_info("usb: serial number: %s\n",
+ gs->serialnumber);
gs->strings[USB_GADGET_SERIAL_IDX].s = gs->serialnumber;
i++;
}
KOBJ_CHANGE, connected);
pr_info("%s: sent uevent %s\n", __func__, connected[0]);
uevent_sent = true;
+#ifdef CONFIG_USB_TYPEC_MANAGER_NOTIFIER
+ set_usb_enumeration_state(true);
+#endif
}
if (status[1]) {
struct gadget_info *gi = container_of(cdev, struct gadget_info, cdev);
int value = -EOPNOTSUPP;
struct usb_function_instance *fi;
+ struct usb_configuration *configuration;
+ struct usb_function *f;
spin_lock_irqsave(&cdev->lock, flags);
if (!gi->connected) {
break;
}
}
+ list_for_each_entry(configuration, &cdev->configs, list) {
+ list_for_each_entry(f, &configuration->functions, list) {
+ if (f != NULL && f->ctrlrequest != NULL) {
+ value = f->ctrlrequest(f, c);
+ if (value >= 0)
+ break;
+ }
+ }
+ }
+
+/* //todo
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ if (value < 0)
+ value = terminal_ctrl_request(cdev, c);
+#endif
+*/
#ifdef CONFIG_USB_CONFIGFS_F_ACC
if (value < 0)
value = composite_setup(gadget, c);
spin_lock_irqsave(&cdev->lock, flags);
+
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ if (c->bRequest == USB_REQ_SET_CONFIGURATION &&
+ cdev->mute_switch == true)
+ cdev->mute_switch = false;
+#endif
+
if (c->bRequest == USB_REQ_SET_CONFIGURATION &&
cdev->config) {
schedule_work(&gi->work);
acc_disconnect();
#endif
gi->connected = 0;
+
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ printk(KERN_DEBUG "usb: %s con(%d), sw(%d)\n",
+ __func__, gi->connected, gi->sw_connected);
+ /* avoid sending a disconnect switch event
+ * until after we disconnect.
+ */
+ if (cdev->mute_switch) {
+ gi->sw_connected = gi->connected;
+ printk(KERN_DEBUG"usb: %s mute_switch con(%d) sw(%d)\n",
+ __func__, gi->connected, gi->sw_connected);
+ } else {
+
+ // set_ncm_ready(false);
+ if (cdev->force_disconnect) {
+ gi->sw_connected = 1;
+ printk(KERN_DEBUG"usb: %s force_disconnect\n",
+ __func__);
+ cdev->force_disconnect = 0;
+ }
+ printk(KERN_DEBUG"usb: %s schedule_work con(%d) sw(%d)\n",
+ __func__, gi->connected, gi->sw_connected);
+ schedule_work(&gi->work);
+ }
+ composite_disconnect(gadget);
+#else
schedule_work(&gi->work);
composite_disconnect(gadget);
+#endif
}
#endif
};
#ifdef CONFIG_USB_CONFIGFS_UEVENT
+static ssize_t
+functions_show(struct device *pdev, struct device_attribute *attr, char *buf)
+{
+ struct gadget_info *dev = dev_get_drvdata(pdev);
+ struct usb_composite_dev *cdev;
+ struct usb_configuration *c;
+ struct usb_function *f;
+ char *buff = buf;
+
+ cdev = &dev->cdev;
+ if (!cdev)
+ return -ENODEV;
+
+ mutex_lock(&dev->lock);
+
+ list_for_each_entry(c, &cdev->configs, list) {
+ list_for_each_entry(f, &c->functions, list) {
+ buff += sprintf(buff, "%s,", f->name);
+ }
+ }
+
+ mutex_unlock(&dev->lock);
+
+ if (buff != buf)
+ *(buff-1) = '\n';
+
+ return buff - buf;
+}
+
+static ssize_t
+functions_store(struct device *pdev, struct device_attribute *attr,
+ const char *buff, size_t size)
+{
+ struct gadget_info *dev = dev_get_drvdata(pdev);
+ struct usb_composite_dev *cdev;
+ struct usb_configuration *c;
+ struct config_usb_cfg *cfg;
+ struct usb_function *f, *tmp;
+ char *name;
+ char buf[256], *b;
+
+ cdev = &dev->cdev;
+ if (!cdev)
+ return -ENODEV;
+
+ mutex_lock(&dev->lock);
+
+ if (dev->enabled) {
+ mutex_unlock(&dev->lock);
+ pr_err("%s: gadget is enabled\n", __func__);
+ return -EBUSY;
+ }
+
+ strlcpy(buf, buff, sizeof(buf));
+ b = strim(buf);
+
+ printk("Function stored : %s\n", b);
+ while (b) {
+ name = strsep(&b, ",");
+ if (!name)
+ continue;
+
+ list_for_each_entry(c, &cdev->configs, list) {
+ cfg = container_of(c, struct config_usb_cfg, c);
+ list_for_each_entry_safe(f, tmp, &dev->linked_func, list) {
+ if (!strcmp(f->name, name)) {
+ pr_err("%s: enable device[%s]\n", __func__, name);
+ list_move_tail(&f->list, &cfg->func_list);
+ }
+ }
+ }
+ }
+
+ mutex_unlock(&dev->lock);
+
+ return size;
+}
+
+static ssize_t enable_show(struct device *pdev, struct device_attribute *attr,
+ char *buf)
+{
+ struct gadget_info *dev = dev_get_drvdata(pdev);
+ return sprintf(buf, "%d\n", dev->enabled);
+}
+
+static ssize_t enable_store(struct device *pdev, struct device_attribute *attr,
+ const char *buff, size_t size)
+{
+ struct gadget_info *dev = dev_get_drvdata(pdev);
+ struct usb_composite_dev *cdev;
+ struct usb_gadget *gadget;
+ struct usb_configuration *c;
+ struct config_usb_cfg *cfg;
+ struct usb_function *f, *tmp;
+ int enabled = 0;
+
+ if (!dev)
+ return -ENODEV;
+
+ cdev = &dev->cdev;
+ if (!cdev)
+ return -ENODEV;
+
+ gadget = cdev->gadget;
+ mutex_lock(&dev->lock);
+
+ sscanf(buff, "%d", &enabled);
+ if (enabled && !dev->enabled) {
+ pr_info("%s: Connect gadget: enabled=%d, dev->enabled=%d\n",
+ __func__, enabled, dev->enabled);
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ cdev->next_string_id = 4; //composite string index
+#else
+ cdev->next_string_id = 0;
+#endif
+ if (!gadget) {
+ pr_info("%s: Gadget is NULL: %p\n", __func__, gadget);
+ mutex_unlock(&dev->lock);
+ return -ENODEV;
+ }
+
+ usb_gadget_connect(gadget);
+ dev->enabled = true;
+#ifdef CONFIG_USB_TYPEC_MANAGER_NOTIFIER
+ set_usb_enable_state();
+#endif
+ } else if (!enabled && dev->enabled) {
+ pr_info("%s: Disconnect gadget: enabled=%d, dev->enabled=%d\n",
+ __func__, enabled, dev->enabled);
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ /* avoid sending a disconnect switch event
+ * until after we disconnect.
+ */
+ cdev->mute_switch = true;
+#endif
+ unregister_gadget(dev);
+ list_for_each_entry(c, &cdev->configs, list) {
+ cfg = container_of(c, struct config_usb_cfg, c);
+ list_for_each_entry_safe(f, tmp, &cfg->func_list, list) {
+ list_move_tail(&f->list, &dev->linked_func);
+ }
+ c->next_interface_id = 0;
+ memset(c->interface, 0, sizeof(c->interface));
+ }
+ dev->enabled = false;
+ } else {
+ pr_err("%s: already %s\n", __func__,
+ dev->enabled ? "enabled" : "disabled");
+ }
+
+ mutex_unlock(&dev->lock);
+ return size;
+}
+
static ssize_t state_show(struct device *pdev, struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%s\n", state);
}
+static DEVICE_ATTR(functions, S_IRUGO | S_IWUSR, functions_show,
+ functions_store);
+static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, enable_show, enable_store);
static DEVICE_ATTR(state, S_IRUGO, state_show, NULL);
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+static ssize_t
+bcdUSB_show(struct device *pdev, struct device_attribute *attr, char *buf)
+{
+ struct gadget_info *dev = dev_get_drvdata(pdev);
+
+ return sprintf(buf, "%04x\n", dev->cdev.desc.bcdUSB);
+
+}
+static DEVICE_ATTR(bcdUSB, S_IRUGO, bcdUSB_show, NULL);
+#endif
+
static struct device_attribute *android_usb_attributes[] = {
&dev_attr_state,
+ &dev_attr_enable,
+ &dev_attr_functions,
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ &dev_attr_bcdUSB,
+#endif
NULL
};
mutex_init(&gi->lock);
INIT_LIST_HEAD(&gi->string_list);
INIT_LIST_HEAD(&gi->available_func);
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ INIT_LIST_HEAD(&gi->linked_func);
+#endif
composite_init_dev(&gi->cdev);
gi->cdev.desc.bLength = USB_DT_DEVICE_SIZE;
# USB Functions
usb_f_acm-y := f_acm.o
obj-$(CONFIG_USB_F_ACM) += usb_f_acm.o
+usb_f_uts-y := f_uts.o
+obj-$(CONFIG_USB_F_ACM) += usb_f_uts.o
usb_f_ss_lb-y := f_loopback.o f_sourcesink.o
obj-$(CONFIG_USB_F_SS_LB) += usb_f_ss_lb.o
obj-$(CONFIG_USB_U_SERIAL) += u_serial.o
obj-$(CONFIG_USB_F_HID) += usb_f_hid.o
usb_f_printer-y := f_printer.o
obj-$(CONFIG_USB_F_PRINTER) += usb_f_printer.o
+usb_f_dm-y := f_dm.o
+obj-$(CONFIG_USB_CONFIGFS_F_DM) += usb_f_dm.o
+usb_f_adb-y := f_adb.o
+obj-$(CONFIG_USB_CONFIGFS_F_ADB)+= usb_f_adb.o
usb_f_tcm-y := f_tcm.o
obj-$(CONFIG_USB_F_TCM) += usb_f_tcm.o
+usb_f_mtp-y := f_mtp.o
+obj-$(CONFIG_USB_F_MTP) += usb_f_mtp.o
+usb_f_ptp-y := f_ptp.o
+obj-$(CONFIG_USB_F_PTP) += usb_f_ptp.o
usb_f_audio_source-y := f_audio_source.o
obj-$(CONFIG_USB_F_AUDIO_SRC) += usb_f_audio_source.o
usb_f_accessory-y := f_accessory.o
#include <linux/mmu_context.h>
#include <linux/poll.h>
#include <linux/eventfd.h>
+#include <linux/delay.h>
#include "u_fs.h"
#include "u_f.h"
if (interrupted)
ret = -EINTR;
- else if (io_data->read && ep->status > 0)
+ else if (io_data->read && ep->status > 0) {
+ if (ep->status > 0xFFFFFF) {
+ pr_info("%s abnormal size=%d\n",
+ __func__, (int)ep->status);
+ ret = -ENOMEM;
+ goto error_mutex;
+ }
ret = __ffs_epfile_read_data(epfile, data, ep->status,
&io_data->data);
+ }
else
ret = ep->status;
goto error_mutex;
struct f_fs_opts *ffs_opts =
container_of(f->fi, struct f_fs_opts, func_inst);
int ret;
+ int retries = 500;
ENTER();
*
* Configfs-enabled gadgets however do need ffs_dev_lock.
*/
- if (!ffs_opts->no_configfs)
- ffs_dev_lock();
- ret = ffs_opts->dev->desc_ready ? 0 : -ENODEV;
- func->ffs = ffs_opts->dev->ffs_data;
- if (!ffs_opts->no_configfs)
- ffs_dev_unlock();
+ do {
+ if (!ffs_opts->no_configfs)
+ ffs_dev_lock();
+ ret = ffs_opts->dev->desc_ready ? 0 : -ENODEV;
+ func->ffs = ffs_opts->dev->ffs_data;
+ if (!ffs_opts->no_configfs)
+ ffs_dev_unlock();
+ if (ret)
+ msleep(20);
+ else
+ break;
+ } while (--retries);
+
+ pr_info("ffs_do_functionfs_bind %d %d\n", ret, retries);
+
if (ret)
return ERR_PTR(ret);
if (unlikely(!func))
return ERR_PTR(-ENOMEM);
- func->function.name = "Function FS Gadget";
+ func->function.name = "adb";
func->function.bind = ffs_func_bind;
func->function.unbind = ffs_func_unbind;
mutex_init(&opts->lock);
opts->func_inst.free_func_inst = rndis_free_inst;
- opts->net = gether_setup_default();
+ opts->net = gether_setup_name_default("rndis");
if (IS_ERR(opts->net)) {
struct net_device *net = opts->net;
kfree(opts);
static void rndis_unbind(struct usb_configuration *c, struct usb_function *f)
{
struct f_rndis *rndis = func_to_rndis(f);
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ struct f_rndis_opts *opts;
+ struct usb_composite_dev *cdev = f->config->cdev;
+#endif
kfree(f->os_desc_table);
f->os_desc_n = 0;
kfree(rndis->notify_req->buf);
usb_ep_free_request(rndis->notify, rndis->notify_req);
+
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ opts = container_of(f->fi, struct f_rndis_opts, func_inst);
+ if (!opts->borrowed_net) {
+ if (opts->bound)
+ gether_cleanup(netdev_priv(opts->net));
+ else
+ free_netdev(opts->net);
+ }
+
+ opts->net = gether_setup_name_default("rndis");
+ if (IS_ERR(opts->net)) {
+ ERROR(cdev, "%s: failed to setup ethernet\n", f->name);
+ return;
+ }
+
+ gether_get_host_addr_u8(opts->net, rndis->ethaddr);
+ rndis->port.ioport = netdev_priv(opts->net);
+
+ opts->bound = false;
+#endif
}
static struct usb_function *rndis_alloc(struct usb_function_instance *fi)
coding.bParityType = USB_CDC_NO_PARITY;
coding.bDataBits = USB_CDC_1_STOP_BITS;
- for (port_num = 0; port_num < MAX_U_SERIAL_PORTS; port_num++) {
+ if (*line_num)
+ port_num = *line_num;
+ else
+ port_num = 0;
+
+ for (; port_num < MAX_U_SERIAL_PORTS; port_num++) {
ret = gs_port_alloc(port_num, &coding);
if (ret == -EBUSY)
continue;
return port_status;
}
+ /* W/A for Synopsys HC HSIC port.
+ * Return at this point to prevent port owner change
+ * and retry port reset.
+ */
+ if (ehci->has_synopsys_hsic_bug) {
+ if ((index + 1) == ehci->hsic_ports) {
+ ehci_err (ehci,
+ "Failed to enable HSIC port %d\n",
+ index + 1);
+ return port_status;
+ }
+ }
+
ehci_dbg (ehci, "port %d full speed --> companion\n",
index + 1);
wIndex + 1);
temp |= PORT_OWNER;
} else {
+ ehci_vdbg (ehci, "port %d reset\n", wIndex + 1);
+
+ /* W/A for Synopsys HC HSIC port.
+ * Disable HSIC port to prevent
+ * the port reset failure.
+ */
+ if (ehci->has_synopsys_hsic_bug) {
+ if ((wIndex + 1) == ehci->hsic_ports) {
+ ehci_writel(ehci,
+ temp & ~PORT_PE,
+ status_reg);
+ }
+ }
+
temp |= PORT_RESET;
temp &= ~PORT_PE;
suspended */
unsigned long resuming_ports; /* which ports have
started to resume */
+ unsigned long hsic_ports; /* which ports are
+ used for HSIC */
/* per-HC memory pools (could be per-bus, but ...) */
struct dma_pool *qh_pool; /* qh per active urb */
unsigned amd_pll_fix:1;
unsigned use_dummy_qh:1; /* AMD Frame List table quirk*/
unsigned has_synopsys_hc_bug:1; /* Synopsys HC */
+ unsigned has_synopsys_hsic_bug:1; /* Synop HSIC port */
unsigned frame_index_bug:1; /* MosChip (AKA NetMos) */
unsigned need_oc_pp_cycle:1; /* MPC834X port power */
unsigned imx28_write_fix:1; /* For Freescale i.MX28 */
temp = readl(port_array[wIndex]);
bus_state->suspended_ports |= 1 << wIndex;
+
+ /* WA for Lhotse U3 Suspend */
+ if (xhci_hub_check_speed(hcd))
+ phy_ilbk(xhci->main_hcd->phy);
+
break;
case USB_PORT_FEAT_LINK_STATE:
temp = readl(port_array[wIndex]);
return status ? retval : 0;
}
+int xhci_hub_check_speed(struct usb_hcd *hcd)
+{
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ int slot_id;
+ int i;
+ enum usb_device_speed speed;
+
+ if (hcd->speed < HCD_USB3)
+ return 0;
+
+ slot_id = 0;
+ for (i = 0; i < MAX_HC_SLOTS; i++) {
+ if (!xhci->devs[i])
+ continue;
+ speed = xhci->devs[i]->udev->speed;
+ if (speed >= USB_SPEED_SUPER) {
+ return 1;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int xhci_check_usbl2_support(struct usb_hcd *hcd)
+{
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+ if (xhci->quirks & XHCI_L2_SUPPORT)
+ return true;
+ else
+ return false;
+}
+
#ifdef CONFIG_PM
int xhci_bus_suspend(struct usb_hcd *hcd)
__le32 __iomem **port_array;
struct xhci_bus_state *bus_state;
unsigned long flags;
+ int is_port_connect = 0;
+ int ret;
max_ports = xhci_get_ports(hcd, &port_array);
bus_state = &xhci->bus_state[hcd_index(hcd)];
t2 = xhci_port_state_to_neutral(t1);
if ((t1 & PORT_PE) && !(t1 & PORT_PLS_MASK)) {
- xhci_dbg(xhci, "port %d not suspended\n", port_index);
slot_id = xhci_find_slot_id_by_port(hcd, xhci,
port_index + 1);
if (slot_id) {
if (t1 & PORT_CONNECT) {
t2 |= PORT_WKOC_E | PORT_WKDISC_E;
t2 &= ~PORT_WKCONN_E;
+ is_port_connect = 1;
} else {
t2 |= PORT_WKOC_E | PORT_WKCONN_E;
t2 &= ~PORT_WKDISC_E;
}
- } else
+ } else {
t2 &= ~PORT_WAKE_BITS;
+ }
t1 = xhci_port_state_to_neutral(t1);
- if (t1 != t2)
+ if (t1 != t2) {
writel(t2, port_array[port_index]);
+ }
}
+
+ if (is_port_connect && usb_hcd_is_primary_hcd(hcd)) {
+ xhci_info(xhci, "port is connected, phy vendor set\n");
+ ret = phy_vendor_set(xhci->main_hcd->phy, 1, 0);
+ if (ret) {
+ xhci_info(xhci, "phy vendor set fail\n");
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ return ret;
+ }
+ }
+
+ xhci_info(xhci, "%s 'HC_STATE_SUSPENDED' portcon: %d primary_hcd: %d\n",
+ __func__, is_port_connect, usb_hcd_is_primary_hcd(hcd));
hcd->state = HC_STATE_SUSPENDED;
bus_state->next_statechange = jiffies + msecs_to_jiffies(10);
+
spin_unlock_irqrestore(&xhci->lock, flags);
+
return 0;
}
u32 next_state;
u32 temp, portsc;
+ if (usb_hcd_is_primary_hcd(hcd)) {
+ xhci_info(xhci, "[%s] phy vendor set \n",__func__);
+ phy_vendor_set(xhci->main_hcd->phy, 1, 1);
+ }
max_ports = xhci_get_ports(hcd, &port_array);
bus_state = &xhci->bus_state[hcd_index(hcd)];
temp = readl(&xhci->op_regs->command);
spin_unlock_irqrestore(&xhci->lock, flags);
+
+ hcd->state = HC_STATE_RESUMING;
+ xhci_info(xhci, "%s is done, hcd state is 'HC_STATE_RESUMING'\n",__func__);
+
return 0;
}
xhci->event_ring = NULL;
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed event ring");
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+ if (xhci->save_addr)
+ dma_free_coherent(dev, sizeof(PAGE_SIZE), xhci->save_addr, xhci->save_dma);
+ xhci_info(xhci, "%s: Freed save-restore buffer for Audio offloading", __func__);
+
+ size = sizeof(struct xhci_erst_entry)*(xhci->erst_audio.num_entries);
+ if (xhci->erst_audio.entries)
+ dma_free_coherent(dev, size,
+ xhci->erst_audio.entries, xhci->erst_audio.erst_dma_addr);
+ xhci->erst_audio.entries = NULL;
+ xhci_info(xhci, "%s: Freed ERST for Audio offloading", __func__);
+
+ if (xhci->event_ring_audio)
+ xhci_ring_free(xhci, xhci->event_ring_audio);
+ xhci->event_ring_audio = NULL;
+ xhci_info(xhci, "%s: Freed event ring for Audio offloading", __func__);
+#endif
+
if (xhci->lpm_command)
xhci_free_command(xhci, xhci->lpm_command);
xhci->lpm_command = NULL;
return 0;
}
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+static void xhci_set_hc_event_deq_audio(struct xhci_hcd *xhci)
+{
+ u64 temp;
+ dma_addr_t deq;
+
+ deq = xhci_trb_virt_to_dma(xhci->event_ring_audio->deq_seg,
+ xhci->event_ring_audio->dequeue);
+ if (deq == 0 && !in_interrupt())
+ xhci_warn(xhci, "WARN something wrong with SW event ring "
+ "dequeue ptr.\n");
+ /* Update HC event ring dequeue pointer */
+ temp = xhci_read_64(xhci, &xhci->ir_set_audio->erst_dequeue);
+ temp &= ERST_PTR_MASK;
+ /* Don't clear the EHB bit (which is RW1C) because
+ * there might be more events to service.
+ */
+ temp &= ~ERST_EHB;
+ xhci_info(xhci,
+ "//[%s] Write event ring dequeue pointer = 0x%llx, "
+ "preserving EHB bit",__func__, ((u64) deq & (u64) ~ERST_PTR_MASK) | temp);
+ xhci_write_64(xhci, ((u64) deq & (u64) ~ERST_PTR_MASK) | temp,
+ &xhci->ir_set_audio->erst_dequeue);
+}
+#endif
+
static void xhci_set_hc_event_deq(struct xhci_hcd *xhci)
{
u64 temp;
xhci_print_run_regs(xhci);
/* Set ir_set to interrupt register set 0 */
xhci->ir_set = &xhci->run_regs->ir_set[0];
-
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+ xhci->ir_set_audio = &xhci->run_regs->ir_set[1];
+#endif
/*
* Event ring setup: Allocate a normal ring, but also setup
* the event ring segment table (ERST). Section 4.9.3.
"Wrote ERST address to ir_set 0.");
xhci_print_ir_set(xhci, 0);
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+ xhci->save_addr = dma_alloc_coherent(dev, sizeof(PAGE_SIZE), &dma,
+ flags);
+ xhci->save_dma = dma;
+ xhci_info(xhci, "// Save address = 0x%llx (DMA), %p (virt)",
+ (unsigned long long)xhci->save_dma, xhci->save_addr);
+ /* for AUDIO erst */
+ xhci->event_ring_audio = xhci_ring_alloc(xhci, ERST_NUM_SEGS, 1, TYPE_EVENT,
+ 0, flags);
+ if (!xhci->event_ring_audio)
+ goto fail;
+ if (xhci_check_trb_in_td_math(xhci) < 0)
+ goto fail;
+
+ xhci->erst_audio.entries = dma_alloc_coherent(dev,
+ sizeof(struct xhci_erst_entry) * ERST_NUM_SEGS, &dma,
+ flags);
+ if (!xhci->erst_audio.entries)
+ goto fail;
+ xhci_info(xhci,
+ "// Allocated event ring segment table at 0x%llx",
+ (unsigned long long)dma);
+
+ memset(xhci->erst_audio.entries, 0, sizeof(struct xhci_erst_entry)*ERST_NUM_SEGS);
+ xhci->erst_audio.num_entries = ERST_NUM_SEGS;
+ xhci->erst_audio.erst_dma_addr = dma;
+ xhci_info(xhci,
+ "// Set ERST to 0; private num segs = %i, virt addr = %p, dma addr = 0x%llx",
+ xhci->erst.num_entries,
+ xhci->erst.entries,
+ (unsigned long long)xhci->erst.erst_dma_addr);
+
+ /* set ring base address and size for each segment table entry */
+ for (val = 0, seg = xhci->event_ring_audio->first_seg; val < ERST_NUM_SEGS; val++) {
+ struct xhci_erst_entry *entry = &xhci->erst_audio.entries[val];
+ entry->seg_addr = cpu_to_le64(seg->dma);
+ entry->seg_size = cpu_to_le32(TRBS_PER_SEGMENT);
+ entry->rsvd = 0;
+ seg = seg->next;
+ }
+
+ /* set ERST count with the number of entries in the segment table */
+ val = readl(&xhci->ir_set_audio->erst_size);
+ val &= ERST_SIZE_MASK;
+ val |= ERST_NUM_SEGS;
+ xhci_info(xhci,
+ "// Write ERST size = %i to ir_set 0 (some bits preserved)",
+ val);
+ writel(val, &xhci->ir_set_audio->erst_size);
+
+ xhci_info(xhci,
+ "// Set ERST entries to point to event ring.");
+ /* set the segment table base address */
+ xhci_info(xhci,
+ "// Set ERST base address for ir_set 0 = 0x%llx",
+ (unsigned long long)xhci->erst_audio.erst_dma_addr);
+ val_64 = xhci_read_64(xhci, &xhci->ir_set_audio->erst_base);
+ val_64 &= ERST_PTR_MASK;
+ val_64 |= (xhci->erst_audio.erst_dma_addr & (u64) ~ERST_PTR_MASK);
+ xhci_write_64(xhci, val_64, &xhci->ir_set_audio->erst_base);
+
+ /* Set the event ring dequeue address */
+ xhci_set_hc_event_deq_audio(xhci);
+ xhci_info(xhci,
+ "// Wrote ERST address to ir_set 1.");
+ xhci_print_ir_set(xhci, 1);
+#endif
/*
* XXX: Might need to set the Interrupter Moderation Register to
* something other than the default (~1ms minimum between interrupts).
#include <linux/platform_device.h>
#include <linux/usb/phy.h>
#include <linux/slab.h>
+#include <linux/phy/phy.h>
#include <linux/acpi.h>
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+#include <linux/usb/exynos_usb_audio.h>
+#endif
+
#include "xhci.h"
#include "xhci-plat.h"
#include "xhci-mvebu.h"
static int xhci_plat_setup(struct usb_hcd *hcd);
static int xhci_plat_start(struct usb_hcd *hcd);
+void __iomem *usb3_portsc;
+static u32 pp_set_delayed;
+#define PORTSC_OFFSET 0x430
static const struct xhci_driver_overrides xhci_plat_overrides __initconst = {
.extra_priv_size = sizeof(struct xhci_plat_priv),
return priv->init_quirk(hcd);
}
-static int xhci_priv_resume_quirk(struct usb_hcd *hcd)
-{
- struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd);
-
- if (!priv->resume_quirk)
- return 0;
-
- return priv->resume_quirk(hcd);
-}
-
static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci)
{
/*
{
int ret;
-
ret = xhci_priv_init_quirk(hcd);
if (ret)
return ret;
- return xhci_gen_setup(hcd, xhci_plat_quirks);
+ ret = xhci_gen_setup(hcd, xhci_plat_quirks);
+
+ /*
+ * DWC3 WORKAROUND: xhci reset clears PHY CR port settings,
+ * so USB3.0 PHY should be tuned again.
+ */
+ if (hcd->phy)
+ phy_tune(hcd->phy, OTG_STATE_A_HOST);
+
+ return ret;
}
static int xhci_plat_start(struct usb_hcd *hcd)
return xhci_run(hcd);
}
+static ssize_t
+xhci_plat_show_ss_compliance(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ u32 reg;
+ void __iomem *reg_base;
+
+ reg_base = hcd->regs;
+ reg = readl(reg_base + PORTSC_OFFSET);
+
+ return snprintf(buf, PAGE_SIZE, "0x%x\n", reg);
+}
+
+static ssize_t
+xhci_platg_store_ss_compliance(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t n)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ int value;
+ u32 reg;
+ void __iomem *reg_base;
+
+ if (sscanf(buf, "%d", &value) != 1)
+ return -EINVAL;
+
+ reg_base = hcd->regs;
+
+ if (value == 1) {
+ /* PORTSC PLS is set to 10, LWS to 1 */
+ reg = readl(reg_base + PORTSC_OFFSET);
+ reg &= ~((0xF << 5) | (1 << 16));
+ reg |= (10 << 5) | (1 << 16);
+ writel(reg, reg_base + PORTSC_OFFSET);
+ pr_info("Super speed host compliance enabled portsc 0x%x\n", reg);
+ } else
+ pr_info("Only 1 is allowed for input value\n");
+
+ return n;
+}
+
+static DEVICE_ATTR(ss_compliance, S_IWUSR | S_IRUSR | S_IRGRP,
+ xhci_plat_show_ss_compliance, xhci_platg_store_ss_compliance);
+
+static ssize_t
+xhci_plat_show_l2_state(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ xhci->l2_state);
+}
+
+static DEVICE_ATTR(l2_state, S_IRUSR | S_IRGRP | S_IROTH,
+ xhci_plat_show_l2_state, NULL);
+
+static struct attribute *exynos_xhci_attributes[] = {
+ &dev_attr_l2_state.attr,
+ &dev_attr_ss_compliance.attr,
+ NULL
+};
+
+static const struct attribute_group xhci_plat_attr_group = {
+ .attrs = exynos_xhci_attributes,
+};
#ifdef CONFIG_OF
static const struct xhci_plat_priv xhci_plat_marvell_armada = {
.init_quirk = xhci_mvebu_mbus_init_quirk,
MODULE_DEVICE_TABLE(of, usb_xhci_of_match);
#endif
+void xhci_portsc_power_off(void __iomem *portsc, u32 on)
+{
+ u32 reg;
+
+ reg = readl(portsc);
+
+ if (on)
+ reg |= PORT_POWER;
+ else
+ reg &= ~PORT_POWER;
+
+ writel(reg, portsc);
+ reg = readl(portsc);
+
+ pr_info("%s, reg = 0x%x addr = %p\n",
+ __func__, reg, portsc);
+}
+
+int xhci_portsc_set(u32 on)
+{
+ if (usb3_portsc != NULL) {
+ xhci_portsc_power_off(usb3_portsc, 0);
+ pp_set_delayed = 0;
+ return 0;
+ }
+
+ pp_set_delayed = 1;
+ pr_info("%s, usb3_portsc is NULL\n", __func__);
+ return -EIO;
+}
+
static int xhci_plat_probe(struct platform_device *pdev)
{
+ struct device *parent = pdev->dev.parent;
const struct of_device_id *match;
const struct hc_driver *driver;
struct device *sysdev;
int ret;
int irq;
+ struct wake_lock *wakelock;
+ int value;
+
+ dev_info(&pdev->dev, "XHCI PLAT START\n");
+
+ wakelock = kzalloc(sizeof(struct wake_lock), GFP_KERNEL);
+ wake_lock_init(wakelock, WAKE_LOCK_SUSPEND, dev_name(&pdev->dev));
+ wake_lock(wakelock);
+
if (usb_disabled())
return -ENODEV;
hcd->rsrc_start = res->start;
hcd->rsrc_len = resource_size(res);
+ usb3_portsc = hcd->regs + PORTSC_OFFSET;
+ pr_info("get usb3_portsc addr = %p pp_set = %d\n",
+ usb3_portsc, pp_set_delayed);
+
+ if (pp_set_delayed) {
+ xhci_portsc_power_off(usb3_portsc, 0);
+ pp_set_delayed = 0;
+ }
+
+ /* Get USB2.0 PHY for main hcd */
+ if (parent) {
+ hcd->phy = devm_phy_get(parent, "usb2-phy");
+ if (IS_ERR_OR_NULL(hcd->phy)) {
+ hcd->phy = NULL;
+ dev_err(&pdev->dev,
+ "%s: failed to get phy\n", __func__);
+ }
+ }
+
/*
* Not all platforms have a clk so it is not an error if the
* clock does not exists.
device_wakeup_enable(hcd->self.controller);
xhci->clk = clk;
+ xhci->wakelock = wakelock;
xhci->main_hcd = hcd;
xhci->shared_hcd = __usb_create_hcd(driver, sysdev, &pdev->dev,
dev_name(&pdev->dev), hcd);
goto put_usb3_hcd;
}
+ /* Get USB3.0 PHY to tune the PHY */
+ if (parent) {
+ xhci->shared_hcd->phy = devm_phy_get(parent, "usb3-phy");
+ if (IS_ERR_OR_NULL(hcd->phy)) {
+ hcd->phy = NULL;
+ dev_err(&pdev->dev,
+ "%s: failed to get phy\n", __func__);
+ }
+ }
+
+ ret = of_property_read_u32(parent->of_node, "xhci_l2_support", &value);
+ if (ret == 0 && value == 1)
+ xhci->quirks |= XHCI_L2_SUPPORT;
+ else {
+ dev_err(&pdev->dev,
+ "can't get xhci l2 support, error = %d\n", ret);
+ }
+
ret = usb_add_hcd(hcd, irq, IRQF_SHARED);
if (ret)
goto disable_usb_phy;
if (ret)
goto dealloc_usb2_hcd;
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+ ret = of_property_read_u32(parent->of_node, "usb_audio_offloading", &value);
+ if (ret == 0 && value == 1) {
+ ret = exynos_usb_audio_init(parent, pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "USB Audio INIT fail\n");
+ return ret;
+ } else {
+ dev_info(&pdev->dev, "USB Audio offloading is supported\n");
+ }
+ } else {
+ dev_err(&pdev->dev, "can't get audio support, error = %d\n", ret);
+ return ret;
+ }
+
+ xhci->out_dma = xhci_data.out_data_dma;
+ xhci->out_addr = xhci_data.out_data_addr;
+ xhci->in_dma = xhci_data.in_data_dma;
+ xhci->in_addr = xhci_data.in_data_addr;
+#endif
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &xhci_plat_attr_group);
+ if (ret)
+ dev_err(&pdev->dev, "failed to create xhci-plat attributes\n");
+
device_enable_async_suspend(&pdev->dev);
pm_runtime_put_noidle(&pdev->dev);
static int xhci_plat_remove(struct platform_device *dev)
{
+ struct device *parent = dev->dev.parent;
struct usb_hcd *hcd = platform_get_drvdata(dev);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct clk *clk = xhci->clk;
+ int timeout = 0;
+
+ dev_info(&dev->dev, "XHCI PLAT REMOVE\n");
+
+ usb3_portsc = NULL;
+ pp_set_delayed = 0;
+
+ /*
+ * Sometimes deadlock occurred in this function.
+ * So, below waiting for completion of hub_event was added.
+ */
+ while (xhci->shared_hcd->is_in_hub_event || hcd->is_in_hub_event) {
+ msleep(10);
+ timeout += 10;
+ if (timeout >= XHCI_HUB_EVENT_TIMEOUT) {
+ xhci_err(xhci,
+ "ERROR: hub_event completion timeout\n");
+ break;
+ }
+ }
+ xhci_dbg(xhci, "%s: waited %dmsec", __func__, timeout);
xhci->xhc_state |= XHCI_STATE_REMOVING;
usb_remove_hcd(xhci->shared_hcd);
usb_phy_shutdown(hcd->usb_phy);
+ /*
+ * In usb_remove_hcd, phy_exit is called if phy is not NULL.
+ * However, in the case that PHY was turn on or off as runtime PM,
+ * PHY sould not exit at this time. So, to prevent the PHY exit,
+ * PHY pointer have to be NULL.
+ */
+ if (parent && hcd->phy)
+ hcd->phy = NULL;
+
usb_remove_hcd(hcd);
usb_put_hcd(xhci->shared_hcd);
clk_disable_unprepare(clk);
usb_put_hcd(hcd);
+ dev_info(&dev->dev, "WAKE UNLOCK\n");
+ wake_unlock(xhci->wakelock);
+ wake_lock_destroy(xhci->wakelock);
+
pm_runtime_set_suspended(&dev->dev);
pm_runtime_disable(&dev->dev);
static int __maybe_unused xhci_plat_suspend(struct device *dev)
{
- struct usb_hcd *hcd = dev_get_drvdata(dev);
- struct xhci_hcd *xhci = hcd_to_xhci(hcd);
- int ret;
+ /*
+ *struct usb_hcd *hcd = dev_get_drvdata(dev);
+ *struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ *int ret;
+ */
+
+ pr_info("[%s] \n",__func__);
/*
* xhci_suspend() needs `do_wakeup` to know whether host is allowed
* reconsider this when xhci_plat_suspend enlarges its scope, e.g.,
* also applies to runtime suspend.
*/
- ret = xhci_suspend(xhci, device_may_wakeup(dev));
- if (!device_may_wakeup(dev) && !IS_ERR(xhci->clk))
- clk_disable_unprepare(xhci->clk);
-
- return ret;
+ /*
+ *ret = xhci_suspend(xhci, device_may_wakeup(dev));
+ *
+ *if (!device_may_wakeup(dev) && !IS_ERR(xhci->clk))
+ * clk_disable_unprepare(xhci->clk);
+ */
+ return 0;
}
static int __maybe_unused xhci_plat_resume(struct device *dev)
{
- struct usb_hcd *hcd = dev_get_drvdata(dev);
- struct xhci_hcd *xhci = hcd_to_xhci(hcd);
- int ret;
-
- if (!device_may_wakeup(dev) && !IS_ERR(xhci->clk))
- clk_prepare_enable(xhci->clk);
+ /*
+ *struct usb_hcd *hcd = dev_get_drvdata(dev);
+ *struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ *int ret;
+ */
- ret = xhci_priv_resume_quirk(hcd);
- if (ret)
- return ret;
+ pr_info("[%s] \n",__func__);
- return xhci_resume(xhci, 0);
+ /*
+ *if (!device_may_wakeup(dev) && !IS_ERR(xhci->clk))
+ * clk_prepare_enable(xhci->clk);
+ *
+ *ret = xhci_priv_resume_quirk(hcd);
+ *if (ret)
+ * return ret;
+ *
+ *return xhci_resume(xhci, 0);
+ */
+ return 0;
}
static int __maybe_unused xhci_plat_runtime_suspend(struct device *dev)
goto cleanup;
} else if (!test_bit(faked_port_index,
&bus_state->resuming_ports)) {
- xhci_dbg(xhci, "resume HS port %d\n", port_id);
+ xhci_info(xhci, "resume HS port %d\n", port_id);
bus_state->resume_done[faked_port_index] = jiffies +
msecs_to_jiffies(USB_RESUME_TIMEOUT);
set_bit(faked_port_index, &bus_state->resuming_ports);
mod_timer(&hcd->rh_timer,
bus_state->resume_done[faked_port_index]);
/* Do the rest in GetPortStatus */
+ /* REWA Disable */
+ phy_vendor_set(xhci->main_hcd->phy, 0, 0);
}
}
#include <linux/dmi.h>
#include <linux/dma-mapping.h>
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+#include <linux/usb/exynos_usb_audio.h>
+#endif
+
#include "xhci.h"
#include "xhci-trace.h"
#include "xhci-mtk.h"
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"ERST deq = 64'h%0lx", (long unsigned int) temp_64);
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+ temp_64 = xhci_read_64(xhci, &xhci->ir_set_audio->erst_dequeue);
+ temp_64 &= ~ERST_PTR_MASK;
+ xhci_info(xhci, "ERST2 deq = 64'h%0lx", (long unsigned int) temp_64);
+#endif
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"// Set the interrupt modulation register");
temp = readl(&xhci->ir_set->irq_control);
temp |= (u32) ((xhci->quirks & XHCI_MTK_HOST) ? 20 : 160);
writel(temp, &xhci->ir_set->irq_control);
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+ xhci_info(xhci, "// [USB Audio] Set the interrupt modulation register");
+ temp = readl(&xhci->ir_set_audio->irq_control);
+ temp &= ~ER_IRQ_INTERVAL_MASK;
+ /*
+ * the increment interval is 8 times as much as that defined
+ * in xHCI spec on MTK's controller
+ */
+ temp |= (u32) ((xhci->quirks & XHCI_MTK_HOST) ? 20 : 160);
+ writel(temp, &xhci->ir_set_audio->irq_control);
+#endif
/* Set the HCD state before we enable the irqs */
temp = readl(&xhci->op_regs->command);
temp |= (CMD_EIE);
writel(ER_IRQ_ENABLE(temp), &xhci->ir_set->irq_pending);
xhci_print_ir_set(xhci, 0);
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+ temp = readl(&xhci->ir_set_audio->irq_pending);
+ xhci_info(xhci, "// [USB Audio] Enabling event ring interrupter %p by writing 0x%x to irq_pending",
+ xhci->ir_set_audio, (unsigned int) ER_IRQ_ENABLE(temp));
+ writel(ER_IRQ_ENABLE(temp), &xhci->ir_set_audio->irq_pending);
+ xhci_print_ir_set(xhci, 1);
+#endif
if (xhci->quirks & XHCI_NEC_HOST) {
struct xhci_command *command;
/* Callee should call reset_bandwidth() */
goto command_cleanup;
+ xhci_set_deq(xhci, virt_dev->out_ctx, LAST_CTX_TO_EP_NUM(le32_to_cpu(slot_ctx->dev_info)), udev);
+
/* Free any rings that were dropped, but not changed. */
for (i = 1; i < 31; i++) {
if ((le32_to_cpu(ctrl_ctx->drop_flags) & (1 << (i + 1))) &&
* device.
*/
if (ret <= 0 && ret != -ENODEV)
- return;
+ goto out;
virt_dev = xhci->devs[udev->slot_id];
slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->out_ctx);
* Event command completion handler will free any data structures
* associated with the slot. XXX Can free sleep?
*/
+
+out:
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+ exynos_usb_audio_conn(0);
+#endif
+ return;
}
int xhci_disable_slot(struct xhci_hcd *xhci, u32 slot_id)
return 0;
}
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+int xhci_store_hw_info(struct usb_hcd *hcd, struct usb_device *udev)
+{
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ struct hcd_hw_info *hwinfo = &udev->hwinfo;
+ struct xhci_virt_device *virt_dev;
+ struct xhci_erst_entry *entry = &xhci->erst_audio.entries[0];
+
+ virt_dev = xhci->devs[udev->slot_id];
+
+ hwinfo->dcbaa_dma = xhci->dcbaa->dma;
+ hwinfo->save_dma = xhci->save_dma;
+ hwinfo->cmd_ring = xhci->op_regs->cmd_ring;
+ hwinfo->slot_id = xhci->slot_id;
+ hwinfo->in_dma = xhci->in_dma;
+ hwinfo->in_buf = xhci->in_addr;
+ hwinfo->in_ctx = virt_dev->in_ctx->dma;
+ hwinfo->out_ctx = virt_dev->out_ctx->dma;
+ hwinfo->erst_addr = entry->seg_addr;
+ hwinfo->speed = udev->speed;
+
+ return 0;
+}
+#endif
+
+int xhci_set_deq(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx,
+ unsigned int last_ep, struct usb_device *udev)
+{
+ int i;
+ int last_ep_ctx = 31;
+
+ if (last_ep < 31)
+ last_ep_ctx = last_ep + 1;
+ for (i = 0; i < last_ep_ctx; ++i) {
+ unsigned int epaddr = xhci_get_endpoint_address(i);
+ struct xhci_ep_ctx *ep_ctx = xhci_get_ep_ctx(xhci, ctx, i);
+ if (epaddr == udev->hwinfo.in_ep ) {
+ pr_debug("[%s] set in deq : %#08llx \n",__func__, ep_ctx->deq);
+ if (udev->hwinfo.in_deq)
+ udev->hwinfo.old_in_deq = udev->hwinfo.in_deq;
+ udev->hwinfo.in_deq = ep_ctx->deq;
+ } else if (epaddr == udev->hwinfo.out_ep) {
+ pr_debug("[%s] set out deq : %#08llx \n",__func__, ep_ctx->deq);
+ if (udev->hwinfo.out_deq)
+ udev->hwinfo.old_out_deq = udev->hwinfo.out_deq;
+ udev->hwinfo.out_deq = ep_ctx->deq;
+ }
+ }
+
+ return 0;
+}
/*
* Returns 0 if the xHC ran out of device slots, the Enable Slot command
int ret, slot_id;
struct xhci_command *command;
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+ exynos_usb_audio_conn(1);
+#endif
command = xhci_alloc_command(xhci, false, true, GFP_KERNEL);
if (!command)
return 0;
xhci_dbg_trace(xhci, trace_xhci_dbg_address,
"Internal device address = %d",
le32_to_cpu(slot_ctx->dev_state) & DEV_ADDR_MASK);
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+ xhci_store_hw_info(hcd, udev);
+#endif
out:
mutex_unlock(&xhci->mutex);
if (command) {
}
EXPORT_SYMBOL_GPL(xhci_gen_setup);
+int xhci_wake_lock(struct usb_hcd *hcd, int is_lock) {
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+ xhci_info(xhci, "%s: WAKE %s\n", __func__, is_lock ? "LOCK" : "UNLOCK");
+ if (is_lock) {
+ xhci->l2_state = 0;
+ wake_lock(xhci->wakelock);
+ } else {
+ xhci->l2_state = 1;
+ wake_unlock(xhci->wakelock);
+ }
+
+ return 0;
+}
+
static const struct hc_driver xhci_hc_driver = {
.description = "xhci-hcd",
.product_desc = "xHCI Host Controller",
.irq = xhci_irq,
.flags = HCD_MEMORY | HCD_USB3 | HCD_SHARED,
+ .wake_lock = xhci_wake_lock,
/*
* basic lifecycle operations
*/
*/
.hub_control = xhci_hub_control,
.hub_status_data = xhci_hub_status_data,
+ .hub_check_speed = xhci_hub_check_speed,
+ .usb_l2_check = xhci_check_usbl2_support,
.bus_suspend = xhci_bus_suspend,
.bus_resume = xhci_bus_resume,
#include <linux/kernel.h>
#include <linux/usb/hcd.h>
#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/wakelock.h>
+#include <linux/phy/phy.h>
/* Code sharing between pci-quirks and xhci hcd */
#include "xhci-ext-caps.h"
u8 psi_uid_count;
};
+/*
+ * Sometimes deadlock occurred between hub_event and remove_hcd.
+ * In order to prevent it, waiting for completion of hub_event was added.
+ * This is a timeout (300msec) value for the waiting.
+ */
+#define XHCI_HUB_EVENT_TIMEOUT (300)
+
/* There is one xhci_hcd structure per controller */
struct xhci_hcd {
struct usb_hcd *main_hcd;
struct xhci_doorbell_array __iomem *dba;
/* Our HCD's current interrupter register set */
struct xhci_intr_reg __iomem *ir_set;
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+ struct xhci_intr_reg __iomem *ir_set_audio;
+#endif
/* Cached register copies of read-only HC data */
__u32 hcs_params1;
struct xhci_command *current_cmd;
struct xhci_ring *event_ring;
struct xhci_erst erst;
+
+#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
+ struct xhci_ring *event_ring_audio;
+ struct xhci_erst erst_audio;
+ dma_addr_t save_dma;
+ void *save_addr;
+ dma_addr_t out_dma;
+ void *out_addr;
+ dma_addr_t in_dma;
+ void *in_addr;
+#endif
+
/* Scratchpad */
struct xhci_scratchpad *scratchpad;
/* Store LPM test failed devices' information */
struct dma_pool *small_streams_pool;
struct dma_pool *medium_streams_pool;
+ struct wake_lock *wakelock;
+ int l2_state;
+
/* Host controller watchdog timer structures */
unsigned int xhc_state;
#define XHCI_LIMIT_ENDPOINT_INTERVAL_7 (1 << 26)
/* Reserved. It was XHCI_U2_DISABLE_WAKE */
#define XHCI_ASMEDIA_MODIFY_FLOWCONTROL (1 << 28)
+#define XHCI_L2_SUPPORT (1 << 29)
#define XHCI_SUSPEND_DELAY (1 << 30)
unsigned int num_active_eps;
irqreturn_t xhci_irq(struct usb_hcd *hcd);
irqreturn_t xhci_msi_irq(int irq, void *hcd);
int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev);
+int xhci_store_hw_info(struct usb_hcd *hcd, struct usb_device *udev);
+int xhci_set_deq(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx,
+ unsigned int last_ep, struct usb_device *udev);
int xhci_alloc_tt_info(struct xhci_hcd *xhci,
struct xhci_virt_device *virt_dev,
struct usb_device *hdev,
int xhci_hub_status_data(struct usb_hcd *hcd, char *buf);
int xhci_find_raw_port_number(struct usb_hcd *hcd, int port1);
void xhci_hc_died(struct xhci_hcd *xhci);
+int xhci_hub_check_speed(struct usb_hcd *hcd);
+int xhci_check_usbl2_support(struct usb_hcd *hcd);
#ifdef CONFIG_PM
int xhci_bus_suspend(struct usb_hcd *hcd);
int timeout;
};
+struct hcd_hw_info {
+ /* for XHCI */
+ int slot_id;
+ dma_addr_t erst_addr;
+ dma_addr_t dcbaa_dma;
+ dma_addr_t in_ctx;
+ dma_addr_t out_ctx;
+ dma_addr_t save_dma;
+ u64 cmd_ring;
+ u64 old_out_deq;
+ u64 old_in_deq;
+ u64 out_deq;
+ u64 in_deq;
+ int in_ep;
+ int out_ep;
+ int speed;
+ void *out_buf;
+ u64 out_dma;
+ void *in_buf;
+ u64 in_dma;
+};
+
/**
* struct usb_device - kernel's representation of a USB device
* @devnum: device number; address on a USB bus
struct usb_host_endpoint *ep_out[16];
char **rawdescriptors;
+ int rawdesc_length;
+
+ struct hcd_hw_info hwinfo;
unsigned short bus_mA;
u8 portnum;
void (*free_func)(struct usb_function *f);
struct module *mod;
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ /* Optional function for vendor specific processing */
+ int (*ctrlrequest)(struct usb_function *,
+ const struct usb_ctrlrequest *);
+#endif
+
/* runtime state management */
int (*set_alt)(struct usb_function *,
unsigned interface, unsigned alt);
--- /dev/null
+#include <sound/samsung/abox.h>
+
+/* for KM */
+
+#define USB_AUDIO_MEM_BASE 0xC0000000
+
+#define USB_AUDIO_SAVE_RESTORE USB_AUDIO_MEM_BASE
+#define USB_AUDIO_DEV_CTX USB_AUDIO_SAVE_RESTORE+PAGE_SIZE
+#define USB_AUDIO_INPUT_CTX USB_AUDIO_DEV_CTX+PAGE_SIZE
+#define USB_AUDIO_OUT_DEQ USB_AUDIO_INPUT_CTX+PAGE_SIZE
+#define USB_AUDIO_IN_DEQ USB_AUDIO_OUT_DEQ+PAGE_SIZE
+#define USB_AUDIO_ERST USB_AUDIO_IN_DEQ+PAGE_SIZE
+#define USB_AUDIO_DESC USB_AUDIO_ERST+PAGE_SIZE
+#define USB_AUDIO_PCM_OUTBUF USB_AUDIO_MEM_BASE+0x100000
+#define USB_AUDIO_PCM_INBUF USB_AUDIO_MEM_BASE+0x800000
+
+#define USB_AUDIO_XHCI_BASE 0x10c00000
+
+struct host_data {
+ dma_addr_t out_data_dma;
+ dma_addr_t in_data_dma;
+ void *out_data_addr;
+ void *in_data_addr;
+};
+
+extern struct host_data xhci_data;
+
+struct exynos_usb_audio {
+ struct usb_device *udev;
+ struct platform_device *abox;
+ struct platform_device *hcd_pdev;
+ struct mutex lock;
+ struct completion in_task_done;
+ struct completion out_task_done;
+
+ u64 out_buf_addr;
+ u64 in_buf_addr;
+ u64 pcm_offset;
+ u64 desc_addr;
+ u64 offset;
+
+ /* for hw_info */
+ u64 dcbaa_dma;
+ u64 in_ctx;
+ u64 out_ctx;
+ u64 erst_addr;
+
+ int speed;
+ /* 1: in, 0: out */
+ int set_ep;
+ int is_audio;
+
+ void *pcm_buf;
+ u64 save_dma;
+};
+
+int exynos_usb_audio_map_buf(struct usb_device *udev);
+int exynos_usb_audio_pcmbuf(struct usb_device *udev);
+int exynos_usb_audio_setrate(int intf, int rate, int alt);
+int exynos_usb_audio_setintf(struct usb_device *udev, int iface, int alt, int direction);
+int exynos_usb_audio_conn(int is_conn);
+int exynos_usb_audio_pcm(int is_open, int direction);
+int exynos_usb_audio_l2(int is_l2);
+int exynos_usb_audio_desc(struct usb_device *udev);
+int exynos_usb_audio_hcd(struct usb_device *udev);
+int exynos_usb_audio_init(struct device *dev, struct platform_device *pdev);
+int exynos_usb_audio_exit(void);
+void exynos_usb_audio_set_device(struct usb_device *udev);
struct usb_hcd *shared_hcd;
struct usb_hcd *primary_hcd;
+ bool is_in_hub_event;
#define HCD_BUFFER_POOLS 4
struct dma_pool *pool[HCD_BUFFER_POOLS];
#define HCD_MASK 0x0070
#define HCD_BH 0x0100 /* URB complete in BH context */
+ int (*wake_lock) (struct usb_hcd *hcd, int is_lock);
/* called to init HCD and root hub */
int (*reset) (struct usb_hcd *hcd);
int (*start) (struct usb_hcd *hcd);
int (*hub_control) (struct usb_hcd *hcd,
u16 typeReq, u16 wValue, u16 wIndex,
char *buf, u16 wLength);
+ int (*hub_check_speed) (struct usb_hcd *hcd);
+ int (*usb_l2_check) (struct usb_hcd *hcd);
int (*bus_suspend)(struct usb_hcd *);
int (*bus_resume)(struct usb_hcd *);
int (*start_port_reset)(struct usb_hcd *, unsigned port_num);
struct device_node *usb_of_get_child_node(struct device_node *parent,
int portnum);
struct device *usb_of_get_companion_dev(struct device *dev);
+
+unsigned int of_usb_get_suspend_clk_freq(struct device *dev);
#else
static inline enum usb_dr_mode
of_usb_get_dr_mode_by_phy(struct device_node *np, int arg0)
--- /dev/null
+/*
+ * Copyright (C) 2012 Samsung Electronics Co.Ltd
+ * http://www.samsung.com/
+ *
+ * Common defines for samsung usb controllers
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#ifndef __LINUX_USB_SAMSUNG_USB_H
+#define __LINUX_USB_SAMSUNG_USB_H
+
+enum samsung_cpu_type {
+ TYPE_S3C64XX,
+ TYPE_EXYNOS4210,
+ TYPE_EXYNOS5250,
+ TYPE_EXYNOS5430,
+ TYPE_EXYNOS7420,
+ TYPE_EXYNOS7580,
+ TYPE_EXYNOS8890,
+ TYPE_EXYNOS8895,
+};
+
+enum samsung_usb_ip_type {
+ TYPE_USB3DRD = 0,
+ TYPE_USB3HOST,
+ TYPE_USB2DRD,
+ TYPE_USB2HOST,
+};
+
+enum samsung_phy_set_option {
+ SET_DPPULLUP_ENABLE,
+ SET_DPPULLUP_DISABLE,
+ SET_DPDM_PULLDOWN,
+};
+#endif /* __LINUX_USB_SAMSUNG_USB_H */