Commit 1c41a957 authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman

Merge tag 'usb-for-v4.1' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb into usb-next

Felipe writes:

usb: patches for v4.1 merge window

As usual, a big pile of commits. This time a total
of 111 non-merge commits.

Other than the usual set of cleanups and non-critical
fixes, we have some interesting work for AM335x's MUSB
babble recovery. Now that takes a lot less time and we
don't have to Reset MUSB all the time.

The printer gadget has been converted to configfs interface
and the atmel udc has learned suspend/resume with wakeup.
Signed-off-by: default avatarFelipe Balbi <balbi@ti.com>
parents cd0e0757 3e457371
What: /config/usb-gadget/gadget/functions/printer.name
Date: Apr 2015
KernelVersion: 4.1
Description:
The attributes:
pnp_string - Data to be passed to the host in pnp string
q_len - Number of requests per endpoint
...@@ -14,6 +14,7 @@ Optional properties: ...@@ -14,6 +14,7 @@ Optional properties:
- phys: from the *Generic PHY* bindings - phys: from the *Generic PHY* bindings
- phy-names: from the *Generic PHY* bindings - phy-names: from the *Generic PHY* bindings
- tx-fifo-resize: determines if the FIFO *has* to be reallocated. - tx-fifo-resize: determines if the FIFO *has* to be reallocated.
- snps,usb3_lpm_capable: determines if platform is USB3 LPM capable
- snps,disable_scramble_quirk: true when SW should disable data scrambling. - snps,disable_scramble_quirk: true when SW should disable data scrambling.
Only really useful for FPGA builds. Only really useful for FPGA builds.
- snps,has-lpm-erratum: true when DWC3 was configured with LPM Erratum enabled - snps,has-lpm-erratum: true when DWC3 was configured with LPM Erratum enabled
......
...@@ -15,7 +15,10 @@ Optional properties: ...@@ -15,7 +15,10 @@ Optional properties:
- phys: phandle + phy specifier pair - phys: phandle + phy specifier pair
- phy-names: must be "usb" - phy-names: must be "usb"
- dmas: Must contain a list of references to DMA specifiers. - dmas: Must contain a list of references to DMA specifiers.
- dma-names : Must contain a list of DMA names, "tx" or "rx". - dma-names : Must contain a list of DMA names:
- tx0 ... tx<n>
- rx0 ... rx<n>
- This <n> means DnFIFO in USBHS module.
Example: Example:
usbhs: usb@e6590000 { usbhs: usb@e6590000 {
......
...@@ -19,6 +19,7 @@ provided by gadgets. ...@@ -19,6 +19,7 @@ provided by gadgets.
16. UAC1 function 16. UAC1 function
17. UAC2 function 17. UAC2 function
18. UVC function 18. UVC function
19. PRINTER function
1. ACM function 1. ACM function
...@@ -726,3 +727,49 @@ with these patches: ...@@ -726,3 +727,49 @@ with these patches:
http://www.spinics.net/lists/linux-usb/msg99220.html http://www.spinics.net/lists/linux-usb/msg99220.html
host: luvcview -f yuv host: luvcview -f yuv
19. PRINTER function
====================
The function is provided by usb_f_printer.ko module.
Function-specific configfs interface
------------------------------------
The function name to use when creating the function directory is "printer".
The printer function provides these attributes in its function directory:
pnp_string - Data to be passed to the host in pnp string
q_len - Number of requests per endpoint
Testing the PRINTER function
----------------------------
The most basic testing:
device: run the gadget
# ls -l /devices/virtual/usb_printer_gadget/
should show g_printer<number>.
If udev is active, then /dev/g_printer<number> should appear automatically.
host:
If udev is active, then e.g. /dev/usb/lp0 should appear.
host->device transmission:
device:
# cat /dev/g_printer<number>
host:
# cat > /dev/usb/lp0
device->host transmission:
# cat > /dev/g_printer<number>
host:
# cat /dev/usb/lp0
More advanced testing can be done with the prn_example
described in Documentation/usb/gadget-printer.txt.
...@@ -86,10 +86,8 @@ static int hw_device_state(struct ci_hdrc *ci, u32 dma) ...@@ -86,10 +86,8 @@ static int hw_device_state(struct ci_hdrc *ci, u32 dma)
/* interrupt, error, port change, reset, sleep/suspend */ /* interrupt, error, port change, reset, sleep/suspend */
hw_write(ci, OP_USBINTR, ~0, hw_write(ci, OP_USBINTR, ~0,
USBi_UI|USBi_UEI|USBi_PCI|USBi_URI|USBi_SLI); USBi_UI|USBi_UEI|USBi_PCI|USBi_URI|USBi_SLI);
hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS);
} else { } else {
hw_write(ci, OP_USBINTR, ~0, 0); hw_write(ci, OP_USBINTR, ~0, 0);
hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
} }
return 0; return 0;
} }
...@@ -1508,7 +1506,9 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active) ...@@ -1508,7 +1506,9 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active)
hw_device_reset(ci); hw_device_reset(ci);
hw_device_state(ci, ci->ep0out->qh.dma); hw_device_state(ci, ci->ep0out->qh.dma);
usb_gadget_set_state(_gadget, USB_STATE_POWERED); usb_gadget_set_state(_gadget, USB_STATE_POWERED);
usb_udc_vbus_handler(_gadget, true);
} else { } else {
usb_udc_vbus_handler(_gadget, false);
if (ci->driver) if (ci->driver)
ci->driver->disconnect(&ci->gadget); ci->driver->disconnect(&ci->gadget);
hw_device_state(ci, 0); hw_device_state(ci, 0);
...@@ -1574,13 +1574,12 @@ static int ci_udc_pullup(struct usb_gadget *_gadget, int is_on) ...@@ -1574,13 +1574,12 @@ static int ci_udc_pullup(struct usb_gadget *_gadget, int is_on)
{ {
struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget);
if (!ci->vbus_active) pm_runtime_get_sync(&ci->gadget.dev);
return -EOPNOTSUPP;
if (is_on) if (is_on)
hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS);
else else
hw_write(ci, OP_USBCMD, USBCMD_RS, 0); hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
pm_runtime_put_sync(&ci->gadget.dev);
return 0; return 0;
} }
...@@ -1710,6 +1709,7 @@ static int ci_udc_start(struct usb_gadget *gadget, ...@@ -1710,6 +1709,7 @@ static int ci_udc_start(struct usb_gadget *gadget,
spin_lock_irqsave(&ci->lock, flags); spin_lock_irqsave(&ci->lock, flags);
hw_device_reset(ci); hw_device_reset(ci);
} else { } else {
usb_udc_vbus_handler(&ci->gadget, false);
pm_runtime_put_sync(&ci->gadget.dev); pm_runtime_put_sync(&ci->gadget.dev);
return retval; return retval;
} }
......
...@@ -59,11 +59,13 @@ config USB_DWC2_PLATFORM ...@@ -59,11 +59,13 @@ config USB_DWC2_PLATFORM
config USB_DWC2_PCI config USB_DWC2_PCI
tristate "DWC2 PCI" tristate "DWC2 PCI"
depends on USB_DWC2_HOST && PCI depends on PCI
default USB_DWC2_HOST default n
select USB_DWC2_PLATFORM
select NOP_USB_XCEIV
help help
The Designware USB2.0 PCI interface module for controllers The Designware USB2.0 PCI interface module for controllers
connected to a PCI bus. This is only used for host mode. connected to a PCI bus.
config USB_DWC2_DEBUG config USB_DWC2_DEBUG
bool "Enable Debugging Messages" bool "Enable Debugging Messages"
......
...@@ -19,10 +19,8 @@ endif ...@@ -19,10 +19,8 @@ endif
# mode. The PCI bus interface module will called dwc2_pci.ko and the platform # mode. The PCI bus interface module will called dwc2_pci.ko and the platform
# interface module will be called dwc2_platform.ko. # interface module will be called dwc2_platform.ko.
ifneq ($(CONFIG_USB_DWC2_PCI),) obj-$(CONFIG_USB_DWC2_PCI) += dwc2_pci.o
obj-$(CONFIG_USB_DWC2) += dwc2_pci.o dwc2_pci-y := pci.o
dwc2_pci-y := pci.o
endif
obj-$(CONFIG_USB_DWC2_PLATFORM) += dwc2_platform.o obj-$(CONFIG_USB_DWC2_PLATFORM) += dwc2_platform.o
dwc2_platform-y := platform.o dwc2_platform-y := platform.o
...@@ -593,6 +593,8 @@ struct dwc2_hsotg { ...@@ -593,6 +593,8 @@ struct dwc2_hsotg {
struct dwc2_core_params *core_params; struct dwc2_core_params *core_params;
enum usb_otg_state op_state; enum usb_otg_state op_state;
enum usb_dr_mode dr_mode; enum usb_dr_mode dr_mode;
unsigned int hcd_enabled:1;
unsigned int gadget_enabled:1;
struct phy *phy; struct phy *phy;
struct usb_phy *uphy; struct usb_phy *uphy;
......
...@@ -257,6 +257,14 @@ static void dwc2_hcd_cleanup_channels(struct dwc2_hsotg *hsotg) ...@@ -257,6 +257,14 @@ static void dwc2_hcd_cleanup_channels(struct dwc2_hsotg *hsotg)
*/ */
channel->qh = NULL; channel->qh = NULL;
} }
/* All channels have been freed, mark them available */
if (hsotg->core_params->uframe_sched > 0) {
hsotg->available_host_channels =
hsotg->core_params->host_channels;
} else {
hsotg->non_periodic_channels = 0;
hsotg->periodic_channels = 0;
}
} }
/** /**
......
...@@ -50,113 +50,97 @@ ...@@ -50,113 +50,97 @@
#include <linux/usb/hcd.h> #include <linux/usb/hcd.h>
#include <linux/usb/ch11.h> #include <linux/usb/ch11.h>
#include <linux/platform_device.h>
#include <linux/usb/usb_phy_generic.h>
#include "core.h"
#include "hcd.h"
#define PCI_VENDOR_ID_SYNOPSYS 0x16c3
#define PCI_PRODUCT_ID_HAPS_HSOTG 0xabc0 #define PCI_PRODUCT_ID_HAPS_HSOTG 0xabc0
static const char dwc2_driver_name[] = "dwc2"; static const char dwc2_driver_name[] = "dwc2-pci";
static const struct dwc2_core_params dwc2_module_params = { struct dwc2_pci_glue {
.otg_cap = -1, struct platform_device *dwc2;
.otg_ver = -1, struct platform_device *phy;
.dma_enable = -1,
.dma_desc_enable = 0,
.speed = -1,
.enable_dynamic_fifo = -1,
.en_multiple_tx_fifo = -1,
.host_rx_fifo_size = 1024,
.host_nperio_tx_fifo_size = 256,
.host_perio_tx_fifo_size = 1024,
.max_transfer_size = 65535,
.max_packet_count = 511,
.host_channels = -1,
.phy_type = -1,
.phy_utmi_width = -1,
.phy_ulpi_ddr = -1,
.phy_ulpi_ext_vbus = -1,
.i2c_enable = -1,
.ulpi_fs_ls = -1,
.host_support_fs_ls_low_power = -1,
.host_ls_low_power_phy_clk = -1,
.ts_dline = -1,
.reload_ctl = -1,
.ahbcfg = -1,
.uframe_sched = -1,
}; };
/** static void dwc2_pci_remove(struct pci_dev *pci)
* dwc2_driver_remove() - Called when the DWC_otg core is unregistered with the
* DWC_otg driver
*
* @dev: Bus device
*
* This routine is called, for example, when the rmmod command is executed. The
* device may or may not be electrically present. If it is present, the driver
* stops device processing. Any resources used on behalf of this device are
* freed.
*/
static void dwc2_driver_remove(struct pci_dev *dev)
{ {
struct dwc2_hsotg *hsotg = pci_get_drvdata(dev); struct dwc2_pci_glue *glue = pci_get_drvdata(pci);
dwc2_hcd_remove(hsotg); platform_device_unregister(glue->dwc2);
pci_disable_device(dev); usb_phy_generic_unregister(glue->phy);
kfree(glue);
pci_set_drvdata(pci, NULL);
} }
/** static int dwc2_pci_probe(struct pci_dev *pci,
* dwc2_driver_probe() - Called when the DWC_otg core is bound to the DWC_otg
* driver
*
* @dev: Bus device
*
* This routine creates the driver components required to control the device
* (core, HCD, and PCD) and initializes the device. The driver components are
* stored in a dwc2_hsotg structure. A reference to the dwc2_hsotg is saved
* in the device private data. This allows the driver to access the dwc2_hsotg
* structure on subsequent calls to driver methods for this device.
*/
static int dwc2_driver_probe(struct pci_dev *dev,
const struct pci_device_id *id) const struct pci_device_id *id)
{ {
struct dwc2_hsotg *hsotg; struct resource res[2];
int retval; struct platform_device *dwc2;
struct platform_device *phy;
int ret;
struct device *dev = &pci->dev;
struct dwc2_pci_glue *glue;
ret = pcim_enable_device(pci);
if (ret) {
dev_err(dev, "failed to enable pci device\n");
return -ENODEV;
}
pci_set_master(pci);
hsotg = devm_kzalloc(&dev->dev, sizeof(*hsotg), GFP_KERNEL); dwc2 = platform_device_alloc("dwc2", PLATFORM_DEVID_AUTO);
if (!hsotg) if (!dwc2) {
dev_err(dev, "couldn't allocate dwc2 device\n");
return -ENOMEM; return -ENOMEM;
}
hsotg->dev = &dev->dev; memset(res, 0x00, sizeof(struct resource) * ARRAY_SIZE(res));
hsotg->regs = devm_ioremap_resource(&dev->dev, &dev->resource[0]);
if (IS_ERR(hsotg->regs))
return PTR_ERR(hsotg->regs);
dev_dbg(&dev->dev, "mapped PA %08lx to VA %p\n", res[0].start = pci_resource_start(pci, 0);
(unsigned long)pci_resource_start(dev, 0), hsotg->regs); res[0].end = pci_resource_end(pci, 0);
res[0].name = "dwc2";
res[0].flags = IORESOURCE_MEM;
if (pci_enable_device(dev) < 0) res[1].start = pci->irq;
return -ENODEV; res[1].name = "dwc2";
res[1].flags = IORESOURCE_IRQ;
pci_set_master(dev); ret = platform_device_add_resources(dwc2, res, ARRAY_SIZE(res));
if (ret) {
dev_err(dev, "couldn't add resources to dwc2 device\n");
return ret;
}
retval = devm_request_irq(hsotg->dev, dev->irq, dwc2->dev.parent = dev;
dwc2_handle_common_intr, IRQF_SHARED,
dev_name(hsotg->dev), hsotg);
if (retval)
return retval;
spin_lock_init(&hsotg->lock); phy = usb_phy_generic_register();
retval = dwc2_hcd_init(hsotg, dev->irq, &dwc2_module_params); if (IS_ERR(phy)) {
if (retval) { dev_err(dev, "error registering generic PHY (%ld)\n",
pci_disable_device(dev); PTR_ERR(phy));
return retval; return PTR_ERR(phy);
} }
pci_set_drvdata(dev, hsotg); ret = platform_device_add(dwc2);
if (ret) {
dev_err(dev, "failed to register dwc2 device\n");
goto err;
}
glue = kzalloc(sizeof(*glue), GFP_KERNEL);
if (!glue)
return -ENOMEM;
glue->phy = phy;
glue->dwc2 = dwc2;
pci_set_drvdata(pci, glue);
return retval; return 0;
err:
usb_phy_generic_unregister(phy);
platform_device_put(dwc2);
return ret;
} }
static const struct pci_device_id dwc2_pci_ids[] = { static const struct pci_device_id dwc2_pci_ids[] = {
...@@ -174,8 +158,8 @@ MODULE_DEVICE_TABLE(pci, dwc2_pci_ids); ...@@ -174,8 +158,8 @@ MODULE_DEVICE_TABLE(pci, dwc2_pci_ids);
static struct pci_driver dwc2_pci_driver = { static struct pci_driver dwc2_pci_driver = {
.name = dwc2_driver_name, .name = dwc2_driver_name,
.id_table = dwc2_pci_ids, .id_table = dwc2_pci_ids,
.probe = dwc2_driver_probe, .probe = dwc2_pci_probe,
.remove = dwc2_driver_remove, .remove = dwc2_pci_remove,
}; };
module_pci_driver(dwc2_pci_driver); module_pci_driver(dwc2_pci_driver);
......
...@@ -121,7 +121,9 @@ static int dwc2_driver_remove(struct platform_device *dev) ...@@ -121,7 +121,9 @@ static int dwc2_driver_remove(struct platform_device *dev)
{ {
struct dwc2_hsotg *hsotg = platform_get_drvdata(dev); struct dwc2_hsotg *hsotg = platform_get_drvdata(dev);
if (hsotg->hcd_enabled)
dwc2_hcd_remove(hsotg); dwc2_hcd_remove(hsotg);
if (hsotg->gadget_enabled)
s3c_hsotg_remove(hsotg); s3c_hsotg_remove(hsotg);
return 0; return 0;
...@@ -234,12 +236,23 @@ static int dwc2_driver_probe(struct platform_device *dev) ...@@ -234,12 +236,23 @@ static int dwc2_driver_probe(struct platform_device *dev)
spin_lock_init(&hsotg->lock); spin_lock_init(&hsotg->lock);
mutex_init(&hsotg->init_mutex); mutex_init(&hsotg->init_mutex);
if (hsotg->dr_mode != USB_DR_MODE_HOST) {
retval = dwc2_gadget_init(hsotg, irq); retval = dwc2_gadget_init(hsotg, irq);
if (retval) if (retval)
return retval; return retval;
hsotg->gadget_enabled = 1;
}
if (hsotg->dr_mode != USB_DR_MODE_PERIPHERAL) {
retval = dwc2_hcd_init(hsotg, irq, params); retval = dwc2_hcd_init(hsotg, irq, params);
if (retval) if (retval) {
if (hsotg->gadget_enabled)
s3c_hsotg_remove(hsotg);
return retval; return retval;
}
hsotg->hcd_enabled = 1;
}
platform_set_drvdata(dev, hsotg); platform_set_drvdata(dev, hsotg);
......
...@@ -104,11 +104,4 @@ config USB_DWC3_DEBUG ...@@ -104,11 +104,4 @@ config USB_DWC3_DEBUG
help help
Say Y here to enable debugging messages on DWC3 Driver. Say Y here to enable debugging messages on DWC3 Driver.
config DWC3_HOST_USB3_LPM_ENABLE
bool "Enable USB3 LPM Capability"
depends on USB_DWC3_HOST=y || USB_DWC3_DUAL_ROLE=y
default n
help
Select this when you want to enable USB3 LPM with dwc3 xhci host.
endif endif
...@@ -774,17 +774,13 @@ static int dwc3_probe(struct platform_device *pdev) ...@@ -774,17 +774,13 @@ static int dwc3_probe(struct platform_device *pdev)
* since it will be requested by the xhci-plat driver. * since it will be requested by the xhci-plat driver.
*/ */
regs = devm_ioremap_resource(dev, res); regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs)) if (IS_ERR(regs)) {
return PTR_ERR(regs); ret = PTR_ERR(regs);
goto err0;
}
dwc->regs = regs; dwc->regs = regs;
dwc->regs_size = resource_size(res); dwc->regs_size = resource_size(res);
/*
* restore res->start back to its original value so that,
* in case the probe is deferred, we don't end up getting error in
* request the memory region the next time probe is called.
*/
res->start -= DWC3_GLOBALS_REGS_START;
/* default to highest possible threshold */ /* default to highest possible threshold */
lpm_nyet_threshold = 0xff; lpm_nyet_threshold = 0xff;
...@@ -808,6 +804,8 @@ static int dwc3_probe(struct platform_device *pdev) ...@@ -808,6 +804,8 @@ static int dwc3_probe(struct platform_device *pdev)
"snps,is-utmi-l1-suspend"); "snps,is-utmi-l1-suspend");
of_property_read_u8(node, "snps,hird-threshold", of_property_read_u8(node, "snps,hird-threshold",
&hird_threshold); &hird_threshold);
dwc->usb3_lpm_capable = of_property_read_bool(node,
"snps,usb3_lpm_capable");
dwc->needs_fifo_resize = of_property_read_bool(node, dwc->needs_fifo_resize = of_property_read_bool(node,
"tx-fifo-resize"); "tx-fifo-resize");
...@@ -848,6 +846,7 @@ static int dwc3_probe(struct platform_device *pdev) ...@@ -848,6 +846,7 @@ static int dwc3_probe(struct platform_device *pdev)
hird_threshold = pdata->hird_threshold; hird_threshold = pdata->hird_threshold;
dwc->needs_fifo_resize = pdata->tx_fifo_resize; dwc->needs_fifo_resize = pdata->tx_fifo_resize;
dwc->usb3_lpm_capable = pdata->usb3_lpm_capable;
dwc->dr_mode = pdata->dr_mode; dwc->dr_mode = pdata->dr_mode;
dwc->disable_scramble_quirk = pdata->disable_scramble_quirk; dwc->disable_scramble_quirk = pdata->disable_scramble_quirk;
...@@ -878,7 +877,7 @@ static int dwc3_probe(struct platform_device *pdev) ...@@ -878,7 +877,7 @@ static int dwc3_probe(struct platform_device *pdev)
ret = dwc3_core_get_phy(dwc); ret = dwc3_core_get_phy(dwc);
if (ret) if (ret)
return ret; goto err0;
spin_lock_init(&dwc->lock); spin_lock_init(&dwc->lock);
platform_set_drvdata(pdev, dwc); platform_set_drvdata(pdev, dwc);
...@@ -899,7 +898,7 @@ static int dwc3_probe(struct platform_device *pdev) ...@@ -899,7 +898,7 @@ static int dwc3_probe(struct platform_device *pdev)
if (ret) { if (ret) {
dev_err(dwc->dev, "failed to allocate event buffers\n"); dev_err(dwc->dev, "failed to allocate event buffers\n");
ret = -ENOMEM; ret = -ENOMEM;
goto err0; goto err1;
} }
if (IS_ENABLED(CONFIG_USB_DWC3_HOST)) if (IS_ENABLED(CONFIG_USB_DWC3_HOST))
...@@ -913,65 +912,81 @@ static int dwc3_probe(struct platform_device *pdev) ...@@ -913,65 +912,81 @@ static int dwc3_probe(struct platform_device *pdev)
ret = dwc3_core_init(dwc); ret = dwc3_core_init(dwc);
if (ret) { if (ret) {
dev_err(dev, "failed to initialize core\n"); dev_err(dev, "failed to initialize core\n");
goto err0; goto err1;
} }
usb_phy_set_suspend(dwc->usb2_phy, 0); usb_phy_set_suspend(dwc->usb2_phy, 0);
usb_phy_set_suspend(dwc->usb3_phy, 0); usb_phy_set_suspend(dwc->usb3_phy, 0);
ret = phy_power_on(dwc->usb2_generic_phy); ret = phy_power_on(dwc->usb2_generic_phy);
if (ret < 0) if (ret < 0)
goto err1; goto err2;
ret = phy_power_on(dwc->usb3_generic_phy); ret = phy_power_on(dwc->usb3_generic_phy);
if (ret < 0) if (ret < 0)
goto err_usb2phy_power; goto err3;
ret = dwc3_event_buffers_setup(dwc); ret = dwc3_event_buffers_setup(dwc);
if (ret) { if (ret) {
dev_err(dwc->dev, "failed to setup event buffers\n"); dev_err(dwc->dev, "failed to setup event buffers\n");
goto err_usb3phy_power; goto err4;
} }
ret = dwc3_core_init_mode(dwc); ret = dwc3_core_init_mode(dwc);
if (ret) if (ret)
goto err2; goto err5;
ret = dwc3_debugfs_init(dwc); ret = dwc3_debugfs_init(dwc);
if (ret) { if (ret) {
dev_err(dev, "failed to initialize debugfs\n"); dev_err(dev, "failed to initialize debugfs\n");
goto err3; goto err6;
} }
pm_runtime_allow(dev); pm_runtime_allow(dev);
return 0; return 0;
err3: err6:
dwc3_core_exit_mode(dwc); dwc3_core_exit_mode(dwc);
err2: err5:
dwc3_event_buffers_cleanup(dwc); dwc3_event_buffers_cleanup(dwc);
err_usb3phy_power: err4:
phy_power_off(dwc->usb3_generic_phy); phy_power_off(dwc->usb3_generic_phy);
err_usb2phy_power: err3:
phy_power_off(dwc->usb2_generic_phy); phy_power_off(dwc->usb2_generic_phy);
err1: err2:
usb_phy_set_suspend(dwc->usb2_phy, 1); usb_phy_set_suspend(dwc->usb2_phy, 1);
usb_phy_set_suspend(dwc->usb3_phy, 1); usb_phy_set_suspend(dwc->usb3_phy, 1);
dwc3_core_exit(dwc); dwc3_core_exit(dwc);
err0: err1:
dwc3_free_event_buffers(dwc); dwc3_free_event_buffers(dwc);
err0:
/*
* restore res->start back to its original value so that, in case the
* probe is deferred, we don't end up getting error in request the
* memory region the next time probe is called.
*/
res->start -= DWC3_GLOBALS_REGS_START;
return ret; return ret;
} }
static int dwc3_remove(struct platform_device *pdev) static int dwc3_remove(struct platform_device *pdev)
{ {
struct dwc3 *dwc = platform_get_drvdata(pdev); struct dwc3 *dwc = platform_get_drvdata(pdev);
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
/*
* restore res->start back to its original value so that, in case the
* probe is deferred, we don't end up getting error in request the
* memory region the next time probe is called.
*/
res->start -= DWC3_GLOBALS_REGS_START;
dwc3_debugfs_exit(dwc); dwc3_debugfs_exit(dwc);
dwc3_core_exit_mode(dwc); dwc3_core_exit_mode(dwc);
......
...@@ -689,6 +689,7 @@ struct dwc3_scratchpad_array { ...@@ -689,6 +689,7 @@ struct dwc3_scratchpad_array {
* @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround * @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround
* @start_config_issued: true when StartConfig command has been issued * @start_config_issued: true when StartConfig command has been issued
* @three_stage_setup: set if we perform a three phase setup * @three_stage_setup: set if we perform a three phase setup
* @usb3_lpm_capable: set if hadrware supports Link Power Management
* @disable_scramble_quirk: set if we enable the disable scramble quirk * @disable_scramble_quirk: set if we enable the disable scramble quirk
* @u2exit_lfps_quirk: set if we enable u2exit lfps quirk * @u2exit_lfps_quirk: set if we enable u2exit lfps quirk
* @u2ss_inp3_quirk: set if we enable P3 OK for U2/SS Inactive quirk * @u2ss_inp3_quirk: set if we enable P3 OK for U2/SS Inactive quirk
...@@ -812,6 +813,7 @@ struct dwc3 { ...@@ -812,6 +813,7 @@ struct dwc3 {
unsigned setup_packet_pending:1; unsigned setup_packet_pending:1;
unsigned start_config_issued:1; unsigned start_config_issued:1;
unsigned three_stage_setup:1; unsigned three_stage_setup:1;
unsigned usb3_lpm_capable:1;
unsigned disable_scramble_quirk:1; unsigned disable_scramble_quirk:1;
unsigned u2exit_lfps_quirk:1; unsigned u2exit_lfps_quirk:1;
......
...@@ -325,15 +325,6 @@ static irqreturn_t dwc3_omap_interrupt(int irq, void *_omap) ...@@ -325,15 +325,6 @@ static irqreturn_t dwc3_omap_interrupt(int irq, void *_omap)
return IRQ_HANDLED; return IRQ_HANDLED;
} }
static int dwc3_omap_remove_core(struct device *dev, void *c)
{
struct platform_device *pdev = to_platform_device(dev);
of_device_unregister(pdev);
return 0;
}
static void dwc3_omap_enable_irqs(struct dwc3_omap *omap) static void dwc3_omap_enable_irqs(struct dwc3_omap *omap)
{ {
u32 reg; u32 reg;
...@@ -600,7 +591,7 @@ static int dwc3_omap_remove(struct platform_device *pdev) ...@@ -600,7 +591,7 @@ static int dwc3_omap_remove(struct platform_device *pdev)
if (omap->extcon_id_dev.edev) if (omap->extcon_id_dev.edev)
extcon_unregister_interest(&omap->extcon_id_dev); extcon_unregister_interest(&omap->extcon_id_dev);
dwc3_omap_disable_irqs(omap); dwc3_omap_disable_irqs(omap);
device_for_each_child(&pdev->dev, NULL, dwc3_omap_remove_core); of_platform_depopulate(omap->dev);
pm_runtime_put_sync(&pdev->dev); pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev); pm_runtime_disable(&pdev->dev);
......
...@@ -24,8 +24,6 @@ ...@@ -24,8 +24,6 @@
#include "platform_data.h" #include "platform_data.h"
/* FIXME define these in <linux/pci_ids.h> */
#define PCI_VENDOR_ID_SYNOPSYS 0x16c3
#define PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3 0xabcd #define PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3 0xabcd
#define PCI_DEVICE_ID_INTEL_BYT 0x0f37 #define PCI_DEVICE_ID_INTEL_BYT 0x0f37
#define PCI_DEVICE_ID_INTEL_MRFLD 0x119e #define PCI_DEVICE_ID_INTEL_MRFLD 0x119e
......
...@@ -1855,7 +1855,6 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep, ...@@ -1855,7 +1855,6 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep,
unsigned int i; unsigned int i;
int ret; int ret;
do {
req = next_request(&dep->req_queued); req = next_request(&dep->req_queued);
if (!req) { if (!req) {
WARN_ON_ONCE(1); WARN_ON_ONCE(1);
...@@ -1874,14 +1873,10 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep, ...@@ -1874,14 +1873,10 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep,
event, status); event, status);
if (ret) if (ret)
break; break;
}while (++i < req->request.num_mapped_sgs); } while (++i < req->request.num_mapped_sgs);
dwc3_gadget_giveback(dep, req, status); dwc3_gadget_giveback(dep, req, status);
if (ret)
break;
} while (1);
if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && if (usb_endpoint_xfer_isoc(dep->endpoint.desc) &&
list_empty(&dep->req_queued)) { list_empty(&dep->req_queued)) {
if (list_empty(&dep->request_list)) { if (list_empty(&dep->request_list)) {
......
...@@ -49,9 +49,7 @@ int dwc3_host_init(struct dwc3 *dwc) ...@@ -49,9 +49,7 @@ int dwc3_host_init(struct dwc3 *dwc)
memset(&pdata, 0, sizeof(pdata)); memset(&pdata, 0, sizeof(pdata));
#ifdef CONFIG_DWC3_HOST_USB3_LPM_ENABLE pdata.usb3_lpm_capable = dwc->usb3_lpm_capable;
pdata.usb3_lpm_capable = 1;
#endif
ret = platform_device_add_data(xhci, &pdata, sizeof(pdata)); ret = platform_device_add_data(xhci, &pdata, sizeof(pdata));
if (ret) { if (ret) {
......
...@@ -24,6 +24,7 @@ struct dwc3_platform_data { ...@@ -24,6 +24,7 @@ struct dwc3_platform_data {
enum usb_device_speed maximum_speed; enum usb_device_speed maximum_speed;
enum usb_dr_mode dr_mode; enum usb_dr_mode dr_mode;
bool tx_fifo_resize; bool tx_fifo_resize;
bool usb3_lpm_capable;
unsigned is_utmi_l1_suspend:1; unsigned is_utmi_l1_suspend:1;
u8 hird_threshold; u8 hird_threshold;
......
...@@ -196,6 +196,9 @@ config USB_F_MIDI ...@@ -196,6 +196,9 @@ config USB_F_MIDI
config USB_F_HID config USB_F_HID
tristate tristate
config USB_F_PRINTER
tristate
choice choice
tristate "USB Gadget Drivers" tristate "USB Gadget Drivers"
default USB_ETH default USB_ETH
...@@ -434,6 +437,20 @@ config USB_CONFIGFS_F_UVC ...@@ -434,6 +437,20 @@ config USB_CONFIGFS_F_UVC
device. It provides a userspace API to process UVC control requests device. It provides a userspace API to process UVC control requests
and stream video data to the host. and stream video data to the host.
config USB_CONFIGFS_F_PRINTER
bool "Printer function"
select USB_F_PRINTER
depends on USB_CONFIGFS
help
The Printer function channels data between the USB host and a
userspace program driving the print engine. The user space
program reads and writes the device file /dev/g_printer<X> to
receive or send printer data. It can use ioctl calls to
the device file to get or set printer status.
For more information, see Documentation/usb/gadget_printer.txt
which includes sample code for accessing the device file.
source "drivers/usb/gadget/legacy/Kconfig" source "drivers/usb/gadget/legacy/Kconfig"
endchoice endchoice
......
...@@ -1161,11 +1161,11 @@ static struct usb_gadget_string_container *copy_gadget_strings( ...@@ -1161,11 +1161,11 @@ static struct usb_gadget_string_container *copy_gadget_strings(
* This function will create a deep copy of usb_gadget_strings and usb_string * This function will create a deep copy of usb_gadget_strings and usb_string
* and attach it to the cdev. The actual string (usb_string.s) will not be * and attach it to the cdev. The actual string (usb_string.s) will not be
* copied but only a referenced will be made. The struct usb_gadget_strings * copied but only a referenced will be made. The struct usb_gadget_strings
* array may contain multiple languges and should be NULL terminated. * array may contain multiple languages and should be NULL terminated.
* The ->language pointer of each struct usb_gadget_strings has to contain the * The ->language pointer of each struct usb_gadget_strings has to contain the
* same amount of entries. * same amount of entries.
* For instance: sp[0] is en-US, sp[1] is es-ES. It is expected that the first * For instance: sp[0] is en-US, sp[1] is es-ES. It is expected that the first
* usb_string entry of es-ES containts the translation of the first usb_string * usb_string entry of es-ES contains the translation of the first usb_string
* entry of en-US. Therefore both entries become the same id assign. * entry of en-US. Therefore both entries become the same id assign.
*/ */
struct usb_string *usb_gstrings_attach(struct usb_composite_dev *cdev, struct usb_string *usb_gstrings_attach(struct usb_composite_dev *cdev,
...@@ -1472,6 +1472,13 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) ...@@ -1472,6 +1472,13 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
req->length = 0; req->length = 0;
gadget->ep0->driver_data = cdev; gadget->ep0->driver_data = cdev;
/*
* Don't let non-standard requests match any of the cases below
* by accident.
*/
if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD)
goto unknown;
switch (ctrl->bRequest) { switch (ctrl->bRequest) {
/* we handle all standard USB descriptors */ /* we handle all standard USB descriptors */
...@@ -1751,6 +1758,10 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) ...@@ -1751,6 +1758,10 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
* take such requests too, if that's ever needed: to work * take such requests too, if that's ever needed: to work
* in config 0, etc. * in config 0, etc.
*/ */
list_for_each_entry(f, &cdev->config->functions, list)
if (f->req_match && f->req_match(f, ctrl))
goto try_fun_setup;
f = NULL;
switch (ctrl->bRequestType & USB_RECIP_MASK) { switch (ctrl->bRequestType & USB_RECIP_MASK) {
case USB_RECIP_INTERFACE: case USB_RECIP_INTERFACE:
if (!cdev->config || intf >= MAX_CONFIG_INTERFACES) if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
...@@ -1768,7 +1779,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) ...@@ -1768,7 +1779,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
f = NULL; f = NULL;
break; break;
} }
try_fun_setup:
if (f && f->setup) if (f && f->setup)
value = f->setup(f, ctrl); value = f->setup(f, ctrl);
else { else {
......
...@@ -42,3 +42,5 @@ usb_f_midi-y := f_midi.o ...@@ -42,3 +42,5 @@ usb_f_midi-y := f_midi.o
obj-$(CONFIG_USB_F_MIDI) += usb_f_midi.o obj-$(CONFIG_USB_F_MIDI) += usb_f_midi.o
usb_f_hid-y := f_hid.o usb_f_hid-y := f_hid.o
obj-$(CONFIG_USB_F_HID) += usb_f_hid.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
...@@ -908,7 +908,6 @@ static void hidg_unbind(struct usb_configuration *c, struct usb_function *f) ...@@ -908,7 +908,6 @@ static void hidg_unbind(struct usb_configuration *c, struct usb_function *f)
/* disable/free request and end point */ /* disable/free request and end point */
usb_ep_disable(hidg->in_ep); usb_ep_disable(hidg->in_ep);
usb_ep_dequeue(hidg->in_ep, hidg->req);
kfree(hidg->req->buf); kfree(hidg->req->buf);
usb_ep_free_request(hidg->in_ep, hidg->req); usb_ep_free_request(hidg->in_ep, hidg->req);
......
...@@ -1085,7 +1085,7 @@ static int do_inquiry(struct fsg_common *common, struct fsg_buffhd *bh) ...@@ -1085,7 +1085,7 @@ static int do_inquiry(struct fsg_common *common, struct fsg_buffhd *bh)
if (!curlun) { /* Unsupported LUNs are okay */ if (!curlun) { /* Unsupported LUNs are okay */
common->bad_lun_okay = 1; common->bad_lun_okay = 1;
memset(buf, 0, 36); memset(buf, 0, 36);
buf[0] = 0x7f; /* Unsupported, no device-type */ buf[0] = TYPE_NO_LUN; /* Unsupported, no device-type */
buf[4] = 31; /* Additional length */ buf[4] = 31; /* Additional length */
return 36; return 36;
} }
......
This diff is collapsed.
/*
* u_printer.h
*
* Utility definitions for the printer function
*
* Copyright (c) 2015 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef U_PRINTER_H
#define U_PRINTER_H
#include <linux/usb/composite.h>
#define PNP_STRING_LEN 1024
struct f_printer_opts {
struct usb_function_instance func_inst;
int minor;
char pnp_string[PNP_STRING_LEN];
unsigned q_len;
/*
* Protect the data from concurrent access by read/write
* and create symlink/remove symlink
*/
struct mutex lock;
int refcnt;
};
#endif /* U_PRINTER_H */
...@@ -912,7 +912,7 @@ static int gs_put_char(struct tty_struct *tty, unsigned char ch) ...@@ -912,7 +912,7 @@ static int gs_put_char(struct tty_struct *tty, unsigned char ch)
unsigned long flags; unsigned long flags;
int status; int status;
pr_vdebug("gs_put_char: (%d,%p) char=0x%x, called from %pf\n", pr_vdebug("gs_put_char: (%d,%p) char=0x%x, called from %ps\n",
port->port_num, tty, ch, __builtin_return_address(0)); port->port_num, tty, ch, __builtin_return_address(0));
spin_lock_irqsave(&port->port_lock, flags); spin_lock_irqsave(&port->port_lock, flags);
......
...@@ -301,6 +301,7 @@ config USB_MIDI_GADGET ...@@ -301,6 +301,7 @@ config USB_MIDI_GADGET
config USB_G_PRINTER config USB_G_PRINTER
tristate "Printer Gadget" tristate "Printer Gadget"
select USB_LIBCOMPOSITE select USB_LIBCOMPOSITE
select USB_F_PRINTER
help help
The Printer Gadget channels data between the USB host and a The Printer Gadget channels data between the USB host and a
userspace program driving the print engine. The user space userspace program driving the print engine. The user space
......
This diff is collapsed.
...@@ -152,7 +152,7 @@ static int regs_dbg_open(struct inode *inode, struct file *file) ...@@ -152,7 +152,7 @@ static int regs_dbg_open(struct inode *inode, struct file *file)
spin_lock_irq(&udc->lock); spin_lock_irq(&udc->lock);
for (i = 0; i < inode->i_size / 4; i++) for (i = 0; i < inode->i_size / 4; i++)
data[i] = __raw_readl(udc->regs + i * 4); data[i] = usba_io_readl(udc->regs + i * 4);
spin_unlock_irq(&udc->lock); spin_unlock_irq(&udc->lock);
file->private_data = data; file->private_data = data;
...@@ -1249,7 +1249,7 @@ static int handle_ep0_setup(struct usba_udc *udc, struct usba_ep *ep, ...@@ -1249,7 +1249,7 @@ static int handle_ep0_setup(struct usba_udc *udc, struct usba_ep *ep,
if (crq->wLength != cpu_to_le16(sizeof(status))) if (crq->wLength != cpu_to_le16(sizeof(status)))
goto stall; goto stall;
ep->state = DATA_STAGE_IN; ep->state = DATA_STAGE_IN;
__raw_writew(status, ep->fifo); usba_io_writew(status, ep->fifo);
usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY); usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY);
break; break;
} }
...@@ -1739,43 +1739,95 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) ...@@ -1739,43 +1739,95 @@ static irqreturn_t usba_udc_irq(int irq, void *devid)
return IRQ_HANDLED; return IRQ_HANDLED;
} }
static irqreturn_t usba_vbus_irq(int irq, void *devid) static int start_clock(struct usba_udc *udc)
{ {
struct usba_udc *udc = devid; int ret;
int vbus;
/* debounce */ if (udc->clocked)
udelay(10); return 0;
spin_lock(&udc->lock); ret = clk_prepare_enable(udc->pclk);
if (ret)
return ret;
ret = clk_prepare_enable(udc->hclk);
if (ret) {
clk_disable_unprepare(udc->pclk);
return ret;
}
/* May happen if Vbus pin toggles during probe() */ udc->clocked = true;
if (!udc->driver) return 0;
goto out; }
vbus = vbus_is_present(udc); static void stop_clock(struct usba_udc *udc)
if (vbus != udc->vbus_prev) { {
if (vbus) { if (!udc->clocked)
return;
clk_disable_unprepare(udc->hclk);
clk_disable_unprepare(udc->pclk);
udc->clocked = false;
}
static int usba_start(struct usba_udc *udc)
{
unsigned long flags;
int ret;
ret = start_clock(udc);
if (ret)
return ret;
spin_lock_irqsave(&udc->lock, flags);
toggle_bias(udc, 1); toggle_bias(udc, 1);
usba_writel(udc, CTRL, USBA_ENABLE_MASK); usba_writel(udc, CTRL, USBA_ENABLE_MASK);
usba_int_enb_set(udc, USBA_END_OF_RESET); usba_int_enb_set(udc, USBA_END_OF_RESET);
} else { spin_unlock_irqrestore(&udc->lock, flags);
return 0;
}
static void usba_stop(struct usba_udc *udc)
{
unsigned long flags;
spin_lock_irqsave(&udc->lock, flags);
udc->gadget.speed = USB_SPEED_UNKNOWN; udc->gadget.speed = USB_SPEED_UNKNOWN;
reset_all_endpoints(udc); reset_all_endpoints(udc);
/* This will also disable the DP pullup */
toggle_bias(udc, 0); toggle_bias(udc, 0);
usba_writel(udc, CTRL, USBA_DISABLE_MASK); usba_writel(udc, CTRL, USBA_DISABLE_MASK);
if (udc->driver->disconnect) { spin_unlock_irqrestore(&udc->lock, flags);
spin_unlock(&udc->lock);
stop_clock(udc);
}
static irqreturn_t usba_vbus_irq_thread(int irq, void *devid)
{
struct usba_udc *udc = devid;
int vbus;
/* debounce */
udelay(10);
mutex_lock(&udc->vbus_mutex);
vbus = vbus_is_present(udc);
if (vbus != udc->vbus_prev) {
if (vbus) {
usba_start(udc);
} else {
usba_stop(udc);
if (udc->driver->disconnect)
udc->driver->disconnect(&udc->gadget); udc->driver->disconnect(&udc->gadget);
spin_lock(&udc->lock);
}
} }
udc->vbus_prev = vbus; udc->vbus_prev = vbus;
} }
out: mutex_unlock(&udc->vbus_mutex);
spin_unlock(&udc->lock);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
...@@ -1787,55 +1839,47 @@ static int atmel_usba_start(struct usb_gadget *gadget, ...@@ -1787,55 +1839,47 @@ static int atmel_usba_start(struct usb_gadget *gadget,
unsigned long flags; unsigned long flags;
spin_lock_irqsave(&udc->lock, flags); spin_lock_irqsave(&udc->lock, flags);
udc->devstatus = 1 << USB_DEVICE_SELF_POWERED; udc->devstatus = 1 << USB_DEVICE_SELF_POWERED;
udc->driver = driver; udc->driver = driver;
spin_unlock_irqrestore(&udc->lock, flags); spin_unlock_irqrestore(&udc->lock, flags);
ret = clk_prepare_enable(udc->pclk); mutex_lock(&udc->vbus_mutex);
if (ret)
return ret;
ret = clk_prepare_enable(udc->hclk);
if (ret) {
clk_disable_unprepare(udc->pclk);
return ret;
}
udc->vbus_prev = 0;
if (gpio_is_valid(udc->vbus_pin)) if (gpio_is_valid(udc->vbus_pin))
enable_irq(gpio_to_irq(udc->vbus_pin)); enable_irq(gpio_to_irq(udc->vbus_pin));
/* If Vbus is present, enable the controller and wait for reset */ /* If Vbus is present, enable the controller and wait for reset */
spin_lock_irqsave(&udc->lock, flags); udc->vbus_prev = vbus_is_present(udc);
if (vbus_is_present(udc) && udc->vbus_prev == 0) { if (udc->vbus_prev) {
toggle_bias(udc, 1); ret = usba_start(udc);
usba_writel(udc, CTRL, USBA_ENABLE_MASK); if (ret)
usba_int_enb_set(udc, USBA_END_OF_RESET); goto err;
} }
spin_unlock_irqrestore(&udc->lock, flags);
mutex_unlock(&udc->vbus_mutex);
return 0; return 0;
err:
if (gpio_is_valid(udc->vbus_pin))
disable_irq(gpio_to_irq(udc->vbus_pin));
mutex_unlock(&udc->vbus_mutex);
spin_lock_irqsave(&udc->lock, flags);
udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED);
udc->driver = NULL;
spin_unlock_irqrestore(&udc->lock, flags);
return ret;
} }
static int atmel_usba_stop(struct usb_gadget *gadget) static int atmel_usba_stop(struct usb_gadget *gadget)
{ {
struct usba_udc *udc = container_of(gadget, struct usba_udc, gadget); struct usba_udc *udc = container_of(gadget, struct usba_udc, gadget);
unsigned long flags;
if (gpio_is_valid(udc->vbus_pin)) if (gpio_is_valid(udc->vbus_pin))
disable_irq(gpio_to_irq(udc->vbus_pin)); disable_irq(gpio_to_irq(udc->vbus_pin));
spin_lock_irqsave(&udc->lock, flags); usba_stop(udc);
udc->gadget.speed = USB_SPEED_UNKNOWN;
reset_all_endpoints(udc);
spin_unlock_irqrestore(&udc->lock, flags);
/* This will also disable the DP pullup */
toggle_bias(udc, 0);
usba_writel(udc, CTRL, USBA_DISABLE_MASK);
clk_disable_unprepare(udc->hclk);
clk_disable_unprepare(udc->pclk);
udc->driver = NULL; udc->driver = NULL;
...@@ -2057,6 +2101,7 @@ static int usba_udc_probe(struct platform_device *pdev) ...@@ -2057,6 +2101,7 @@ static int usba_udc_probe(struct platform_device *pdev)
return PTR_ERR(hclk); return PTR_ERR(hclk);
spin_lock_init(&udc->lock); spin_lock_init(&udc->lock);
mutex_init(&udc->vbus_mutex);
udc->pdev = pdev; udc->pdev = pdev;
udc->pclk = pclk; udc->pclk = pclk;
udc->hclk = hclk; udc->hclk = hclk;
...@@ -2111,17 +2156,17 @@ static int usba_udc_probe(struct platform_device *pdev) ...@@ -2111,17 +2156,17 @@ static int usba_udc_probe(struct platform_device *pdev)
if (gpio_is_valid(udc->vbus_pin)) { if (gpio_is_valid(udc->vbus_pin)) {
if (!devm_gpio_request(&pdev->dev, udc->vbus_pin, "atmel_usba_udc")) { if (!devm_gpio_request(&pdev->dev, udc->vbus_pin, "atmel_usba_udc")) {
ret = devm_request_irq(&pdev->dev, irq_set_status_flags(gpio_to_irq(udc->vbus_pin),
gpio_to_irq(udc->vbus_pin), IRQ_NOAUTOEN);
usba_vbus_irq, 0, ret = devm_request_threaded_irq(&pdev->dev,
gpio_to_irq(udc->vbus_pin), NULL,
usba_vbus_irq_thread, IRQF_ONESHOT,
"atmel_usba_udc", udc); "atmel_usba_udc", udc);
if (ret) { if (ret) {
udc->vbus_pin = -ENODEV; udc->vbus_pin = -ENODEV;
dev_warn(&udc->pdev->dev, dev_warn(&udc->pdev->dev,
"failed to request vbus irq; " "failed to request vbus irq; "
"assuming always on\n"); "assuming always on\n");
} else {
disable_irq(gpio_to_irq(udc->vbus_pin));
} }
} else { } else {
/* gpio_request fail so use -EINVAL for gpio_is_valid */ /* gpio_request fail so use -EINVAL for gpio_is_valid */
...@@ -2132,6 +2177,7 @@ static int usba_udc_probe(struct platform_device *pdev) ...@@ -2132,6 +2177,7 @@ static int usba_udc_probe(struct platform_device *pdev)
ret = usb_add_gadget_udc(&pdev->dev, &udc->gadget); ret = usb_add_gadget_udc(&pdev->dev, &udc->gadget);
if (ret) if (ret)
return ret; return ret;
device_init_wakeup(&pdev->dev, 1);
usba_init_debugfs(udc); usba_init_debugfs(udc);
for (i = 1; i < udc->num_ep; i++) for (i = 1; i < udc->num_ep; i++)
...@@ -2147,6 +2193,7 @@ static int __exit usba_udc_remove(struct platform_device *pdev) ...@@ -2147,6 +2193,7 @@ static int __exit usba_udc_remove(struct platform_device *pdev)
udc = platform_get_drvdata(pdev); udc = platform_get_drvdata(pdev);
device_init_wakeup(&pdev->dev, 0);
usb_del_gadget_udc(&udc->gadget); usb_del_gadget_udc(&udc->gadget);
for (i = 1; i < udc->num_ep; i++) for (i = 1; i < udc->num_ep; i++)
...@@ -2156,10 +2203,65 @@ static int __exit usba_udc_remove(struct platform_device *pdev) ...@@ -2156,10 +2203,65 @@ static int __exit usba_udc_remove(struct platform_device *pdev)
return 0; return 0;
} }
#ifdef CONFIG_PM
static int usba_udc_suspend(struct device *dev)
{
struct usba_udc *udc = dev_get_drvdata(dev);
/* Not started */
if (!udc->driver)
return 0;
mutex_lock(&udc->vbus_mutex);
if (!device_may_wakeup(dev)) {
usba_stop(udc);
goto out;
}
/*
* Device may wake up. We stay clocked if we failed
* to request vbus irq, assuming always on.
*/
if (gpio_is_valid(udc->vbus_pin)) {
usba_stop(udc);
enable_irq_wake(gpio_to_irq(udc->vbus_pin));
}
out:
mutex_unlock(&udc->vbus_mutex);
return 0;
}
static int usba_udc_resume(struct device *dev)
{
struct usba_udc *udc = dev_get_drvdata(dev);
/* Not started */
if (!udc->driver)
return 0;
if (device_may_wakeup(dev) && gpio_is_valid(udc->vbus_pin))
disable_irq_wake(gpio_to_irq(udc->vbus_pin));
/* If Vbus is present, enable the controller and wait for reset */
mutex_lock(&udc->vbus_mutex);
udc->vbus_prev = vbus_is_present(udc);
if (udc->vbus_prev)
usba_start(udc);
mutex_unlock(&udc->vbus_mutex);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(usba_udc_pm_ops, usba_udc_suspend, usba_udc_resume);
static struct platform_driver udc_driver = { static struct platform_driver udc_driver = {
.remove = __exit_p(usba_udc_remove), .remove = __exit_p(usba_udc_remove),
.driver = { .driver = {
.name = "atmel_usba_udc", .name = "atmel_usba_udc",
.pm = &usba_udc_pm_ops,
.of_match_table = of_match_ptr(atmel_udc_dt_ids), .of_match_table = of_match_ptr(atmel_udc_dt_ids),
}, },
}; };
......
...@@ -191,18 +191,28 @@ ...@@ -191,18 +191,28 @@
| USBA_BF(name, value)) | USBA_BF(name, value))
/* Register access macros */ /* Register access macros */
#ifdef CONFIG_AVR32
#define usba_io_readl __raw_readl
#define usba_io_writel __raw_writel
#define usba_io_writew __raw_writew
#else
#define usba_io_readl readl_relaxed
#define usba_io_writel writel_relaxed
#define usba_io_writew writew_relaxed
#endif
#define usba_readl(udc, reg) \ #define usba_readl(udc, reg) \
__raw_readl((udc)->regs + USBA_##reg) usba_io_readl((udc)->regs + USBA_##reg)
#define usba_writel(udc, reg, value) \ #define usba_writel(udc, reg, value) \
__raw_writel((value), (udc)->regs + USBA_##reg) usba_io_writel((value), (udc)->regs + USBA_##reg)
#define usba_ep_readl(ep, reg) \ #define usba_ep_readl(ep, reg) \
__raw_readl((ep)->ep_regs + USBA_EPT_##reg) usba_io_readl((ep)->ep_regs + USBA_EPT_##reg)
#define usba_ep_writel(ep, reg, value) \ #define usba_ep_writel(ep, reg, value) \
__raw_writel((value), (ep)->ep_regs + USBA_EPT_##reg) usba_io_writel((value), (ep)->ep_regs + USBA_EPT_##reg)
#define usba_dma_readl(ep, reg) \ #define usba_dma_readl(ep, reg) \
__raw_readl((ep)->dma_regs + USBA_DMA_##reg) usba_io_readl((ep)->dma_regs + USBA_DMA_##reg)
#define usba_dma_writel(ep, reg, value) \ #define usba_dma_writel(ep, reg, value) \
__raw_writel((value), (ep)->dma_regs + USBA_DMA_##reg) usba_io_writel((value), (ep)->dma_regs + USBA_DMA_##reg)
/* Calculate base address for a given endpoint or DMA controller */ /* Calculate base address for a given endpoint or DMA controller */
#define USBA_EPT_BASE(x) (0x100 + (x) * 0x20) #define USBA_EPT_BASE(x) (0x100 + (x) * 0x20)
...@@ -313,6 +323,9 @@ struct usba_udc { ...@@ -313,6 +323,9 @@ struct usba_udc {
/* Protect hw registers from concurrent modifications */ /* Protect hw registers from concurrent modifications */
spinlock_t lock; spinlock_t lock;
/* Mutex to prevent concurrent start or stop */
struct mutex vbus_mutex;
void __iomem *regs; void __iomem *regs;
void __iomem *fifo; void __iomem *fifo;
...@@ -328,6 +341,7 @@ struct usba_udc { ...@@ -328,6 +341,7 @@ struct usba_udc {
struct clk *hclk; struct clk *hclk;
struct usba_ep *usba_ep; struct usba_ep *usba_ep;
bool bias_pulse_needed; bool bias_pulse_needed;
bool clocked;
u16 devstatus; u16 devstatus;
......
...@@ -2631,7 +2631,7 @@ static int __init init(void) ...@@ -2631,7 +2631,7 @@ static int __init init(void)
return -EINVAL; return -EINVAL;
if (mod_data.num < 1 || mod_data.num > MAX_NUM_UDC) { if (mod_data.num < 1 || mod_data.num > MAX_NUM_UDC) {
pr_err("Number of emulated UDC must be in range of 1%d\n", pr_err("Number of emulated UDC must be in range of 1...%d\n",
MAX_NUM_UDC); MAX_NUM_UDC);
return -EINVAL; return -EINVAL;
} }
......
...@@ -1024,8 +1024,7 @@ static const char proc_node_name [] = "driver/udc"; ...@@ -1024,8 +1024,7 @@ static const char proc_node_name [] = "driver/udc";
static void dump_intmask(struct seq_file *m, const char *label, u32 mask) static void dump_intmask(struct seq_file *m, const char *label, u32 mask)
{ {
/* int_status is the same format ... */ /* int_status is the same format ... */
seq_printf(m, seq_printf(m, "%s %05X =" FOURBITS EIGHTBITS EIGHTBITS "\n",
"%s %05X =" FOURBITS EIGHTBITS EIGHTBITS "\n",
label, mask, label, mask,
(mask & INT_PWRDETECT) ? " power" : "", (mask & INT_PWRDETECT) ? " power" : "",
(mask & INT_SYSERROR) ? " sys" : "", (mask & INT_SYSERROR) ? " sys" : "",
...@@ -1053,6 +1052,51 @@ static void dump_intmask(struct seq_file *m, const char *label, u32 mask) ...@@ -1053,6 +1052,51 @@ static void dump_intmask(struct seq_file *m, const char *label, u32 mask)
(mask & INT_SUSPEND) ? " suspend" : ""); (mask & INT_SUSPEND) ? " suspend" : "");
} }
static const char *udc_ep_state(enum ep0state state)
{
switch (state) {
case EP0_DISCONNECT:
return "ep0_disconnect";
case EP0_IDLE:
return "ep0_idle";
case EP0_IN:
return "ep0_in";
case EP0_OUT:
return "ep0_out";
case EP0_STATUS:
return "ep0_status";
case EP0_STALL:
return "ep0_stall";
case EP0_SUSPEND:
return "ep0_suspend";
}
return "ep0_?";
}
static const char *udc_ep_status(u32 status)
{
switch (status & EPxSTATUS_EP_MASK) {
case EPxSTATUS_EP_READY:
return "ready";
case EPxSTATUS_EP_DATAIN:
return "packet";
case EPxSTATUS_EP_FULL:
return "full";
case EPxSTATUS_EP_TX_ERR: /* host will retry */
return "tx_err";
case EPxSTATUS_EP_RX_ERR:
return "rx_err";
case EPxSTATUS_EP_BUSY: /* ep0 only */
return "busy";
case EPxSTATUS_EP_STALL:
return "stall";
case EPxSTATUS_EP_INVALID: /* these "can't happen" */
return "invalid";
}
return "?";
}
static int udc_proc_read(struct seq_file *m, void *v) static int udc_proc_read(struct seq_file *m, void *v)
{ {
...@@ -1079,18 +1123,7 @@ static int udc_proc_read(struct seq_file *m, void *v) ...@@ -1079,18 +1123,7 @@ static int udc_proc_read(struct seq_file *m, void *v)
is_usb_connected is_usb_connected
? ((tmp & PW_PULLUP) ? "full speed" : "powered") ? ((tmp & PW_PULLUP) ? "full speed" : "powered")
: "disconnected", : "disconnected",
({const char *state; udc_ep_state(dev->ep0state));
switch(dev->ep0state){
case EP0_DISCONNECT: state = "ep0_disconnect"; break;
case EP0_IDLE: state = "ep0_idle"; break;
case EP0_IN: state = "ep0_in"; break;
case EP0_OUT: state = "ep0_out"; break;
case EP0_STATUS: state = "ep0_status"; break;
case EP0_STALL: state = "ep0_stall"; break;
case EP0_SUSPEND: state = "ep0_suspend"; break;
default: state = "ep0_?"; break;
} state; })
);
dump_intmask(m, "int_status", readl(&regs->int_status)); dump_intmask(m, "int_status", readl(&regs->int_status));
dump_intmask(m, "int_enable", readl(&regs->int_enable)); dump_intmask(m, "int_enable", readl(&regs->int_enable));
...@@ -1099,17 +1132,17 @@ static int udc_proc_read(struct seq_file *m, void *v) ...@@ -1099,17 +1132,17 @@ static int udc_proc_read(struct seq_file *m, void *v)
goto done; goto done;
/* registers for (active) device and ep0 */ /* registers for (active) device and ep0 */
if (seq_printf(m, "\nirqs %lu\ndataset %02x " seq_printf(m, "\nirqs %lu\ndataset %02x single.bcs %02x.%02x state %x addr %u\n",
"single.bcs %02x.%02x state %x addr %u\n",
dev->irqs, readl(&regs->DataSet), dev->irqs, readl(&regs->DataSet),
readl(&regs->EPxSingle), readl(&regs->EPxBCS), readl(&regs->EPxSingle), readl(&regs->EPxBCS),
readl(&regs->UsbState), readl(&regs->UsbState),
readl(&regs->address)) < 0) readl(&regs->address));
if (seq_has_overflowed(m))
goto done; goto done;
tmp = readl(&regs->dma_master); tmp = readl(&regs->dma_master);
if (seq_printf(m, seq_printf(m, "dma %03X =" EIGHTBITS "%s %s\n",
"dma %03X =" EIGHTBITS "%s %s\n", tmp, tmp,
(tmp & MST_EOPB_DIS) ? " eopb-" : "", (tmp & MST_EOPB_DIS) ? " eopb-" : "",
(tmp & MST_EOPB_ENA) ? " eopb+" : "", (tmp & MST_EOPB_ENA) ? " eopb+" : "",
(tmp & MST_TIMEOUT_DIS) ? " tmo-" : "", (tmp & MST_TIMEOUT_DIS) ? " tmo-" : "",
...@@ -1121,9 +1154,8 @@ static int udc_proc_read(struct seq_file *m, void *v) ...@@ -1121,9 +1154,8 @@ static int udc_proc_read(struct seq_file *m, void *v)
(tmp & MST_RD_ENA) ? " IN" : "", (tmp & MST_RD_ENA) ? " IN" : "",
(tmp & MST_WR_ENA) ? " OUT" : "", (tmp & MST_WR_ENA) ? " OUT" : "",
(tmp & MST_CONNECTION) (tmp & MST_CONNECTION) ? "ep1in/ep2out" : "ep1out/ep2in");
? "ep1in/ep2out" if (seq_has_overflowed(m))
: "ep1out/ep2in") < 0)
goto done; goto done;
/* dump endpoint queues */ /* dump endpoint queues */
...@@ -1135,44 +1167,23 @@ static int udc_proc_read(struct seq_file *m, void *v) ...@@ -1135,44 +1167,23 @@ static int udc_proc_read(struct seq_file *m, void *v)
continue; continue;
tmp = readl(ep->reg_status); tmp = readl(ep->reg_status);
if (seq_printf(m, seq_printf(m, "%s %s max %u %s, irqs %lu, status %02x (%s) " FOURBITS "\n",
"%s %s max %u %s, irqs %lu, "
"status %02x (%s) " FOURBITS "\n",
ep->ep.name, ep->ep.name,
ep->is_in ? "in" : "out", ep->is_in ? "in" : "out",
ep->ep.maxpacket, ep->ep.maxpacket,
ep->dma ? "dma" : "pio", ep->dma ? "dma" : "pio",
ep->irqs, ep->irqs,
tmp, ({ char *s; tmp, udc_ep_status(tmp),
switch (tmp & EPxSTATUS_EP_MASK) {
case EPxSTATUS_EP_READY:
s = "ready"; break;
case EPxSTATUS_EP_DATAIN:
s = "packet"; break;
case EPxSTATUS_EP_FULL:
s = "full"; break;
case EPxSTATUS_EP_TX_ERR: // host will retry
s = "tx_err"; break;
case EPxSTATUS_EP_RX_ERR:
s = "rx_err"; break;
case EPxSTATUS_EP_BUSY: /* ep0 only */
s = "busy"; break;
case EPxSTATUS_EP_STALL:
s = "stall"; break;
case EPxSTATUS_EP_INVALID: // these "can't happen"
s = "invalid"; break;
default:
s = "?"; break;
} s; }),
(tmp & EPxSTATUS_TOGGLE) ? "data1" : "data0", (tmp & EPxSTATUS_TOGGLE) ? "data1" : "data0",
(tmp & EPxSTATUS_SUSPEND) ? " suspend" : "", (tmp & EPxSTATUS_SUSPEND) ? " suspend" : "",
(tmp & EPxSTATUS_FIFO_DISABLE) ? " disable" : "", (tmp & EPxSTATUS_FIFO_DISABLE) ? " disable" : "",
(tmp & EPxSTATUS_STAGE_ERROR) ? " ep0stat" : "" (tmp & EPxSTATUS_STAGE_ERROR) ? " ep0stat" : "");
) < 0) if (seq_has_overflowed(m))
goto done; goto done;
if (list_empty(&ep->queue)) { if (list_empty(&ep->queue)) {
if (seq_puts(m, "\t(nothing queued)\n") < 0) seq_puts(m, "\t(nothing queued)\n");
if (seq_has_overflowed(m))
goto done; goto done;
continue; continue;
} }
...@@ -1187,10 +1198,10 @@ static int udc_proc_read(struct seq_file *m, void *v) ...@@ -1187,10 +1198,10 @@ static int udc_proc_read(struct seq_file *m, void *v)
} else } else
tmp = req->req.actual; tmp = req->req.actual;
if (seq_printf(m, seq_printf(m, "\treq %p len %u/%u buf %p\n",
"\treq %p len %u/%u buf %p\n",
&req->req, tmp, req->req.length, &req->req, tmp, req->req.length,
req->req.buf) < 0) req->req.buf);
if (seq_has_overflowed(m))
goto done; goto done;
} }
} }
......
...@@ -1803,23 +1803,14 @@ static int lpc32xx_ep_queue(struct usb_ep *_ep, ...@@ -1803,23 +1803,14 @@ static int lpc32xx_ep_queue(struct usb_ep *_ep,
req = container_of(_req, struct lpc32xx_request, req); req = container_of(_req, struct lpc32xx_request, req);
ep = container_of(_ep, struct lpc32xx_ep, ep); ep = container_of(_ep, struct lpc32xx_ep, ep);
if (!_req || !_req->complete || !_req->buf || if (!_ep || !_req || !_req->complete || !_req->buf ||
!list_empty(&req->queue)) !list_empty(&req->queue))
return -EINVAL; return -EINVAL;
udc = ep->udc; udc = ep->udc;
if (!_ep) { if (udc->gadget.speed == USB_SPEED_UNKNOWN)
dev_dbg(udc->dev, "invalid ep\n"); return -EPIPE;
return -EINVAL;
}
if ((!udc) || (!udc->driver) ||
(udc->gadget.speed == USB_SPEED_UNKNOWN)) {
dev_dbg(udc->dev, "invalid device\n");
return -EINVAL;
}
if (ep->lep) { if (ep->lep) {
struct lpc32xx_usbd_dd_gad *dd; struct lpc32xx_usbd_dd_gad *dd;
......
This diff is collapsed.
...@@ -96,7 +96,6 @@ struct net2280_ep { ...@@ -96,7 +96,6 @@ struct net2280_ep {
struct net2280_ep_regs __iomem *regs; struct net2280_ep_regs __iomem *regs;
struct net2280_dma_regs __iomem *dma; struct net2280_dma_regs __iomem *dma;
struct net2280_dma *dummy; struct net2280_dma *dummy;
struct usb338x_fifo_regs __iomem *fiforegs;
dma_addr_t td_dma; /* of dummy */ dma_addr_t td_dma; /* of dummy */
struct net2280 *dev; struct net2280 *dev;
unsigned long irqs; unsigned long irqs;
...@@ -181,7 +180,6 @@ struct net2280 { ...@@ -181,7 +180,6 @@ struct net2280 {
struct net2280_dma_regs __iomem *dma; struct net2280_dma_regs __iomem *dma;
struct net2280_dep_regs __iomem *dep; struct net2280_dep_regs __iomem *dep;
struct net2280_ep_regs __iomem *epregs; struct net2280_ep_regs __iomem *epregs;
struct usb338x_fifo_regs __iomem *fiforegs;
struct usb338x_ll_regs __iomem *llregs; struct usb338x_ll_regs __iomem *llregs;
struct usb338x_ll_lfps_regs __iomem *ll_lfps_regs; struct usb338x_ll_lfps_regs __iomem *ll_lfps_regs;
struct usb338x_ll_tsn_regs __iomem *ll_tsn_regs; struct usb338x_ll_tsn_regs __iomem *ll_tsn_regs;
......
...@@ -93,23 +93,22 @@ static void handle_ep(struct pxa_ep *ep); ...@@ -93,23 +93,22 @@ static void handle_ep(struct pxa_ep *ep);
static int state_dbg_show(struct seq_file *s, void *p) static int state_dbg_show(struct seq_file *s, void *p)
{ {
struct pxa_udc *udc = s->private; struct pxa_udc *udc = s->private;
int pos = 0, ret;
u32 tmp; u32 tmp;
ret = -ENODEV;
if (!udc->driver) if (!udc->driver)
goto out; return -ENODEV;
/* basic device status */ /* basic device status */
pos += seq_printf(s, DRIVER_DESC "\n" seq_printf(s, DRIVER_DESC "\n"
"%s version: %s\nGadget driver: %s\n", "%s version: %s\n"
"Gadget driver: %s\n",
driver_name, DRIVER_VERSION, driver_name, DRIVER_VERSION,
udc->driver ? udc->driver->driver.name : "(none)"); udc->driver ? udc->driver->driver.name : "(none)");
tmp = udc_readl(udc, UDCCR); tmp = udc_readl(udc, UDCCR);
pos += seq_printf(s, seq_printf(s,
"udccr=0x%0x(%s%s%s%s%s%s%s%s%s%s), " "udccr=0x%0x(%s%s%s%s%s%s%s%s%s%s), con=%d,inter=%d,altinter=%d\n",
"con=%d,inter=%d,altinter=%d\n", tmp, tmp,
(tmp & UDCCR_OEN) ? " oen":"", (tmp & UDCCR_OEN) ? " oen":"",
(tmp & UDCCR_AALTHNP) ? " aalthnp":"", (tmp & UDCCR_AALTHNP) ? " aalthnp":"",
(tmp & UDCCR_AHNP) ? " rem" : "", (tmp & UDCCR_AHNP) ? " rem" : "",
...@@ -124,19 +123,16 @@ static int state_dbg_show(struct seq_file *s, void *p) ...@@ -124,19 +123,16 @@ static int state_dbg_show(struct seq_file *s, void *p)
(tmp & UDCCR_AIN) >> UDCCR_AIN_S, (tmp & UDCCR_AIN) >> UDCCR_AIN_S,
(tmp & UDCCR_AAISN) >> UDCCR_AAISN_S); (tmp & UDCCR_AAISN) >> UDCCR_AAISN_S);
/* registers for device and ep0 */ /* registers for device and ep0 */
pos += seq_printf(s, "udcicr0=0x%08x udcicr1=0x%08x\n", seq_printf(s, "udcicr0=0x%08x udcicr1=0x%08x\n",
udc_readl(udc, UDCICR0), udc_readl(udc, UDCICR1)); udc_readl(udc, UDCICR0), udc_readl(udc, UDCICR1));
pos += seq_printf(s, "udcisr0=0x%08x udcisr1=0x%08x\n", seq_printf(s, "udcisr0=0x%08x udcisr1=0x%08x\n",
udc_readl(udc, UDCISR0), udc_readl(udc, UDCISR1)); udc_readl(udc, UDCISR0), udc_readl(udc, UDCISR1));
pos += seq_printf(s, "udcfnr=%d\n", udc_readl(udc, UDCFNR)); seq_printf(s, "udcfnr=%d\n", udc_readl(udc, UDCFNR));
pos += seq_printf(s, "irqs: reset=%lu, suspend=%lu, resume=%lu, " seq_printf(s, "irqs: reset=%lu, suspend=%lu, resume=%lu, reconfig=%lu\n",
"reconfig=%lu\n",
udc->stats.irqs_reset, udc->stats.irqs_suspend, udc->stats.irqs_reset, udc->stats.irqs_suspend,
udc->stats.irqs_resume, udc->stats.irqs_reconfig); udc->stats.irqs_resume, udc->stats.irqs_reconfig);
ret = 0; return 0;
out:
return ret;
} }
static int queues_dbg_show(struct seq_file *s, void *p) static int queues_dbg_show(struct seq_file *s, void *p)
...@@ -144,50 +140,47 @@ static int queues_dbg_show(struct seq_file *s, void *p) ...@@ -144,50 +140,47 @@ static int queues_dbg_show(struct seq_file *s, void *p)
struct pxa_udc *udc = s->private; struct pxa_udc *udc = s->private;
struct pxa_ep *ep; struct pxa_ep *ep;
struct pxa27x_request *req; struct pxa27x_request *req;
int pos = 0, i, maxpkt, ret; int i, maxpkt;
ret = -ENODEV;
if (!udc->driver) if (!udc->driver)
goto out; return -ENODEV;
/* dump endpoint queues */ /* dump endpoint queues */
for (i = 0; i < NR_PXA_ENDPOINTS; i++) { for (i = 0; i < NR_PXA_ENDPOINTS; i++) {
ep = &udc->pxa_ep[i]; ep = &udc->pxa_ep[i];
maxpkt = ep->fifo_size; maxpkt = ep->fifo_size;
pos += seq_printf(s, "%-12s max_pkt=%d %s\n", seq_printf(s, "%-12s max_pkt=%d %s\n",
EPNAME(ep), maxpkt, "pio"); EPNAME(ep), maxpkt, "pio");
if (list_empty(&ep->queue)) { if (list_empty(&ep->queue)) {
pos += seq_printf(s, "\t(nothing queued)\n"); seq_puts(s, "\t(nothing queued)\n");
continue; continue;
} }
list_for_each_entry(req, &ep->queue, queue) { list_for_each_entry(req, &ep->queue, queue) {
pos += seq_printf(s, "\treq %p len %d/%d buf %p\n", seq_printf(s, "\treq %p len %d/%d buf %p\n",
&req->req, req->req.actual, &req->req, req->req.actual,
req->req.length, req->req.buf); req->req.length, req->req.buf);
} }
} }
ret = 0; return 0;
out:
return ret;
} }
static int eps_dbg_show(struct seq_file *s, void *p) static int eps_dbg_show(struct seq_file *s, void *p)
{ {
struct pxa_udc *udc = s->private; struct pxa_udc *udc = s->private;
struct pxa_ep *ep; struct pxa_ep *ep;
int pos = 0, i, ret; int i;
u32 tmp; u32 tmp;
ret = -ENODEV;
if (!udc->driver) if (!udc->driver)
goto out; return -ENODEV;
ep = &udc->pxa_ep[0]; ep = &udc->pxa_ep[0];
tmp = udc_ep_readl(ep, UDCCSR); tmp = udc_ep_readl(ep, UDCCSR);
pos += seq_printf(s, "udccsr0=0x%03x(%s%s%s%s%s%s%s)\n", tmp, seq_printf(s, "udccsr0=0x%03x(%s%s%s%s%s%s%s)\n",
tmp,
(tmp & UDCCSR0_SA) ? " sa" : "", (tmp & UDCCSR0_SA) ? " sa" : "",
(tmp & UDCCSR0_RNE) ? " rne" : "", (tmp & UDCCSR0_RNE) ? " rne" : "",
(tmp & UDCCSR0_FST) ? " fst" : "", (tmp & UDCCSR0_FST) ? " fst" : "",
...@@ -198,10 +191,7 @@ static int eps_dbg_show(struct seq_file *s, void *p) ...@@ -198,10 +191,7 @@ static int eps_dbg_show(struct seq_file *s, void *p)
for (i = 0; i < NR_PXA_ENDPOINTS; i++) { for (i = 0; i < NR_PXA_ENDPOINTS; i++) {
ep = &udc->pxa_ep[i]; ep = &udc->pxa_ep[i];
tmp = i? udc_ep_readl(ep, UDCCR) : udc_readl(udc, UDCCR); tmp = i? udc_ep_readl(ep, UDCCR) : udc_readl(udc, UDCCR);
pos += seq_printf(s, "%-12s: " seq_printf(s, "%-12s: IN %lu(%lu reqs), OUT %lu(%lu reqs), irqs=%lu, udccr=0x%08x, udccsr=0x%03x, udcbcr=%d\n",
"IN %lu(%lu reqs), OUT %lu(%lu reqs), "
"irqs=%lu, udccr=0x%08x, udccsr=0x%03x, "
"udcbcr=%d\n",
EPNAME(ep), EPNAME(ep),
ep->stats.in_bytes, ep->stats.in_ops, ep->stats.in_bytes, ep->stats.in_ops,
ep->stats.out_bytes, ep->stats.out_ops, ep->stats.out_bytes, ep->stats.out_ops,
...@@ -210,9 +200,7 @@ static int eps_dbg_show(struct seq_file *s, void *p) ...@@ -210,9 +200,7 @@ static int eps_dbg_show(struct seq_file *s, void *p)
udc_ep_readl(ep, UDCBCR)); udc_ep_readl(ep, UDCBCR));
} }
ret = 0; return 0;
out:
return ret;
} }
static int eps_dbg_open(struct inode *inode, struct file *file) static int eps_dbg_open(struct inode *inode, struct file *file)
......
...@@ -35,6 +35,8 @@ ...@@ -35,6 +35,8 @@
* @dev - the child device to the actual controller * @dev - the child device to the actual controller
* @gadget - the gadget. For use by the class code * @gadget - the gadget. For use by the class code
* @list - for use by the udc class driver * @list - for use by the udc class driver
* @vbus - for udcs who care about vbus status, this value is real vbus status;
* for udcs who do not care about vbus status, this value is always true
* *
* This represents the internal data structure which is used by the UDC-class * This represents the internal data structure which is used by the UDC-class
* to hold information about udc driver and gadget together. * to hold information about udc driver and gadget together.
...@@ -44,6 +46,7 @@ struct usb_udc { ...@@ -44,6 +46,7 @@ struct usb_udc {
struct usb_gadget *gadget; struct usb_gadget *gadget;
struct device dev; struct device dev;
struct list_head list; struct list_head list;
bool vbus;
}; };
static struct class *udc_class; static struct class *udc_class;
...@@ -129,19 +132,9 @@ EXPORT_SYMBOL_GPL(usb_gadget_giveback_request); ...@@ -129,19 +132,9 @@ EXPORT_SYMBOL_GPL(usb_gadget_giveback_request);
static void usb_gadget_state_work(struct work_struct *work) static void usb_gadget_state_work(struct work_struct *work)
{ {
struct usb_gadget *gadget = work_to_gadget(work); struct usb_gadget *gadget = work_to_gadget(work);
struct usb_udc *udc = NULL; struct usb_udc *udc = gadget->udc;
mutex_lock(&udc_lock);
list_for_each_entry(udc, &udc_list, list)
if (udc->gadget == gadget)
goto found;
mutex_unlock(&udc_lock);
return;
found:
mutex_unlock(&udc_lock);
if (udc)
sysfs_notify(&udc->dev.kobj, NULL, "state"); sysfs_notify(&udc->dev.kobj, NULL, "state");
} }
...@@ -155,6 +148,34 @@ EXPORT_SYMBOL_GPL(usb_gadget_set_state); ...@@ -155,6 +148,34 @@ EXPORT_SYMBOL_GPL(usb_gadget_set_state);
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
static void usb_udc_connect_control(struct usb_udc *udc)
{
if (udc->vbus)
usb_gadget_connect(udc->gadget);
else
usb_gadget_disconnect(udc->gadget);
}
/**
* usb_udc_vbus_handler - updates the udc core vbus status, and try to
* connect or disconnect gadget
* @gadget: The gadget which vbus change occurs
* @status: The vbus status
*
* The udc driver calls it when it wants to connect or disconnect gadget
* according to vbus status.
*/
void usb_udc_vbus_handler(struct usb_gadget *gadget, bool status)
{
struct usb_udc *udc = gadget->udc;
if (udc) {
udc->vbus = status;
usb_udc_connect_control(udc);
}
}
EXPORT_SYMBOL_GPL(usb_udc_vbus_handler);
/** /**
* usb_gadget_udc_reset - notifies the udc core that bus reset occurs * usb_gadget_udc_reset - notifies the udc core that bus reset occurs
* @gadget: The gadget which bus reset occurs * @gadget: The gadget which bus reset occurs
...@@ -278,6 +299,7 @@ int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, ...@@ -278,6 +299,7 @@ int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget,
goto err3; goto err3;
udc->gadget = gadget; udc->gadget = gadget;
gadget->udc = udc;
mutex_lock(&udc_lock); mutex_lock(&udc_lock);
list_add_tail(&udc->list, &udc_list); list_add_tail(&udc->list, &udc_list);
...@@ -287,6 +309,7 @@ int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, ...@@ -287,6 +309,7 @@ int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget,
goto err4; goto err4;
usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED); usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED);
udc->vbus = true;
mutex_unlock(&udc_lock); mutex_unlock(&udc_lock);
...@@ -348,21 +371,14 @@ static void usb_gadget_remove_driver(struct usb_udc *udc) ...@@ -348,21 +371,14 @@ static void usb_gadget_remove_driver(struct usb_udc *udc)
*/ */
void usb_del_gadget_udc(struct usb_gadget *gadget) void usb_del_gadget_udc(struct usb_gadget *gadget)
{ {
struct usb_udc *udc = NULL; struct usb_udc *udc = gadget->udc;
mutex_lock(&udc_lock);
list_for_each_entry(udc, &udc_list, list)
if (udc->gadget == gadget)
goto found;
dev_err(gadget->dev.parent, "gadget not registered.\n");
mutex_unlock(&udc_lock);
if (!udc)
return; return;
found:
dev_vdbg(gadget->dev.parent, "unregistering gadget\n"); dev_vdbg(gadget->dev.parent, "unregistering gadget\n");
mutex_lock(&udc_lock);
list_del(&udc->list); list_del(&udc->list);
mutex_unlock(&udc_lock); mutex_unlock(&udc_lock);
...@@ -397,7 +413,7 @@ static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *dri ...@@ -397,7 +413,7 @@ static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *dri
driver->unbind(udc->gadget); driver->unbind(udc->gadget);
goto err1; goto err1;
} }
usb_gadget_connect(udc->gadget); usb_udc_connect_control(udc);
kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
return 0; return 0;
......
This diff is collapsed.
...@@ -160,7 +160,8 @@ struct musb_io; ...@@ -160,7 +160,8 @@ struct musb_io;
* @init: turns on clocks, sets up platform-specific registers, etc * @init: turns on clocks, sets up platform-specific registers, etc
* @exit: undoes @init * @exit: undoes @init
* @set_mode: forcefully changes operating mode * @set_mode: forcefully changes operating mode
* @try_ilde: tries to idle the IP * @try_idle: tries to idle the IP
* @recover: platform-specific babble recovery
* @vbus_status: returns vbus status if possible * @vbus_status: returns vbus status if possible
* @set_vbus: forces vbus status * @set_vbus: forces vbus status
* @adjust_channel_params: pre check for standard dma channel_program func * @adjust_channel_params: pre check for standard dma channel_program func
...@@ -196,7 +197,7 @@ struct musb_platform_ops { ...@@ -196,7 +197,7 @@ struct musb_platform_ops {
void (*write_fifo)(struct musb_hw_ep *hw_ep, u16 len, const u8 *buf); void (*write_fifo)(struct musb_hw_ep *hw_ep, u16 len, const u8 *buf);
int (*set_mode)(struct musb *musb, u8 mode); int (*set_mode)(struct musb *musb, u8 mode);
void (*try_idle)(struct musb *musb, unsigned long timeout); void (*try_idle)(struct musb *musb, unsigned long timeout);
int (*reset)(struct musb *musb); int (*recover)(struct musb *musb);
int (*vbus_status)(struct musb *musb); int (*vbus_status)(struct musb *musb);
void (*set_vbus)(struct musb *musb, int on); void (*set_vbus)(struct musb *musb, int on);
...@@ -300,7 +301,6 @@ struct musb { ...@@ -300,7 +301,6 @@ struct musb {
irqreturn_t (*isr)(int, void *); irqreturn_t (*isr)(int, void *);
struct work_struct irq_work; struct work_struct irq_work;
struct delayed_work recover_work;
struct delayed_work deassert_reset_work; struct delayed_work deassert_reset_work;
struct delayed_work finish_resume_work; struct delayed_work finish_resume_work;
u16 hwvers; u16 hwvers;
...@@ -558,12 +558,12 @@ static inline void musb_platform_try_idle(struct musb *musb, ...@@ -558,12 +558,12 @@ static inline void musb_platform_try_idle(struct musb *musb,
musb->ops->try_idle(musb, timeout); musb->ops->try_idle(musb, timeout);
} }
static inline int musb_platform_reset(struct musb *musb) static inline int musb_platform_recover(struct musb *musb)
{ {
if (!musb->ops->reset) if (!musb->ops->recover)
return -EINVAL; return 0;
return musb->ops->reset(musb); return musb->ops->recover(musb);
} }
static inline int musb_platform_get_vbus_status(struct musb *musb) static inline int musb_platform_get_vbus_status(struct musb *musb)
......
...@@ -225,10 +225,12 @@ static void cppi41_dma_callback(void *private_data) ...@@ -225,10 +225,12 @@ static void cppi41_dma_callback(void *private_data)
struct dma_channel *channel = private_data; struct dma_channel *channel = private_data;
struct cppi41_dma_channel *cppi41_channel = channel->private_data; struct cppi41_dma_channel *cppi41_channel = channel->private_data;
struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep; struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep;
struct cppi41_dma_controller *controller;
struct musb *musb = hw_ep->musb; struct musb *musb = hw_ep->musb;
unsigned long flags; unsigned long flags;
struct dma_tx_state txstate; struct dma_tx_state txstate;
u32 transferred; u32 transferred;
int is_hs = 0;
bool empty; bool empty;
spin_lock_irqsave(&musb->lock, flags); spin_lock_irqsave(&musb->lock, flags);
...@@ -248,12 +250,14 @@ static void cppi41_dma_callback(void *private_data) ...@@ -248,12 +250,14 @@ static void cppi41_dma_callback(void *private_data)
transferred < cppi41_channel->packet_sz) transferred < cppi41_channel->packet_sz)
cppi41_channel->prog_len = 0; cppi41_channel->prog_len = 0;
if (cppi41_channel->is_tx)
empty = musb_is_tx_fifo_empty(hw_ep); empty = musb_is_tx_fifo_empty(hw_ep);
if (empty) {
if (!cppi41_channel->is_tx || empty) {
cppi41_trans_done(cppi41_channel); cppi41_trans_done(cppi41_channel);
} else { goto out;
struct cppi41_dma_controller *controller; }
int is_hs = 0;
/* /*
* On AM335x it has been observed that the TX interrupt fires * On AM335x it has been observed that the TX interrupt fires
* too early that means the TXFIFO is not yet empty but the DMA * too early that means the TXFIFO is not yet empty but the DMA
...@@ -277,20 +281,16 @@ static void cppi41_dma_callback(void *private_data) ...@@ -277,20 +281,16 @@ static void cppi41_dma_callback(void *private_data)
unsigned wait = 25; unsigned wait = 25;
do { do {
empty = musb_is_tx_fifo_empty(hw_ep);
if (empty)
break;
wait--;
if (!wait)
break;
udelay(1);
} while (1);
empty = musb_is_tx_fifo_empty(hw_ep); empty = musb_is_tx_fifo_empty(hw_ep);
if (empty) { if (empty) {
cppi41_trans_done(cppi41_channel); cppi41_trans_done(cppi41_channel);
goto out; goto out;
} }
wait--;
if (!wait)
break;
cpu_relax();
} while (1);
} }
list_add_tail(&cppi41_channel->tx_check, list_add_tail(&cppi41_channel->tx_check,
&controller->early_tx_list); &controller->early_tx_list);
...@@ -302,7 +302,7 @@ static void cppi41_dma_callback(void *private_data) ...@@ -302,7 +302,7 @@ static void cppi41_dma_callback(void *private_data)
20 * NSEC_PER_USEC, 20 * NSEC_PER_USEC,
HRTIMER_MODE_REL); HRTIMER_MODE_REL);
} }
}
out: out:
spin_unlock_irqrestore(&musb->lock, flags); spin_unlock_irqrestore(&musb->lock, flags);
} }
......
This diff is collapsed.
...@@ -1876,44 +1876,6 @@ static int musb_gadget_start(struct usb_gadget *g, ...@@ -1876,44 +1876,6 @@ static int musb_gadget_start(struct usb_gadget *g,
return retval; return retval;
} }
static void stop_activity(struct musb *musb, struct usb_gadget_driver *driver)
{
int i;
struct musb_hw_ep *hw_ep;
/* don't disconnect if it's not connected */
if (musb->g.speed == USB_SPEED_UNKNOWN)
driver = NULL;
else
musb->g.speed = USB_SPEED_UNKNOWN;
/* deactivate the hardware */
if (musb->softconnect) {
musb->softconnect = 0;
musb_pullup(musb, 0);
}
musb_stop(musb);
/* killing any outstanding requests will quiesce the driver;
* then report disconnect
*/
if (driver) {
for (i = 0, hw_ep = musb->endpoints;
i < musb->nr_endpoints;
i++, hw_ep++) {
musb_ep_select(musb->mregs, i);
if (hw_ep->is_shared_fifo /* || !epnum */) {
nuke(&hw_ep->ep_in, -ESHUTDOWN);
} else {
if (hw_ep->max_packet_sz_tx)
nuke(&hw_ep->ep_in, -ESHUTDOWN);
if (hw_ep->max_packet_sz_rx)
nuke(&hw_ep->ep_out, -ESHUTDOWN);
}
}
}
}
/* /*
* Unregister the gadget driver. Used by gadget drivers when * Unregister the gadget driver. Used by gadget drivers when
* unregistering themselves from the controller. * unregistering themselves from the controller.
...@@ -1940,7 +1902,7 @@ static int musb_gadget_stop(struct usb_gadget *g) ...@@ -1940,7 +1902,7 @@ static int musb_gadget_stop(struct usb_gadget *g)
(void) musb_gadget_vbus_draw(&musb->g, 0); (void) musb_gadget_vbus_draw(&musb->g, 0);
musb->xceiv->otg->state = OTG_STATE_UNDEFINED; musb->xceiv->otg->state = OTG_STATE_UNDEFINED;
stop_activity(musb, NULL); musb_stop(musb);
otg_set_peripheral(musb->xceiv->otg, NULL); otg_set_peripheral(musb->xceiv->otg, NULL);
musb->is_active = 0; musb->is_active = 0;
......
...@@ -202,13 +202,13 @@ config USB_RCAR_GEN2_PHY ...@@ -202,13 +202,13 @@ config USB_RCAR_GEN2_PHY
config USB_ULPI config USB_ULPI
bool "Generic ULPI Transceiver Driver" bool "Generic ULPI Transceiver Driver"
depends on ARM || ARM64 depends on ARM || ARM64
select USB_ULPI_VIEWPORT
help help
Enable this to support ULPI connected USB OTG transceivers which Enable this to support ULPI connected USB OTG transceivers which
are likely found on embedded boards. are likely found on embedded boards.
config USB_ULPI_VIEWPORT config USB_ULPI_VIEWPORT
bool bool
depends on USB_ULPI
help help
Provides read/write operations to the ULPI phy register set for Provides read/write operations to the ULPI phy register set for
controllers with a viewport register (e.g. Chipidea/ARC controllers). controllers with a viewport register (e.g. Chipidea/ARC controllers).
......
...@@ -27,7 +27,7 @@ static const char *const usbphy_modes[] = { ...@@ -27,7 +27,7 @@ static const char *const usbphy_modes[] = {
* @np: Pointer to the given device_node * @np: Pointer to the given device_node
* *
* The function gets phy interface string from property 'phy_type', * The function gets phy interface string from property 'phy_type',
* and returns the correspondig enum usb_phy_interface * and returns the corresponding enum usb_phy_interface
*/ */
enum usb_phy_interface of_usb_get_phy_mode(struct device_node *np) enum usb_phy_interface of_usb_get_phy_mode(struct device_node *np)
{ {
......
...@@ -893,7 +893,7 @@ static int abx500_usb_link_status_update(struct ab8500_usb *ab) ...@@ -893,7 +893,7 @@ static int abx500_usb_link_status_update(struct ab8500_usb *ab)
/* /*
* Disconnection Sequence: * Disconnection Sequence:
* 1. Disconect Interrupt * 1. Disconnect Interrupt
* 2. Disable regulators * 2. Disable regulators
* 3. Disable AB clock * 3. Disable AB clock
* 4. Disable the Phy * 4. Disable the Phy
......
This diff is collapsed.
...@@ -81,7 +81,9 @@ static void devm_usb_phy_release(struct device *dev, void *res) ...@@ -81,7 +81,9 @@ static void devm_usb_phy_release(struct device *dev, void *res)
static int devm_usb_phy_match(struct device *dev, void *res, void *match_data) static int devm_usb_phy_match(struct device *dev, void *res, void *match_data)
{ {
return res == match_data; struct usb_phy **phy = res;
return *phy == match_data;
} }
/** /**
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -58,6 +58,7 @@ struct usbhs_pkt { ...@@ -58,6 +58,7 @@ struct usbhs_pkt {
struct usbhs_pkt *pkt); struct usbhs_pkt *pkt);
struct work_struct work; struct work_struct work;
dma_addr_t dma; dma_addr_t dma;
dma_cookie_t cookie;
void *buf; void *buf;
int length; int length;
int trans; int trans;
......
This diff is collapsed.
This diff is collapsed.
...@@ -97,6 +97,7 @@ void usbhs_pipe_set_trans_count_if_bulk(struct usbhs_pipe *pipe, int len); ...@@ -97,6 +97,7 @@ void usbhs_pipe_set_trans_count_if_bulk(struct usbhs_pipe *pipe, int len);
void usbhs_pipe_select_fifo(struct usbhs_pipe *pipe, struct usbhs_fifo *fifo); void usbhs_pipe_select_fifo(struct usbhs_pipe *pipe, struct usbhs_fifo *fifo);
void usbhs_pipe_config_update(struct usbhs_pipe *pipe, u16 devsel, void usbhs_pipe_config_update(struct usbhs_pipe *pipe, u16 devsel,
u16 epnum, u16 maxp); u16 epnum, u16 maxp);
void usbhs_pipe_config_change_bfre(struct usbhs_pipe *pipe, int enable);
#define usbhs_pipe_sequence_data0(pipe) usbhs_pipe_data_sequence(pipe, 0) #define usbhs_pipe_sequence_data0(pipe) usbhs_pipe_data_sequence(pipe, 0)
#define usbhs_pipe_sequence_data1(pipe) usbhs_pipe_data_sequence(pipe, 1) #define usbhs_pipe_sequence_data1(pipe) usbhs_pipe_data_sequence(pipe, 1)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment