istoreos/target/linux/rockchip/patches-4.19/0027-usb.patch

13884 lines
438 KiB
Diff

From f7305bd1f1410d82e55e1602d113814b0712e308 Mon Sep 17 00:00:00 2001
From: jjm2473 <1129525450@qq.com>
Date: Fri, 28 Oct 2022 11:35:07 +0800
Subject: [PATCH 27/46] usb
---
drivers/usb/common/common.c | 1 +
drivers/usb/core/config.c | 9 +
drivers/usb/core/driver.c | 12 +
drivers/usb/core/hub.c | 22 +-
drivers/usb/core/message.c | 24 +-
drivers/usb/core/quirks.c | 14 +
drivers/usb/dwc2/Kconfig | 2 +
drivers/usb/dwc2/core.h | 13 +-
drivers/usb/dwc2/core_intr.c | 13 +
drivers/usb/dwc2/gadget.c | 53 +-
drivers/usb/dwc2/hcd.c | 160 +-
drivers/usb/dwc2/hcd_intr.c | 9 +-
drivers/usb/dwc2/hcd_queue.c | 8 +-
drivers/usb/dwc2/params.c | 2 +
drivers/usb/dwc2/platform.c | 183 ++-
drivers/usb/dwc3/Kconfig | 12 +
drivers/usb/dwc3/Makefile | 1 +
drivers/usb/dwc3/core.c | 327 +++-
drivers/usb/dwc3/core.h | 101 +-
drivers/usb/dwc3/debug.h | 159 +-
drivers/usb/dwc3/debugfs.c | 182 ++-
drivers/usb/dwc3/drd.c | 20 +-
drivers/usb/dwc3/dwc3-haps.c | 9 +
drivers/usb/dwc3/ep0.c | 47 +-
drivers/usb/dwc3/gadget.c | 653 +++++++-
drivers/usb/dwc3/gadget.h | 19 +-
drivers/usb/dwc3/host.c | 21 +-
drivers/usb/dwc3/trace.h | 12 +-
drivers/usb/gadget/Kconfig | 60 +-
drivers/usb/gadget/composite.c | 116 ++
drivers/usb/gadget/configfs.c | 274 +++-
drivers/usb/gadget/epautoconf.c | 64 +
drivers/usb/gadget/function/Makefile | 4 +
drivers/usb/gadget/function/f_fs.c | 14 +-
drivers/usb/gadget/function/f_hid.c | 2 +-
drivers/usb/gadget/function/f_midi.c | 66 +
drivers/usb/gadget/function/f_rndis.c | 26 +-
drivers/usb/gadget/function/f_uac1.c | 893 ++++++++---
drivers/usb/gadget/function/f_uac1_legacy.c | 6 +
drivers/usb/gadget/function/f_uac2.c | 950 +++++++++---
drivers/usb/gadget/function/f_uvc.c | 479 +++++-
drivers/usb/gadget/function/u_audio.c | 510 +++++-
drivers/usb/gadget/function/u_audio.h | 57 +-
drivers/usb/gadget/function/u_uac1.h | 38 -
drivers/usb/gadget/function/u_uac1_legacy.c | 4 +
drivers/usb/gadget/function/u_uac2.h | 41 -
drivers/usb/gadget/function/u_uvc.h | 14 +-
drivers/usb/gadget/function/uvc.h | 11 +-
drivers/usb/gadget/function/uvc_configfs.c | 1547 +++++++++++++------
drivers/usb/gadget/function/uvc_queue.c | 8 +
drivers/usb/gadget/function/uvc_v4l2.c | 32 +-
drivers/usb/gadget/function/uvc_video.c | 117 +-
drivers/usb/gadget/function/uvc_video.h | 2 -
drivers/usb/gadget/legacy/Kconfig | 40 +
drivers/usb/gadget/legacy/Makefile | 1 +
drivers/usb/gadget/legacy/audio.c | 91 +-
drivers/usb/gadget/legacy/webcam.c | 2 +
drivers/usb/gadget/udc/Kconfig | 1 +
drivers/usb/gadget/udc/bdc/Kconfig | 2 +
drivers/usb/gadget/udc/core.c | 41 +-
drivers/usb/gadget/udc/dummy_hcd.c | 26 +-
drivers/usb/host/Kconfig | 1 +
drivers/usb/host/ehci-platform.c | 28 +
drivers/usb/host/ehci.h | 7 +
drivers/usb/host/ohci-platform.c | 4 +-
drivers/usb/host/xhci-hub.c | 14 +
drivers/usb/host/xhci-mem.c | 347 ++++-
drivers/usb/host/xhci-plat.c | 26 +
drivers/usb/host/xhci-ring.c | 19 +
drivers/usb/host/xhci.c | 175 +++
drivers/usb/host/xhci.h | 19 +
include/linux/usb.h | 11 +
include/linux/usb/composite.h | 16 +
include/linux/usb/gadget.h | 101 ++
include/linux/usb/hcd.h | 22 +
include/linux/usb/otg.h | 1 +
include/linux/usb/quirks.h | 3 +
include/uapi/linux/usb/ch9.h | 20 +
78 files changed, 6745 insertions(+), 1696 deletions(-)
delete mode 100644 drivers/usb/gadget/function/u_uac1.h
delete mode 100644 drivers/usb/gadget/function/u_uac2.h
diff --git a/drivers/usb/common/common.c b/drivers/usb/common/common.c
index 73c8e6591..9c5c53617 100644
--- a/drivers/usb/common/common.c
+++ b/drivers/usb/common/common.c
@@ -100,6 +100,7 @@ static const char *const usb_dr_modes[] = {
[USB_DR_MODE_HOST] = "host",
[USB_DR_MODE_PERIPHERAL] = "peripheral",
[USB_DR_MODE_OTG] = "otg",
+ [USB_DR_MODE_DRD] = "drd",
};
static enum usb_dr_mode usb_get_dr_mode_from_string(const char *str)
diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c
index 0410f05cc..a0ab8d058 100644
--- a/drivers/usb/core/config.c
+++ b/drivers/usb/core/config.c
@@ -1086,6 +1086,15 @@ int usb_get_bos_descriptor(struct usb_device *dev)
case USB_PTM_CAP_TYPE:
dev->bos->ptm_cap =
(struct usb_ptm_cap_descriptor *)buffer;
+ break;
+ case USB_CAP_TYPE_CONFIG_SUMMARY:
+ /* one such desc per function */
+ if (!dev->bos->num_config_summary_desc)
+ dev->bos->config_summary =
+ (struct usb_config_summary_descriptor *)buffer;
+
+ dev->bos->num_config_summary_desc++;
+ break;
default:
break;
}
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index 3255b2bb0..60c2ec4da 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -1458,6 +1458,9 @@ int usb_suspend(struct device *dev, pm_message_t msg)
struct usb_device *udev = to_usb_device(dev);
int r;
+ if (udev->bus->skip_resume && udev->state == USB_STATE_SUSPENDED)
+ return 0;
+
unbind_no_pm_drivers_interfaces(udev);
/* From now on we are sure all drivers support suspend/resume
@@ -1494,6 +1497,15 @@ int usb_resume(struct device *dev, pm_message_t msg)
struct usb_device *udev = to_usb_device(dev);
int status;
+ /*
+ * Some buses would like to keep their devices in suspend
+ * state after system resume. Their resume happen when
+ * a remote wakeup is detected or interface driver start
+ * I/O.
+ */
+ if (udev->bus->skip_resume)
+ return 0;
+
/* For all calls, take the device back to full power and
* tell the PM core in case it was autosuspended previously.
* Unbind the interfaces that will need rebinding later,
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 29cc2aa5a..bd4f8666c 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -1798,7 +1798,8 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
* bus_resume methods.
*/
if (hdev->parent) { /* normal device */
- usb_enable_autosuspend(hdev);
+ if (!(hdev->parent->quirks & USB_QUIRK_AUTO_SUSPEND))
+ usb_enable_autosuspend(hdev);
} else { /* root hub */
const struct hc_driver *drv = bus_to_hcd(hdev->bus)->driver;
@@ -5363,7 +5364,24 @@ static void port_event(struct usb_hub *hub, int port1)
} else {
usb_unlock_port(port_dev);
usb_lock_device(udev);
- usb_reset_device(udev);
+
+ /**
+ * Some special SoCs (e.g. rk322xh) USB3 PHY lose the
+ * ability to detect a disconnection when USB3 device
+ * plug out, fortunately, it can detect port link state
+ * change here, so we can do soft disconnect according
+ * to the PLC here.
+ *
+ * And we only need to do the soft disconnect for root
+ * hub. In addition, we just reuse the autosuspend quirk
+ * but not add a new quirk for this issue. Because this
+ * issue always occurs with autosuspend problem.
+ */
+ if (!hub->hdev->parent && (hdev->quirks &
+ USB_QUIRK_AUTO_SUSPEND))
+ usb_remove_device(udev);
+ else
+ usb_reset_device(udev);
usb_unlock_device(udev);
usb_lock_port(port_dev);
connect_change = 0;
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index 152228d33..7a808c527 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -1244,7 +1244,8 @@ static void usb_disable_device_endpoints(struct usb_device *dev, int skip_ep0)
*/
void usb_disable_device(struct usb_device *dev, int skip_ep0)
{
- int i;
+ int i, j;
+ struct usb_hcd *hcd = bus_to_hcd(dev->bus);
/* getting rid of interfaces will disconnect
* any drivers bound to them (a key side effect)
@@ -1268,6 +1269,27 @@ void usb_disable_device(struct usb_device *dev, int skip_ep0)
dev_dbg(&dev->dev, "unregistering interface %s\n",
dev_name(&interface->dev));
remove_intf_ep_devs(interface);
+
+ /*
+ * Some special SoCs (e.g. rk322xh) USB 3.0 module
+ * can't handle outstanding URBs by hardware when
+ * when USB 3.0 device disconnect, so we need to
+ * cancel all URBs pending on this device here.
+ *
+ * In addition, we just reuse the hub autosuspend
+ * quirk but not add a new quirk for this issue.
+ * Because it always occurs with autosuspend issue.
+ */
+ if (hcd->self.root_hub->quirks &
+ USB_QUIRK_AUTO_SUSPEND) {
+ for (j = skip_ep0; j < 16; ++j) {
+ usb_hcd_flush_endpoint(dev,
+ dev->ep_out[j]);
+ usb_hcd_flush_endpoint(dev,
+ dev->ep_in[j]);
+ }
+ }
+
device_del(&interface->dev);
}
diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
index a118c44c7..c8161c6f4 100644
--- a/drivers/usb/core/quirks.c
+++ b/drivers/usb/core/quirks.c
@@ -322,6 +322,10 @@ static const struct usb_device_id usb_quirk_list[] = {
/* Alcor Micro Corp. Hub */
{ USB_DEVICE(0x058f, 0x9254), .driver_info = USB_QUIRK_RESET_RESUME },
+ /* HD Camera Manufacturer */
+ { USB_DEVICE(0x05a3, 0x9230), .driver_info = USB_QUIRK_AUTO_SUSPEND },
+ { USB_DEVICE(0x05a3, 0x9320), .driver_info = USB_QUIRK_AUTO_SUSPEND },
+
/* appletouch */
{ USB_DEVICE(0x05ac, 0x021a), .driver_info = USB_QUIRK_RESET_RESUME },
@@ -411,10 +415,17 @@ static const struct usb_device_id usb_quirk_list[] = {
/* Generic RTL8153 based ethernet adapters */
{ USB_DEVICE(0x0bda, 0x8153), .driver_info = USB_QUIRK_NO_LPM },
+ /* Sonix FaceBlack device */
+ { USB_DEVICE(0x0c45, 0x64ab), .driver_info = USB_QUIRK_AUTO_SUSPEND },
+ { USB_DEVICE(0x0c45, 0x64ac), .driver_info = USB_QUIRK_AUTO_SUSPEND },
+
/* SONiX USB DEVICE Touchpad */
{ USB_DEVICE(0x0c45, 0x7056), .driver_info =
USB_QUIRK_IGNORE_REMOTE_WAKEUP },
+ /* MT7668 WIFI/BT */
+ { USB_DEVICE(0x0e8d, 0x7668), .driver_info = USB_QUIRK_NO_LPM },
+
/* Action Semiconductor flash disk */
{ USB_DEVICE(0x10d6, 0x2200), .driver_info =
USB_QUIRK_STRING_FETCH_255 },
@@ -498,6 +509,9 @@ static const struct usb_device_id usb_quirk_list[] = {
{ USB_DEVICE(0x2040, 0x7200), .driver_info =
USB_QUIRK_CONFIG_INTF_STRINGS },
+ /* Lexar Flash */
+ { USB_DEVICE(0x21c4, 0x0cd1), .driver_info = USB_QUIRK_NO_LPM },
+
/* Raydium Touchscreen */
{ USB_DEVICE(0x2386, 0x3114), .driver_info = USB_QUIRK_NO_LPM },
diff --git a/drivers/usb/dwc2/Kconfig b/drivers/usb/dwc2/Kconfig
index b6a495e98..68d095ae2 100644
--- a/drivers/usb/dwc2/Kconfig
+++ b/drivers/usb/dwc2/Kconfig
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
config USB_DWC2
tristate "DesignWare USB2 DRD Core Support"
depends on HAS_DMA
diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h
index 828803b67..cc5f20c43 100644
--- a/drivers/usb/dwc2/core.h
+++ b/drivers/usb/dwc2/core.h
@@ -68,6 +68,9 @@
/* Maximum number of Endpoints/HostChannels */
#define MAX_EPS_CHANNELS 16
+/* Maximum number of dwc2 clocks */
+#define DWC2_MAX_CLKS 3
+
/* dwc2-hsotg declarations */
static const char * const dwc2_hsotg_supply_names[] = {
"vusb_d", /* digital USB supply, 1.2V */
@@ -833,6 +836,7 @@ struct dwc2_hregs_backup {
* @hcd_enabled: Host mode sub-driver initialization indicator.
* @gadget_enabled: Peripheral mode sub-driver initialization indicator.
* @ll_hw_enabled: Status of low-level hardware resources.
+ * @ll_phy_enabled Status of low-level PHY resources.
* @hibernated: True if core is hibernated
* @frame_number: Frame number read from the core. For both device
* and host modes. The value ranges are from 0
@@ -1019,10 +1023,13 @@ struct dwc2_hsotg {
unsigned int hcd_enabled:1;
unsigned int gadget_enabled:1;
unsigned int ll_hw_enabled:1;
+ unsigned int ll_phy_enabled:1;
unsigned int hibernated:1;
+ bool bus_suspended;
u16 frame_number;
struct phy *phy;
+ struct work_struct phy_rst_work;
struct usb_phy *uphy;
struct dwc2_hsotg_plat *plat;
struct regulator_bulk_data supplies[DWC2_NUM_SUPPLIES];
@@ -1032,7 +1039,7 @@ struct dwc2_hsotg {
spinlock_t lock;
void *priv;
int irq;
- struct clk *clk;
+ struct clk *clks[DWC2_MAX_CLKS];
struct reset_control *reset;
struct reset_control *reset_ecc;
@@ -1098,7 +1105,6 @@ struct dwc2_hsotg {
unsigned long hs_periodic_bitmap[
DIV_ROUND_UP(DWC2_HS_SCHEDULE_US, BITS_PER_LONG)];
u16 periodic_qh_count;
- bool bus_suspended;
bool new_connection;
u16 last_frame_num;
@@ -1292,6 +1298,9 @@ extern const struct of_device_id dwc2_of_match_table[];
int dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg);
int dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg);
+int dwc2_lowlevel_phy_enable(struct dwc2_hsotg *hsotg);
+int dwc2_lowlevel_phy_disable(struct dwc2_hsotg *hsotg);
+
/* Common polling functions */
int dwc2_hsotg_wait_bit_set(struct dwc2_hsotg *hs_otg, u32 reg, u32 bit,
u32 timeout);
diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c
index 5919ecb7d..fb2d3860e 100644
--- a/drivers/usb/dwc2/core_intr.c
+++ b/drivers/usb/dwc2/core_intr.c
@@ -404,6 +404,7 @@ static void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg)
static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg)
{
int ret;
+ struct device_node *np = hsotg->dev->of_node;
/* Clear interrupt */
dwc2_writel(hsotg, GINTSTS_WKUPINT, GINTSTS);
@@ -446,6 +447,18 @@ static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg)
/* Restart the Phy Clock */
pcgcctl &= ~PCGCTL_STOPPCLK;
dwc2_writel(hsotg, pcgcctl, PCGCTL);
+
+ /*
+ * It is a quirk in Rockchip RK3288, causing by
+ * a hardware bug. This will propagate out and
+ * eventually we'll re-enumerate the device.
+ * Not great but the best we can do.
+ */
+ if (of_device_is_compatible(np, "rockchip,rk3288-usb")) {
+ /* FIXME: wkp_timer might run early than phy_rst_work */
+ schedule_work(&hsotg->phy_rst_work);
+ }
+
mod_timer(&hsotg->wkp_timer,
jiffies + msecs_to_jiffies(71));
} else {
diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c
index 1e4600592..a12a5fb65 100644
--- a/drivers/usb/dwc2/gadget.c
+++ b/drivers/usb/dwc2/gadget.c
@@ -23,6 +23,7 @@
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/of_platform.h>
+#include <linux/platform_data/s3c-hsotg.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
@@ -104,6 +105,23 @@ static inline bool using_desc_dma(struct dwc2_hsotg *hsotg)
return hsotg->params.g_dma_desc;
}
+/**
+ * dwc2_hsotg_read_frameno - read current frame number
+ * @hsotg: The device instance
+ *
+ * Return the current frame number
+ */
+static u32 dwc2_hsotg_read_frameno(struct dwc2_hsotg *hsotg)
+{
+ u32 dsts;
+
+ dsts = dwc2_readl(hsotg, DSTS);
+ dsts &= DSTS_SOFFN_MASK;
+ dsts >>= DSTS_SOFFN_SHIFT;
+
+ return dsts;
+}
+
/**
* dwc2_gadget_incr_frame_num - Increments the targeted frame number.
* @hs_ep: The endpoint
@@ -113,11 +131,14 @@ static inline bool using_desc_dma(struct dwc2_hsotg *hsotg)
*/
static inline void dwc2_gadget_incr_frame_num(struct dwc2_hsotg_ep *hs_ep)
{
+ struct dwc2_hsotg *hsotg = hs_ep->parent;
+ u32 current_frame = dwc2_hsotg_read_frameno(hsotg);
+
hs_ep->target_frame += hs_ep->interval;
if (hs_ep->target_frame > DSTS_SOFFN_LIMIT) {
hs_ep->frame_overrun = true;
hs_ep->target_frame &= DSTS_SOFFN_LIMIT;
- } else {
+ } else if (current_frame <= hs_ep->target_frame) {
hs_ep->frame_overrun = false;
}
}
@@ -643,23 +664,6 @@ static unsigned int get_ep_limit(struct dwc2_hsotg_ep *hs_ep)
return maxsize;
}
-/**
- * dwc2_hsotg_read_frameno - read current frame number
- * @hsotg: The device instance
- *
- * Return the current frame number
- */
-static u32 dwc2_hsotg_read_frameno(struct dwc2_hsotg *hsotg)
-{
- u32 dsts;
-
- dsts = dwc2_readl(hsotg, DSTS);
- dsts &= DSTS_SOFFN_MASK;
- dsts >>= DSTS_SOFFN_SHIFT;
-
- return dsts;
-}
-
/**
* dwc2_gadget_get_chain_limit - get the maximum data payload value of the
* DMA descriptor chain prepared for specific endpoint
@@ -4337,6 +4341,14 @@ static int dwc2_hsotg_udc_start(struct usb_gadget *gadget,
goto err;
}
+ if (hsotg->dr_mode == USB_DR_MODE_OTG && dwc2_is_device_mode(hsotg)) {
+ if (!hsotg->ll_phy_enabled) {
+ ret = dwc2_lowlevel_phy_enable(hsotg);
+ if (ret)
+ goto err;
+ }
+ }
+
if (!IS_ERR_OR_NULL(hsotg->uphy))
otg_set_peripheral(hsotg->uphy->otg, &hsotg->gadget);
@@ -4395,6 +4407,11 @@ static int dwc2_hsotg_udc_stop(struct usb_gadget *gadget)
if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)
dwc2_lowlevel_hw_disable(hsotg);
+ if (hsotg->dr_mode == USB_DR_MODE_OTG && dwc2_is_device_mode(hsotg)) {
+ if (hsotg->ll_phy_enabled)
+ dwc2_lowlevel_phy_disable(hsotg);
+ }
+
return 0;
}
diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c
index 22c4d5548..0254b3626 100644
--- a/drivers/usb/dwc2/hcd.c
+++ b/drivers/usb/dwc2/hcd.c
@@ -1008,11 +1008,13 @@ void dwc2_hc_halt(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan,
* uframe/frame (in the worst case), the core generates a channel
* halted and disables the channel automatically.
*/
- if ((hsotg->params.g_dma && !hsotg->params.g_dma_desc) ||
+ if ((hsotg->params.host_dma && !hsotg->params.dma_desc_enable) ||
hsotg->hw_params.arch == GHWCFG2_EXT_DMA_ARCH) {
if (!chan->do_split &&
(chan->ep_type == USB_ENDPOINT_XFER_ISOC ||
- chan->ep_type == USB_ENDPOINT_XFER_INT)) {
+ chan->ep_type == USB_ENDPOINT_XFER_INT) &&
+ (halt_status == DWC2_HC_XFER_URB_DEQUEUE)) {
+ chan->halt_status = halt_status;
dev_err(hsotg->dev, "%s() Channel can't be halted\n",
__func__);
return;
@@ -2026,11 +2028,13 @@ void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force)
* Without the extra check here we will end calling disconnect
* and won't get any future interrupts to handle the connect.
*/
- if (!force) {
- hprt0 = dwc2_readl(hsotg, HPRT0);
- if (!(hprt0 & HPRT0_CONNDET) && (hprt0 & HPRT0_CONNSTS))
- dwc2_hcd_connect(hsotg);
- }
+ hprt0 = dwc2_readl(hsotg, HPRT0);
+
+ if (!force && !(hprt0 & HPRT0_CONNDET) &&
+ (hprt0 & HPRT0_CONNSTS))
+ dwc2_hcd_connect(hsotg);
+ else if (hsotg->lx_state != DWC2_L0)
+ usb_hcd_resume_root_hub(hsotg->priv);
}
/**
@@ -2634,10 +2638,13 @@ static void dwc2_hc_init_xfer(struct dwc2_hsotg *hsotg,
}
}
-static int dwc2_alloc_split_dma_aligned_buf(struct dwc2_hsotg *hsotg,
- struct dwc2_qh *qh,
- struct dwc2_host_chan *chan)
+static int dwc2_alloc_qh_dma_aligned_buf(struct dwc2_hsotg *hsotg,
+ struct dwc2_qh *qh,
+ struct dwc2_qtd *qtd,
+ struct dwc2_host_chan *chan)
{
+ u32 offset;
+
if (!hsotg->unaligned_cache ||
chan->max_packet > DWC2_KMEM_UNALIGNED_BUF_SIZE)
return -ENOMEM;
@@ -2649,6 +2656,18 @@ static int dwc2_alloc_split_dma_aligned_buf(struct dwc2_hsotg *hsotg,
return -ENOMEM;
}
+ if (!chan->ep_is_in) {
+ if (qh->do_split) {
+ offset = chan->xfer_dma - qtd->urb->dma;
+ memcpy(qh->dw_align_buf, (u8 *)qtd->urb->buf + offset,
+ (chan->xfer_len > 188 ? 188 : chan->xfer_len));
+ } else {
+ offset = chan->xfer_dma - qtd->urb->dma;
+ memcpy(qh->dw_align_buf, (u8 *)qtd->urb->buf + offset,
+ chan->xfer_len);
+ }
+ }
+
qh->dw_align_buf_dma = dma_map_single(hsotg->dev, qh->dw_align_buf,
DWC2_KMEM_UNALIGNED_BUF_SIZE,
DMA_FROM_DEVICE);
@@ -2853,10 +2872,10 @@ static int dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
dwc2_hc_init_xfer(hsotg, chan, qtd);
/* For non-dword aligned buffers */
- if (hsotg->params.host_dma && qh->do_split &&
- chan->ep_is_in && (chan->xfer_dma & 0x3)) {
+ if (hsotg->params.host_dma && (chan->xfer_dma & 0x3) &&
+ chan->ep_type == USB_ENDPOINT_XFER_ISOC) {
dev_vdbg(hsotg->dev, "Non-aligned buffer\n");
- if (dwc2_alloc_split_dma_aligned_buf(hsotg, qh, chan)) {
+ if (dwc2_alloc_qh_dma_aligned_buf(hsotg, qh, qtd, chan)) {
dev_err(hsotg->dev,
"Failed to allocate memory to handle non-aligned buffer\n");
/* Add channel back to free list */
@@ -2870,8 +2889,8 @@ static int dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
}
} else {
/*
- * We assume that DMA is always aligned in non-split
- * case or split out case. Warn if not.
+ * We assume that DMA is always aligned in other case,
+ * Warn if not.
*/
WARN_ON_ONCE(hsotg->params.host_dma &&
(chan->xfer_dma & 0x3));
@@ -3018,7 +3037,7 @@ static int dwc2_queue_transaction(struct dwc2_hsotg *hsotg,
list_move_tail(&chan->split_order_list_entry,
&hsotg->split_order);
- if (hsotg->params.host_dma) {
+ if (hsotg->params.host_dma && chan->qh) {
if (hsotg->params.dma_desc_enable) {
if (!chan->xfer_started ||
chan->ep_type == USB_ENDPOINT_XFER_ISOC) {
@@ -3042,7 +3061,7 @@ static int dwc2_queue_transaction(struct dwc2_hsotg *hsotg,
if (!chan->xfer_started) {
dwc2_hc_start_transfer(hsotg, chan);
retval = 1;
- } else {
+ } else if (!hsotg->params.host_dma) {
retval = dwc2_hc_continue_transfer(hsotg, chan);
}
} else {
@@ -3052,7 +3071,7 @@ static int dwc2_queue_transaction(struct dwc2_hsotg *hsotg,
if (!chan->xfer_started) {
dwc2_hc_start_transfer(hsotg, chan);
retval = 1;
- } else {
+ } else if (!hsotg->params.host_dma) {
retval = dwc2_hc_continue_transfer(hsotg, chan);
}
}
@@ -3366,6 +3385,9 @@ static void dwc2_conn_id_status_change(struct work_struct *work)
dev_dbg(hsotg->dev, "%s()\n", __func__);
+ if (!hsotg->ll_phy_enabled && dwc2_is_host_mode(hsotg))
+ dwc2_lowlevel_phy_enable(hsotg);
+
gotgctl = dwc2_readl(hsotg, GOTGCTL);
dev_dbg(hsotg->dev, "gotgctl=%0x\n", gotgctl);
dev_dbg(hsotg->dev, "gotgctl.b.conidsts=%d\n",
@@ -4490,6 +4512,7 @@ static int _dwc2_hcd_suspend(struct usb_hcd *hcd)
unsigned long flags;
int ret = 0;
u32 hprt0;
+ u32 pcgctl;
spin_lock_irqsave(&hsotg->lock, flags);
@@ -4505,30 +4528,46 @@ static int _dwc2_hcd_suspend(struct usb_hcd *hcd)
if (hsotg->op_state == OTG_STATE_B_PERIPHERAL)
goto unlock;
- if (hsotg->params.power_down != DWC2_POWER_DOWN_PARAM_PARTIAL)
- goto skip_power_saving;
-
/*
* Drive USB suspend and disable port Power
* if usb bus is not suspended.
*/
if (!hsotg->bus_suspended) {
hprt0 = dwc2_read_hprt0(hsotg);
- hprt0 |= HPRT0_SUSP;
- hprt0 &= ~HPRT0_PWR;
- dwc2_writel(hsotg, hprt0, HPRT0);
- spin_unlock_irqrestore(&hsotg->lock, flags);
- dwc2_vbus_supply_exit(hsotg);
- spin_lock_irqsave(&hsotg->lock, flags);
+ if (hprt0 & HPRT0_CONNSTS) {
+ hprt0 |= HPRT0_SUSP;
+ if (hsotg->params.power_down ==
+ DWC2_POWER_DOWN_PARAM_PARTIAL)
+ hprt0 &= ~HPRT0_PWR;
+ dwc2_writel(hsotg, hprt0, HPRT0);
+ hsotg->bus_suspended = true;
+ }
+ if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) {
+ spin_unlock_irqrestore(&hsotg->lock, flags);
+ dwc2_vbus_supply_exit(hsotg);
+ spin_lock_irqsave(&hsotg->lock, flags);
+ } else {
+ pcgctl = dwc2_readl(hsotg, PCGCTL);
+ pcgctl |= PCGCTL_STOPPCLK;
+ dwc2_writel(hsotg, pcgctl, PCGCTL);
+ }
}
- /* Enter partial_power_down */
- ret = dwc2_enter_partial_power_down(hsotg);
- if (ret) {
- if (ret != -ENOTSUPP)
- dev_err(hsotg->dev,
- "enter partial_power_down failed\n");
- goto skip_power_saving;
+ if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) {
+ /* Enter partial_power_down */
+ ret = dwc2_enter_partial_power_down(hsotg);
+ if (ret) {
+ if (ret != -ENOTSUPP)
+ dev_err(hsotg->dev,
+ "enter partial_power_down failed\n");
+ goto skip_power_saving;
+ }
+
+ /*
+ * After entering partial_power_down,
+ * hardware is no more accessible.
+ */
+ clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
}
/* Ask phy to be suspended */
@@ -4538,9 +4577,6 @@ static int _dwc2_hcd_suspend(struct usb_hcd *hcd)
spin_lock_irqsave(&hsotg->lock, flags);
}
- /* After entering partial_power_down, hardware is no more accessible */
- clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
-
skip_power_saving:
hsotg->lx_state = DWC2_L2;
unlock:
@@ -4554,6 +4590,7 @@ static int _dwc2_hcd_resume(struct usb_hcd *hcd)
struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
unsigned long flags;
int ret = 0;
+ u32 pcgctl;
spin_lock_irqsave(&hsotg->lock, flags);
@@ -4563,17 +4600,6 @@ static int _dwc2_hcd_resume(struct usb_hcd *hcd)
if (hsotg->lx_state != DWC2_L2)
goto unlock;
- if (hsotg->params.power_down != DWC2_POWER_DOWN_PARAM_PARTIAL) {
- hsotg->lx_state = DWC2_L0;
- goto unlock;
- }
-
- /*
- * Set HW accessible bit before powering on the controller
- * since an interrupt may rise.
- */
- set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
-
/*
* Enable power if not already done.
* This must not be spinlocked since duration
@@ -4585,10 +4611,22 @@ static int _dwc2_hcd_resume(struct usb_hcd *hcd)
spin_lock_irqsave(&hsotg->lock, flags);
}
- /* Exit partial_power_down */
- ret = dwc2_exit_partial_power_down(hsotg, true);
- if (ret && (ret != -ENOTSUPP))
- dev_err(hsotg->dev, "exit partial_power_down failed\n");
+ if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) {
+ /*
+ * Set HW accessible bit before powering on the controller
+ * since an interrupt may rise.
+ */
+ set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+
+ /* Exit partial_power_down */
+ ret = dwc2_exit_partial_power_down(hsotg, true);
+ if (ret && (ret != -ENOTSUPP))
+ dev_err(hsotg->dev, "exit partial_power_down failed\n");
+ } else {
+ pcgctl = dwc2_readl(hsotg, PCGCTL);
+ pcgctl &= ~PCGCTL_STOPPCLK;
+ dwc2_writel(hsotg, pcgctl, PCGCTL);
+ }
hsotg->lx_state = DWC2_L0;
@@ -4600,10 +4638,15 @@ static int _dwc2_hcd_resume(struct usb_hcd *hcd)
spin_unlock_irqrestore(&hsotg->lock, flags);
dwc2_port_resume(hsotg);
} else {
- dwc2_vbus_supply_init(hsotg);
+ if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) {
+ dwc2_vbus_supply_init(hsotg);
- /* Wait for controller to correctly update D+/D- level */
- usleep_range(3000, 5000);
+ /*
+ * Wait for controller to correctly
+ * update D+/D- level.
+ */
+ usleep_range(3000, 5000);
+ }
/*
* Clear Port Enable and Port Status changes.
@@ -5389,6 +5432,13 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg)
if (!IS_ERR_OR_NULL(hsotg->uphy))
otg_set_host(hsotg->uphy->otg, &hcd->self);
+ /*
+ * do not manage the PHY state in the HCD core, instead let the driver
+ * handle this (for example if the PHY can only be turned on after a
+ * specific event)
+ */
+ hcd->skip_phy_initialization = 1;
+
/*
* Finish generic HCD initialization and start the HCD. This function
* allocates the DMA buffer pool, registers the USB bus, requests the
diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c
index d5f4ec1b7..4989ec761 100644
--- a/drivers/usb/dwc2/hcd_intr.c
+++ b/drivers/usb/dwc2/hcd_intr.c
@@ -1180,7 +1180,10 @@ static void dwc2_update_urb_state_abn(struct dwc2_hsotg *hsotg,
if (urb->actual_length + xfer_length > urb->length) {
dev_warn(hsotg->dev, "%s(): trimming xfer length\n", __func__);
- xfer_length = urb->length - urb->actual_length;
+ if (urb->length & 0x3)
+ xfer_length = 0;
+ else
+ xfer_length = urb->length - urb->actual_length;
}
urb->actual_length += xfer_length;
@@ -2065,8 +2068,6 @@ static void dwc2_hc_n_intr(struct dwc2_hsotg *hsotg, int chnum)
hcint, hcintmsk, hcint & hcintmsk);
}
- dwc2_writel(hsotg, hcint, HCINT(chnum));
-
/*
* If we got an interrupt after someone called
* dwc2_hcd_endpoint_disable() we don't want to crash below
@@ -2079,6 +2080,8 @@ static void dwc2_hc_n_intr(struct dwc2_hsotg *hsotg, int chnum)
chan->hcint = hcint;
hcint &= hcintmsk;
+ dwc2_writel(hsotg, hcint, HCINT(chnum));
+
/*
* If the channel was halted due to a dequeue, the qtd list might
* be empty or at least the first entry will not be the active qtd.
diff --git a/drivers/usb/dwc2/hcd_queue.c b/drivers/usb/dwc2/hcd_queue.c
index 94af71e98..b2e0721a3 100644
--- a/drivers/usb/dwc2/hcd_queue.c
+++ b/drivers/usb/dwc2/hcd_queue.c
@@ -730,8 +730,14 @@ static int dwc2_uframe_schedule_split(struct dwc2_hsotg *hsotg,
* Note that this will tend to front-load the high speed schedule.
* We may eventually want to try to avoid this by either considering
* both schedules together or doing some sort of round robin.
+ *
+ * For isoc split out, start schedule at the 2 * DWC2_SLICES_PER_UFRAME
+ * to transfer SSPLIT-begin OUT transaction like EHCI controller.
*/
- ls_search_slice = 0;
+ if (qh->ep_type == USB_ENDPOINT_XFER_ISOC && !qh->ep_is_in)
+ ls_search_slice = 2 * DWC2_SLICES_PER_UFRAME;
+ else
+ ls_search_slice = 0;
while (ls_search_slice < DWC2_LS_SCHEDULE_SLICES) {
int start_s_uframe;
diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c
index 2c8fe202a..4747a8ef3 100644
--- a/drivers/usb/dwc2/params.c
+++ b/drivers/usb/dwc2/params.c
@@ -88,6 +88,8 @@ static void dwc2_set_rk_params(struct dwc2_hsotg *hsotg)
p->host_perio_tx_fifo_size = 256;
p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 <<
GAHBCFG_HBSTLEN_SHIFT;
+ p->lpm = false;
+ p->g_dma_desc = false;
p->power_down = 0;
}
diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c
index a9e86f5e6..5b6b4d699 100644
--- a/drivers/usb/dwc2/platform.c
+++ b/drivers/usb/dwc2/platform.c
@@ -121,22 +121,11 @@ static int dwc2_get_dr_mode(struct dwc2_hsotg *hsotg)
return 0;
}
-static int __dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg)
+static int __dwc2_lowlevel_phy_enable(struct dwc2_hsotg *hsotg)
{
struct platform_device *pdev = to_platform_device(hsotg->dev);
int ret;
- ret = regulator_bulk_enable(ARRAY_SIZE(hsotg->supplies),
- hsotg->supplies);
- if (ret)
- return ret;
-
- if (hsotg->clk) {
- ret = clk_prepare_enable(hsotg->clk);
- if (ret)
- return ret;
- }
-
if (hsotg->uphy) {
ret = usb_phy_init(hsotg->uphy);
} else if (hsotg->plat && hsotg->plat->phy_init) {
@@ -151,22 +140,22 @@ static int __dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg)
}
/**
- * dwc2_lowlevel_hw_enable - enable platform lowlevel hw resources
+ * dwc2_lowlevel_phy_enable - enable lowlevel PHY resources
* @hsotg: The driver state
*
* A wrapper for platform code responsible for controlling
- * low-level USB platform resources (phy, clock, regulators)
+ * low-level PHY resources.
*/
-int dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg)
+int dwc2_lowlevel_phy_enable(struct dwc2_hsotg *hsotg)
{
- int ret = __dwc2_lowlevel_hw_enable(hsotg);
+ int ret = __dwc2_lowlevel_phy_enable(hsotg);
if (ret == 0)
- hsotg->ll_hw_enabled = true;
+ hsotg->ll_phy_enabled = true;
return ret;
}
-static int __dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg)
+static int __dwc2_lowlevel_phy_disable(struct dwc2_hsotg *hsotg)
{
struct platform_device *pdev = to_platform_device(hsotg->dev);
int ret = 0;
@@ -180,11 +169,79 @@ static int __dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg)
if (ret == 0)
ret = phy_power_off(hsotg->phy);
}
+
+ return ret;
+}
+
+/**
+ * dwc2_lowlevel_phy_disable - disable lowlevel PHY resources
+ * @hsotg: The driver state
+ *
+ * A wrapper for platform code responsible for controlling
+ * low-level PHY platform resources.
+ */
+int dwc2_lowlevel_phy_disable(struct dwc2_hsotg *hsotg)
+{
+ int ret = __dwc2_lowlevel_phy_disable(hsotg);
+
+ if (ret == 0)
+ hsotg->ll_phy_enabled = false;
+ return ret;
+}
+
+static int __dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg)
+{
+ int clk, ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(hsotg->supplies),
+ hsotg->supplies);
+ if (ret)
+ return ret;
+
+ for (clk = 0; clk < DWC2_MAX_CLKS && hsotg->clks[clk]; clk++) {
+ ret = clk_prepare_enable(hsotg->clks[clk]);
+ if (ret) {
+ while (--clk >= 0)
+ clk_disable_unprepare(hsotg->clks[clk]);
+ return ret;
+ }
+ }
+
+ if (!hsotg->ll_phy_enabled)
+ ret = dwc2_lowlevel_phy_enable(hsotg);
+
+ return ret;
+}
+
+/**
+ * dwc2_lowlevel_hw_enable - enable platform lowlevel hw resources
+ * @hsotg: The driver state
+ *
+ * A wrapper for platform code responsible for controlling
+ * low-level USB platform resources (phy, clock, regulators)
+ */
+int dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg)
+{
+ int ret = __dwc2_lowlevel_hw_enable(hsotg);
+
+ if (ret == 0)
+ hsotg->ll_hw_enabled = true;
+ return ret;
+}
+
+static int __dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg)
+{
+ int clk, ret = 0;
+
+ if (hsotg->ll_phy_enabled)
+ ret = dwc2_lowlevel_phy_disable(hsotg);
+
if (ret)
return ret;
- if (hsotg->clk)
- clk_disable_unprepare(hsotg->clk);
+ for (clk = DWC2_MAX_CLKS - 1; clk >= 0; clk--)
+ if (hsotg->clks[clk])
+ clk_disable_unprepare(hsotg->clks[clk]);
ret = regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies),
hsotg->supplies);
@@ -208,9 +265,17 @@ int dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg)
return ret;
}
+/* Only used to reset usb phy at interrupter runtime */
+static void dwc2_reset_phy_work(struct work_struct *data)
+{
+ struct dwc2_hsotg *hsotg = container_of(data, struct dwc2_hsotg,
+ phy_rst_work);
+ phy_reset(hsotg->phy);
+}
+
static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg)
{
- int i, ret;
+ int i, clk, ret;
hsotg->reset = devm_reset_control_get_optional(hsotg->dev, "dwc2");
if (IS_ERR(hsotg->reset)) {
@@ -252,6 +317,7 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg)
return ret;
}
}
+ INIT_WORK(&hsotg->phy_rst_work, dwc2_reset_phy_work);
if (!hsotg->phy) {
hsotg->uphy = devm_usb_get_phy(hsotg->dev, USB_PHY_TYPE_USB2);
@@ -283,11 +349,19 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg)
hsotg->phyif = GUSBCFG_PHYIF8;
}
- /* Clock */
- hsotg->clk = devm_clk_get(hsotg->dev, "otg");
- if (IS_ERR(hsotg->clk)) {
- hsotg->clk = NULL;
- dev_dbg(hsotg->dev, "cannot get otg clock\n");
+ for (clk = 0; clk < DWC2_MAX_CLKS; clk++) {
+ hsotg->clks[clk] = of_clk_get(hsotg->dev->of_node, clk);
+ if (IS_ERR(hsotg->clks[clk])) {
+ ret = PTR_ERR(hsotg->clks[clk]);
+ if (ret == -EPROBE_DEFER) {
+ while (--clk >= 0)
+ clk_put(hsotg->clks[clk]);
+ return ret;
+ }
+
+ hsotg->clks[clk] = NULL;
+ break;
+ }
}
/* Regulators */
@@ -324,6 +398,9 @@ static int dwc2_driver_remove(struct platform_device *dev)
if (hsotg->gadget_enabled)
dwc2_hsotg_remove(hsotg);
+ pm_runtime_put_sync(hsotg->dev);
+ pm_runtime_disable(hsotg->dev);
+
if (hsotg->ll_hw_enabled)
dwc2_lowlevel_hw_disable(hsotg);
@@ -439,6 +516,11 @@ static int dwc2_driver_probe(struct platform_device *dev)
hsotg->needs_byte_swap = dwc2_check_core_endianness(hsotg);
+ pm_runtime_enable(hsotg->dev);
+ retval = pm_runtime_get_sync(hsotg->dev);
+ if (retval < 0)
+ goto error;
+
retval = dwc2_get_dr_mode(hsotg);
if (retval)
goto error;
@@ -493,6 +575,11 @@ static int dwc2_driver_probe(struct platform_device *dev)
if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)
dwc2_lowlevel_hw_disable(hsotg);
+ if (hsotg->dr_mode == USB_DR_MODE_OTG && dwc2_is_device_mode(hsotg)) {
+ if (hsotg->ll_phy_enabled)
+ dwc2_lowlevel_phy_disable(hsotg);
+ }
+
#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \
IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
/* Postponed adding a new gadget to the udc class driver list */
@@ -508,6 +595,8 @@ static int dwc2_driver_probe(struct platform_device *dev)
return 0;
error:
+ pm_runtime_put_sync(hsotg->dev);
+ pm_runtime_disable(hsotg->dev);
if (hsotg->dr_mode != USB_DR_MODE_PERIPHERAL)
dwc2_lowlevel_hw_disable(hsotg);
return retval;
@@ -530,6 +619,7 @@ static int __maybe_unused dwc2_suspend(struct device *dev)
static int __maybe_unused dwc2_resume(struct device *dev)
{
struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev);
+ unsigned long flags;
int ret = 0;
if (dwc2->ll_hw_enabled) {
@@ -538,8 +628,45 @@ static int __maybe_unused dwc2_resume(struct device *dev)
return ret;
}
- if (dwc2_is_device_mode(dwc2))
- ret = dwc2_hsotg_resume(dwc2);
+ if (dwc2_is_device_mode(dwc2)) {
+ if (dwc2->dr_mode == USB_DR_MODE_HOST) {
+ /* Reinit for Host mode if lost power */
+ dwc2_force_mode(dwc2, true);
+
+ spin_lock_irqsave(&dwc2->lock, flags);
+ dwc2_hsotg_disconnect(dwc2);
+ spin_unlock_irqrestore(&dwc2->lock, flags);
+
+ dwc2->op_state = OTG_STATE_A_HOST;
+ /* Initialize the Core for Host mode */
+ dwc2_core_init(dwc2, false);
+ dwc2_enable_global_interrupts(dwc2);
+ dwc2_hcd_start(dwc2);
+ } else {
+ /* Reinit for OTG in Host mode if lost power */
+ if (dwc2->dr_mode == USB_DR_MODE_OTG &&
+ dwc2->op_state == OTG_STATE_A_HOST) {
+ /*
+ * Reinit the core to device mode, and later
+ * after do dwc2_hsotg_resume, it can trigger
+ * the ID status change interrupt if the OTG
+ * cable is still connected, then we can init
+ * for Host mode in the ID status change
+ * interrupt handler.
+ */
+ spin_lock_irqsave(&dwc2->lock, flags);
+ dwc2_hcd_disconnect(dwc2, true);
+ dwc2->op_state = OTG_STATE_B_PERIPHERAL;
+ dwc2->lx_state = DWC2_L3;
+ if (!dwc2->driver)
+ dwc2_hsotg_core_init_disconnected(dwc2, false);
+ spin_unlock_irqrestore(&dwc2->lock, flags);
+
+ }
+
+ ret = dwc2_hsotg_resume(dwc2);
+ }
+ }
return ret;
}
diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
index 5d22f4bf2..89add5708 100644
--- a/drivers/usb/dwc3/Kconfig
+++ b/drivers/usb/dwc3/Kconfig
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
config USB_DWC3
tristate "DesignWare USB3 DRD Core Support"
depends on (USB || USB_GADGET) && HAS_DMA
@@ -103,6 +105,16 @@ config USB_DWC3_OF_SIMPLE
Currently supports Xilinx and Qualcomm DWC USB3 IP.
Say 'Y' or 'M' if you have one such device.
+config USB_DWC3_ROCKCHIP_INNO
+ tristate "Rockchip Platforms with INNO PHY"
+ depends on OF && COMMON_CLK && (ARCH_ROCKCHIP || COMPILE_TEST)
+ depends on USB=y || USB=USB_DWC3
+ default USB_DWC3
+ help
+ Support of USB2/3 functionality in Rockchip platforms
+ with INNO USB 3.0 PHY IP inside.
+ say 'Y' or 'M' if you have one such device.
+
config USB_DWC3_ST
tristate "STMicroelectronics Platforms"
depends on (ARCH_STI || COMPILE_TEST) && OF
diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
index 6e3ef6144..f933b68d3 100644
--- a/drivers/usb/dwc3/Makefile
+++ b/drivers/usb/dwc3/Makefile
@@ -48,5 +48,6 @@ obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o
obj-$(CONFIG_USB_DWC3_HAPS) += dwc3-haps.o
obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o
obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o
+obj-$(CONFIG_USB_DWC3_ROCKCHIP_INNO) += dwc3-rockchip-inno.o
obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o
obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 87e38a753..b06b0d083 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -8,6 +8,7 @@
* Sebastian Andrzej Siewior <bigeasy@linutronix.de>
*/
+#include <linux/async.h>
#include <linux/clk.h>
#include <linux/version.h>
#include <linux/module.h>
@@ -80,11 +81,12 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc)
mode = USB_DR_MODE_PERIPHERAL;
/*
- * dwc_usb31 does not support OTG mode. If the controller
- * supports DRD but the dr_mode is not specified or set to OTG,
- * then set the mode to peripheral.
+ * DWC_usb31 and DWC_usb3 v3.30a and higher do not support OTG
+ * mode. If the controller supports DRD but the dr_mode is not
+ * specified or set to OTG, then set the mode to peripheral.
*/
- if (mode == USB_DR_MODE_OTG && dwc3_is_usb31(dwc))
+ if (mode == USB_DR_MODE_OTG &&
+ dwc->revision >= DWC3_REVISION_330A)
mode = USB_DR_MODE_PERIPHERAL;
}
@@ -126,6 +128,9 @@ static void __dwc3_set_mode(struct work_struct *work)
if (!dwc->desired_dr_role)
return;
+ if (dwc->en_runtime)
+ goto runtime;
+
if (dwc->desired_dr_role == dwc->current_dr_role)
return;
@@ -167,7 +172,9 @@ static void __dwc3_set_mode(struct work_struct *work)
otg_set_vbus(dwc->usb2_phy->otg, true);
phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_HOST);
phy_set_mode(dwc->usb3_generic_phy, PHY_MODE_USB_HOST);
- phy_calibrate(dwc->usb2_generic_phy);
+ if (!of_device_is_compatible(dwc->dev->parent->of_node,
+ "rockchip,rk3399-dwc3"))
+ phy_calibrate(dwc->usb2_generic_phy);
}
break;
case DWC3_GCTL_PRTCAP_DEVICE:
@@ -190,6 +197,118 @@ static void __dwc3_set_mode(struct work_struct *work)
break;
}
+ return;
+
+runtime:
+ if (extcon_get_state(dwc->edev, EXTCON_USB) ||
+ extcon_get_state(dwc->edev, EXTCON_USB_HOST)) {
+ if (dwc->drd_connected) {
+ /*
+ * If the connected flag is true, and the DWC3 is
+ * in device mode, it means that the Type-C cable
+ * is doing data role swap (UFP -> DFP), so we need
+ * to disconnect UFP first, and then switch DWC3 to
+ * DFP depends on the next extcon notifier.
+ */
+ if (extcon_get_state(dwc->edev, EXTCON_USB_HOST) &&
+ dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE)
+ goto disconnect;
+ else
+ return;
+ }
+
+ dwc->current_dr_role = dwc->desired_dr_role;
+ pm_runtime_get_sync(dwc->dev);
+ /*
+ * We should set drd_connected true after runtime_resume to
+ * enable reset deassert.
+ */
+ dwc->drd_connected = true;
+
+ spin_lock_irqsave(&dwc->lock, flags);
+
+ dwc3_set_prtcap(dwc, dwc->desired_dr_role);
+
+ spin_unlock_irqrestore(&dwc->lock, flags);
+
+ switch (dwc->current_dr_role) {
+ case DWC3_GCTL_PRTCAP_HOST:
+ phy_power_on(dwc->usb3_generic_phy);
+ ret = dwc3_host_init(dwc);
+ if (ret) {
+ dev_err(dwc->dev,
+ "failed to initialize host\n");
+ } else {
+ if (dwc->usb2_phy)
+ otg_set_vbus(dwc->usb2_phy->otg, true);
+ phy_set_mode(dwc->usb2_generic_phy,
+ PHY_MODE_USB_HOST);
+ phy_set_mode(dwc->usb3_generic_phy,
+ PHY_MODE_USB_HOST);
+ if (!of_device_is_compatible(
+ dwc->dev->parent->of_node,
+ "rockchip,rk3399-dwc3"))
+ phy_calibrate(dwc->usb2_generic_phy);
+ }
+ break;
+ case DWC3_GCTL_PRTCAP_DEVICE:
+ if (dwc->usb2_phy)
+ otg_set_vbus(dwc->usb2_phy->otg, false);
+ phy_set_mode(dwc->usb2_generic_phy,
+ PHY_MODE_USB_DEVICE);
+ phy_set_mode(dwc->usb3_generic_phy,
+ PHY_MODE_USB_DEVICE);
+ dwc->gadget.ops->udc_set_speed(&dwc->gadget,
+ dwc->maximum_speed);
+ break;
+ case DWC3_GCTL_PRTCAP_OTG:
+ break;
+ default:
+ break;
+ }
+ } else {
+disconnect:
+ switch (dwc->current_dr_role) {
+ case DWC3_GCTL_PRTCAP_HOST:
+ if (dwc->drd_connected) {
+ /*
+ * Set device mode to disable otg-vbus supply
+ * and enable vbus detect for inno USB2PHY.
+ */
+ phy_set_mode(dwc->usb2_generic_phy,
+ PHY_MODE_USB_DEVICE);
+ phy_set_mode(dwc->usb3_generic_phy,
+ PHY_MODE_USB_DEVICE);
+ phy_power_off(dwc->usb3_generic_phy);
+ dwc3_host_exit(dwc);
+ }
+ break;
+ case DWC3_GCTL_PRTCAP_DEVICE:
+ if (dwc->connected) {
+ ret = wait_for_completion_timeout(&dwc->discon_done,
+ msecs_to_jiffies(DWC3_DISCON_TIMEOUT));
+ if (!ret)
+ dev_warn(dwc->dev,
+ "timed out waiting for disconnect\n");
+ }
+
+ break;
+ case DWC3_GCTL_PRTCAP_OTG:
+ break;
+ default:
+ dwc->current_dr_role = dwc->desired_dr_role;
+ return;
+ }
+
+ /*
+ * We should set drd_connected to false before
+ * runtime_suspend to enable reset assert.
+ */
+ if (dwc->drd_connected) {
+ dwc->drd_connected = false;
+ pm_runtime_put_sync_suspend(dwc->dev);
+ }
+ }
}
void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
@@ -660,6 +779,8 @@ static int dwc3_phy_setup(struct dwc3 *dwc)
if (dwc->dis_enblslpm_quirk)
reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM;
+ else
+ reg |= DWC3_GUSB2PHYCFG_ENBLSLPM;
if (dwc->dis_u2_freeclk_exists_quirk)
reg &= ~DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS;
@@ -684,7 +805,9 @@ static void dwc3_core_exit(struct dwc3 *dwc)
phy_power_off(dwc->usb3_generic_phy);
clk_bulk_disable(dwc->num_clks, dwc->clks);
clk_bulk_unprepare(dwc->num_clks, dwc->clks);
- reset_control_assert(dwc->reset);
+
+ if (!dwc->drd_connected && dwc->dr_mode == USB_DR_MODE_OTG)
+ reset_control_assert(dwc->reset);
}
static bool dwc3_core_is_valid(struct dwc3 *dwc)
@@ -701,6 +824,7 @@ static bool dwc3_core_is_valid(struct dwc3 *dwc)
/* Detected DWC_usb31 IP */
dwc->revision = dwc3_readl(dwc->regs, DWC3_VER_NUMBER);
dwc->revision |= DWC3_REVISION_IS_DWC31;
+ dwc->version_type = dwc3_readl(dwc->regs, DWC3_VER_TYPE);
} else {
return false;
}
@@ -823,6 +947,7 @@ static void dwc3_set_incr_burst_type(struct dwc3 *dwc)
ret = device_property_read_u32_array(dev,
"snps,incr-burst-type-adjustment", vals, ntype);
if (ret) {
+ kfree(vals);
dev_err(dev, "Error to get property\n");
return;
}
@@ -841,6 +966,8 @@ static void dwc3_set_incr_burst_type(struct dwc3 *dwc)
incrx_mode = INCRX_BURST_MODE;
}
+ kfree(vals);
+
/* Enable Undefined Length INCR Burst and Enable INCRx Burst */
cfg &= ~DWC3_GSBUSCFG0_INCRBRST_MASK;
if (incrx_mode)
@@ -888,12 +1015,6 @@ static int dwc3_core_init(struct dwc3 *dwc)
u32 reg;
int ret;
- if (!dwc3_core_is_valid(dwc)) {
- dev_err(dwc->dev, "this is not a DesignWare USB3 DRD Core\n");
- ret = -ENODEV;
- goto err0;
- }
-
/*
* Write Linux Version Code to our GUID register so it's easy to figure
* out which kernel version a bug was found.
@@ -984,6 +1105,10 @@ static int dwc3_core_init(struct dwc3 *dwc)
if (dwc->parkmode_disable_ss_quirk)
reg |= DWC3_GUCTL1_PARKMODE_DISABLE_SS;
+ if (dwc->maximum_speed == USB_SPEED_HIGH ||
+ dwc->maximum_speed == USB_SPEED_FULL)
+ reg |= DWC3_GUCTL1_DEV_FORCE_20_CLK_FOR_30_CLK;
+
dwc3_writel(dwc->regs, DWC3_GUCTL1, reg);
}
@@ -1152,8 +1277,23 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
dev_err(dev, "failed to initialize gadget\n");
return ret;
}
+
+ if (dwc->uwk_en)
+ device_init_wakeup(dev, true);
break;
case USB_DR_MODE_HOST:
+ /*
+ * To prevent usb device be reenumerated when resume from PM
+ * suspend, we set the flag dwc->power.can_wakeup which can
+ * keep PD on and run phy_power_on again to avoid
+ * phy_power_on failed (error -110) in Rockchip platform.
+ */
+ if (!of_machine_is_compatible("rockchip,rk3568") &&
+ !of_machine_is_compatible("rockchip,rk3566"))
+ device_init_wakeup(dev, true);
+
+ phy_power_on(dwc->usb3_generic_phy);
+
dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST);
if (dwc->usb2_phy)
@@ -1167,16 +1307,32 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
dev_err(dev, "failed to initialize host\n");
return ret;
}
- phy_calibrate(dwc->usb2_generic_phy);
+ if (!of_device_is_compatible(dwc->dev->parent->of_node,
+ "rockchip,rk3399-dwc3"))
+ phy_calibrate(dwc->usb2_generic_phy);
break;
case USB_DR_MODE_OTG:
INIT_WORK(&dwc->drd_work, __dwc3_set_mode);
+ if (dwc->en_runtime) {
+ ret = dwc3_gadget_init(dwc);
+ if (ret) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to initialize gadget\n");
+ return ret;
+ }
+ }
ret = dwc3_drd_init(dwc);
if (ret) {
+ if (dwc->en_runtime)
+ dwc3_gadget_exit(dwc);
+
if (ret != -EPROBE_DEFER)
dev_err(dev, "failed to initialize dual-role\n");
return ret;
}
+
+ if (dwc->uwk_en)
+ device_init_wakeup(dev, true);
break;
default:
dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
@@ -1249,8 +1405,12 @@ static void dwc3_get_properties(struct dwc3 *dwc)
"snps,is-utmi-l1-suspend");
device_property_read_u8(dev, "snps,hird-threshold",
&hird_threshold);
+ dwc->dis_start_transfer_quirk = device_property_read_bool(dev,
+ "snps,dis-start-transfer-quirk");
dwc->usb3_lpm_capable = device_property_read_bool(dev,
"snps,usb3_lpm_capable");
+ dwc->usb2_lpm_disable = device_property_read_bool(dev,
+ "snps,usb2-lpm-disable");
device_property_read_u8(dev, "snps,rx-thr-num-pkt-prd",
&rx_thr_num_pkt_prd);
device_property_read_u8(dev, "snps,rx-max-burst-prd",
@@ -1280,6 +1440,8 @@ static void dwc3_get_properties(struct dwc3 *dwc)
"snps,dis_u3_susphy_quirk");
dwc->dis_u2_susphy_quirk = device_property_read_bool(dev,
"snps,dis_u2_susphy_quirk");
+ dwc->dis_u1u2_quirk = device_property_read_bool(dev,
+ "snps,dis-u1u2-quirk");
dwc->dis_enblslpm_quirk = device_property_read_bool(dev,
"snps,dis_enblslpm_quirk");
dwc->dis_rxdet_inp3_quirk = device_property_read_bool(dev,
@@ -1292,6 +1454,12 @@ static void dwc3_get_properties(struct dwc3 *dwc)
"snps,dis-tx-ipgap-linecheck-quirk");
dwc->parkmode_disable_ss_quirk = device_property_read_bool(dev,
"snps,parkmode-disable-ss-quirk");
+ dwc->xhci_slow_suspend_quirk = device_property_read_bool(dev,
+ "snps,xhci-slow-suspend-quirk");
+ dwc->xhci_trb_ent_quirk = device_property_read_bool(dev,
+ "snps,xhci-trb-ent-quirk");
+ dwc->dis_u3_autosuspend_quirk = device_property_read_bool(dev,
+ "snps,dis-u3-autosuspend-quirk");
dwc->tx_de_emphasis_quirk = device_property_read_bool(dev,
"snps,tx_de_emphasis_quirk");
@@ -1304,6 +1472,12 @@ static void dwc3_get_properties(struct dwc3 *dwc)
dwc->dis_metastability_quirk = device_property_read_bool(dev,
"snps,dis_metastability_quirk");
+ dwc->needs_fifo_resize = device_property_read_bool(dev,
+ "snps,tx-fifo-resize");
+ dwc->xhci_warm_reset_on_suspend_quirk = device_property_read_bool(dev,
+ "snps,xhci-warm-reset-on-suspend-quirk");
+ dwc->uwk_en = device_property_read_bool(dev,
+ "wakeup-source");
dwc->lpm_nyet_threshold = lpm_nyet_threshold;
dwc->tx_de_emphasis = tx_de_emphasis;
@@ -1378,6 +1552,23 @@ static void dwc3_check_params(struct dwc3 *dwc)
}
}
+static void dwc3_rockchip_async_probe(void *data, async_cookie_t cookie)
+{
+ struct dwc3 *dwc = data;
+ struct device *dev = dwc->dev;
+ int id;
+
+ if (dwc->edev && !dwc->drd_connected) {
+ id = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
+ if (id < 0)
+ id = 0;
+ dwc->current_dr_role = id ? DWC3_GCTL_PRTCAP_HOST :
+ DWC3_GCTL_PRTCAP_DEVICE;
+ }
+
+ pm_runtime_put_sync_suspend(dev);
+}
+
static int dwc3_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -1425,18 +1616,38 @@ static int dwc3_probe(struct platform_device *pdev)
dwc->regs = regs;
dwc->regs_size = resource_size(&dwc_res);
- dwc3_get_properties(dwc);
-
dwc->reset = devm_reset_control_get_optional_shared(dev, NULL);
if (IS_ERR(dwc->reset))
return PTR_ERR(dwc->reset);
+ ret = reset_control_deassert(dwc->reset);
+ if (ret)
+ return ret;
+
+ /* Reset the whole dwc3 controller */
+ ret = reset_control_assert(dwc->reset);
+ if (ret)
+ return ret;
+
+ udelay(1);
+
+ ret = reset_control_deassert(dwc->reset);
+ if (ret)
+ return ret;
+
+ if (!dwc3_core_is_valid(dwc)) {
+ dev_err(dwc->dev, "this is not a DesignWare USB3 DRD Core\n");
+ return -ENODEV;
+ }
+
+ dwc3_get_properties(dwc);
+
if (dev->of_node) {
dwc->num_clks = ARRAY_SIZE(dwc3_core_clks);
ret = clk_bulk_get(dev, dwc->num_clks, dwc->clks);
if (ret == -EPROBE_DEFER)
- return ret;
+ goto assert_reset;
/*
* Clocks are optional, but new DT platforms should support all
* clocks as required by the DT-binding.
@@ -1445,13 +1656,9 @@ static int dwc3_probe(struct platform_device *pdev)
dwc->num_clks = 0;
}
- ret = reset_control_deassert(dwc->reset);
- if (ret)
- goto put_clks;
-
ret = clk_bulk_prepare(dwc->num_clks, dwc->clks);
if (ret)
- goto assert_reset;
+ goto put_clks;
ret = clk_bulk_enable(dwc->num_clks, dwc->clks);
if (ret)
@@ -1483,6 +1690,13 @@ static int dwc3_probe(struct platform_device *pdev)
if (ret)
goto err3;
+ if (dwc->dr_mode == USB_DR_MODE_OTG &&
+ of_device_is_compatible(dev->parent->of_node,
+ "rockchip,rk3399-dwc3")) {
+ pm_runtime_allow(dev);
+ dwc->en_runtime = true;
+ }
+
ret = dwc3_alloc_scratch_buffers(dwc);
if (ret)
goto err3;
@@ -1501,7 +1715,10 @@ static int dwc3_probe(struct platform_device *pdev)
if (ret)
goto err5;
- pm_runtime_put(dev);
+ if (dwc->en_runtime)
+ async_schedule(dwc3_rockchip_async_probe, dwc);
+ else
+ pm_runtime_put(dev);
return 0;
@@ -1537,10 +1754,10 @@ static int dwc3_probe(struct platform_device *pdev)
clk_bulk_disable(dwc->num_clks, dwc->clks);
unprepare_clks:
clk_bulk_unprepare(dwc->num_clks, dwc->clks);
-assert_reset:
- reset_control_assert(dwc->reset);
put_clks:
clk_bulk_put(dwc->num_clks, dwc->clks);
+assert_reset:
+ reset_control_assert(dwc->reset);
return ret;
}
@@ -1573,9 +1790,11 @@ static int dwc3_core_init_for_resume(struct dwc3 *dwc)
{
int ret;
- ret = reset_control_deassert(dwc->reset);
- if (ret)
- return ret;
+ if (!dwc->drd_connected && dwc->dr_mode == USB_DR_MODE_OTG) {
+ ret = reset_control_deassert(dwc->reset);
+ if (ret)
+ return ret;
+ }
ret = clk_bulk_prepare(dwc->num_clks, dwc->clks);
if (ret)
@@ -1596,7 +1815,8 @@ static int dwc3_core_init_for_resume(struct dwc3 *dwc)
unprepare_clks:
clk_bulk_unprepare(dwc->num_clks, dwc->clks);
assert_reset:
- reset_control_assert(dwc->reset);
+ if (!dwc->drd_connected && dwc->dr_mode == USB_DR_MODE_OTG)
+ reset_control_assert(dwc->reset);
return ret;
}
@@ -1615,7 +1835,7 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
dwc3_core_exit(dwc);
break;
case DWC3_GCTL_PRTCAP_HOST:
- if (!PMSG_IS_AUTO(msg)) {
+ if (!PMSG_IS_AUTO(msg) || dwc->en_runtime) {
dwc3_core_exit(dwc);
break;
}
@@ -1676,7 +1896,7 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg)
spin_unlock_irqrestore(&dwc->lock, flags);
break;
case DWC3_GCTL_PRTCAP_HOST:
- if (!PMSG_IS_AUTO(msg)) {
+ if (!PMSG_IS_AUTO(msg) || dwc->en_runtime) {
ret = dwc3_core_init_for_resume(dwc);
if (ret)
return ret;
@@ -1753,7 +1973,7 @@ static int dwc3_runtime_suspend(struct device *dev)
if (ret)
return ret;
- device_init_wakeup(dev, true);
+ device_init_wakeup(dev, false);
return 0;
}
@@ -1763,7 +1983,7 @@ static int dwc3_runtime_resume(struct device *dev)
struct dwc3 *dwc = dev_get_drvdata(dev);
int ret;
- device_init_wakeup(dev, false);
+ device_init_wakeup(dev, true);
ret = dwc3_resume_common(dwc, PMSG_AUTO_RESUME);
if (ret)
@@ -1812,10 +2032,41 @@ static int dwc3_suspend(struct device *dev)
struct dwc3 *dwc = dev_get_drvdata(dev);
int ret;
+ if (dwc->uwk_en) {
+ dwc3_gadget_disable_irq(dwc);
+ synchronize_irq(dwc->irq_gadget);
+ return 0;
+ }
+
+ if (pm_runtime_suspended(dwc->dev))
+ return 0;
+
ret = dwc3_suspend_common(dwc, PMSG_SUSPEND);
if (ret)
return ret;
+ /*
+ * If link state is Rx.Detect, it means that
+ * no usb device is connecting with the DWC3
+ * Host, and need to power off the USB3 PHY.
+ *
+ * If link state is in other state, like U0
+ * or U3 state, it means that at least one
+ * USB3 device is connecting with the Host
+ * port, in this case, we don't power off
+ * the USB3 PHY because some USB3 PHYs (like
+ * RK3399 Type-C USB3 PHY) require that the
+ * power on operation must be done while the
+ * DWC3 controller is in P2 state, but the
+ * state is in P0 after resume with a USB3
+ * device connected. So we set the USB3 PHY
+ * in power on state in this case.
+ */
+ dwc->link_state = dwc3_gadget_get_link_state(dwc);
+ if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST &&
+ dwc->link_state == DWC3_LINK_STATE_RX_DET)
+ phy_power_off(dwc->usb3_generic_phy);
+
pinctrl_pm_select_sleep_state(dev);
return 0;
@@ -1826,8 +2077,20 @@ static int dwc3_resume(struct device *dev)
struct dwc3 *dwc = dev_get_drvdata(dev);
int ret;
+ if (dwc->uwk_en) {
+ dwc3_gadget_enable_irq(dwc);
+ return 0;
+ }
+
+ if (pm_runtime_suspended(dwc->dev))
+ return 0;
+
pinctrl_pm_select_default_state(dev);
+ if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST &&
+ dwc->link_state == DWC3_LINK_STATE_RX_DET)
+ phy_power_on(dwc->usb3_generic_phy);
+
ret = dwc3_resume_common(dwc, PMSG_RESUME);
if (ret)
return ret;
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 55ee41283..4f23717d2 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -32,11 +32,13 @@
#define DWC3_MSG_MAX 500
/* Global constants */
+#define DWC3_DISCON_TIMEOUT 1000 /* ms */
#define DWC3_PULL_UP_TIMEOUT 500 /* ms */
#define DWC3_BOUNCE_SIZE 1024 /* size of a superspeed bulk */
#define DWC3_EP0_SETUP_SIZE 512
#define DWC3_ENDPOINTS_NUM 32
#define DWC3_XHCI_RESOURCES_NUM 2
+#define DWC3_ISOC_MAX_RETRIES 50
#define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */
#define DWC3_EVENT_BUFFERS_SIZE 4096
@@ -174,13 +176,19 @@
#define DWC3_GSBUSCFG0_INCRBRSTENA (1 << 0) /* undefined length enable */
#define DWC3_GSBUSCFG0_INCRBRST_MASK 0xff
+/* Global Debug LSP MUX Select */
+#define DWC3_GDBGLSPMUX_ENDBC BIT(15) /* Host only */
+#define DWC3_GDBGLSPMUX_HOSTSELECT(n) ((n) & 0x3fff)
+#define DWC3_GDBGLSPMUX_DEVSELECT(n) (((n) & 0xf) << 4)
+#define DWC3_GDBGLSPMUX_EPSELECT(n) ((n) & 0xf)
+
/* Global Debug Queue/FIFO Space Available Register */
#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_TXFIFOQ 0
-#define DWC3_RXFIFOQ 1
+#define DWC3_TXFIFO 0
+#define DWC3_RXFIFO 1
#define DWC3_TXREQQ 2
#define DWC3_RXREQQ 3
#define DWC3_RXINFOQ 4
@@ -242,9 +250,10 @@
#define DWC3_GUCTL_HSTINAUTORETRY BIT(14)
/* Global User Control 1 Register */
-#define DWC3_GUCTL1_PARKMODE_DISABLE_SS BIT(17)
#define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS BIT(28)
+#define DWC3_GUCTL1_DEV_FORCE_20_CLK_FOR_30_CLK BIT(26)
#define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24)
+#define DWC3_GUCTL1_PARKMODE_DISABLE_SS BIT(17)
/* Global Status Register */
#define DWC3_GSTS_OTG_IP BIT(10)
@@ -254,6 +263,9 @@
#define DWC3_GSTS_DEVICE_IP BIT(6)
#define DWC3_GSTS_CSR_TIMEOUT BIT(5)
#define DWC3_GSTS_BUS_ERR_ADDR_VLD BIT(4)
+#define DWC3_GSTS_CURMOD(n) ((n) & 0x3)
+#define DWC3_GSTS_CURMOD_DEVICE 0
+#define DWC3_GSTS_CURMOD_HOST 1
/* Global USB2 PHY Configuration Register */
#define DWC3_GUSB2PHYCFG_PHYSOFTRST BIT(31)
@@ -327,6 +339,7 @@
#define DWC3_GHWPARAMS1_EN_PWROPT_HIB 2
#define DWC3_GHWPARAMS1_PWROPT(n) ((n) << 24)
#define DWC3_GHWPARAMS1_PWROPT_MASK DWC3_GHWPARAMS1_PWROPT(3)
+#define DWC3_GHWPARAMS1_ENDBC BIT(31)
/* Global HWPARAMS3 Register */
#define DWC3_GHWPARAMS3_SSPHY_IFC(n) ((n) & 3)
@@ -401,8 +414,7 @@
#define DWC3_DCTL_TRGTULST_SS_INACT (DWC3_DCTL_TRGTULST(6))
/* These apply for core versions 1.94a and later */
-#define DWC3_DCTL_LPM_ERRATA_MASK DWC3_DCTL_LPM_ERRATA(0xf)
-#define DWC3_DCTL_LPM_ERRATA(n) ((n) << 20)
+#define DWC3_DCTL_NYET_THRES(n) (((n) & 0xf) << 20)
#define DWC3_DCTL_KEEP_CONNECT BIT(19)
#define DWC3_DCTL_L1_HIBER_EN BIT(18)
@@ -662,6 +674,10 @@ struct dwc3_event_buffer {
* @name: a human readable name e.g. ep1out-bulk
* @direction: true for TX, false for RX
* @stream_capable: true when streams are enabled
+ * @combo_num: the test combination BIT[15:14] of the frame number to test
+ * isochronous START TRANSFER command failure workaround
+ * @start_cmd_status: the status of testing START TRANSFER command with
+ * combo_num = 'b00
*/
struct dwc3_ep {
struct usb_ep endpoint;
@@ -682,8 +698,9 @@ struct dwc3_ep {
#define DWC3_EP_STALL BIT(1)
#define DWC3_EP_WEDGE BIT(2)
#define DWC3_EP_TRANSFER_STARTED BIT(3)
+#define DWC3_EP_END_TRANSFER_PENDING BIT(4)
#define DWC3_EP_PENDING_REQUEST BIT(5)
-#define DWC3_EP_END_TRANSFER_PENDING BIT(7)
+#define DWC3_EP_DELAY_START BIT(6)
/* This last one is specific to EP0 */
#define DWC3_EP0_DIR_IN BIT(31)
@@ -710,6 +727,10 @@ struct dwc3_ep {
unsigned direction:1;
unsigned stream_capable:1;
+
+ /* For isochronous START TRANSFER workaround only */
+ u8 combo_num;
+ int start_cmd_status;
};
enum dwc3_phy {
@@ -771,6 +792,7 @@ enum dwc3_link_state {
#define DWC3_TRB_CTRL_ISP_IMI BIT(10)
#define DWC3_TRB_CTRL_IOC BIT(11)
#define DWC3_TRB_CTRL_SID_SOFN(n) (((n) & 0xffff) << 14)
+#define DWC3_TRB_CTRL_GET_SID_SOFN(n) (((n) & (0xffff << 14)) >> 14)
#define DWC3_TRBCTL_TYPE(n) ((n) & (0x3f << 4))
#define DWC3_TRBCTL_NORMAL DWC3_TRB_CTRL_TRBCTL(1)
@@ -849,6 +871,7 @@ struct dwc3_hwparams {
* @num_pending_sgs: counter to pending sgs
* @num_queued_sgs: counter to the number of sgs which already got queued
* @remaining: amount of data remaining
+ * @status: internal dwc3 request status tracking
* @epnum: endpoint number to which this request refers
* @trb: pointer to struct dwc3_trb
* @trb_dma: DMA address of @trb
@@ -857,7 +880,6 @@ struct dwc3_hwparams {
* or unaligned OUT)
* @direction: IN or OUT direction flag
* @mapped: true when request has been dma-mapped
- * @started: request is started
*/
struct dwc3_request {
struct usb_request request;
@@ -869,6 +891,14 @@ struct dwc3_request {
unsigned num_pending_sgs;
unsigned int num_queued_sgs;
unsigned remaining;
+
+ unsigned int status;
+#define DWC3_REQUEST_STATUS_QUEUED 0
+#define DWC3_REQUEST_STATUS_STARTED 1
+#define DWC3_REQUEST_STATUS_CANCELLED 2
+#define DWC3_REQUEST_STATUS_COMPLETED 3
+#define DWC3_REQUEST_STATUS_UNKNOWN -1
+
u8 epnum;
struct dwc3_trb *trb;
dma_addr_t trb_dma;
@@ -878,7 +908,6 @@ struct dwc3_request {
unsigned needs_extra_trb:1;
unsigned direction:1;
unsigned mapped:1;
- unsigned started:1;
};
/*
@@ -901,6 +930,7 @@ struct dwc3_scratchpad_array {
* @ep0_usb_req: dummy req used while handling STD USB requests
* @scratch_addr: dma address of scratchbuf
* @ep0_in_setup: one control transfer is completed and enter setup phase
+ * @discon_done: disconnect event is completed
* @lock: for synchronizing
* @dev: pointer to our struct device
* @sysdev: pointer to the DMA-capable device
@@ -925,6 +955,7 @@ struct dwc3_scratchpad_array {
* @u1u2: only used on revisions <1.83a for workaround
* @maximum_speed: maximum speed requested (mainly for testing purposes)
* @revision: revision register contents
+ * @version_type: VERSIONTYPE register contents, a sub release of a revision
* @dr_mode: requested mode of operation
* @current_dr_role: current role of operation when in dual-role mode
* @desired_dr_role: desired role of operation when in dual-role mode
@@ -952,6 +983,7 @@ struct dwc3_scratchpad_array {
* @hwparams: copy of hwparams registers
* @root: debugfs root folder pointer
* @regset: debugfs pointer to regdump file
+ * @dbg_lsp_select: current debug lsp mux register selection
* @test_mode: true when we're entering a USB test mode
* @test_mode_nr: test feature selector
* @lpm_nyet_threshold: LPM NYET response threshold
@@ -977,7 +1009,10 @@ struct dwc3_scratchpad_array {
* @pullups_connected: true when Run/Stop bit is set
* @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround
* @three_stage_setup: set if we perform a three phase setup
+ * @dis_start_transfer_quirk: set if start_transfer failure SW workaround is
+ * not needed for DWC_usb31 version 1.70a-ea06 and below
* @usb3_lpm_capable: set if hadrware supports Link Power Management
+ * @usb2_lpm_disable: set to disable usb2 lpm
* @disable_scramble_quirk: set if we enable the disable scramble 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
@@ -988,6 +1023,7 @@ struct dwc3_scratchpad_array {
* @rx_detect_poll_quirk: set if we enable rx_detect to polling lfps quirk
* @dis_u3_susphy_quirk: set if we disable usb3 suspend phy
* @dis_u2_susphy_quirk: set if we disable usb2 suspend phy
+ * @dis_u1u2_quirk: set if we reject transition to U1 or U2 state
* @dis_enblslpm_quirk: set if we clear enblslpm in GUSB2PHYCFG,
* disabling the suspend signal to the PHY.
* @dis_rxdet_inp3_quirk: set if we disable Rx.Detect in P3
@@ -1000,6 +1036,15 @@ struct dwc3_scratchpad_array {
* check during HS transmit.
* @parkmode_disable_ss_quirk: set if we need to disable all SuperSpeed
* instances in park mode.
+ * @xhci_slow_suspend_quirk: set if need an extraordinary delay to wait
+ * for xHC enter the Halted state after the Run/Stop
+ * (R/S) bit is cleared to '0'.
+ * @xhci_trb_ent_quirk: set if need to enable the Evaluate Next TRB(ENT)
+ flag in the TRB data structure to force xHC to
+ pre-fetch the next TRB of a TD.
+ * @xhci_warm_reset_on_suspend_quirk: set if need to do a warm port reset
+ * for xHC USB3 port upon suspend.
+ * @dis_u3_autosuspend_quirk: set if the we want to disable usb3 autosuspend
* @tx_de_emphasis_quirk: set if we enable Tx de-emphasis quirk
* @tx_de_emphasis: Tx de-emphasis value
* 0 - -6dB de-emphasis
@@ -1007,6 +1052,13 @@ struct dwc3_scratchpad_array {
* 2 - No de-emphasis
* 3 - Reserved
* @dis_metastability_quirk: set to disable metastability quirk.
+ * @needs_fifo_resize: set if we want to resize TXFIFO.
+ * @fifo_resize_status: true if the TXFIFOs have been resized.
+ * @drd_connected: true when usb connected to a host or a device(drd mode),
+ * false otherwise.
+ * @en_runtime: true when need runtime PM management. For example, RK3399 need
+ * reset dwc3 and usb3phy to support typec interface.
+ * @uwk_en: true when enable usb wakeup from host resume signal.
* @imod_interval: set the interrupt moderation interval in 250ns
* increments or 0 to disable.
*/
@@ -1021,6 +1073,7 @@ struct dwc3 {
dma_addr_t scratch_addr;
struct dwc3_request ep0_usb_req;
struct completion ep0_in_setup;
+ struct completion discon_done;
/* device lock */
spinlock_t lock;
@@ -1104,6 +1157,7 @@ struct dwc3 {
#define DWC3_REVISION_290A 0x5533290a
#define DWC3_REVISION_300A 0x5533300a
#define DWC3_REVISION_310A 0x5533310a
+#define DWC3_REVISION_330A 0x5533330a
/*
* NOTICE: we're using bit 31 as a "is usb 3.1" flag. This is really
@@ -1112,6 +1166,17 @@ struct dwc3 {
#define DWC3_REVISION_IS_DWC31 0x80000000
#define DWC3_USB31_REVISION_110A (0x3131302a | DWC3_REVISION_IS_DWC31)
#define DWC3_USB31_REVISION_120A (0x3132302a | DWC3_REVISION_IS_DWC31)
+#define DWC3_USB31_REVISION_160A (0x3136302a | DWC3_REVISION_IS_DWC31)
+#define DWC3_USB31_REVISION_170A (0x3137302a | DWC3_REVISION_IS_DWC31)
+
+ u32 version_type;
+
+#define DWC31_VERSIONTYPE_EA01 0x65613031
+#define DWC31_VERSIONTYPE_EA02 0x65613032
+#define DWC31_VERSIONTYPE_EA03 0x65613033
+#define DWC31_VERSIONTYPE_EA04 0x65613034
+#define DWC31_VERSIONTYPE_EA05 0x65613035
+#define DWC31_VERSIONTYPE_EA06 0x65613036
enum dwc3_ep0_next ep0_next_event;
enum dwc3_ep0_state ep0state;
@@ -1130,6 +1195,8 @@ struct dwc3 {
struct dentry *root;
struct debugfs_regset32 *regset;
+ u32 dbg_lsp_select;
+
u8 test_mode;
u8 test_mode_nr;
u8 lpm_nyet_threshold;
@@ -1154,7 +1221,9 @@ struct dwc3 {
unsigned pullups_connected:1;
unsigned setup_packet_pending:1;
unsigned three_stage_setup:1;
+ unsigned dis_start_transfer_quirk:1;
unsigned usb3_lpm_capable:1;
+ unsigned usb2_lpm_disable:1;
unsigned disable_scramble_quirk:1;
unsigned u2exit_lfps_quirk:1;
@@ -1166,17 +1235,27 @@ struct dwc3 {
unsigned rx_detect_poll_quirk:1;
unsigned dis_u3_susphy_quirk:1;
unsigned dis_u2_susphy_quirk:1;
+ unsigned dis_u1u2_quirk:1;
unsigned dis_enblslpm_quirk:1;
unsigned dis_rxdet_inp3_quirk:1;
unsigned dis_u2_freeclk_exists_quirk:1;
unsigned dis_del_phy_power_chg_quirk:1;
unsigned dis_tx_ipgap_linecheck_quirk:1;
unsigned parkmode_disable_ss_quirk:1;
+ unsigned xhci_slow_suspend_quirk:1;
+ unsigned xhci_trb_ent_quirk:1;
+ unsigned xhci_warm_reset_on_suspend_quirk:1;
+ unsigned dis_u3_autosuspend_quirk:1;
unsigned tx_de_emphasis_quirk:1;
unsigned tx_de_emphasis:2;
unsigned dis_metastability_quirk:1;
+ unsigned needs_fifo_resize:1;
+ unsigned fifo_resize_status:1;
+ unsigned drd_connected:1;
+ unsigned en_runtime:1;
+ unsigned uwk_en:1;
u16 imod_interval;
};
@@ -1379,6 +1458,8 @@ int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state);
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_disable_irq(struct dwc3 *dwc);
+void dwc3_gadget_enable_irq(struct dwc3 *dwc);
#else
static inline int dwc3_gadget_init(struct dwc3 *dwc)
{ return 0; }
@@ -1398,6 +1479,10 @@ static inline int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd,
static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc,
int cmd, u32 param)
{ return 0; }
+static inline void dwc3_gadget_enable_irq(struct dwc3 *dwc)
+{ }
+static inline void dwc3_gadget_disable_irq(struct dwc3 *dwc)
+{ }
#endif
#if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
diff --git a/drivers/usb/dwc3/debug.h b/drivers/usb/dwc3/debug.h
index 069db13f3..5c7996034 100644
--- a/drivers/usb/dwc3/debug.h
+++ b/drivers/usb/dwc3/debug.h
@@ -193,65 +193,72 @@ static inline const char *dwc3_ep0_state_string(enum dwc3_ep0_state state)
* dwc3_gadget_event_string - returns event name
* @event: the event code
*/
-static inline const char *
-dwc3_gadget_event_string(char *str, const struct dwc3_event_devt *event)
+static inline const char *dwc3_gadget_event_string(char *str, size_t size,
+ const struct dwc3_event_devt *event)
{
enum dwc3_link_state state = event->event_info & DWC3_LINK_STATE_MASK;
switch (event->type) {
case DWC3_DEVICE_EVENT_DISCONNECT:
- sprintf(str, "Disconnect: [%s]",
+ snprintf(str, size, "Disconnect: [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_RESET:
- sprintf(str, "Reset [%s]", dwc3_gadget_link_string(state));
+ snprintf(str, size, "Reset [%s]",
+ dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_CONNECT_DONE:
- sprintf(str, "Connection Done [%s]",
+ snprintf(str, size, "Connection Done [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE:
- sprintf(str, "Link Change [%s]",
+ snprintf(str, size, "Link Change [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_WAKEUP:
- sprintf(str, "WakeUp [%s]", dwc3_gadget_link_string(state));
+ snprintf(str, size, "WakeUp [%s]",
+ dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_EOPF:
- sprintf(str, "End-Of-Frame [%s]",
+ snprintf(str, size, "End-Of-Frame [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_SOF:
- sprintf(str, "Start-Of-Frame [%s]",
+ snprintf(str, size, "Start-Of-Frame [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_ERRATIC_ERROR:
- sprintf(str, "Erratic Error [%s]",
+ snprintf(str, size, "Erratic Error [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_CMD_CMPL:
- sprintf(str, "Command Complete [%s]",
+ snprintf(str, size, "Command Complete [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_OVERFLOW:
- sprintf(str, "Overflow [%s]", dwc3_gadget_link_string(state));
+ snprintf(str, size, "Overflow [%s]",
+ dwc3_gadget_link_string(state));
break;
default:
- sprintf(str, "UNKNOWN");
+ snprintf(str, size, "UNKNOWN");
}
return str;
}
-static inline void dwc3_decode_get_status(__u8 t, __u16 i, __u16 l, char *str)
+static inline void dwc3_decode_get_status(__u8 t, __u16 i, __u16 l, char *str,
+ size_t size)
{
switch (t & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ snprintf(str, size, "Get Device Status(Length = %d)", l);
+ break;
case USB_RECIP_INTERFACE:
- sprintf(str, "Get Interface Status(Intf = %d, Length = %d)",
- i, l);
+ snprintf(str, size, "Get Interface Status(Intf = %d, Length = %d)",
+ i, l);
break;
case USB_RECIP_ENDPOINT:
- sprintf(str, "Get Endpoint Status(ep%d%s)",
+ snprintf(str, size, "Get Endpoint Status(ep%d%s)",
i & ~USB_DIR_IN,
i & USB_DIR_IN ? "in" : "out");
break;
@@ -259,11 +266,11 @@ static inline void dwc3_decode_get_status(__u8 t, __u16 i, __u16 l, char *str)
}
static inline void dwc3_decode_set_clear_feature(__u8 t, __u8 b, __u16 v,
- __u16 i, char *str)
+ __u16 i, char *str, size_t size)
{
switch (t & USB_RECIP_MASK) {
case USB_RECIP_DEVICE:
- sprintf(str, "%s Device Feature(%s%s)",
+ snprintf(str, size, "%s Device Feature(%s%s)",
b == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set",
({char *s;
switch (v) {
@@ -311,13 +318,13 @@ static inline void dwc3_decode_set_clear_feature(__u8 t, __u8 b, __u16 v,
} s; }) : "");
break;
case USB_RECIP_INTERFACE:
- sprintf(str, "%s Interface Feature(%s)",
+ snprintf(str, size, "%s Interface Feature(%s)",
b == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set",
v == USB_INTRF_FUNC_SUSPEND ?
"Function Suspend" : "UNKNOWN");
break;
case USB_RECIP_ENDPOINT:
- sprintf(str, "%s Endpoint Feature(%s ep%d%s)",
+ snprintf(str, size, "%s Endpoint Feature(%s ep%d%s)",
b == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set",
v == USB_ENDPOINT_HALT ? "Halt" : "UNKNOWN",
i & ~USB_DIR_IN,
@@ -326,15 +333,15 @@ static inline void dwc3_decode_set_clear_feature(__u8 t, __u8 b, __u16 v,
}
}
-static inline void dwc3_decode_set_address(__u16 v, char *str)
+static inline void dwc3_decode_set_address(__u16 v, char *str, size_t size)
{
- sprintf(str, "Set Address(Addr = %02x)", v);
+ snprintf(str, size, "Set Address(Addr = %02x)", v);
}
static inline void dwc3_decode_get_set_descriptor(__u8 t, __u8 b, __u16 v,
- __u16 i, __u16 l, char *str)
+ __u16 i, __u16 l, char *str, size_t size)
{
- sprintf(str, "%s %s Descriptor(Index = %d, Length = %d)",
+ snprintf(str, size, "%s %s Descriptor(Index = %d, Length = %d)",
b == USB_REQ_GET_DESCRIPTOR ? "Get" : "Set",
({ char *s;
switch (v >> 8) {
@@ -393,87 +400,92 @@ static inline void dwc3_decode_get_set_descriptor(__u8 t, __u8 b, __u16 v,
}
-static inline void dwc3_decode_get_configuration(__u16 l, char *str)
+static inline void dwc3_decode_get_configuration(__u16 l, char *str,
+ size_t size)
{
- sprintf(str, "Get Configuration(Length = %d)", l);
+ snprintf(str, size, "Get Configuration(Length = %d)", l);
}
-static inline void dwc3_decode_set_configuration(__u8 v, char *str)
+static inline void dwc3_decode_set_configuration(__u8 v, char *str, size_t size)
{
- sprintf(str, "Set Configuration(Config = %d)", v);
+ snprintf(str, size, "Set Configuration(Config = %d)", v);
}
-static inline void dwc3_decode_get_intf(__u16 i, __u16 l, char *str)
+static inline void dwc3_decode_get_intf(__u16 i, __u16 l, char *str,
+ size_t size)
{
- sprintf(str, "Get Interface(Intf = %d, Length = %d)", i, l);
+ snprintf(str, size, "Get Interface(Intf = %d, Length = %d)", i, l);
}
-static inline void dwc3_decode_set_intf(__u8 v, __u16 i, char *str)
+static inline void dwc3_decode_set_intf(__u8 v, __u16 i, char *str, size_t size)
{
- sprintf(str, "Set Interface(Intf = %d, Alt.Setting = %d)", i, v);
+ snprintf(str, size, "Set Interface(Intf = %d, Alt.Setting = %d)", i, v);
}
-static inline void dwc3_decode_synch_frame(__u16 i, __u16 l, char *str)
+static inline void dwc3_decode_synch_frame(__u16 i, __u16 l, char *str,
+ size_t size)
{
- sprintf(str, "Synch Frame(Endpoint = %d, Length = %d)", i, l);
+ snprintf(str, size, "Synch Frame(Endpoint = %d, Length = %d)", i, l);
}
-static inline void dwc3_decode_set_sel(__u16 l, char *str)
+static inline void dwc3_decode_set_sel(__u16 l, char *str, size_t size)
{
- sprintf(str, "Set SEL(Length = %d)", l);
+ snprintf(str, size, "Set SEL(Length = %d)", l);
}
-static inline void dwc3_decode_set_isoch_delay(__u8 v, char *str)
+static inline void dwc3_decode_set_isoch_delay(__u8 v, char *str, size_t size)
{
- sprintf(str, "Set Isochronous Delay(Delay = %d ns)", v);
+ snprintf(str, size, "Set Isochronous Delay(Delay = %d ns)", v);
}
/**
* dwc3_decode_ctrl - returns a string represetion of ctrl request
*/
-static inline const char *dwc3_decode_ctrl(char *str, __u8 bRequestType,
- __u8 bRequest, __u16 wValue, __u16 wIndex, __u16 wLength)
+static inline const char *dwc3_decode_ctrl(char *str, size_t size,
+ __u8 bRequestType, __u8 bRequest, __u16 wValue, __u16 wIndex,
+ __u16 wLength)
{
switch (bRequest) {
case USB_REQ_GET_STATUS:
- dwc3_decode_get_status(bRequestType, wIndex, wLength, str);
+ dwc3_decode_get_status(bRequestType, wIndex, wLength, str,
+ size);
break;
case USB_REQ_CLEAR_FEATURE:
case USB_REQ_SET_FEATURE:
dwc3_decode_set_clear_feature(bRequestType, bRequest, wValue,
- wIndex, str);
+ wIndex, str, size);
break;
case USB_REQ_SET_ADDRESS:
- dwc3_decode_set_address(wValue, str);
+ dwc3_decode_set_address(wValue, str, size);
break;
case USB_REQ_GET_DESCRIPTOR:
case USB_REQ_SET_DESCRIPTOR:
dwc3_decode_get_set_descriptor(bRequestType, bRequest, wValue,
- wIndex, wLength, str);
+ wIndex, wLength, str, size);
break;
case USB_REQ_GET_CONFIGURATION:
- dwc3_decode_get_configuration(wLength, str);
+ dwc3_decode_get_configuration(wLength, str, size);
break;
case USB_REQ_SET_CONFIGURATION:
- dwc3_decode_set_configuration(wValue, str);
+ dwc3_decode_set_configuration(wValue, str, size);
break;
case USB_REQ_GET_INTERFACE:
- dwc3_decode_get_intf(wIndex, wLength, str);
+ dwc3_decode_get_intf(wIndex, wLength, str, size);
break;
case USB_REQ_SET_INTERFACE:
- dwc3_decode_set_intf(wValue, wIndex, str);
+ dwc3_decode_set_intf(wValue, wIndex, str, size);
break;
case USB_REQ_SYNCH_FRAME:
- dwc3_decode_synch_frame(wIndex, wLength, str);
+ dwc3_decode_synch_frame(wIndex, wLength, str, size);
break;
case USB_REQ_SET_SEL:
- dwc3_decode_set_sel(wLength, str);
+ dwc3_decode_set_sel(wLength, str, size);
break;
case USB_REQ_SET_ISOCH_DELAY:
- dwc3_decode_set_isoch_delay(wValue, str);
+ dwc3_decode_set_isoch_delay(wValue, str, size);
break;
default:
- sprintf(str, "%02x %02x %02x %02x %02x %02x %02x %02x",
+ snprintf(str, size, "%02x %02x %02x %02x %02x %02x %02x %02x",
bRequestType, bRequest,
cpu_to_le16(wValue) & 0xff,
cpu_to_le16(wValue) >> 8,
@@ -490,16 +502,15 @@ static inline const char *dwc3_decode_ctrl(char *str, __u8 bRequestType,
* dwc3_ep_event_string - returns event name
* @event: then event code
*/
-static inline const char *
-dwc3_ep_event_string(char *str, const struct dwc3_event_depevt *event,
- u32 ep0state)
+static inline const char *dwc3_ep_event_string(char *str, size_t size,
+ const struct dwc3_event_depevt *event, u32 ep0state)
{
u8 epnum = event->endpoint_number;
size_t len;
int status;
int ret;
- ret = sprintf(str, "ep%d%s: ", epnum >> 1,
+ ret = snprintf(str, size, "ep%d%s: ", epnum >> 1,
(epnum & 1) ? "in" : "out");
if (ret < 0)
return "UNKNOWN";
@@ -509,7 +520,7 @@ dwc3_ep_event_string(char *str, const struct dwc3_event_depevt *event,
switch (event->endpoint_event) {
case DWC3_DEPEVT_XFERCOMPLETE:
len = strlen(str);
- sprintf(str + len, "Transfer Complete (%c%c%c)",
+ snprintf(str + len, size - len, "Transfer Complete (%c%c%c)",
status & DEPEVT_STATUS_SHORT ? 'S' : 's',
status & DEPEVT_STATUS_IOC ? 'I' : 'i',
status & DEPEVT_STATUS_LST ? 'L' : 'l');
@@ -517,12 +528,13 @@ dwc3_ep_event_string(char *str, const struct dwc3_event_depevt *event,
len = strlen(str);
if (epnum <= 1)
- sprintf(str + len, " [%s]", dwc3_ep0_state_string(ep0state));
+ snprintf(str + len, size - len, " [%s]",
+ dwc3_ep0_state_string(ep0state));
break;
case DWC3_DEPEVT_XFERINPROGRESS:
len = strlen(str);
- sprintf(str + len, "Transfer In Progress [%d] (%c%c%c)",
+ snprintf(str + len, size - len, "Transfer In Progress [%d] (%c%c%c)",
event->parameters,
status & DEPEVT_STATUS_SHORT ? 'S' : 's',
status & DEPEVT_STATUS_IOC ? 'I' : 'i',
@@ -531,47 +543,51 @@ dwc3_ep_event_string(char *str, const struct dwc3_event_depevt *event,
case DWC3_DEPEVT_XFERNOTREADY:
len = strlen(str);
- sprintf(str + len, "Transfer Not Ready [%d]%s",
+ snprintf(str + len, size - len, "Transfer Not Ready [%d]%s",
event->parameters,
status & DEPEVT_STATUS_TRANSFER_ACTIVE ?
" (Active)" : " (Not Active)");
+ len = strlen(str);
+
/* Control Endpoints */
if (epnum <= 1) {
int phase = DEPEVT_STATUS_CONTROL_PHASE(event->status);
switch (phase) {
case DEPEVT_STATUS_CONTROL_DATA:
- strcat(str, " [Data Phase]");
+ snprintf(str + ret, size - ret,
+ " [Data Phase]");
break;
case DEPEVT_STATUS_CONTROL_STATUS:
- strcat(str, " [Status Phase]");
+ snprintf(str + ret, size - ret,
+ " [Status Phase]");
}
}
break;
case DWC3_DEPEVT_RXTXFIFOEVT:
- strcat(str, "FIFO");
+ snprintf(str + ret, size - ret, "FIFO");
break;
case DWC3_DEPEVT_STREAMEVT:
status = event->status;
switch (status) {
case DEPEVT_STREAMEVT_FOUND:
- sprintf(str + ret, " Stream %d Found",
+ snprintf(str + ret, size - ret, " Stream %d Found",
event->parameters);
break;
case DEPEVT_STREAMEVT_NOTFOUND:
default:
- strcat(str, " Stream Not Found");
+ snprintf(str + ret, size - ret, " Stream Not Found");
break;
}
break;
case DWC3_DEPEVT_EPCMDCMPLT:
- strcat(str, "Endpoint Command Complete");
+ snprintf(str + ret, size - ret, "Endpoint Command Complete");
break;
default:
- sprintf(str, "UNKNOWN");
+ snprintf(str, size, "UNKNOWN");
}
return str;
@@ -611,14 +627,15 @@ static inline const char *dwc3_gadget_event_type_string(u8 event)
}
}
-static inline const char *dwc3_decode_event(char *str, u32 event, u32 ep0state)
+static inline const char *dwc3_decode_event(char *str, size_t size, u32 event,
+ u32 ep0state)
{
const union dwc3_event evt = (union dwc3_event) event;
if (evt.type.is_devspec)
- return dwc3_gadget_event_string(str, &evt.devt);
+ return dwc3_gadget_event_string(str, size, &evt.devt);
else
- return dwc3_ep_event_string(str, &evt.depevt, ep0state);
+ return dwc3_ep_event_string(str, size, &evt.depevt, ep0state);
}
static inline const char *dwc3_ep_cmd_status_string(int status)
diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c
index 58a2479d8..9fb519b2e 100644
--- a/drivers/usb/dwc3/debugfs.c
+++ b/drivers/usb/dwc3/debugfs.c
@@ -25,6 +25,8 @@
#include "io.h"
#include "debug.h"
+#define DWC3_LSP_MUX_UNSELECTED 0xfffff
+
#define dump_register(nm) \
{ \
.name = __stringify(nm), \
@@ -82,10 +84,6 @@ static const struct debugfs_reg32 dwc3_regs[] = {
dump_register(GDBGFIFOSPACE),
dump_register(GDBGLTSSM),
dump_register(GDBGBMU),
- dump_register(GDBGLSPMUX),
- dump_register(GDBGLSP),
- dump_register(GDBGEPINFO0),
- dump_register(GDBGEPINFO1),
dump_register(GPRTBIMAP_HS0),
dump_register(GPRTBIMAP_HS1),
dump_register(GPRTBIMAP_FS0),
@@ -279,6 +277,114 @@ static const struct debugfs_reg32 dwc3_regs[] = {
dump_register(OSTS),
};
+static void dwc3_host_lsp(struct seq_file *s)
+{
+ struct dwc3 *dwc = s->private;
+ bool dbc_enabled;
+ u32 sel;
+ u32 reg;
+ u32 val;
+
+ dbc_enabled = !!(dwc->hwparams.hwparams1 & DWC3_GHWPARAMS1_ENDBC);
+
+ sel = dwc->dbg_lsp_select;
+ if (sel == DWC3_LSP_MUX_UNSELECTED) {
+ seq_puts(s, "Write LSP selection to print for host\n");
+ return;
+ }
+
+ reg = DWC3_GDBGLSPMUX_HOSTSELECT(sel);
+
+ dwc3_writel(dwc->regs, DWC3_GDBGLSPMUX, reg);
+ val = dwc3_readl(dwc->regs, DWC3_GDBGLSP);
+ seq_printf(s, "GDBGLSP[%d] = 0x%08x\n", sel, val);
+
+ if (dbc_enabled && sel < 256) {
+ reg |= DWC3_GDBGLSPMUX_ENDBC;
+ dwc3_writel(dwc->regs, DWC3_GDBGLSPMUX, reg);
+ val = dwc3_readl(dwc->regs, DWC3_GDBGLSP);
+ seq_printf(s, "GDBGLSP_DBC[%d] = 0x%08x\n", sel, val);
+ }
+}
+
+static void dwc3_gadget_lsp(struct seq_file *s)
+{
+ struct dwc3 *dwc = s->private;
+ int i;
+ u32 reg;
+
+ for (i = 0; i < 16; i++) {
+ reg = DWC3_GDBGLSPMUX_DEVSELECT(i);
+ dwc3_writel(dwc->regs, DWC3_GDBGLSPMUX, reg);
+ reg = dwc3_readl(dwc->regs, DWC3_GDBGLSP);
+ seq_printf(s, "GDBGLSP[%d] = 0x%08x\n", i, reg);
+ }
+}
+
+static int dwc3_lsp_show(struct seq_file *s, void *unused)
+{
+ struct dwc3 *dwc = s->private;
+ unsigned int current_mode;
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(&dwc->lock, flags);
+ reg = dwc3_readl(dwc->regs, DWC3_GSTS);
+ current_mode = DWC3_GSTS_CURMOD(reg);
+
+ switch (current_mode) {
+ case DWC3_GSTS_CURMOD_HOST:
+ dwc3_host_lsp(s);
+ break;
+ case DWC3_GSTS_CURMOD_DEVICE:
+ dwc3_gadget_lsp(s);
+ break;
+ default:
+ seq_puts(s, "Mode is unknown, no LSP register printed\n");
+ break;
+ }
+ spin_unlock_irqrestore(&dwc->lock, flags);
+
+ return 0;
+}
+
+static int dwc3_lsp_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dwc3_lsp_show, inode->i_private);
+}
+
+static ssize_t dwc3_lsp_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct seq_file *s = file->private_data;
+ struct dwc3 *dwc = s->private;
+ unsigned long flags;
+ char buf[32] = { 0 };
+ u32 sel;
+ int ret;
+
+ if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
+ return -EFAULT;
+
+ ret = kstrtouint(buf, 0, &sel);
+ if (ret)
+ return ret;
+
+ spin_lock_irqsave(&dwc->lock, flags);
+ dwc->dbg_lsp_select = sel;
+ spin_unlock_irqrestore(&dwc->lock, flags);
+
+ return count;
+}
+
+static const struct file_operations dwc3_lsp_fops = {
+ .open = dwc3_lsp_open,
+ .write = dwc3_lsp_write,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
static int dwc3_mode_show(struct seq_file *s, void *unused)
{
struct dwc3 *dwc = s->private;
@@ -436,6 +542,13 @@ static int dwc3_link_state_show(struct seq_file *s, void *unused)
u8 speed;
spin_lock_irqsave(&dwc->lock, flags);
+ reg = dwc3_readl(dwc->regs, DWC3_GSTS);
+ if (DWC3_GSTS_CURMOD(reg) != DWC3_GSTS_CURMOD_DEVICE) {
+ seq_puts(s, "Not available\n");
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ return 0;
+ }
+
reg = dwc3_readl(dwc->regs, DWC3_DSTS);
state = DWC3_DSTS_USBLNKST(reg);
speed = reg & DWC3_DSTS_CONNECTSPD;
@@ -483,6 +596,12 @@ static ssize_t dwc3_link_state_write(struct file *file,
return -EINVAL;
spin_lock_irqsave(&dwc->lock, flags);
+ reg = dwc3_readl(dwc->regs, DWC3_GSTS);
+ if (DWC3_GSTS_CURMOD(reg) != DWC3_GSTS_CURMOD_DEVICE) {
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ return -EINVAL;
+ }
+
reg = dwc3_readl(dwc->regs, DWC3_DSTS);
speed = reg & DWC3_DSTS_CONNECTSPD;
@@ -511,7 +630,7 @@ struct dwc3_ep_file_map {
const struct file_operations *const fops;
};
-static int dwc3_tx_fifo_queue_show(struct seq_file *s, void *unused)
+static int dwc3_tx_fifo_size_show(struct seq_file *s, void *unused)
{
struct dwc3_ep *dep = s->private;
struct dwc3 *dwc = dep->dwc;
@@ -519,14 +638,18 @@ static int dwc3_tx_fifo_queue_show(struct seq_file *s, void *unused)
u32 val;
spin_lock_irqsave(&dwc->lock, flags);
- val = dwc3_core_fifo_space(dep, DWC3_TXFIFOQ);
+ val = dwc3_core_fifo_space(dep, DWC3_TXFIFO);
+
+ /* Convert to bytes */
+ val *= DWC3_MDWIDTH(dwc->hwparams.hwparams0);
+ val >>= 3;
seq_printf(s, "%u\n", val);
spin_unlock_irqrestore(&dwc->lock, flags);
return 0;
}
-static int dwc3_rx_fifo_queue_show(struct seq_file *s, void *unused)
+static int dwc3_rx_fifo_size_show(struct seq_file *s, void *unused)
{
struct dwc3_ep *dep = s->private;
struct dwc3 *dwc = dep->dwc;
@@ -534,7 +657,11 @@ static int dwc3_rx_fifo_queue_show(struct seq_file *s, void *unused)
u32 val;
spin_lock_irqsave(&dwc->lock, flags);
- val = dwc3_core_fifo_space(dep, DWC3_RXFIFOQ);
+ val = dwc3_core_fifo_space(dep, DWC3_RXFIFO);
+
+ /* Convert to bytes */
+ val *= DWC3_MDWIDTH(dwc->hwparams.hwparams0);
+ val >>= 3;
seq_printf(s, "%u\n", val);
spin_unlock_irqrestore(&dwc->lock, flags);
@@ -690,8 +817,32 @@ static int dwc3_trb_ring_show(struct seq_file *s, void *unused)
return 0;
}
-DEFINE_SHOW_ATTRIBUTE(dwc3_tx_fifo_queue);
-DEFINE_SHOW_ATTRIBUTE(dwc3_rx_fifo_queue);
+static int dwc3_ep_info_register_show(struct seq_file *s, void *unused)
+{
+ struct dwc3_ep *dep = s->private;
+ struct dwc3 *dwc = dep->dwc;
+ unsigned long flags;
+ u64 ep_info;
+ u32 lower_32_bits;
+ u32 upper_32_bits;
+ u32 reg;
+
+ spin_lock_irqsave(&dwc->lock, flags);
+ reg = DWC3_GDBGLSPMUX_EPSELECT(dep->number);
+ dwc3_writel(dwc->regs, DWC3_GDBGLSPMUX, reg);
+
+ lower_32_bits = dwc3_readl(dwc->regs, DWC3_GDBGEPINFO0);
+ upper_32_bits = dwc3_readl(dwc->regs, DWC3_GDBGEPINFO1);
+
+ ep_info = ((u64)upper_32_bits << 32) | lower_32_bits;
+ seq_printf(s, "0x%016llx\n", ep_info);
+ spin_unlock_irqrestore(&dwc->lock, flags);
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(dwc3_tx_fifo_size);
+DEFINE_SHOW_ATTRIBUTE(dwc3_rx_fifo_size);
DEFINE_SHOW_ATTRIBUTE(dwc3_tx_request_queue);
DEFINE_SHOW_ATTRIBUTE(dwc3_rx_request_queue);
DEFINE_SHOW_ATTRIBUTE(dwc3_rx_info_queue);
@@ -699,10 +850,11 @@ DEFINE_SHOW_ATTRIBUTE(dwc3_descriptor_fetch_queue);
DEFINE_SHOW_ATTRIBUTE(dwc3_event_queue);
DEFINE_SHOW_ATTRIBUTE(dwc3_transfer_type);
DEFINE_SHOW_ATTRIBUTE(dwc3_trb_ring);
+DEFINE_SHOW_ATTRIBUTE(dwc3_ep_info_register);
static const struct dwc3_ep_file_map dwc3_ep_file_map[] = {
- { "tx_fifo_queue", &dwc3_tx_fifo_queue_fops, },
- { "rx_fifo_queue", &dwc3_rx_fifo_queue_fops, },
+ { "tx_fifo_size", &dwc3_tx_fifo_size_fops, },
+ { "rx_fifo_size", &dwc3_rx_fifo_size_fops, },
{ "tx_request_queue", &dwc3_tx_request_queue_fops, },
{ "rx_request_queue", &dwc3_rx_request_queue_fops, },
{ "rx_info_queue", &dwc3_rx_info_queue_fops, },
@@ -710,6 +862,7 @@ static const struct dwc3_ep_file_map dwc3_ep_file_map[] = {
{ "event_queue", &dwc3_event_queue_fops, },
{ "transfer_type", &dwc3_transfer_type_fops, },
{ "trb_ring", &dwc3_trb_ring_fops, },
+ { "GDBGEPINFO", &dwc3_ep_info_register_fops, },
};
static void dwc3_debugfs_create_endpoint_files(struct dwc3_ep *dep,
@@ -741,6 +894,8 @@ void dwc3_debugfs_init(struct dwc3 *dwc)
if (!dwc->regset)
return;
+ dwc->dbg_lsp_select = DWC3_LSP_MUX_UNSELECTED;
+
dwc->regset->regs = dwc3_regs;
dwc->regset->nregs = ARRAY_SIZE(dwc3_regs);
dwc->regset->base = dwc->regs - DWC3_GLOBALS_REGS_START;
@@ -750,6 +905,9 @@ void dwc3_debugfs_init(struct dwc3 *dwc)
debugfs_create_regset32("regdump", S_IRUGO, root, dwc->regset);
+ debugfs_create_file("lsp_dump", S_IRUGO | S_IWUSR, root, dwc,
+ &dwc3_lsp_fops);
+
if (IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)) {
debugfs_create_file("mode", S_IRUGO | S_IWUSR, root, dwc,
&dwc3_mode_fops);
diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c
index 218371f98..726100d1a 100644
--- a/drivers/usb/dwc3/drd.c
+++ b/drivers/usb/dwc3/drd.c
@@ -10,6 +10,7 @@
#include <linux/extcon.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
#include "debug.h"
#include "core.h"
@@ -445,9 +446,24 @@ static struct extcon_dev *dwc3_get_extcon(struct dwc3 *dwc)
struct device *dev = dwc->dev;
struct device_node *np_phy, *np_conn;
struct extcon_dev *edev;
+ const char *name;
- if (of_property_read_bool(dev->of_node, "extcon"))
- return extcon_get_edev_by_phandle(dwc->dev, 0);
+ if (device_property_read_bool(dev, "extcon"))
+ return extcon_get_edev_by_phandle(dev, 0);
+
+ /*
+ * Device tree platforms should get extcon via phandle.
+ * On ACPI platforms, we get the name from a device property.
+ * This device property is for kernel internal use only and
+ * is expected to be set by the glue code.
+ */
+ if (device_property_read_string(dev, "linux,extcon-name", &name) == 0) {
+ edev = extcon_get_extcon_dev(name);
+ if (!edev)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ return edev;
+ }
np_phy = of_parse_phandle(dev->of_node, "phys", 0);
np_conn = of_graph_get_remote_node(np_phy, -1, -1);
diff --git a/drivers/usb/dwc3/dwc3-haps.c b/drivers/usb/dwc3/dwc3-haps.c
index 02d57d98e..3cecbf169 100644
--- a/drivers/usb/dwc3/dwc3-haps.c
+++ b/drivers/usb/dwc3/dwc3-haps.c
@@ -106,6 +106,15 @@ static const struct pci_device_id dwc3_haps_id_table[] = {
{
PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS,
PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3),
+ /*
+ * i.MX6QP and i.MX7D platform use a PCIe controller with the
+ * same VID and PID as this USB controller. The system may
+ * incorrectly match this driver to that PCIe controller. To
+ * workaround this, specifically use class type USB to prevent
+ * incorrect driver matching.
+ */
+ .class = (PCI_CLASS_SERIAL_USB << 8),
+ .class_mask = 0xffff00,
},
{
PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS,
diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c
index f0e492e25..154dadcef 100644
--- a/drivers/usb/dwc3/ep0.c
+++ b/drivers/usb/dwc3/ep0.c
@@ -283,19 +283,33 @@ void dwc3_ep0_out_start(struct dwc3 *dwc)
static struct dwc3_ep *dwc3_wIndex_to_dep(struct dwc3 *dwc, __le16 wIndex_le)
{
- struct dwc3_ep *dep;
+ struct dwc3_ep *dep = NULL;
u32 windex = le16_to_cpu(wIndex_le);
- u32 epnum;
+ u32 epnum, ep_index;
+ u8 num, direction;
+
+ epnum = windex & USB_ENDPOINT_NUMBER_MASK;
+ direction = windex & USB_ENDPOINT_DIR_MASK;
+ ep_index = 0;
+
+ for (num = 0; num < dwc->num_eps; num++) {
+ dep = dwc->eps[num];
+ if (!dep) {
+ dev_warn(dwc->dev, "dep is NULL, num %d, windex 0x%08x\n",
+ num, windex);
+ return NULL;
+ }
+
+ if ((direction == USB_DIR_IN && dep->direction) ||
+ (direction == USB_DIR_OUT && !dep->direction))
+ ep_index++;
- epnum = (windex & USB_ENDPOINT_NUMBER_MASK) << 1;
- if ((windex & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN)
- epnum |= 1;
+ if (ep_index == epnum + 1)
+ break;
+ }
- dep = dwc->eps[epnum];
- if (dep == NULL)
- return NULL;
- if (dep->flags & DWC3_EP_ENABLED)
+ if (dep && (dep->flags & DWC3_EP_ENABLED))
return dep;
return NULL;
@@ -384,7 +398,7 @@ static int dwc3_ep0_handle_u1(struct dwc3 *dwc, enum usb_device_state state,
return -EINVAL;
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
- if (set)
+ if (set && !dwc->dis_u1u2_quirk)
reg |= DWC3_DCTL_INITU1ENA;
else
reg &= ~DWC3_DCTL_INITU1ENA;
@@ -406,7 +420,7 @@ static int dwc3_ep0_handle_u2(struct dwc3 *dwc, enum usb_device_state state,
return -EINVAL;
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
- if (set)
+ if (set && !dwc->dis_u1u2_quirk)
reg |= DWC3_DCTL_INITU2ENA;
else
reg &= ~DWC3_DCTL_INITU2ENA;
@@ -629,7 +643,12 @@ static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl)
* nothing is pending from application.
*/
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
- reg |= (DWC3_DCTL_ACCEPTU1ENA | DWC3_DCTL_ACCEPTU2ENA);
+ if (dwc->dis_u1u2_quirk)
+ reg &= ~(DWC3_DCTL_ACCEPTU1ENA |
+ DWC3_DCTL_ACCEPTU2ENA);
+ else
+ reg |= (DWC3_DCTL_ACCEPTU1ENA |
+ DWC3_DCTL_ACCEPTU2ENA);
dwc3_writel(dwc->regs, DWC3_DCTL, reg);
}
break;
@@ -1139,8 +1158,10 @@ void dwc3_ep0_interrupt(struct dwc3 *dwc,
case DWC3_DEPEVT_EPCMDCMPLT:
cmd = DEPEVT_PARAMETER_CMD(event->parameters);
- if (cmd == DWC3_DEPCMD_ENDTRANSFER)
+ if (cmd == DWC3_DEPCMD_ENDTRANSFER) {
+ dep->flags &= ~DWC3_EP_END_TRANSFER_PENDING;
dep->flags &= ~DWC3_EP_TRANSFER_STARTED;
+ }
break;
}
}
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 8620ac8e3..945606a0f 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -27,7 +27,7 @@
#include "gadget.h"
#include "io.h"
-#define DWC3_ALIGN_FRAME(d) (((d)->frame_number + (d)->interval) \
+#define DWC3_ALIGN_FRAME(d, n) (((d)->frame_number + ((d)->interval * (n))) \
& ~((d)->interval - 1))
/**
@@ -136,6 +136,182 @@ int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state)
return -ETIMEDOUT;
}
+#ifdef CONFIG_ARCH_ROCKCHIP
+/**
+ * dwc3_gadget_resize_tx_fifos - reallocate fifo spaces for current use-case
+ * @dwc: pointer to our context structure
+ *
+ * This function will a best effort FIFO allocation in order
+ * to improve FIFO usage and throughput, while still allowing
+ * us to enable as many endpoints as possible.
+ *
+ * Keep in mind that this operation will be highly dependent
+ * on the configured size for RAM1 - which contains TxFifo -,
+ * the amount of endpoints enabled on coreConsultant tool, and
+ * the width of the Master Bus.
+ *
+ * In the ideal world, we would always be able to satisfy the
+ * following equation:
+ *
+ * ((512 + 2 * MDWIDTH-Bytes) + (Number of IN Endpoints - 1) * \
+ * (3 * (1024 + MDWIDTH-Bytes) + MDWIDTH-Bytes)) / MDWIDTH-Bytes
+ *
+ * Unfortunately, due to many variables that's not always the case.
+ */
+static int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc)
+{
+ int last_fifo_depth;
+ int fifo_size, total_size, total_resize = 0;
+ int mdwidth;
+ u8 num, fifo_number, num_in_eps;
+
+ /*
+ * Only support Tx fifos resize for gadget speed <= high speed
+ * for the time being and do fifo resize operation only once
+ * when connect done event occurs, because if resize Tx fifos
+ * during controller transfer data, it may cause controller
+ * run into abnormal and unrecoverable state.
+ */
+ if (!dwc->needs_fifo_resize || dwc->fifo_resize_status)
+ return 0;
+
+ num_in_eps = DWC3_NUM_IN_EPS(&dwc->hwparams);
+ mdwidth = DWC3_MDWIDTH(dwc->hwparams.hwparams0);
+ /* MDWIDTH is represented in bits, we need it in bytes */
+ mdwidth >>= 3;
+ fifo_number = 0;
+ /* Get the Tx FIFO 0 size and depth */
+ fifo_size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(0));
+ last_fifo_depth = DWC3_GTXFIFOSIZ_TXFSTADDR(fifo_size) >> 16;
+ /* Get the Tx FIFO (num_in_eps - 1) size and depth */
+ fifo_size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(num_in_eps - 1));
+ /* Get the Tx FIFOs total size */
+ total_size = (DWC3_GTXFIFOSIZ_TXFSTADDR(fifo_size) >> 16) +
+ DWC3_GTXFIFOSIZ_TXFDEF(fifo_size) - last_fifo_depth;
+
+ for (num = 0; num < dwc->num_eps; num++) {
+ struct dwc3_ep *dep = dwc->eps[num];
+ int mult = 1, maxpacket = 512;
+ int tmp;
+
+ /* Skip out endpoints */
+ if (!dep || !dep->direction)
+ continue;
+
+ switch (dep->endpoint.transfer_type) {
+ case USB_ENDPOINT_XFER_CONTROL:
+ if (!dep->endpoint.caps.type_control) {
+ dev_dbg(dwc->dev, "%s may not be used\n",
+ dep->name);
+ goto out;
+ }
+
+ mult = 1;
+ if (dwc->gadget.speed <= USB_SPEED_HIGH)
+ maxpacket = 64;
+ else
+ maxpacket = 512;
+ break;
+ case USB_ENDPOINT_XFER_ISOC:
+ if (!dep->endpoint.caps.type_iso) {
+ dev_WARN(dwc->dev, "%s not support isoc type\n",
+ dep->name);
+ goto out;
+ }
+
+ /*
+ * Set enough tx fifos for Isochronous endpoints
+ * to get better performance and more compliance
+ * with bus latency.
+ */
+ maxpacket = dep->endpoint.maxpacket;
+ if (dwc->gadget.speed <= USB_SPEED_HIGH)
+ mult = dep->endpoint.mult;
+ else
+ mult = dep->endpoint.mult *
+ dep->endpoint.maxburst;
+ mult = mult > 0 ? mult * 2 : 3;
+ if (mult > 6)
+ mult = 6;
+ break;
+ case USB_ENDPOINT_XFER_BULK:
+ if (!dep->endpoint.caps.type_bulk) {
+ dev_WARN(dwc->dev, "%s not support bulk type\n",
+ dep->name);
+ goto out;
+ }
+
+ /*
+ * Set enough tx fifos for Bulk endpoints to get
+ * better transmission performance.
+ */
+ mult = 3;
+ if (dwc->gadget.speed <= USB_SPEED_HIGH) {
+ maxpacket = 512;
+ } else {
+ if (dep->endpoint.maxburst > mult) {
+ mult = dep->endpoint.maxburst;
+ if (mult > 6)
+ mult = 6;
+ }
+ maxpacket = 1024;
+ }
+ break;
+ case USB_ENDPOINT_XFER_INT:
+ /* Bulk endpoints handle interrupt transfers. */
+ if (!dep->endpoint.caps.type_int &&
+ !dep->endpoint.caps.type_bulk) {
+ dev_WARN(dwc->dev, "%s not support int type\n",
+ dep->name);
+ goto out;
+ }
+
+ /*
+ * REVIST: we assume that the maxpacket of interrupt
+ * endpoint is 64 Bytes for MTP and the other functions.
+ */
+ mult = 1;
+ maxpacket = 64;
+ break;
+ default:
+ /*
+ * This is only possible with faulty memory
+ * because we checked it already.
+ */
+ dev_WARN(dwc->dev, "Unknown endpoint type %d\n",
+ dep->endpoint.transfer_type);
+ goto out;
+ }
+
+ tmp = mult * (maxpacket + mdwidth);
+ tmp += mdwidth;
+
+ fifo_size = DIV_ROUND_UP(tmp, mdwidth);
+ total_resize += fifo_size;
+ fifo_size |= (last_fifo_depth << 16);
+
+ if (total_resize > total_size) {
+ dev_WARN(dwc->dev, "Tx FIFO resize overflow!\n");
+ break;
+ }
+
+ dev_dbg(dwc->dev, "%s: FIFO Addr %04x Size %d\n",
+ dep->name, last_fifo_depth, fifo_size & 0xffff);
+
+ dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(fifo_number),
+ fifo_size);
+
+ last_fifo_depth += (fifo_size & 0xffff);
+ fifo_number++;
+ }
+
+out:
+ dwc->fifo_resize_status = true;
+
+ return 0;
+}
+#endif
+
/**
* dwc3_ep_inc_trb - increment a trb index.
* @index: Pointer to the TRB index to increment.
@@ -174,7 +350,6 @@ static void dwc3_gadget_del_and_unmap_request(struct dwc3_ep *dep,
{
struct dwc3 *dwc = dep->dwc;
- req->started = false;
list_del(&req->list);
req->remaining = 0;
req->needs_extra_trb = false;
@@ -209,6 +384,7 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
struct dwc3 *dwc = dep->dwc;
dwc3_gadget_del_and_unmap_request(dep, req, status);
+ req->status = DWC3_REQUEST_STATUS_COMPLETED;
spin_unlock(&dwc->lock);
usb_gadget_giveback_request(&dep->endpoint, &req->request);
@@ -595,12 +771,14 @@ static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action)
u8 bInterval_m1;
/*
- * Valid range for DEPCFG.bInterval_m1 is from 0 to 13, and it
- * must be set to 0 when the controller operates in full-speed.
+ * Valid range for DEPCFG.bInterval_m1 is from 0 to 13.
+ *
+ * NOTE: The programming guide incorrectly stated bInterval_m1
+ * must be set to 0 when operating in fullspeed. Internally the
+ * controller does not have this limitation. See DWC_usb3x
+ * programming guide section 3.2.2.1.
*/
bInterval_m1 = min_t(u8, desc->bInterval - 1, 13);
- if (dwc->gadget.speed == USB_SPEED_FULL)
- bInterval_m1 = 0;
if (usb_endpoint_type(desc) == USB_ENDPOINT_XFER_INT &&
dwc->gadget.speed == USB_SPEED_FULL)
@@ -646,7 +824,6 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action)
dep->type = usb_endpoint_type(desc);
dep->flags |= DWC3_EP_ENABLED;
- dep->flags &= ~DWC3_EP_END_TRANSFER_PENDING;
reg = dwc3_readl(dwc->regs, DWC3_DALEPENA);
reg |= DWC3_DALEPENA_EP(dep->number);
@@ -675,7 +852,7 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action)
* Issue StartTransfer here with no-op TRB so we can always rely on No
* Response Update Transfer command.
*/
- if (usb_endpoint_xfer_bulk(desc) ||
+ if ((usb_endpoint_xfer_bulk(desc) && !dep->stream_capable) ||
usb_endpoint_xfer_int(desc)) {
struct dwc3_gadget_ep_cmd_params params;
struct dwc3_trb *trb;
@@ -759,7 +936,7 @@ static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep)
dep->stream_capable = false;
dep->type = 0;
- dep->flags &= DWC3_EP_END_TRANSFER_PENDING;
+ dep->flags = 0;
/* Clear out the ep descriptors for non-ep0 */
if (dep->number > 1) {
@@ -858,6 +1035,7 @@ static struct usb_request *dwc3_gadget_ep_alloc_request(struct usb_ep *ep,
req->direction = dep->direction;
req->epnum = dep->number;
req->dep = dep;
+ req->status = DWC3_REQUEST_STATUS_UNKNOWN;
trace_dwc3_alloc_request(req);
@@ -1333,6 +1511,9 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep)
params.param1 = lower_32_bits(req->trb_dma);
cmd = DWC3_DEPCMD_STARTTRANSFER;
+ if (dep->stream_capable)
+ cmd |= DWC3_DEPCMD_PARAM(req->request.stream_id);
+
if (usb_endpoint_xfer_isoc(dep->endpoint.desc))
cmd |= DWC3_DEPCMD_PARAM(dep->frame_number);
} else {
@@ -1344,6 +1525,15 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep)
if (ret < 0) {
struct dwc3_request *tmp;
+ /*
+ * Isochronous endpoints request needs to
+ * return directly and retry to transfer next
+ * time. Otherwise, it will fail to giveback
+ * the req to the udc gadget driver.
+ */
+ if (usb_endpoint_xfer_isoc(dep->endpoint.desc))
+ return ret;
+
if (ret == -EAGAIN)
return ret;
@@ -1370,17 +1560,198 @@ static int __dwc3_gadget_get_frame(struct dwc3 *dwc)
return DWC3_DSTS_SOFFN(reg);
}
-static void __dwc3_gadget_start_isoc(struct dwc3_ep *dep)
+/**
+ * dwc3_gadget_start_isoc_quirk - workaround invalid frame number
+ * @dep: isoc endpoint
+ *
+ * This function tests for the correct combination of BIT[15:14] from the 16-bit
+ * microframe number reported by the XferNotReady event for the future frame
+ * number to start the isoc transfer.
+ *
+ * In DWC_usb31 version 1.70a-ea06 and prior, for highspeed and fullspeed
+ * isochronous IN, BIT[15:14] of the 16-bit microframe number reported by the
+ * XferNotReady event are invalid. The driver uses this number to schedule the
+ * isochronous transfer and passes it to the START TRANSFER command. Because
+ * this number is invalid, the command may fail. If BIT[15:14] matches the
+ * internal 16-bit microframe, the START TRANSFER command will pass and the
+ * transfer will start at the scheduled time, if it is off by 1, the command
+ * will still pass, but the transfer will start 2 seconds in the future. For all
+ * other conditions, the START TRANSFER command will fail with bus-expiry.
+ *
+ * In order to workaround this issue, we can test for the correct combination of
+ * BIT[15:14] by sending START TRANSFER commands with different values of
+ * BIT[15:14]: 'b00, 'b01, 'b10, and 'b11. Each combination is 2^14 uframe apart
+ * (or 2 seconds). 4 seconds into the future will result in a bus-expiry status.
+ * As the result, within the 4 possible combinations for BIT[15:14], there will
+ * be 2 successful and 2 failure START COMMAND status. One of the 2 successful
+ * command status will result in a 2-second delay start. The smaller BIT[15:14]
+ * value is the correct combination.
+ *
+ * Since there are only 4 outcomes and the results are ordered, we can simply
+ * test 2 START TRANSFER commands with BIT[15:14] combinations 'b00 and 'b01 to
+ * deduce the smaller successful combination.
+ *
+ * Let test0 = test status for combination 'b00 and test1 = test status for 'b01
+ * of BIT[15:14]. The correct combination is as follow:
+ *
+ * if test0 fails and test1 passes, BIT[15:14] is 'b01
+ * if test0 fails and test1 fails, BIT[15:14] is 'b10
+ * if test0 passes and test1 fails, BIT[15:14] is 'b11
+ * if test0 passes and test1 passes, BIT[15:14] is 'b00
+ *
+ * Synopsys STAR 9001202023: Wrong microframe number for isochronous IN
+ * endpoints.
+ */
+static int dwc3_gadget_start_isoc_quirk(struct dwc3_ep *dep)
+{
+ int cmd_status = 0;
+ bool test0;
+ bool test1;
+
+ while (dep->combo_num < 2) {
+ struct dwc3_gadget_ep_cmd_params params;
+ u32 test_frame_number;
+ u32 cmd;
+
+ /*
+ * Check if we can start isoc transfer on the next interval or
+ * 4 uframes in the future with BIT[15:14] as dep->combo_num
+ */
+ test_frame_number = dep->frame_number & 0x3fff;
+ test_frame_number |= dep->combo_num << 14;
+ test_frame_number += max_t(u32, 4, dep->interval);
+
+ params.param0 = upper_32_bits(dep->dwc->bounce_addr);
+ params.param1 = lower_32_bits(dep->dwc->bounce_addr);
+
+ cmd = DWC3_DEPCMD_STARTTRANSFER;
+ cmd |= DWC3_DEPCMD_PARAM(test_frame_number);
+ cmd_status = dwc3_send_gadget_ep_cmd(dep, cmd, &params);
+
+ /* Redo if some other failure beside bus-expiry is received */
+ if (cmd_status && cmd_status != -EAGAIN) {
+ dep->start_cmd_status = 0;
+ dep->combo_num = 0;
+ return 0;
+ }
+
+ /* Store the first test status */
+ if (dep->combo_num == 0)
+ dep->start_cmd_status = cmd_status;
+
+ dep->combo_num++;
+
+ /*
+ * End the transfer if the START_TRANSFER command is successful
+ * to wait for the next XferNotReady to test the command again
+ */
+ if (cmd_status == 0) {
+ dwc3_stop_active_transfer(dep, true, true);
+ return 0;
+ }
+ }
+
+ /* test0 and test1 are both completed at this point */
+ test0 = (dep->start_cmd_status == 0);
+ test1 = (cmd_status == 0);
+
+ if (!test0 && test1)
+ dep->combo_num = 1;
+ else if (!test0 && !test1)
+ dep->combo_num = 2;
+ else if (test0 && !test1)
+ dep->combo_num = 3;
+ else if (test0 && test1)
+ dep->combo_num = 0;
+
+ dep->frame_number &= 0x3fff;
+ dep->frame_number |= dep->combo_num << 14;
+ dep->frame_number += max_t(u32, 4, dep->interval);
+
+ /* Reinitialize test variables */
+ dep->start_cmd_status = 0;
+ dep->combo_num = 0;
+
+ return __dwc3_gadget_kick_transfer(dep);
+}
+
+static int __dwc3_gadget_start_isoc(struct dwc3_ep *dep)
{
+ const struct usb_endpoint_descriptor *desc = dep->endpoint.desc;
+ struct dwc3 *dwc = dep->dwc;
+ int ret;
+ int i;
+
if (list_empty(&dep->pending_list)) {
- dev_info(dep->dwc->dev, "%s: ran out of requests\n",
- dep->name);
dep->flags |= DWC3_EP_PENDING_REQUEST;
- return;
+ return -EAGAIN;
}
- dep->frame_number = DWC3_ALIGN_FRAME(dep);
- __dwc3_gadget_kick_transfer(dep);
+ if (!dwc->dis_start_transfer_quirk && dwc3_is_usb31(dwc) &&
+ (dwc->revision <= DWC3_USB31_REVISION_160A ||
+ (dwc->revision == DWC3_USB31_REVISION_170A &&
+ dwc->version_type >= DWC31_VERSIONTYPE_EA01 &&
+ dwc->version_type <= DWC31_VERSIONTYPE_EA06))) {
+
+ if (dwc->gadget.speed <= USB_SPEED_HIGH && dep->direction)
+ return dwc3_gadget_start_isoc_quirk(dep);
+ }
+
+ if (desc->bInterval <= 14 &&
+ dwc->gadget.speed >= USB_SPEED_HIGH) {
+ u32 frame = __dwc3_gadget_get_frame(dwc);
+ bool rollover = frame <
+ (dep->frame_number & 0x3fff);
+
+ /*
+ * frame_number is set from XferNotReady and may be already
+ * out of date. DSTS only provides the lower 14 bit of the
+ * current frame number. So add the upper two bits of
+ * frame_number and handle a possible rollover.
+ * This will provide the correct frame_number unless more than
+ * rollover has happened since XferNotReady.
+ */
+
+ dep->frame_number = (dep->frame_number & ~0x3fff) |
+ frame;
+ if (rollover)
+ dep->frame_number += BIT(14);
+ }
+
+ for (i = 0; i < DWC3_ISOC_MAX_RETRIES; i++) {
+ dep->frame_number = DWC3_ALIGN_FRAME(dep, i + 1);
+
+ ret = __dwc3_gadget_kick_transfer(dep);
+ if (ret != -EAGAIN)
+ break;
+ }
+
+ return ret;
+}
+
+static void dwc3_gadget_ep_skip_trbs(struct dwc3_ep *dep, struct dwc3_request *req)
+{
+ int i;
+
+ /*
+ * If request was already started, this means we had to
+ * stop the transfer. With that we also need to ignore
+ * all TRBs used by the request, however TRBs can only
+ * be modified after completion of END_TRANSFER
+ * command. So what we do here is that we wait for
+ * END_TRANSFER completion and only after that, we jump
+ * over TRBs by clearing HWO and incrementing dequeue
+ * pointer.
+ */
+ for (i = 0; i < req->num_trbs; i++) {
+ struct dwc3_trb *trb;
+
+ trb = &dep->trb_pool[dep->trb_dequeue];
+ trb->ctrl &= ~DWC3_TRB_CTRL_HWO;
+ dwc3_ep_inc_deq(dep);
+ }
+
+ req->num_trbs = 0;
}
static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
@@ -1397,6 +1768,17 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
&req->request, req->dep->name))
return -EINVAL;
+ if (WARN(req->status < DWC3_REQUEST_STATUS_COMPLETED,
+ "%s: request %pK already in flight\n",
+ dep->name, &req->request)) {
+ if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) {
+ dwc3_gadget_ep_skip_trbs(dep, req);
+ req->status = DWC3_REQUEST_STATUS_COMPLETED;
+ dwc3_gadget_del_and_unmap_request(dep, req, -EINVAL);
+ }
+ return -EINVAL;
+ }
+
pm_runtime_get(dwc->dev);
req->request.actual = 0;
@@ -1405,6 +1787,13 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
trace_dwc3_ep_queue(req);
list_add_tail(&req->list, &dep->pending_list);
+ req->status = DWC3_REQUEST_STATUS_QUEUED;
+
+ /* Start the transfer only after the END_TRANSFER is completed */
+ if (dep->flags & DWC3_EP_END_TRANSFER_PENDING) {
+ dep->flags |= DWC3_EP_DELAY_START;
+ return 0;
+ }
/*
* NOTICE: Isochronous endpoints should NEVER be prestarted. We must
@@ -1421,8 +1810,7 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
if ((dep->flags & DWC3_EP_PENDING_REQUEST)) {
if (!(dep->flags & DWC3_EP_TRANSFER_STARTED)) {
- __dwc3_gadget_start_isoc(dep);
- return 0;
+ return __dwc3_gadget_start_isoc(dep);
}
}
}
@@ -1450,31 +1838,6 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request,
return ret;
}
-static void dwc3_gadget_ep_skip_trbs(struct dwc3_ep *dep, struct dwc3_request *req)
-{
- int i;
-
- /*
- * If request was already started, this means we had to
- * stop the transfer. With that we also need to ignore
- * all TRBs used by the request, however TRBs can only
- * be modified after completion of END_TRANSFER
- * command. So what we do here is that we wait for
- * END_TRANSFER completion and only after that, we jump
- * over TRBs by clearing HWO and incrementing dequeue
- * pointer.
- */
- for (i = 0; i < req->num_trbs; i++) {
- struct dwc3_trb *trb;
-
- trb = &dep->trb_pool[dep->trb_dequeue];
- trb->ctrl &= ~DWC3_TRB_CTRL_HWO;
- dwc3_ep_inc_deq(dep);
- }
-
- req->num_trbs = 0;
-}
-
static void dwc3_gadget_ep_cleanup_cancelled_requests(struct dwc3_ep *dep)
{
struct dwc3_request *req;
@@ -1532,6 +1895,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep,
}
out1:
+ dwc3_gadget_ep_skip_trbs(dep, req);
dwc3_gadget_giveback(dep, req, -ECONNRESET);
out0:
@@ -1686,6 +2050,7 @@ static int __dwc3_gadget_wakeup(struct dwc3 *dwc)
link_state = DWC3_DSTS_USBLNKST(reg);
switch (link_state) {
+ case DWC3_LINK_STATE_U0:
case DWC3_LINK_STATE_RESET:
case DWC3_LINK_STATE_RX_DET: /* in HS, means Early Suspend */
case DWC3_LINK_STATE_U3: /* in HS, means SUSPEND */
@@ -1697,6 +2062,15 @@ static int __dwc3_gadget_wakeup(struct dwc3 *dwc)
return -EINVAL;
}
+ /*
+ * dwc3 gadget wakeup from host resume signal
+ * when the whole system enter suspend.
+ */
+ if (link_state == DWC3_LINK_STATE_U0) {
+ dwc->link_state = link_state;
+ return 0;
+ }
+
ret = dwc3_gadget_set_link_state(dwc, DWC3_LINK_STATE_RECOV);
if (ret < 0) {
dev_err(dwc->dev, "failed to put link in Recovery\n");
@@ -1813,7 +2187,8 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on)
* 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.
*/
- if (!is_on && dwc->ep0state != EP0_SETUP_PHASE) {
+ if (!is_on && dwc->ep0state != EP0_SETUP_PHASE &&
+ dwc->ep0state != EP0_UNCONNECTED) {
reinit_completion(&dwc->ep0_in_setup);
ret = wait_for_completion_timeout(&dwc->ep0_in_setup,
@@ -1829,7 +2204,7 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on)
return ret;
}
-static void dwc3_gadget_enable_irq(struct dwc3 *dwc)
+void dwc3_gadget_enable_irq(struct dwc3 *dwc)
{
u32 reg;
@@ -1853,7 +2228,7 @@ static void dwc3_gadget_enable_irq(struct dwc3 *dwc)
dwc3_writel(dwc->regs, DWC3_DEVTEN, reg);
}
-static void dwc3_gadget_disable_irq(struct dwc3 *dwc)
+void dwc3_gadget_disable_irq(struct dwc3 *dwc)
{
/* mask all interrupts */
dwc3_writel(dwc->regs, DWC3_DEVTEN, 0x00);
@@ -2028,17 +2403,26 @@ static int dwc3_gadget_stop(struct usb_gadget *g)
spin_lock_irqsave(&dwc->lock, flags);
+ if (!dwc->gadget_driver) {
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ dev_warn(dwc->dev, "%s is already stopped\n",
+ dwc->gadget.name);
+ goto out0;
+ }
+
if (pm_runtime_suspended(dwc->dev))
- goto out;
+ goto out1;
__dwc3_gadget_stop(dwc);
-out:
+out1:
+ dwc->fifo_resize_status = false;
dwc->gadget_driver = NULL;
spin_unlock_irqrestore(&dwc->lock, flags);
free_irq(dwc->irq_gadget, dwc->ev_buf);
+out0:
return 0;
}
@@ -2049,6 +2433,14 @@ static void dwc3_gadget_set_speed(struct usb_gadget *g,
unsigned long flags;
u32 reg;
+ /*
+ * To prevent Android 10 from trying to call UDC and failed constantly
+ * while dwc3 is suspended, we let the UDC node always exist.
+ * If not return here, it may cause crashes.
+ */
+ if (pm_runtime_suspended(dwc->dev))
+ return;
+
spin_lock_irqsave(&dwc->lock, flags);
reg = dwc3_readl(dwc->regs, DWC3_DCFG);
reg &= ~(DWC3_DCFG_SPEED_MASK);
@@ -2161,6 +2553,14 @@ static int dwc3_gadget_init_in_endpoint(struct dwc3_ep *dep)
else
size /= 2;
+ /*
+ * If enable tx fifos resize, set each in ep maxpacket
+ * to 1024, it can avoid being dependent on the default
+ * fifo size, and more flexible use of endpoints.
+ */
+ if (dwc->needs_fifo_resize)
+ size = 1024;
+
usb_ep_set_maxpacket_limit(&dep->endpoint, size);
dep->endpoint.max_streams = 15;
@@ -2227,16 +2627,29 @@ static int dwc3_gadget_init_endpoint(struct dwc3 *dwc, u8 epnum)
bool direction = epnum & 1;
int ret;
u8 num = epnum >> 1;
+ u8 num_in_eps, num_out_eps;
+
+ num_in_eps = DWC3_NUM_IN_EPS(&dwc->hwparams);
+ num_out_eps = dwc->num_eps - num_in_eps;
dep = kzalloc(sizeof(*dep), GFP_KERNEL);
if (!dep)
return -ENOMEM;
+ /* reconfig direction and num if num_out_eps != num_in_eps */
+ if ((!direction && ((epnum >> 1) + 1) > num_out_eps) ||
+ (direction && ((epnum >> 1) + 1) > num_in_eps)) {
+ direction = !direction;
+ num = num + (epnum & 1);
+ }
+
dep->dwc = dwc;
- dep->number = epnum;
+ dep->number = num << 1 | direction;
dep->direction = direction;
dep->regs = dwc->regs + DWC3_DEP_BASE(epnum);
dwc->eps[epnum] = dep;
+ dep->combo_num = 0;
+ dep->start_cmd_status = 0;
snprintf(dep->name, sizeof(dep->name), "ep%u%s", num,
direction ? "in" : "out");
@@ -2246,6 +2659,9 @@ static int dwc3_gadget_init_endpoint(struct dwc3 *dwc, u8 epnum)
if (!(dep->number > 1)) {
dep->endpoint.desc = &dwc3_gadget_ep0_desc;
dep->endpoint.comp_desc = NULL;
+#ifdef CONFIG_ARCH_ROCKCHIP
+ dep->endpoint.transfer_type = USB_ENDPOINT_XFER_CONTROL;
+#endif
}
spin_lock_init(&dep->lock);
@@ -2343,6 +2759,19 @@ static int dwc3_gadget_ep_reclaim_completed_trb(struct dwc3_ep *dep,
if (chain && (trb->ctrl & DWC3_TRB_CTRL_HWO))
trb->ctrl &= ~DWC3_TRB_CTRL_HWO;
+ /*
+ * For isochronous transfers, the first TRB in a service interval must
+ * have the Isoc-First type. Track and report its interval frame number.
+ */
+ if (usb_endpoint_xfer_isoc(dep->endpoint.desc) &&
+ (trb->ctrl & DWC3_TRBCTL_ISOCHRONOUS_FIRST)) {
+ unsigned int frame_number;
+
+ frame_number = DWC3_TRB_CTRL_GET_SID_SOFN(trb->ctrl);
+ frame_number &= ~(dep->interval - 1);
+ req->request.frame_number = frame_number;
+ }
+
/*
* If we're dealing with unaligned size OUT transfer, we will be left
* with one TRB pending in the ring. We need to manually clear HWO bit
@@ -2415,6 +2844,7 @@ static int dwc3_gadget_ep_cleanup_completed_request(struct dwc3_ep *dep,
const struct dwc3_event_depevt *event,
struct dwc3_request *req, int status)
{
+ struct dwc3 *dwc = dep->dwc;
int ret;
if (req->request.num_mapped_sgs)
@@ -2426,7 +2856,8 @@ static int dwc3_gadget_ep_cleanup_completed_request(struct dwc3_ep *dep,
req->request.actual = req->request.length - req->remaining;
- if (!dwc3_gadget_ep_request_completed(req))
+ if (!dwc3_gadget_ep_request_completed(req) &&
+ !usb_endpoint_xfer_isoc(dep->endpoint.desc))
goto out;
if (req->needs_extra_trb) {
@@ -2444,6 +2875,28 @@ static int dwc3_gadget_ep_cleanup_completed_request(struct dwc3_ep *dep,
req->needs_extra_trb = false;
}
+ if (event->status & DEPEVT_STATUS_MISSED_ISOC &&
+ usb_endpoint_xfer_isoc(dep->endpoint.desc)) {
+ /*
+ * unmap isoc request and move the request
+ * to the pending list to wait for kicking
+ * transfer again.
+ */
+ req->remaining = 0;
+ req->needs_extra_trb = false;
+ dwc3_gadget_move_cancelled_request(req);
+ if (req->trb) {
+ usb_gadget_unmap_request_by_dev(dwc->sysdev,
+ &req->request,
+ req->direction);
+ req->trb->ctrl &= ~DWC3_TRB_CTRL_HWO;
+ req->trb = NULL;
+ }
+ ret = 0;
+
+ goto out;
+ }
+
dwc3_gadget_giveback(dep, req, status);
out:
@@ -2495,25 +2948,37 @@ static void dwc3_gadget_endpoint_transfer_in_progress(struct dwc3_ep *dep,
{
struct dwc3 *dwc = dep->dwc;
unsigned status = 0;
- bool stop = false;
+ struct dwc3_request *req;
+ struct dwc3_request *tmp;
dwc3_gadget_endpoint_frame_from_event(dep, event);
if (event->status & DEPEVT_STATUS_BUSERR)
status = -ECONNRESET;
- if (event->status & DEPEVT_STATUS_MISSED_ISOC) {
+ if (event->status & DEPEVT_STATUS_MISSED_ISOC)
status = -EXDEV;
- if (list_empty(&dep->started_list))
- stop = true;
+ dwc3_gadget_ep_cleanup_completed_requests(dep, event, status);
+
+ if (event->status & DEPEVT_STATUS_MISSED_ISOC &&
+ !list_empty(&dep->cancelled_list) &&
+ !list_empty(&dep->pending_list)) {
+ list_for_each_entry_safe(req, tmp, &dep->pending_list, list)
+ dwc3_gadget_move_cancelled_request(req);
}
- dwc3_gadget_ep_cleanup_completed_requests(dep, event, status);
+ if (event->status & DEPEVT_STATUS_MISSED_ISOC &&
+ !list_empty(&dep->cancelled_list)) {
+ list_for_each_entry_safe(req, tmp, &dep->cancelled_list, list)
+ dwc3_gadget_move_queued_request(req);
+ }
- if (stop)
+ if (event->status & DEPEVT_STATUS_MISSED_ISOC &&
+ list_empty(&dep->started_list))
dwc3_stop_active_transfer(dep, true, true);
- else if (dwc3_gadget_ep_should_continue(dep))
+ else if (!usb_endpoint_xfer_isoc(dep->endpoint.desc) &&
+ dwc3_gadget_ep_should_continue(dep))
__dwc3_gadget_kick_transfer(dep);
/*
@@ -2546,7 +3011,7 @@ static void dwc3_gadget_endpoint_transfer_not_ready(struct dwc3_ep *dep,
const struct dwc3_event_depevt *event)
{
dwc3_gadget_endpoint_frame_from_event(dep, event);
- __dwc3_gadget_start_isoc(dep);
+ (void) __dwc3_gadget_start_isoc(dep);
}
static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
@@ -2559,7 +3024,7 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
dep = dwc->eps[epnum];
if (!(dep->flags & DWC3_EP_ENABLED)) {
- if (!(dep->flags & DWC3_EP_END_TRANSFER_PENDING))
+ if (!(dep->flags & DWC3_EP_TRANSFER_STARTED))
return;
/* Handle only EPCMDCMPLT when EP disabled */
@@ -2568,6 +3033,11 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
}
if (epnum == 0 || epnum == 1) {
+ if (!dwc->connected &&
+ event->endpoint_event == DWC3_DEPEVT_XFERCOMPLETE) {
+ reinit_completion(&dwc->discon_done);
+ dwc->connected = true;
+ }
dwc3_ep0_interrupt(dwc, event);
return;
}
@@ -2583,9 +3053,14 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
cmd = DEPEVT_PARAMETER_CMD(event->parameters);
if (cmd == DWC3_DEPCMD_ENDTRANSFER) {
- dep->flags &= ~(DWC3_EP_END_TRANSFER_PENDING |
- DWC3_EP_TRANSFER_STARTED);
+ dep->flags &= ~DWC3_EP_END_TRANSFER_PENDING;
+ dep->flags &= ~DWC3_EP_TRANSFER_STARTED;
dwc3_gadget_ep_cleanup_cancelled_requests(dep);
+ if ((dep->flags & DWC3_EP_DELAY_START) &&
+ !usb_endpoint_xfer_isoc(dep->endpoint.desc))
+ __dwc3_gadget_kick_transfer(dep);
+
+ dep->flags &= ~DWC3_EP_DELAY_START;
}
break;
case DWC3_DEPEVT_STREAMEVT:
@@ -2637,13 +3112,12 @@ static void dwc3_reset_gadget(struct dwc3 *dwc)
static void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force,
bool interrupt)
{
- struct dwc3 *dwc = dep->dwc;
struct dwc3_gadget_ep_cmd_params params;
u32 cmd;
int ret;
- if ((dep->flags & DWC3_EP_END_TRANSFER_PENDING) ||
- !dep->resource_index)
+ if (!(dep->flags & DWC3_EP_TRANSFER_STARTED) ||
+ (dep->flags & DWC3_EP_END_TRANSFER_PENDING))
return;
/*
@@ -2653,16 +3127,13 @@ static void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force,
* much trouble synchronizing between us and gadget driver.
*
* We have discussed this with the IP Provider and it was
- * suggested to giveback all requests here, but give HW some
- * extra time to synchronize with the interconnect. We're using
- * an arbitrary 100us delay for that.
+ * suggested to giveback all requests here.
*
* Note also that a similar handling was tested by Synopsys
* (thanks a lot Paul) and nothing bad has come out of it.
- * In short, what we're doing is:
- *
- * - Issue EndTransfer WITH CMDIOC bit set
- * - Wait 100us
+ * In short, what we're doing is issuing EndTransfer with
+ * CMDIOC bit set and delay kicking transfer until the
+ * EndTransfer command had completed.
*
* As of IP version 3.10a of the DWC_usb3 IP, the controller
* supports a mode to work around the above limitation. The
@@ -2671,8 +3142,7 @@ static void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force,
* by writing GUCTL2[14]. This polling is already done in the
* dwc3_send_gadget_ep_cmd() function so if the mode is
* enabled, the EndTransfer command will have completed upon
- * returning from this function and we don't need to delay for
- * 100us.
+ * returning from this function.
*
* This mode is NOT available on the DWC_usb31 IP.
*/
@@ -2686,10 +3156,10 @@ static void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force,
WARN_ON_ONCE(ret);
dep->resource_index = 0;
- if (dwc3_is_usb31(dwc) || dwc->revision < DWC3_REVISION_310A) {
+ if (!interrupt)
+ dep->flags &= ~DWC3_EP_TRANSFER_STARTED;
+ else
dep->flags |= DWC3_EP_END_TRANSFER_PENDING;
- udelay(100);
- }
}
static void dwc3_clear_stall_all_ep(struct dwc3 *dwc)
@@ -2732,13 +3202,16 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc)
usb_gadget_set_state(&dwc->gadget, USB_STATE_NOTATTACHED);
dwc->connected = false;
+ complete(&dwc->discon_done);
}
static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc)
{
u32 reg;
- dwc->connected = true;
+ if (of_device_is_compatible(dwc->dev->parent->of_node,
+ "rockchip,rk3399-dwc3"))
+ phy_calibrate(dwc->usb2_generic_phy);
/*
* Ideally, dwc3_reset_gadget() would trigger the function
@@ -2885,7 +3358,7 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc)
"LPM Erratum not available on dwc3 revisions < 2.40a\n");
if (dwc->has_lpm_erratum && dwc->revision >= DWC3_REVISION_240A)
- reg |= DWC3_DCTL_LPM_ERRATA(dwc->lpm_nyet_threshold);
+ reg |= DWC3_DCTL_NYET_THRES(dwc->lpm_nyet_threshold);
dwc3_writel(dwc->regs, DWC3_DCTL, reg);
} else {
@@ -2894,6 +3367,10 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc)
dwc3_writel(dwc->regs, DWC3_DCTL, reg);
}
+#ifdef CONFIG_ARCH_ROCKCHIP
+ dwc3_gadget_resize_tx_fifos(dwc);
+#endif
+
dep = dwc->eps[0];
ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_MODIFY);
if (ret) {
@@ -2917,18 +3394,21 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc)
*/
}
-static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc)
+static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc, unsigned int evtinfo)
{
+ enum dwc3_link_state next = evtinfo & DWC3_LINK_STATE_MASK;
/*
* TODO take core out of low power mode when that's
* implemented.
*/
- if (dwc->gadget_driver && dwc->gadget_driver->resume) {
+ if (dwc->gadget_driver && dwc->gadget_driver->resume && dwc->uwk_en) {
spin_unlock(&dwc->lock);
dwc->gadget_driver->resume(&dwc->gadget);
spin_lock(&dwc->lock);
}
+
+ dwc->link_state = next;
}
static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,
@@ -3034,7 +3514,8 @@ static void dwc3_gadget_suspend_interrupt(struct dwc3 *dwc,
{
enum dwc3_link_state next = evtinfo & DWC3_LINK_STATE_MASK;
- if (dwc->link_state != next && next == DWC3_LINK_STATE_U3)
+ if (dwc->link_state != next && next == DWC3_LINK_STATE_U3 &&
+ dwc->uwk_en)
dwc3_suspend_gadget(dwc);
dwc->link_state = next;
@@ -3069,16 +3550,19 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc,
{
switch (event->type) {
case DWC3_DEVICE_EVENT_DISCONNECT:
+ dev_info(dwc->dev, "device disconnect\n");
dwc3_gadget_disconnect_interrupt(dwc);
break;
case DWC3_DEVICE_EVENT_RESET:
+ dev_info(dwc->dev, "device reset\n");
dwc3_gadget_reset_interrupt(dwc);
break;
case DWC3_DEVICE_EVENT_CONNECT_DONE:
dwc3_gadget_conndone_interrupt(dwc);
break;
case DWC3_DEVICE_EVENT_WAKEUP:
- dwc3_gadget_wakeup_interrupt(dwc);
+ dev_dbg(dwc->dev, "device wakeup\n");
+ dwc3_gadget_wakeup_interrupt(dwc, event->event_info);
break;
case DWC3_DEVICE_EVENT_HIBER_REQ:
if (dev_WARN_ONCE(dwc->dev, !dwc->has_hibernation,
@@ -3097,6 +3581,7 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc,
* Ignore suspend event until the gadget enters into
* USB_STATE_CONFIGURED state.
*/
+ dev_dbg(dwc->dev, "device suspend\n");
if (dwc->gadget.state >= USB_STATE_CONFIGURED)
dwc3_gadget_suspend_interrupt(dwc,
event->event_info);
@@ -3319,11 +3804,17 @@ int dwc3_gadget_init(struct dwc3 *dwc)
}
init_completion(&dwc->ep0_in_setup);
+ init_completion(&dwc->discon_done);
dwc->gadget.ops = &dwc3_gadget_ops;
dwc->gadget.speed = USB_SPEED_UNKNOWN;
dwc->gadget.sg_supported = true;
dwc->gadget.name = "dwc3-gadget";
+#ifdef CONFIG_ARCH_ROCKCHIP
+ dwc->gadget.lpm_capable = false;
+#else
+ dwc->gadget.lpm_capable = true;
+#endif
/*
* FIXME We might be setting max_speed to <SUPER, however versions
diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h
index 023a47364..9ca5ac361 100644
--- a/drivers/usb/dwc3/gadget.h
+++ b/drivers/usb/dwc3/gadget.h
@@ -64,6 +64,21 @@ static inline struct dwc3_request *next_request(struct list_head *list)
return list_first_entry_or_null(list, struct dwc3_request, list);
}
+/**
+ * dwc3_gadget_move_queued_request - move @req to the pending_list
+ * @req: the request to be moved
+ *
+ * Caller should take care of locking. This function will move @req from its
+ * current list to the endpoint's pending_list.
+ */
+static inline void dwc3_gadget_move_queued_request(struct dwc3_request *req)
+{
+ struct dwc3_ep *dep = req->dep;
+
+ req->status = DWC3_REQUEST_STATUS_QUEUED;
+ list_move_tail(&req->list, &dep->pending_list);
+}
+
/**
* dwc3_gadget_move_started_request - move @req to the started_list
* @req: the request to be moved
@@ -75,7 +90,7 @@ static inline void dwc3_gadget_move_started_request(struct dwc3_request *req)
{
struct dwc3_ep *dep = req->dep;
- req->started = true;
+ req->status = DWC3_REQUEST_STATUS_STARTED;
list_move_tail(&req->list, &dep->started_list);
}
@@ -90,7 +105,7 @@ static inline void dwc3_gadget_move_cancelled_request(struct dwc3_request *req)
{
struct dwc3_ep *dep = req->dep;
- req->started = false;
+ req->status = DWC3_REQUEST_STATUS_CANCELLED;
list_move_tail(&req->list, &dep->cancelled_list);
}
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index 1a3878a3b..fc42ff29c 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -46,7 +46,7 @@ static int dwc3_host_get_irq(struct dwc3 *dwc)
int dwc3_host_init(struct dwc3 *dwc)
{
- struct property_entry props[3];
+ struct property_entry props[8];
struct platform_device *xhci;
int ret, irq;
struct resource *res;
@@ -93,6 +93,17 @@ int dwc3_host_init(struct dwc3 *dwc)
if (dwc->usb3_lpm_capable)
props[prop_idx++].name = "usb3-lpm-capable";
+ if (dwc->xhci_slow_suspend_quirk)
+ props[prop_idx++].name = "xhci-slow-suspend";
+
+ if (dwc->xhci_trb_ent_quirk)
+ props[prop_idx++].name = "xhci-trb-ent-quirk";
+
+ if (dwc->usb2_lpm_disable)
+ props[prop_idx++].name = "usb2-lpm-disable";
+
+ if (dwc->dis_u3_autosuspend_quirk)
+ props[prop_idx++].name = "usb3-dis-autosuspend";
/**
* WORKAROUND: dwc3 revisions <=3.00a have a limitation
* where Port Disable command doesn't work.
@@ -105,6 +116,12 @@ int dwc3_host_init(struct dwc3 *dwc)
if (dwc->revision <= DWC3_REVISION_300A)
props[prop_idx++].name = "quirk-broken-port-ped";
+ if (dwc->xhci_warm_reset_on_suspend_quirk)
+ props[prop_idx++].name = "xhci-warm-reset-on-suspend";
+
+ if (!dwc->dis_u2_susphy_quirk)
+ props[prop_idx++].name = "xhci-u2-broken-suspend";
+
if (prop_idx) {
ret = platform_device_add_properties(xhci, props);
if (ret) {
@@ -134,6 +151,7 @@ int dwc3_host_init(struct dwc3 *dwc)
platform_device_put(xhci);
return ret;
}
+EXPORT_SYMBOL_GPL(dwc3_host_init);
void dwc3_host_exit(struct dwc3 *dwc)
{
@@ -143,3 +161,4 @@ void dwc3_host_exit(struct dwc3 *dwc)
dev_name(dwc->dev));
platform_device_unregister(dwc->xhci);
}
+EXPORT_SYMBOL_GPL(dwc3_host_exit);
diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h
index f27c5cbe2..818a63da1 100644
--- a/drivers/usb/dwc3/trace.h
+++ b/drivers/usb/dwc3/trace.h
@@ -59,8 +59,8 @@ DECLARE_EVENT_CLASS(dwc3_log_event,
__entry->ep0state = dwc->ep0state;
),
TP_printk("event (%08x): %s", __entry->event,
- dwc3_decode_event(__get_str(str), __entry->event,
- __entry->ep0state))
+ dwc3_decode_event(__get_str(str), DWC3_MSG_MAX,
+ __entry->event, __entry->ep0state))
);
DEFINE_EVENT(dwc3_log_event, dwc3_event,
@@ -86,7 +86,8 @@ DECLARE_EVENT_CLASS(dwc3_log_ctrl,
__entry->wIndex = le16_to_cpu(ctrl->wIndex);
__entry->wLength = le16_to_cpu(ctrl->wLength);
),
- TP_printk("%s", dwc3_decode_ctrl(__get_str(str), __entry->bRequestType,
+ TP_printk("%s", dwc3_decode_ctrl(__get_str(str), DWC3_MSG_MAX,
+ __entry->bRequestType,
__entry->bRequest, __entry->wValue,
__entry->wIndex, __entry->wLength)
)
@@ -199,7 +200,7 @@ DECLARE_EVENT_CLASS(dwc3_log_gadget_ep_cmd,
__entry->param2 = params->param2;
__entry->cmd_status = cmd_status;
),
- TP_printk("%s: cmd '%s' [%d] params %08x %08x %08x --> status: %s",
+ TP_printk("%s: cmd '%s' [%x] params %08x %08x %08x --> status: %s",
__get_str(name), dwc3_gadget_ep_cmd_string(__entry->cmd),
__entry->cmd, __entry->param0,
__entry->param1, __entry->param2,
@@ -305,7 +306,7 @@ DECLARE_EVENT_CLASS(dwc3_log_ep,
__entry->trb_enqueue = dep->trb_enqueue;
__entry->trb_dequeue = dep->trb_dequeue;
),
- TP_printk("%s: mps %d/%d streams %d burst %d ring %d/%d flags %c:%c%c%c%c:%c:%c",
+ TP_printk("%s: mps %d/%d streams %d burst %d ring %d/%d flags %c:%c%c%c%c:%c",
__get_str(name), __entry->maxpacket,
__entry->maxpacket_limit, __entry->max_streams,
__entry->maxburst, __entry->trb_enqueue,
@@ -315,7 +316,6 @@ DECLARE_EVENT_CLASS(dwc3_log_ep,
__entry->flags & DWC3_EP_WEDGE ? 'W' : 'w',
__entry->flags & DWC3_EP_TRANSFER_STARTED ? 'B' : 'b',
__entry->flags & DWC3_EP_PENDING_REQUEST ? 'P' : 'p',
- __entry->flags & DWC3_EP_END_TRANSFER_PENDING ? 'E' : 'e',
__entry->direction ? '<' : '>'
)
);
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index c6afc4242..45849657f 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -1,3 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
#
# USB Gadget support on a system involves
# (a) a peripheral controller, and
@@ -215,6 +216,12 @@ config USB_F_PRINTER
config USB_F_TCM
tristate
+config USB_F_ACC
+ tristate
+
+config USB_F_AUDIO_SRC
+ tristate
+
# this first set of drivers all depend on bulk-capable hardware.
config USB_CONFIGFS
@@ -229,6 +236,14 @@ config USB_CONFIGFS
appropriate symbolic links.
For more information see Documentation/usb/gadget_configfs.txt.
+config USB_CONFIGFS_UEVENT
+ bool "Uevent notification of Gadget state"
+ depends on USB_CONFIGFS
+ help
+ Enable uevent notifications to userspace when the gadget
+ state changes. The gadget can be in any of the following
+ three states: "CONNECTED/DISCONNECTED/CONFIGURED"
+
config USB_CONFIGFS_SERIAL
bool "Generic serial bulk in/out"
depends on USB_CONFIGFS
@@ -370,6 +385,23 @@ config USB_CONFIGFS_F_FS
implemented in kernel space (for instance Ethernet, serial or
mass storage) and other are implemented in user space.
+config USB_CONFIGFS_F_ACC
+ bool "Accessory gadget"
+ depends on USB_CONFIGFS
+ depends on HID=y
+ select USB_F_ACC
+ help
+ USB gadget Accessory support
+
+config USB_CONFIGFS_F_AUDIO_SRC
+ bool "Audio Source gadget"
+ depends on USB_CONFIGFS
+ depends on SND
+ select SND_PCM
+ select USB_F_AUDIO_SRC
+ help
+ USB gadget Audio Source support
+
config USB_CONFIGFS_F_UAC1
bool "Audio Class 1.0"
depends on USB_CONFIGFS
@@ -484,34 +516,6 @@ config USB_CONFIGFS_F_TCM
Both protocols can work on USB2.0 and USB3.0.
UAS utilizes the USB 3.0 feature called streams support.
-choice
- tristate "USB Gadget precomposed configurations"
- default USB_ETH
- optional
- help
- A Linux "Gadget Driver" talks to the USB Peripheral Controller
- driver through the abstract "gadget" API. Some other operating
- systems call these "client" drivers, of which "class drivers"
- are a subset (implementing a USB device class specification).
- A gadget driver implements one or more USB functions using
- the peripheral hardware.
-
- Gadget drivers are hardware-neutral, or "platform independent",
- except that they sometimes must understand quirks or limitations
- of the particular controllers they work with. For example, when
- a controller doesn't support alternate configurations or provide
- enough of the right types of endpoints, the gadget driver might
- not be able work with that controller, or might need to implement
- a less common variant of a device class protocol.
-
- The available choices each represent a single precomposed USB
- gadget configuration. In the device model, each option contains
- both the device instantiation as a child for a USB gadget
- controller, and the relevant drivers for each function declared
- by the device.
-
source "drivers/usb/gadget/legacy/Kconfig"
-endchoice
-
endif # USB_GADGET
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index 42f267ae9..deb8cfa97 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -476,6 +476,122 @@ int usb_interface_id(struct usb_configuration *config,
}
EXPORT_SYMBOL_GPL(usb_interface_id);
+static int usb_func_wakeup_int(struct usb_function *func)
+{
+ int ret;
+ struct usb_gadget *gadget;
+
+ if (!func || !func->config || !func->config->cdev ||
+ !func->config->cdev->gadget)
+ return -EINVAL;
+
+ pr_debug("%s - %s function wakeup\n",
+ __func__, func->name ? func->name : "");
+
+ gadget = func->config->cdev->gadget;
+ if ((gadget->speed != USB_SPEED_SUPER) || !func->func_wakeup_allowed) {
+ DBG(func->config->cdev,
+ "Function Wakeup is not possible. speed=%u, func_wakeup_allowed=%u\n",
+ gadget->speed,
+ func->func_wakeup_allowed);
+
+ return -ENOTSUPP;
+ }
+
+ ret = usb_gadget_func_wakeup(gadget, func->intf_id);
+
+ return ret;
+}
+
+/**
+ * usb_func_wakeup - wakes up a composite device function.
+ * @func: composite device function to wake up.
+ *
+ * Returns 0 on success or a negative error value.
+ */
+int usb_func_wakeup(struct usb_function *func)
+{
+ int ret;
+ unsigned long flags;
+
+ if (!func || !func->config || !func->config->cdev)
+ return -EINVAL;
+
+ pr_debug("%s function wakeup\n",
+ func->name ? func->name : "");
+
+ spin_lock_irqsave(&func->config->cdev->lock, flags);
+ ret = usb_func_wakeup_int(func);
+ if (ret == -EAGAIN) {
+ DBG(func->config->cdev,
+ "Function wakeup for %s could not complete due to suspend state. Delayed until after bus resume.\n",
+ func->name ? func->name : "");
+ ret = 0;
+ } else if (ret < 0 && ret != -ENOTSUPP) {
+ ERROR(func->config->cdev,
+ "Failed to wake function %s from suspend state. ret=%d. Canceling USB request.\n",
+ func->name ? func->name : "", ret);
+ }
+
+ spin_unlock_irqrestore(&func->config->cdev->lock, flags);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(usb_func_wakeup);
+
+/**
+ * usb_func_ep_queue - queues (submits) an I/O request to a function endpoint.
+ * This function is similar to the usb_ep_queue function, but in addition it
+ * also checks whether the function is in Super Speed USB Function Suspend
+ * state, and if so a Function Wake notification is sent to the host
+ * (USB 3.0 spec, section 9.2.5.2).
+ * @func: the function which issues the USB I/O request.
+ * @ep:the endpoint associated with the request
+ * @req:the request being submitted
+ * @gfp_flags: GFP_* flags to use in case the lower level driver couldn't
+ * pre-allocate all necessary memory with the request.
+ */
+int usb_func_ep_queue(struct usb_function *func, struct usb_ep *ep,
+ struct usb_request *req, gfp_t gfp_flags)
+{
+ int ret;
+ struct usb_gadget *gadget;
+
+ if (!func || !func->config || !func->config->cdev ||
+ !func->config->cdev->gadget || !ep || !req) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ pr_debug("Function %s queueing new data into ep %u\n",
+ func->name ? func->name : "", ep->address);
+
+ gadget = func->config->cdev->gadget;
+ if (func->func_is_suspended && func->func_wakeup_allowed) {
+ ret = usb_gadget_func_wakeup(gadget, func->intf_id);
+ if (ret == -EAGAIN) {
+ pr_debug("bus suspended func wakeup for %s delayed until bus resume.\n",
+ func->name ? func->name : "");
+ } else if (ret < 0 && ret != -ENOTSUPP) {
+ pr_err("Failed to wake function %s from suspend state. ret=%d.\n",
+ func->name ? func->name : "", ret);
+ }
+ goto done;
+ }
+
+ if (!func->func_is_suspended)
+ ret = 0;
+
+ if (func->func_is_suspended && !func->func_wakeup_allowed) {
+ ret = -ENOTSUPP;
+ goto done;
+ }
+
+ ret = usb_ep_queue(ep, req, gfp_flags);
+done:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(usb_func_ep_queue);
+
static u8 encode_bMaxPower(enum usb_device_speed speed,
struct usb_configuration *c)
{
diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
index 3d4710cc3..5098c114d 100644
--- a/drivers/usb/gadget/configfs.c
+++ b/drivers/usb/gadget/configfs.c
@@ -10,6 +10,31 @@
#include "u_f.h"
#include "u_os_desc.h"
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+#include <linux/platform_device.h>
+#include <linux/kdev_t.h>
+#include <linux/usb/ch9.h>
+
+#ifdef CONFIG_USB_CONFIGFS_F_ACC
+extern int acc_ctrlrequest(struct usb_composite_dev *cdev,
+ const struct usb_ctrlrequest *ctrl);
+void acc_disconnect(void);
+#endif
+static struct class *android_class;
+static struct device *android_device;
+static int index;
+
+struct device *create_function_device(char *name)
+{
+ if (android_device && !IS_ERR(android_device))
+ return device_create(android_class, android_device,
+ MKDEV(0, index++), NULL, name);
+ else
+ return ERR_PTR(-EINVAL);
+}
+EXPORT_SYMBOL_GPL(create_function_device);
+#endif
+
int check_user_usb_string(const char *name,
struct usb_gadget_strings *stringtab_dev)
{
@@ -63,6 +88,12 @@ struct gadget_info {
char qw_sign[OS_STRING_QW_SIGN_LEN];
spinlock_t spinlock;
bool unbind;
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ bool connected;
+ bool sw_connected;
+ struct work_struct work;
+ struct device *dev;
+#endif
};
static inline struct gadget_info *to_gadget_info(struct config_item *item)
@@ -284,7 +315,7 @@ static ssize_t gadget_dev_desc_UDC_store(struct config_item *item,
mutex_lock(&gi->lock);
- if (!strlen(name)) {
+ if (!strlen(name) || strcmp(name, "none") == 0) {
ret = unregister_gadget(gi);
if (ret)
goto err;
@@ -1239,6 +1270,9 @@ static void purge_configs_funcs(struct gadget_info *gi)
f->name, f);
f->unbind(c, f);
}
+
+ if (f->bind_deactivated)
+ usb_function_activate(f);
}
c->next_interface_id = 0;
memset(c->interface, 0, sizeof(c->interface));
@@ -1391,6 +1425,60 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
return ret;
}
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+static void android_work(struct work_struct *data)
+{
+ struct gadget_info *gi = container_of(data, struct gadget_info, work);
+ struct usb_composite_dev *cdev = &gi->cdev;
+ char *disconnected[2] = { "USB_STATE=DISCONNECTED", NULL };
+ char *connected[2] = { "USB_STATE=CONNECTED", NULL };
+ char *configured[2] = { "USB_STATE=CONFIGURED", NULL };
+ /* 0-connected 1-configured 2-disconnected*/
+ bool status[3] = { false, false, false };
+ unsigned long flags;
+ bool uevent_sent = false;
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ if (cdev->config)
+ status[1] = true;
+
+ if (gi->connected != gi->sw_connected) {
+ if (gi->connected)
+ status[0] = true;
+ else
+ status[2] = true;
+ gi->sw_connected = gi->connected;
+ }
+ spin_unlock_irqrestore(&cdev->lock, flags);
+
+ if (status[0]) {
+ kobject_uevent_env(&android_device->kobj,
+ KOBJ_CHANGE, connected);
+ pr_info("%s: sent uevent %s\n", __func__, connected[0]);
+ uevent_sent = true;
+ }
+
+ if (status[1]) {
+ kobject_uevent_env(&android_device->kobj,
+ KOBJ_CHANGE, configured);
+ pr_info("%s: sent uevent %s\n", __func__, configured[0]);
+ uevent_sent = true;
+ }
+
+ if (status[2]) {
+ kobject_uevent_env(&android_device->kobj,
+ KOBJ_CHANGE, disconnected);
+ pr_info("%s: sent uevent %s\n", __func__, disconnected[0]);
+ uevent_sent = true;
+ }
+
+ if (!uevent_sent) {
+ pr_info("%s: did not send uevent (%d %d %p)\n", __func__,
+ gi->connected, gi->sw_connected, cdev->config);
+ }
+}
+#endif
+
static void configfs_composite_unbind(struct usb_gadget *gadget)
{
struct usb_composite_dev *cdev;
@@ -1416,6 +1504,7 @@ static void configfs_composite_unbind(struct usb_gadget *gadget)
spin_unlock_irqrestore(&gi->spinlock, flags);
}
+#ifndef CONFIG_USB_CONFIGFS_UEVENT
static int configfs_composite_setup(struct usb_gadget *gadget,
const struct usb_ctrlrequest *ctrl)
{
@@ -1462,6 +1551,7 @@ static void configfs_composite_disconnect(struct usb_gadget *gadget)
composite_disconnect(gadget);
spin_unlock_irqrestore(&gi->spinlock, flags);
}
+#endif
static void configfs_composite_suspend(struct usb_gadget *gadget)
{
@@ -1507,14 +1597,92 @@ static void configfs_composite_resume(struct usb_gadget *gadget)
spin_unlock_irqrestore(&gi->spinlock, flags);
}
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+static int android_setup(struct usb_gadget *gadget,
+ const struct usb_ctrlrequest *c)
+{
+ struct usb_composite_dev *cdev = get_gadget_data(gadget);
+ unsigned long flags;
+ struct gadget_info *gi = container_of(cdev, struct gadget_info, cdev);
+ int value = -EOPNOTSUPP;
+ struct usb_function_instance *fi;
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ if (c->bRequest == USB_REQ_GET_DESCRIPTOR &&
+ (c->wValue >> 8) == USB_DT_CONFIG && !gi->connected) {
+ gi->connected = 1;
+ schedule_work(&gi->work);
+ }
+ spin_unlock_irqrestore(&cdev->lock, flags);
+ list_for_each_entry(fi, &gi->available_func, cfs_list) {
+ if (fi != NULL && fi->f != NULL && fi->f->setup != NULL) {
+ value = fi->f->setup(fi->f, c);
+ if (value >= 0)
+ break;
+ }
+ }
+
+#ifdef CONFIG_USB_CONFIGFS_F_ACC
+ if (value < 0)
+ value = acc_ctrlrequest(cdev, c);
+#endif
+
+ if (value < 0)
+ value = composite_setup(gadget, c);
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ if (c->bRequest == USB_REQ_SET_CONFIGURATION &&
+ cdev->config) {
+ schedule_work(&gi->work);
+ }
+ spin_unlock_irqrestore(&cdev->lock, flags);
+
+ return value;
+}
+
+static void android_disconnect(struct usb_gadget *gadget)
+{
+ struct usb_composite_dev *cdev = get_gadget_data(gadget);
+ struct gadget_info *gi = container_of(cdev, struct gadget_info, cdev);
+
+ /* FIXME: There's a race between usb_gadget_udc_stop() which is likely
+ * to set the gadget driver to NULL in the udc driver and this drivers
+ * gadget disconnect fn which likely checks for the gadget driver to
+ * be a null ptr. It happens that unbind (doing set_gadget_data(NULL))
+ * is called before the gadget driver is set to NULL and the udc driver
+ * calls disconnect fn which results in cdev being a null ptr.
+ */
+ if (cdev == NULL) {
+ WARN(1, "%s: gadget driver already disconnected\n", __func__);
+ return;
+ }
+
+ /* accessory HID support can be active while the
+ accessory function is not actually enabled,
+ so we need to inform it when we are disconnected.
+ */
+
+#ifdef CONFIG_USB_CONFIGFS_F_ACC
+ acc_disconnect();
+#endif
+ gi->connected = 0;
+ schedule_work(&gi->work);
+ composite_disconnect(gadget);
+}
+#endif
+
static const struct usb_gadget_driver configfs_driver_template = {
.bind = configfs_composite_bind,
.unbind = configfs_composite_unbind,
-
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ .setup = android_setup,
+ .reset = android_disconnect,
+ .disconnect = android_disconnect,
+#else
.setup = configfs_composite_setup,
.reset = configfs_composite_disconnect,
.disconnect = configfs_composite_disconnect,
-
+#endif
.suspend = configfs_composite_suspend,
.resume = configfs_composite_resume,
@@ -1526,6 +1694,89 @@ static const struct usb_gadget_driver configfs_driver_template = {
.match_existing_only = 1,
};
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+static ssize_t state_show(struct device *pdev, struct device_attribute *attr,
+ char *buf)
+{
+ struct gadget_info *dev = dev_get_drvdata(pdev);
+ struct usb_composite_dev *cdev;
+ char *state = "DISCONNECTED";
+ unsigned long flags;
+
+ if (!dev)
+ goto out;
+
+ cdev = &dev->cdev;
+
+ if (!cdev)
+ goto out;
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ if (cdev->config)
+ state = "CONFIGURED";
+ else if (dev->connected)
+ state = "CONNECTED";
+ spin_unlock_irqrestore(&cdev->lock, flags);
+out:
+ return sprintf(buf, "%s\n", state);
+}
+
+static DEVICE_ATTR(state, S_IRUGO, state_show, NULL);
+
+static struct device_attribute *android_usb_attributes[] = {
+ &dev_attr_state,
+ NULL
+};
+
+static int android_device_create(struct gadget_info *gi)
+{
+ struct device_attribute **attrs;
+ struct device_attribute *attr;
+
+ INIT_WORK(&gi->work, android_work);
+ android_device = device_create(android_class, NULL,
+ MKDEV(0, 0), NULL, "android0");
+ if (IS_ERR(android_device))
+ return PTR_ERR(android_device);
+
+ dev_set_drvdata(android_device, gi);
+
+ attrs = android_usb_attributes;
+ while ((attr = *attrs++)) {
+ int err;
+
+ err = device_create_file(android_device, attr);
+ if (err) {
+ device_destroy(android_device->class,
+ android_device->devt);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static void android_device_destroy(void)
+{
+ struct device_attribute **attrs;
+ struct device_attribute *attr;
+
+ attrs = android_usb_attributes;
+ while ((attr = *attrs++))
+ device_remove_file(android_device, attr);
+ device_destroy(android_device->class, android_device->devt);
+}
+#else
+static inline int android_device_create(struct gadget_info *gi)
+{
+ return 0;
+}
+
+static inline void android_device_destroy(void)
+{
+}
+#endif
+
static struct config_group *gadgets_make(
struct config_group *group,
const char *name)
@@ -1578,7 +1829,11 @@ static struct config_group *gadgets_make(
if (!gi->composite.gadget_driver.function)
goto err;
+ if (android_device_create(gi) < 0)
+ goto err;
+
return &gi->group;
+
err:
kfree(gi);
return ERR_PTR(-ENOMEM);
@@ -1587,6 +1842,7 @@ static struct config_group *gadgets_make(
static void gadgets_drop(struct config_group *group, struct config_item *item)
{
config_item_put(item);
+ android_device_destroy();
}
static struct configfs_group_operations gadgets_ops = {
@@ -1626,6 +1882,13 @@ static int __init gadget_cfs_init(void)
config_group_init(&gadget_subsys.su_group);
ret = configfs_register_subsystem(&gadget_subsys);
+
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ android_class = class_create(THIS_MODULE, "android_usb");
+ if (IS_ERR(android_class))
+ return PTR_ERR(android_class);
+#endif
+
return ret;
}
module_init(gadget_cfs_init);
@@ -1633,5 +1896,10 @@ module_init(gadget_cfs_init);
static void __exit gadget_cfs_exit(void)
{
configfs_unregister_subsystem(&gadget_subsys);
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ if (!IS_ERR(android_class))
+ class_destroy(android_class);
+#endif
+
}
module_exit(gadget_cfs_exit);
diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c
index 71b15c65b..c80f9ba67 100644
--- a/drivers/usb/gadget/epautoconf.c
+++ b/drivers/usb/gadget/epautoconf.c
@@ -123,6 +123,27 @@ struct usb_ep *usb_ep_autoconfig_ss(
ep->desc = NULL;
ep->comp_desc = NULL;
ep->claimed = true;
+#ifdef CONFIG_ARCH_ROCKCHIP
+ ep->transfer_type = type;
+ if (gadget_is_superspeed(gadget) && ep_comp) {
+ switch (type) {
+ case USB_ENDPOINT_XFER_ISOC:
+ /* mult: bits 1:0 of bmAttributes */
+ ep->mult = (ep_comp->bmAttributes & 0x3) + 1;
+ /* fall through */
+ case USB_ENDPOINT_XFER_BULK:
+ case USB_ENDPOINT_XFER_INT:
+ ep->maxburst = ep_comp->bMaxBurst + 1;
+ break;
+ default:
+ break;
+ }
+ } else if (gadget_is_dualspeed(gadget) &&
+ (type == USB_ENDPOINT_XFER_ISOC ||
+ type == USB_ENDPOINT_XFER_INT)) {
+ ep->mult = usb_endpoint_maxp_mult(desc);
+ }
+#endif
return ep;
}
EXPORT_SYMBOL_GPL(usb_ep_autoconfig_ss);
@@ -205,3 +226,46 @@ void usb_ep_autoconfig_reset (struct usb_gadget *gadget)
gadget->out_epnum = 0;
}
EXPORT_SYMBOL_GPL(usb_ep_autoconfig_reset);
+
+/**
+ * usb_ep_autoconfig_by_name - Used to pick the endpoint by name. eg gsi-epin1
+ * @gadget: The device to which the endpoint must belong.
+ * @desc: Endpoint descriptor, with endpoint direction and transfer mode
+ * initialized.
+ * @ep_name: EP name that is to be searched.
+ *
+ */
+struct usb_ep *usb_ep_autoconfig_by_name(
+ struct usb_gadget *gadget,
+ struct usb_endpoint_descriptor *desc,
+ const char *ep_name
+)
+{
+ struct usb_ep *ep;
+ bool ep_found = false;
+
+ if (!ep_name || !strlen(ep_name))
+ goto err;
+
+ list_for_each_entry(ep, &gadget->ep_list, ep_list)
+ if (strncmp(ep->name, ep_name, strlen(ep_name)) == 0 &&
+ !ep->driver_data) {
+ ep_found = true;
+ break;
+ }
+
+ if (ep_found) {
+ desc->bEndpointAddress &= USB_DIR_IN;
+ desc->bEndpointAddress |= ep->ep_num;
+ ep->address = desc->bEndpointAddress;
+ pr_debug("Allocating ep address:%x\n", ep->address);
+ ep->desc = NULL;
+ ep->comp_desc = NULL;
+ return ep;
+ }
+
+err:
+ pr_err("%s:error finding ep %s\n", __func__, ep_name);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(usb_ep_autoconfig_by_name);
diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile
index 5d3a6cf02..dd33a1243 100644
--- a/drivers/usb/gadget/function/Makefile
+++ b/drivers/usb/gadget/function/Makefile
@@ -50,3 +50,7 @@ usb_f_printer-y := f_printer.o
obj-$(CONFIG_USB_F_PRINTER) += usb_f_printer.o
usb_f_tcm-y := f_tcm.o
obj-$(CONFIG_USB_F_TCM) += usb_f_tcm.o
+usb_f_accessory-y := f_accessory.o
+obj-$(CONFIG_USB_F_ACC) += usb_f_accessory.o
+usb_f_audio_source-y := f_audio_source.o
+obj-$(CONFIG_USB_F_AUDIO_SRC) += usb_f_audio_source.o
diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
index 49eb4e3c7..996be7e22 100644
--- a/drivers/usb/gadget/function/f_fs.c
+++ b/drivers/usb/gadget/function/f_fs.c
@@ -66,7 +66,7 @@ struct ffs_function {
struct ffs_data *ffs;
struct ffs_ep *eps;
- u8 eps_revmap[16];
+ u8 eps_revmap[32];
short *interfaces_nums;
struct usb_function function;
@@ -2739,7 +2739,7 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep,
struct ffs_function *func = priv;
struct ffs_ep *ffs_ep;
unsigned ep_desc_id;
- int idx;
+ int idx, ep_num;
static const char *speed_names[] = { "full", "high", "super" };
if (type != FFS_DESCRIPTOR)
@@ -2806,8 +2806,9 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep,
ffs_ep->ep = ep;
ffs_ep->req = req;
- func->eps_revmap[ds->bEndpointAddress &
- USB_ENDPOINT_NUMBER_MASK] = idx + 1;
+ ep_num = ((ds->bEndpointAddress & USB_ENDPOINT_DIR_MASK) >> 3) |
+ (ds->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+ func->eps_revmap[ep_num] = idx + 1;
/*
* If we use virtual address mapping, we restore
* original bEndpointAddress value.
@@ -3337,7 +3338,10 @@ static void ffs_func_resume(struct usb_function *f)
static int ffs_func_revmap_ep(struct ffs_function *func, u8 num)
{
- num = func->eps_revmap[num & USB_ENDPOINT_NUMBER_MASK];
+ int ep_num = ((num & USB_ENDPOINT_DIR_MASK) >> 3) |
+ (num & USB_ENDPOINT_NUMBER_MASK);
+
+ num = func->eps_revmap[ep_num];
return num ? num : -EDOM;
}
diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c
index 1d10d29c1..21b32e251 100644
--- a/drivers/usb/gadget/function/f_hid.c
+++ b/drivers/usb/gadget/function/f_hid.c
@@ -398,7 +398,7 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer,
}
req->status = 0;
- req->zero = 0;
+ req->zero = ((count % hidg->in_ep->maxpacket) == 0);
req->length = count;
req->complete = f_hidg_req_complete;
req->context = hidg;
diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c
index 0e083a53d..674591914 100644
--- a/drivers/usb/gadget/function/f_midi.c
+++ b/drivers/usb/gadget/function/f_midi.c
@@ -1222,6 +1222,65 @@ static void f_midi_free_inst(struct usb_function_instance *f)
}
}
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+extern struct device *create_function_device(char *name);
+static ssize_t alsa_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_function_instance *fi_midi = dev_get_drvdata(dev);
+ struct f_midi *midi;
+
+ if (!fi_midi->f)
+ dev_warn(dev, "f_midi: function not set\n");
+
+ if (fi_midi && fi_midi->f) {
+ midi = func_to_midi(fi_midi->f);
+ if (midi->rmidi && midi->rmidi->card)
+ return sprintf(buf, "%d %d\n",
+ midi->rmidi->card->number, midi->rmidi->device);
+ }
+
+ /* print PCM card and device numbers */
+ return sprintf(buf, "%d %d\n", -1, -1);
+}
+
+static DEVICE_ATTR(alsa, S_IRUGO, alsa_show, NULL);
+
+static struct device_attribute *alsa_function_attributes[] = {
+ &dev_attr_alsa,
+ NULL
+};
+
+static int create_alsa_device(struct usb_function_instance *fi)
+{
+ struct device *dev;
+ struct device_attribute **attrs;
+ struct device_attribute *attr;
+ int err = 0;
+
+ dev = create_function_device("f_midi");
+ if (IS_ERR(dev))
+ return PTR_ERR(dev);
+
+ attrs = alsa_function_attributes;
+ if (attrs) {
+ while ((attr = *attrs++) && !err)
+ err = device_create_file(dev, attr);
+ if (err) {
+ device_destroy(dev->class, dev->devt);
+ return -EINVAL;
+ }
+ }
+ dev_set_drvdata(dev, fi);
+ return 0;
+}
+#else
+static int create_alsa_device(struct usb_function_instance *fi)
+{
+ return 0;
+}
+#endif
+
static struct usb_function_instance *f_midi_alloc_inst(void)
{
struct f_midi_opts *opts;
@@ -1240,6 +1299,11 @@ static struct usb_function_instance *f_midi_alloc_inst(void)
opts->out_ports = 1;
opts->refcnt = 1;
+ if (create_alsa_device(&opts->func_inst)) {
+ kfree(opts);
+ return ERR_PTR(-ENODEV);
+ }
+
config_group_init_type_name(&opts->func_inst.group, "",
&midi_func_type);
@@ -1260,6 +1324,7 @@ static void f_midi_free(struct usb_function *f)
kfifo_free(&midi->in_req_fifo);
kfree(midi);
free = true;
+ opts->func_inst.f = NULL;
}
mutex_unlock(&opts->lock);
@@ -1347,6 +1412,7 @@ static struct usb_function *f_midi_alloc(struct usb_function_instance *fi)
midi->func.disable = f_midi_disable;
midi->func.free_func = f_midi_free;
+ fi->f = &midi->func;
return &midi->func;
midi_free:
diff --git a/drivers/usb/gadget/function/f_rndis.c b/drivers/usb/gadget/function/f_rndis.c
index f20c55d42..bd5ace22f 100644
--- a/drivers/usb/gadget/function/f_rndis.c
+++ b/drivers/usb/gadget/function/f_rndis.c
@@ -115,9 +115,9 @@ static struct usb_interface_descriptor rndis_control_intf = {
/* .bInterfaceNumber = DYNAMIC */
/* status endpoint is optional; this could be patched later */
.bNumEndpoints = 1,
- .bInterfaceClass = USB_CLASS_COMM,
- .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
- .bInterfaceProtocol = USB_CDC_ACM_PROTO_VENDOR,
+ .bInterfaceClass = USB_CLASS_WIRELESS_CONTROLLER,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 3,
/* .iInterface = DYNAMIC */
};
@@ -176,9 +176,9 @@ rndis_iad_descriptor = {
.bFirstInterface = 0, /* XXX, hardcoded */
.bInterfaceCount = 2, // control + data
- .bFunctionClass = USB_CLASS_COMM,
- .bFunctionSubClass = USB_CDC_SUBCLASS_ETHERNET,
- .bFunctionProtocol = USB_CDC_PROTO_NONE,
+ .bFunctionClass = USB_CLASS_WIRELESS_CONTROLLER,
+ .bFunctionSubClass = 1,
+ .bFunctionProtocol = 3,
/* .iFunction = DYNAMIC */
};
@@ -512,6 +512,20 @@ rndis_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
}
break;
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_SET_ETHERNET_PACKET_FILTER:
+ /*
+ * see 6.2.30: no data, wIndex = interface, wValue = packet
+ * filter bitmap. However, we don't really set cdc_filter to
+ * wValue for rndis, because cdc_filter is not RNDIS-specific.
+ * Return value 0 to avoid usb controllers stall ep0.
+ */
+ if (w_length != 0 || w_index != rndis->ctrl_id)
+ goto invalid;
+ DBG(cdev, "packet filter %02x\n", w_value);
+ value = 0;
+ break;
+
default:
invalid:
VDBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index 41e7b29f5..acd976b7c 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -3,6 +3,7 @@
* f_uac1.c -- USB Audio Class 1.0 Function (using u_audio API)
*
* Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@gmail.com>
+ * Copyright (C) 2017 Julian Scheel <julian@jusst.de>
*
* This driver doesn't expect any real Audio codec to be present
* on the device - the audio streams are simply sinked to and
@@ -17,27 +18,11 @@
#include <linux/module.h>
#include "u_audio.h"
-#include "u_uac1.h"
+#include "u_uac.h"
/* UAC1 spec: 3.7.2.3 Audio Channel Cluster Format */
#define UAC1_CHANNEL_MASK 0x0FFF
-struct f_uac1 {
- struct g_audio g_audio;
- u8 ac_intf, as_in_intf, as_out_intf;
- u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */
-};
-
-static inline struct f_uac1 *func_to_uac1(struct usb_function *f)
-{
- return container_of(f, struct f_uac1, g_audio.func);
-}
-
-static inline struct f_uac1_opts *g_audio_to_uac1_opts(struct g_audio *audio)
-{
- return container_of(audio->func.fi, struct f_uac1_opts, func_inst);
-}
-
/*
* DESCRIPTORS ... most are static, but strings and full
* configuration descriptors are built on demand.
@@ -56,6 +41,16 @@ static inline struct f_uac1_opts *g_audio_to_uac1_opts(struct g_audio *audio)
/* Number of streaming interfaces */
#define F_AUDIO_NUM_INTERFACES 2
+static struct usb_interface_assoc_descriptor iad_desc = {
+ .bLength = sizeof(iad_desc),
+ .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
+
+ .bFirstInterface = 0,
+ .bFunctionClass = USB_CLASS_AUDIO,
+ .bFunctionSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+ .bFunctionProtocol = UAC_VERSION_1,
+};
+
/* B.3.1 Standard AC Interface Descriptor */
static struct usb_interface_descriptor ac_interface_desc = {
.bLength = USB_DT_INTERFACE_SIZE,
@@ -71,67 +66,112 @@ static struct usb_interface_descriptor ac_interface_desc = {
*/
DECLARE_UAC_AC_HEADER_DESCRIPTOR(2);
-#define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES)
-/* 2 input terminals and 2 output terminals */
-#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH \
- + 2*UAC_DT_INPUT_TERMINAL_SIZE + 2*UAC_DT_OUTPUT_TERMINAL_SIZE)
/* B.3.2 Class-Specific AC Interface Descriptor */
static struct uac1_ac_header_descriptor_2 ac_header_desc = {
- .bLength = UAC_DT_AC_HEADER_LENGTH,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_HEADER,
.bcdADC = cpu_to_le16(0x0100),
- .wTotalLength = cpu_to_le16(UAC_DT_TOTAL_LENGTH),
- .bInCollection = F_AUDIO_NUM_INTERFACES,
- .baInterfaceNr = {
- /* Interface number of the AudioStream interfaces */
- [0] = 1,
- [1] = 2,
- }
+ /* .baInterfaceNr[0] = DYNAMIC */
+ /* .baInterfaceNr[1] = DYNAMIC */
};
-#define USB_OUT_IT_ID 1
static struct uac_input_terminal_descriptor usb_out_it_desc = {
.bLength = UAC_DT_INPUT_TERMINAL_SIZE,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_INPUT_TERMINAL,
- .bTerminalID = USB_OUT_IT_ID,
.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
.bAssocTerminal = 0,
.wChannelConfig = cpu_to_le16(0x3),
};
-#define IO_OUT_OT_ID 2
+DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0);
+
+static struct uac_feature_unit_descriptor_0 io_out_ot_fu_desc = {
+ .bLength = UAC_DT_FEATURE_UNIT_SIZE(0),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubtype = UAC_FEATURE_UNIT,
+ .bControlSize = 2,
+ .bmaControls[0] = (UAC_CONTROL_BIT(UAC_FU_MUTE) |
+ UAC_CONTROL_BIT(UAC_FU_VOLUME)),
+};
+
+static struct usb_audio_control c_mute_control = {
+ .list = LIST_HEAD_INIT(c_mute_control.list),
+ .name = "Capture Mute",
+ .type = UAC_FU_MUTE,
+ .set = u_audio_fu_set_cmd,
+ .get = u_audio_fu_get_cmd,
+};
+
+static struct usb_audio_control c_volume_control = {
+ .list = LIST_HEAD_INIT(c_volume_control.list),
+ .name = "Capture Volume",
+ .type = UAC_FU_VOLUME,
+ .set = u_audio_fu_set_cmd,
+ .get = u_audio_fu_get_cmd,
+};
+
+static struct usb_audio_control_selector c_feature_unit = {
+ .list = LIST_HEAD_INIT(c_feature_unit.list),
+ .name = "Capture Mute & Volume Control",
+ .type = UAC_FEATURE_UNIT,
+ .desc = (struct usb_descriptor_header *)&io_out_ot_fu_desc,
+};
+
static struct uac1_output_terminal_descriptor io_out_ot_desc = {
.bLength = UAC_DT_OUTPUT_TERMINAL_SIZE,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
- .bTerminalID = IO_OUT_OT_ID,
.wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_SPEAKER),
.bAssocTerminal = 0,
- .bSourceID = USB_OUT_IT_ID,
};
-#define IO_IN_IT_ID 3
static struct uac_input_terminal_descriptor io_in_it_desc = {
.bLength = UAC_DT_INPUT_TERMINAL_SIZE,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_INPUT_TERMINAL,
- .bTerminalID = IO_IN_IT_ID,
.wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_MICROPHONE),
.bAssocTerminal = 0,
.wChannelConfig = cpu_to_le16(0x3),
};
-#define USB_IN_OT_ID 4
+static struct uac_feature_unit_descriptor_0 usb_in_ot_fu_desc = {
+ .bLength = UAC_DT_FEATURE_UNIT_SIZE(0),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubtype = UAC_FEATURE_UNIT,
+ .bControlSize = 2,
+ .bmaControls[0] = (UAC_FU_MUTE | UAC_FU_VOLUME),
+};
+
+static struct usb_audio_control p_mute_control = {
+ .list = LIST_HEAD_INIT(p_mute_control.list),
+ .name = "Playback Mute",
+ .type = UAC_FU_MUTE,
+ .set = u_audio_fu_set_cmd,
+ .get = u_audio_fu_get_cmd,
+};
+
+static struct usb_audio_control p_volume_control = {
+ .list = LIST_HEAD_INIT(p_volume_control.list),
+ .name = "Playback Volume",
+ .type = UAC_FU_VOLUME,
+ .set = u_audio_fu_set_cmd,
+ .get = u_audio_fu_get_cmd,
+};
+
+static struct usb_audio_control_selector p_feature_unit = {
+ .list = LIST_HEAD_INIT(p_feature_unit.list),
+ .name = "Playback Mute & Volume Control",
+ .type = UAC_FEATURE_UNIT,
+ .desc = (struct usb_descriptor_header *)&usb_in_ot_fu_desc,
+};
+
static struct uac1_output_terminal_descriptor usb_in_ot_desc = {
.bLength = UAC_DT_OUTPUT_TERMINAL_SIZE,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
- .bTerminalID = USB_IN_OT_ID,
.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
.bAssocTerminal = 0,
- .bSourceID = IO_IN_IT_ID,
};
/* B.4.1 Standard AS Interface Descriptor */
@@ -176,7 +216,6 @@ static struct uac1_as_header_descriptor as_out_header_desc = {
.bLength = UAC_DT_AS_HEADER_SIZE,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_AS_GENERAL,
- .bTerminalLink = USB_OUT_IT_ID,
.bDelay = 1,
.wFormatTag = cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),
};
@@ -185,21 +224,22 @@ static struct uac1_as_header_descriptor as_in_header_desc = {
.bLength = UAC_DT_AS_HEADER_SIZE,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_AS_GENERAL,
- .bTerminalLink = USB_IN_OT_ID,
.bDelay = 1,
.wFormatTag = cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),
};
-DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1);
+DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(UAC_MAX_RATES);
+#define uac_format_type_i_discrete_descriptor \
+ uac_format_type_i_discrete_descriptor_##UAC_MAX_RATES
-static struct uac_format_type_i_discrete_descriptor_1 as_out_type_i_desc = {
- .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+static struct uac_format_type_i_discrete_descriptor as_out_type_i_desc = {
+ .bLength = 0, /* filled on rate setup */
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_FORMAT_TYPE,
.bFormatType = UAC_FORMAT_TYPE_I,
.bSubframeSize = 2,
.bBitResolution = 16,
- .bSamFreqType = 1,
+ .bSamFreqType = 0, /* filled on rate setup */
};
/* Standard ISO OUT Endpoint Descriptor */
@@ -209,10 +249,28 @@ static struct usb_endpoint_descriptor as_out_ep_desc = {
.bEndpointAddress = USB_DIR_OUT,
.bmAttributes = USB_ENDPOINT_SYNC_ADAPTIVE
| USB_ENDPOINT_XFER_ISOC,
- .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
+ /* .wMaxPacketSize = DYNAMIC */
.bInterval = 4,
};
+static struct usb_endpoint_descriptor ss_out_ep_desc = {
+ .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE,
+ /* .wMaxPacketSize = DYNAMIC */
+ .bInterval = 4,
+};
+
+static struct usb_ss_ep_comp_descriptor ss_out_ep_desc_comp = {
+ .bLength = sizeof(ss_out_ep_desc_comp),
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+ .bMaxBurst = 0,
+ .bmAttributes = 0,
+ /* wBytesPerInterval = DYNAMIC */
+};
+
/* Class-specific AS ISO OUT Endpoint Descriptor */
static struct uac_iso_endpoint_descriptor as_iso_out_desc = {
.bLength = UAC_ISO_ENDPOINT_DESC_SIZE,
@@ -223,14 +281,14 @@ static struct uac_iso_endpoint_descriptor as_iso_out_desc = {
.wLockDelay = cpu_to_le16(1),
};
-static struct uac_format_type_i_discrete_descriptor_1 as_in_type_i_desc = {
- .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+static struct uac_format_type_i_discrete_descriptor as_in_type_i_desc = {
+ .bLength = 0, /* filled on rate setup */
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_FORMAT_TYPE,
.bFormatType = UAC_FORMAT_TYPE_I,
.bSubframeSize = 2,
.bBitResolution = 16,
- .bSamFreqType = 1,
+ .bSamFreqType = 0, /* filled on rate setup */
};
/* Standard ISO OUT Endpoint Descriptor */
@@ -240,10 +298,28 @@ static struct usb_endpoint_descriptor as_in_ep_desc = {
.bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_SYNC_ASYNC
| USB_ENDPOINT_XFER_ISOC,
- .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
+ /* .wMaxPacketSize = DYNAMIC */
.bInterval = 4,
};
+static struct usb_endpoint_descriptor ss_in_ep_desc = {
+ .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+ /* .wMaxPacketSize = DYNAMIC */
+ .bInterval = 4,
+};
+
+static struct usb_ss_ep_comp_descriptor ss_in_ep_desc_comp = {
+ .bLength = sizeof(ss_in_ep_desc_comp),
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+ .bMaxBurst = 0,
+ .bmAttributes = 0,
+ /* wBytesPerInterval = DYNAMIC */
+};
+
/* Class-specific AS ISO OUT Endpoint Descriptor */
static struct uac_iso_endpoint_descriptor as_iso_in_desc = {
.bLength = UAC_ISO_ENDPOINT_DESC_SIZE,
@@ -255,12 +331,15 @@ static struct uac_iso_endpoint_descriptor as_iso_in_desc = {
};
static struct usb_descriptor_header *f_audio_desc[] = {
+ (struct usb_descriptor_header *)&iad_desc,
(struct usb_descriptor_header *)&ac_interface_desc,
(struct usb_descriptor_header *)&ac_header_desc,
(struct usb_descriptor_header *)&usb_out_it_desc,
+ (struct usb_descriptor_header *)&io_out_ot_fu_desc,
(struct usb_descriptor_header *)&io_out_ot_desc,
(struct usb_descriptor_header *)&io_in_it_desc,
+ (struct usb_descriptor_header *)&usb_in_ot_fu_desc,
(struct usb_descriptor_header *)&usb_in_ot_desc,
(struct usb_descriptor_header *)&as_out_interface_alt_0_desc,
@@ -283,13 +362,50 @@ static struct usb_descriptor_header *f_audio_desc[] = {
NULL,
};
+static struct usb_descriptor_header *f_ss_audio_desc[] = {
+ (struct usb_descriptor_header *)&iad_desc,
+ (struct usb_descriptor_header *)&ac_interface_desc,
+ (struct usb_descriptor_header *)&ac_header_desc,
+
+ (struct usb_descriptor_header *)&usb_out_it_desc,
+ (struct usb_descriptor_header *)&io_out_ot_fu_desc,
+ (struct usb_descriptor_header *)&io_out_ot_desc,
+ (struct usb_descriptor_header *)&io_in_it_desc,
+ (struct usb_descriptor_header *)&usb_in_ot_fu_desc,
+ (struct usb_descriptor_header *)&usb_in_ot_desc,
+
+ (struct usb_descriptor_header *)&as_out_interface_alt_0_desc,
+ (struct usb_descriptor_header *)&as_out_interface_alt_1_desc,
+ (struct usb_descriptor_header *)&as_out_header_desc,
+
+ (struct usb_descriptor_header *)&as_out_type_i_desc,
+
+ (struct usb_descriptor_header *)&ss_out_ep_desc,
+ (struct usb_descriptor_header *)&ss_out_ep_desc_comp,
+ (struct usb_descriptor_header *)&as_iso_out_desc,
+
+ (struct usb_descriptor_header *)&as_in_interface_alt_0_desc,
+ (struct usb_descriptor_header *)&as_in_interface_alt_1_desc,
+ (struct usb_descriptor_header *)&as_in_header_desc,
+
+ (struct usb_descriptor_header *)&as_in_type_i_desc,
+
+ (struct usb_descriptor_header *)&ss_in_ep_desc,
+ (struct usb_descriptor_header *)&ss_in_ep_desc_comp,
+ (struct usb_descriptor_header *)&as_iso_in_desc,
+ NULL,
+};
+
enum {
+ STR_ASSOC,
STR_AC_IF,
STR_USB_OUT_IT,
STR_USB_OUT_IT_CH_NAMES,
+ STR_IO_OUT_OT_FU,
STR_IO_OUT_OT,
STR_IO_IN_IT,
STR_IO_IN_IT_CH_NAMES,
+ STR_USB_IN_OT_FU,
STR_USB_IN_OT,
STR_AS_OUT_IF_ALT0,
STR_AS_OUT_IF_ALT1,
@@ -298,12 +414,15 @@ enum {
};
static struct usb_string strings_uac1[] = {
+ [STR_ASSOC].s = "Source/Sink",
[STR_AC_IF].s = "AC Interface",
[STR_USB_OUT_IT].s = "Playback Input terminal",
[STR_USB_OUT_IT_CH_NAMES].s = "Playback Channels",
+ [STR_IO_OUT_OT_FU].s = "Playback Feature Unit",
[STR_IO_OUT_OT].s = "Playback Output terminal",
[STR_IO_IN_IT].s = "Capture Input terminal",
[STR_IO_IN_IT_CH_NAMES].s = "Capture Channels",
+ [STR_USB_IN_OT_FU].s = "Capture Feature Unit",
[STR_USB_IN_OT].s = "Capture Output terminal",
[STR_AS_OUT_IF_ALT0].s = "Playback Inactive",
[STR_AS_OUT_IF_ALT1].s = "Playback Active",
@@ -325,24 +444,151 @@ static struct usb_gadget_strings *uac1_strings[] = {
/*
* This function is an ALSA sound card following USB Audio Class Spec 1.0.
*/
+static void intf_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct f_uac *uac1 = req->context;
+ int status = req->status;
+ u32 data = 0;
+
+ switch (status) {
+ case 0: /* normal completion? */
+ if (uac1->set_con) {
+ memcpy(&data, req->buf, req->length);
+ uac1->set_con->set(uac1->set_con, uac1->set_cmd,
+ le16_to_cpu(data));
+ uac1->set_con = NULL;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void uac_cs_attr_sample_rate(struct usb_ep *ep, struct usb_request *req)
+{
+ struct usb_function *fn = ep->driver_data;
+ struct usb_composite_dev *cdev = fn->config->cdev;
+ struct g_audio *agdev = func_to_g_audio(fn);
+ struct f_uac *uac1 = func_to_uac(fn);
+ struct f_uac_opts *opts = g_audio_to_uac_opts(agdev);
+ u8 *buf = (u8 *)req->buf;
+ u32 val = 0;
+
+ if (req->actual != 3) {
+ WARN(cdev, "Invalid data size for UAC_EP_CS_ATTR_SAMPLE_RATE.\n");
+ return;
+ }
+
+ val = buf[0] | (buf[1] << 8) | (buf[2] << 16);
+
+ if (uac1->ctl_id == agdev->in_ep->address) {
+ opts->p_srate_active = val;
+ u_audio_set_playback_srate(agdev, opts->p_srate_active);
+ } else if (uac1->ctl_id == agdev->out_ep->address) {
+ opts->c_srate_active = val;
+ u_audio_set_capture_srate(agdev, opts->c_srate_active);
+ }
+}
+
+static int audio_set_intf_req(struct usb_function *f,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct f_uac *uac1 = func_to_uac(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_request *req = cdev->req;
+ u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
+ u16 len = le16_to_cpu(ctrl->wLength);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u8 con_sel = (w_value >> 8) & 0xFF;
+ u8 cmd = (ctrl->bRequest & 0x0F);
+ struct usb_audio_control_selector *cs;
+ struct usb_audio_control *con;
+
+ DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n",
+ ctrl->bRequest, w_value, len, id);
+
+ list_for_each_entry(cs, &uac1->cs, list) {
+ if (cs->id == id) {
+ list_for_each_entry(con, &cs->control, list) {
+ if (con->type == con_sel) {
+ uac1->set_con = con;
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ uac1->set_cmd = cmd;
+ req->context = uac1;
+ req->complete = intf_complete;
+
+ return len;
+}
+
+static int audio_get_intf_req(struct usb_function *f,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct f_uac *uac1 = func_to_uac(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_request *req = cdev->req;
+ int value = -EOPNOTSUPP;
+ u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
+ u16 len = le16_to_cpu(ctrl->wLength);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u8 con_sel = (w_value >> 8) & 0xFF;
+ u8 cmd = (ctrl->bRequest & 0x0F);
+ struct usb_audio_control_selector *cs;
+ struct usb_audio_control *con;
+
+ DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n",
+ ctrl->bRequest, w_value, len, id);
+
+ list_for_each_entry(cs, &uac1->cs, list) {
+ if (cs->id == id) {
+ list_for_each_entry(con, &cs->control, list) {
+ if (con->type == con_sel && con->get) {
+ value = con->get(con, cmd);
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ req->context = uac1;
+ req->complete = intf_complete;
+ len = min_t(size_t, sizeof(value), len);
+ memcpy(req->buf, &value, len);
+
+ return len;
+}
static int audio_set_endpoint_req(struct usb_function *f,
const struct usb_ctrlrequest *ctrl)
{
struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_request *req = f->config->cdev->req;
+ struct f_uac *uac1 = func_to_uac(f);
int value = -EOPNOTSUPP;
- u16 ep = le16_to_cpu(ctrl->wIndex);
+ u8 ep = le16_to_cpu(ctrl->wIndex) & 0xff;
u16 len = le16_to_cpu(ctrl->wLength);
u16 w_value = le16_to_cpu(ctrl->wValue);
+ u8 cs = w_value >> 8;
DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
ctrl->bRequest, w_value, len, ep);
switch (ctrl->bRequest) {
- case UAC_SET_CUR:
+ case UAC_SET_CUR: {
+ if (cs == UAC_EP_CS_ATTR_SAMPLE_RATE) {
+ cdev->gadget->ep0->driver_data = f;
+ uac1->ctl_id = ep;
+ req->complete = uac_cs_attr_sample_rate;
+ }
value = len;
break;
-
+ }
case UAC_SET_MIN:
break;
@@ -366,16 +612,34 @@ static int audio_get_endpoint_req(struct usb_function *f,
const struct usb_ctrlrequest *ctrl)
{
struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_request *req = f->config->cdev->req;
+ struct g_audio *agdev = func_to_g_audio(f);
+ struct f_uac_opts *opts = g_audio_to_uac_opts(agdev);
+ u8 *buf = (u8 *)req->buf;
int value = -EOPNOTSUPP;
- u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
+ u8 ep = le16_to_cpu(ctrl->wIndex) & 0xff;
u16 len = le16_to_cpu(ctrl->wLength);
u16 w_value = le16_to_cpu(ctrl->wValue);
+ u8 cs = w_value >> 8;
+ u32 val = 0;
DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
ctrl->bRequest, w_value, len, ep);
switch (ctrl->bRequest) {
- case UAC_GET_CUR:
+ case UAC_GET_CUR: {
+ if (cs == UAC_EP_CS_ATTR_SAMPLE_RATE) {
+ if (ep == agdev->in_ep->address)
+ val = opts->p_srate_active;
+ else if (ep == agdev->out_ep->address)
+ val = opts->c_srate_active;
+ buf[2] = (val >> 16) & 0xff;
+ buf[1] = (val >> 8) & 0xff;
+ buf[0] = val & 0xff;
+ }
+ value = len;
+ break;
+ }
case UAC_GET_MIN:
case UAC_GET_MAX:
case UAC_GET_RES:
@@ -404,6 +668,14 @@ f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
* activation uses set_alt().
*/
switch (ctrl->bRequestType) {
+ case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE:
+ value = audio_set_intf_req(f, ctrl);
+ break;
+
+ case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE:
+ value = audio_get_intf_req(f, ctrl);
+ break;
+
case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
value = audio_set_endpoint_req(f, ctrl);
break;
@@ -439,7 +711,7 @@ static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
struct usb_composite_dev *cdev = f->config->cdev;
struct usb_gadget *gadget = cdev->gadget;
struct device *dev = &gadget->dev;
- struct f_uac1 *uac1 = func_to_uac1(f);
+ struct f_uac *uac1 = func_to_uac(f);
int ret = 0;
/* No i/f has more than 2 alt settings */
@@ -484,7 +756,7 @@ static int f_audio_get_alt(struct usb_function *f, unsigned intf)
struct usb_composite_dev *cdev = f->config->cdev;
struct usb_gadget *gadget = cdev->gadget;
struct device *dev = &gadget->dev;
- struct f_uac1 *uac1 = func_to_uac1(f);
+ struct f_uac *uac1 = func_to_uac(f);
if (intf == uac1->ac_intf)
return uac1->ac_alt;
@@ -502,7 +774,7 @@ static int f_audio_get_alt(struct usb_function *f, unsigned intf)
static void f_audio_disable(struct usb_function *f)
{
- struct f_uac1 *uac1 = func_to_uac1(f);
+ struct f_uac *uac1 = func_to_uac(f);
uac1->as_out_alt = 0;
uac1->as_in_alt = 0;
@@ -512,10 +784,195 @@ static void f_audio_disable(struct usb_function *f)
}
/*-------------------------------------------------------------------------*/
+#define USBDHDR(p) (struct usb_descriptor_header *)(p)
+
+static void setup_headers(struct f_uac_opts *opts,
+ struct usb_descriptor_header **headers,
+ enum usb_device_speed speed)
+{
+ struct usb_ss_ep_comp_descriptor *epout_desc_comp = NULL;
+ struct usb_ss_ep_comp_descriptor *epin_desc_comp = NULL;
+ struct usb_endpoint_descriptor *epout_desc;
+ struct usb_endpoint_descriptor *epin_desc;
+ int i;
+
+ switch (speed) {
+ case USB_SPEED_FULL:
+ /* fall through */
+ case USB_SPEED_HIGH:
+ epout_desc = &as_out_ep_desc;
+ epin_desc = &as_in_ep_desc;
+ break;
+ default:
+ epout_desc = &ss_out_ep_desc;
+ epin_desc = &ss_in_ep_desc;
+ epout_desc_comp = &ss_out_ep_desc_comp;
+ epin_desc_comp = &ss_in_ep_desc_comp;
+ }
+
+ i = 0;
+ headers[i++] = USBDHDR(&iad_desc);
+ headers[i++] = USBDHDR(&ac_interface_desc);
+ headers[i++] = USBDHDR(&ac_header_desc);
+
+ if (EPOUT_EN(opts)) {
+ headers[i++] = USBDHDR(&usb_out_it_desc);
+ if (EPOUT_FU(opts))
+ headers[i++] = USBDHDR(&io_out_ot_fu_desc);
+ headers[i++] = USBDHDR(&io_out_ot_desc);
+ }
+
+ if (EPIN_EN(opts)) {
+ headers[i++] = USBDHDR(&io_in_it_desc);
+ if (EPIN_FU(opts))
+ headers[i++] = USBDHDR(&usb_in_ot_fu_desc);
+ headers[i++] = USBDHDR(&usb_in_ot_desc);
+ }
+
+ if (EPOUT_EN(opts)) {
+ headers[i++] = USBDHDR(&as_out_interface_alt_0_desc);
+ headers[i++] = USBDHDR(&as_out_interface_alt_1_desc);
+ headers[i++] = USBDHDR(&as_out_header_desc);
+ headers[i++] = USBDHDR(&as_out_type_i_desc);
+ headers[i++] = USBDHDR(epout_desc);
+ if (epout_desc_comp)
+ headers[i++] = USBDHDR(epout_desc_comp);
+
+ headers[i++] = USBDHDR(&as_iso_out_desc);
+ }
+
+ if (EPIN_EN(opts)) {
+ headers[i++] = USBDHDR(&as_in_interface_alt_0_desc);
+ headers[i++] = USBDHDR(&as_in_interface_alt_1_desc);
+ headers[i++] = USBDHDR(&as_in_header_desc);
+ headers[i++] = USBDHDR(&as_in_type_i_desc);
+ headers[i++] = USBDHDR(epin_desc);
+ if (epin_desc_comp)
+ headers[i++] = USBDHDR(epin_desc_comp);
+
+ headers[i++] = USBDHDR(&as_iso_in_desc);
+ }
+ headers[i] = NULL;
+}
+
+static void setup_descriptor(struct f_uac_opts *opts)
+{
+ int i = 1;
+ u16 len = 0;
+
+ if (EPOUT_EN(opts))
+ usb_out_it_desc.bTerminalID = i++;
+ if (EPIN_EN(opts))
+ io_in_it_desc.bTerminalID = i++;
+ if (EPOUT_EN(opts) && EPOUT_FU(opts))
+ io_out_ot_fu_desc.bUnitID = i++;
+ if (EPIN_EN(opts) && EPIN_FU(opts))
+ usb_in_ot_fu_desc.bUnitID = i++;
+ if (EPOUT_EN(opts))
+ io_out_ot_desc.bTerminalID = i++;
+ if (EPIN_EN(opts))
+ usb_in_ot_desc.bTerminalID = i++;
+
+ if (EPIN_FU(opts)) {
+ usb_in_ot_desc.bSourceID = usb_in_ot_fu_desc.bUnitID;
+ usb_in_ot_fu_desc.bSourceID = io_in_it_desc.bTerminalID;
+ p_feature_unit.id = usb_in_ot_fu_desc.bUnitID;
+ } else {
+ usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID;
+ }
+ if (EPOUT_FU(opts)) {
+ io_out_ot_desc.bSourceID = io_out_ot_fu_desc.bUnitID;
+ io_out_ot_fu_desc.bSourceID = usb_out_it_desc.bTerminalID;
+ c_feature_unit.id = io_out_ot_fu_desc.bUnitID;
+ } else {
+ io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID;
+ }
+ as_out_header_desc.bTerminalLink = usb_out_it_desc.bTerminalID;
+ as_in_header_desc.bTerminalLink = usb_in_ot_desc.bTerminalID;
+
+ iad_desc.bInterfaceCount = 1;
+ ac_header_desc.bInCollection = 0;
+
+ if (EPIN_EN(opts)) {
+ len += UAC_DT_INPUT_TERMINAL_SIZE + UAC_DT_OUTPUT_TERMINAL_SIZE;
+ if (EPIN_FU(opts))
+ len += UAC_DT_FEATURE_UNIT_SIZE(0);
+ iad_desc.bInterfaceCount++;
+ ac_header_desc.bInCollection++;
+ }
+
+ if (EPOUT_EN(opts)) {
+ len += UAC_DT_INPUT_TERMINAL_SIZE + UAC_DT_OUTPUT_TERMINAL_SIZE;
+ if (EPOUT_FU(opts))
+ len += UAC_DT_FEATURE_UNIT_SIZE(0);
+ iad_desc.bInterfaceCount++;
+ ac_header_desc.bInCollection++;
+ }
+ ac_header_desc.bLength =
+ UAC_DT_AC_HEADER_SIZE(ac_header_desc.bInCollection);
+ ac_header_desc.wTotalLength = cpu_to_le16(len + ac_header_desc.bLength);
+
+ setup_headers(opts, f_audio_desc, USB_SPEED_HIGH);
+ setup_headers(opts, f_ss_audio_desc, USB_SPEED_SUPER);
+}
+
+static int set_ep_max_packet_size(const struct f_uac_opts *opts,
+ struct usb_endpoint_descriptor *ep_desc,
+ enum usb_device_speed speed, bool is_playback)
+{
+ int chmask, srate = 0, ssize;
+ u16 max_size_bw, max_size_ep;
+ unsigned int factor;
+ int i;
+
+ switch (speed) {
+ case USB_SPEED_FULL:
+ max_size_ep = 1023;
+ factor = 1000;
+ break;
+
+ case USB_SPEED_HIGH:
+ /* fall through */
+ case USB_SPEED_SUPER:
+ max_size_ep = 1024;
+ factor = 8000;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (is_playback) {
+ chmask = opts->p_chmask;
+ for (i = 0; i < UAC_MAX_RATES; i++) {
+ if (opts->p_srate[i] == 0)
+ break;
+ if (opts->p_srate[i] > srate)
+ srate = opts->p_srate[i];
+ }
+ ssize = opts->p_ssize;
+ } else {
+ chmask = opts->c_chmask;
+ for (i = 0; i < UAC_MAX_RATES; i++) {
+ if (opts->c_srate[i] == 0)
+ break;
+ if (opts->c_srate[i] > srate)
+ srate = opts->c_srate[i];
+ }
+ ssize = opts->c_ssize;
+ }
+
+ max_size_bw = num_channels(chmask) * ssize *
+ ((srate / (factor / (1 << (ep_desc->bInterval - 1)))) + 1);
+ ep_desc->wMaxPacketSize = cpu_to_le16(min_t(u16, max_size_bw,
+ max_size_ep));
+
+ return 0;
+}
static int f_audio_validate_opts(struct g_audio *audio, struct device *dev)
{
- struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio);
+ struct f_uac_opts *opts = g_audio_to_uac_opts(audio);
if (!opts->p_chmask && !opts->c_chmask) {
dev_err(dev, "Error: no playback and capture channels\n");
@@ -548,33 +1005,36 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
{
struct usb_composite_dev *cdev = c->cdev;
struct usb_gadget *gadget = cdev->gadget;
- struct device *dev = &gadget->dev;
- struct f_uac1 *uac1 = func_to_uac1(f);
+ struct f_uac *uac1 = func_to_uac(f);
struct g_audio *audio = func_to_g_audio(f);
- struct f_uac1_opts *audio_opts;
+ struct f_uac_opts *audio_opts;
struct usb_ep *ep = NULL;
struct usb_string *us;
- u8 *sam_freq;
- int rate;
+ struct device *dev = &gadget->dev;
int status;
+ int idx, i;
status = f_audio_validate_opts(audio, dev);
if (status)
return status;
- audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst);
+ audio_opts = container_of(f->fi, struct f_uac_opts, func_inst);
us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1));
if (IS_ERR(us))
return PTR_ERR(us);
+
+ iad_desc.iFunction = us[STR_ASSOC].id;
ac_interface_desc.iInterface = us[STR_AC_IF].id;
usb_out_it_desc.iTerminal = us[STR_USB_OUT_IT].id;
usb_out_it_desc.iChannelNames = us[STR_USB_OUT_IT_CH_NAMES].id;
+ io_out_ot_fu_desc.iFeature = us[STR_IO_OUT_OT_FU].id;
io_out_ot_desc.iTerminal = us[STR_IO_OUT_OT].id;
as_out_interface_alt_0_desc.iInterface = us[STR_AS_OUT_IF_ALT0].id;
as_out_interface_alt_1_desc.iInterface = us[STR_AS_OUT_IF_ALT1].id;
io_in_it_desc.iTerminal = us[STR_IO_IN_IT].id;
io_in_it_desc.iChannelNames = us[STR_IO_IN_IT_CH_NAMES].id;
+ usb_in_ot_fu_desc.iFeature = us[STR_USB_IN_OT_FU].id;
usb_in_ot_desc.iTerminal = us[STR_USB_IN_OT].id;
as_in_interface_alt_0_desc.iInterface = us[STR_AS_IN_IF_ALT0].id;
as_in_interface_alt_1_desc.iInterface = us[STR_AS_IN_IF_ALT1].id;
@@ -592,67 +1052,141 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
as_in_type_i_desc.bBitResolution = audio_opts->p_ssize * 8;
/* Set sample rates */
- rate = audio_opts->c_srate;
- sam_freq = as_out_type_i_desc.tSamFreq[0];
- memcpy(sam_freq, &rate, 3);
- rate = audio_opts->p_srate;
- sam_freq = as_in_type_i_desc.tSamFreq[0];
- memcpy(sam_freq, &rate, 3);
+ for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) {
+ if (audio_opts->c_srate[i] == 0)
+ break;
+ memcpy(as_out_type_i_desc.tSamFreq[idx++],
+ &audio_opts->c_srate[i], 3);
+ }
+
+ /*
+ * Calculate wMaxPacketSize according to audio bandwidth.
+ * Set the max packet with USB_SPEED_HIGH by default to
+ * be compatible with larger bandwidth requirements for
+ * high speed mode.
+ */
+ status = set_ep_max_packet_size(audio_opts, &as_out_ep_desc,
+ USB_SPEED_HIGH, false);
+ if (status < 0) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ goto fail;
+ }
+
+ status = set_ep_max_packet_size(audio_opts, &as_in_ep_desc,
+ USB_SPEED_HIGH, true);
+ if (status < 0) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ goto fail;
+ }
+
+ status = set_ep_max_packet_size(audio_opts, &ss_out_ep_desc,
+ USB_SPEED_SUPER, false);
+ if (status < 0) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ goto fail;
+ }
+
+ ss_out_ep_desc_comp.wBytesPerInterval = ss_out_ep_desc.wMaxPacketSize;
+
+ status = set_ep_max_packet_size(audio_opts, &ss_in_ep_desc,
+ USB_SPEED_SUPER, true);
+ if (status < 0) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ goto fail;
+ }
+
+ ss_in_ep_desc_comp.wBytesPerInterval = ss_in_ep_desc.wMaxPacketSize;
+
+ as_out_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx);
+ as_out_type_i_desc.bSamFreqType = idx;
+
+ for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) {
+ if (audio_opts->p_srate[i] == 0)
+ break;
+ memcpy(as_in_type_i_desc.tSamFreq[idx++],
+ &audio_opts->p_srate[i], 3);
+ }
+ as_in_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx);
+ as_in_type_i_desc.bSamFreqType = idx;
/* allocate instance-specific interface IDs, and patch descriptors */
status = usb_interface_id(c, f);
if (status < 0)
goto fail;
+ iad_desc.bFirstInterface = status;
ac_interface_desc.bInterfaceNumber = status;
uac1->ac_intf = status;
uac1->ac_alt = 0;
+ ac_header_desc.baInterfaceNr[0] = ++status;
+ ac_header_desc.baInterfaceNr[1] = ++status;
+
+ if (EPOUT_EN(audio_opts)) {
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto fail;
+ as_out_interface_alt_0_desc.bInterfaceNumber = status;
+ as_out_interface_alt_1_desc.bInterfaceNumber = status;
+ uac1->as_out_intf = status;
+ uac1->as_out_alt = 0;
+ }
- status = usb_interface_id(c, f);
- if (status < 0)
- goto fail;
- as_out_interface_alt_0_desc.bInterfaceNumber = status;
- as_out_interface_alt_1_desc.bInterfaceNumber = status;
- uac1->as_out_intf = status;
- uac1->as_out_alt = 0;
-
- status = usb_interface_id(c, f);
- if (status < 0)
- goto fail;
- as_in_interface_alt_0_desc.bInterfaceNumber = status;
- as_in_interface_alt_1_desc.bInterfaceNumber = status;
- uac1->as_in_intf = status;
- uac1->as_in_alt = 0;
+ if (EPIN_EN(audio_opts)) {
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto fail;
+ as_in_interface_alt_0_desc.bInterfaceNumber = status;
+ as_in_interface_alt_1_desc.bInterfaceNumber = status;
+ uac1->as_in_intf = status;
+ uac1->as_in_alt = 0;
+ }
audio->gadget = gadget;
status = -ENODEV;
/* allocate instance-specific endpoints */
- ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc);
- if (!ep)
- goto fail;
- audio->out_ep = ep;
- audio->out_ep->desc = &as_out_ep_desc;
+ if (EPOUT_EN(audio_opts)) {
+ ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc);
+ if (!ep)
+ goto fail;
+ audio->out_ep = ep;
+ audio->out_ep->desc = &as_out_ep_desc;
+ }
- ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc);
- if (!ep)
- goto fail;
- audio->in_ep = ep;
- audio->in_ep->desc = &as_in_ep_desc;
+ if (EPIN_EN(audio_opts)) {
+ ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc);
+ if (!ep)
+ goto fail;
+ ep->maxpacket = usb_endpoint_maxp(&as_in_ep_desc);
+ audio->in_ep = ep;
+ audio->in_ep->desc = &as_in_ep_desc;
+ }
+
+ ss_out_ep_desc.bEndpointAddress = as_out_ep_desc.bEndpointAddress;
+ ss_in_ep_desc.bEndpointAddress = as_in_ep_desc.bEndpointAddress;
+ setup_descriptor(audio_opts);
/* copy descriptors, and track endpoint copies */
- status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL,
- NULL);
+ status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc,
+ f_ss_audio_desc, f_ss_audio_desc);
if (status)
goto fail;
audio->out_ep_maxpsize = le16_to_cpu(as_out_ep_desc.wMaxPacketSize);
audio->in_ep_maxpsize = le16_to_cpu(as_in_ep_desc.wMaxPacketSize);
+ audio->out_ep_maxpsize = max_t(u16, audio->out_ep_maxpsize,
+ le16_to_cpu(ss_out_ep_desc.wMaxPacketSize));
+ audio->in_ep_maxpsize = max_t(u16, audio->in_ep_maxpsize,
+ le16_to_cpu(ss_in_ep_desc.wMaxPacketSize));
audio->params.c_chmask = audio_opts->c_chmask;
- audio->params.c_srate = audio_opts->c_srate;
+ memcpy(audio->params.c_srate, audio_opts->c_srate,
+ sizeof(audio->params.c_srate));
+ audio->params.c_srate_active = audio_opts->c_srate_active;
audio->params.c_ssize = audio_opts->c_ssize;
audio->params.p_chmask = audio_opts->p_chmask;
- audio->params.p_srate = audio_opts->p_srate;
+ memcpy(audio->params.p_srate, audio_opts->p_srate,
+ sizeof(audio->params.p_srate));
+ audio->params.p_srate_active = audio_opts->p_srate_active;
audio->params.p_ssize = audio_opts->p_ssize;
audio->params.req_number = audio_opts->req_number;
@@ -670,82 +1204,69 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
/*-------------------------------------------------------------------------*/
-static inline struct f_uac1_opts *to_f_uac1_opts(struct config_item *item)
+/* Todo: add more control selecotor dynamically */
+static int control_selector_init(struct f_uac *uac1)
{
- return container_of(to_config_group(item), struct f_uac1_opts,
- func_inst.group);
-}
+ INIT_LIST_HEAD(&uac1->cs);
-static void f_uac1_attr_release(struct config_item *item)
-{
- struct f_uac1_opts *opts = to_f_uac1_opts(item);
+ /* playback feature unit */
+ list_add(&p_feature_unit.list, &uac1->cs);
+
+ INIT_LIST_HEAD(&p_feature_unit.control);
+ list_add(&p_mute_control.list, &p_feature_unit.control);
+ list_add(&p_volume_control.list, &p_feature_unit.control);
+
+ p_volume_control.data[UAC__CUR] = UAC_VOLUME_CUR;
+ p_volume_control.data[UAC__MIN] = UAC_VOLUME_MIN;
+ p_volume_control.data[UAC__MAX] = UAC_VOLUME_MAX;
+ p_volume_control.data[UAC__RES] = UAC_VOLUME_RES;
+
+ p_volume_control.context = &uac1->g_audio;
+ p_mute_control.context = &uac1->g_audio;
+
+ /* capture feature unit */
+ list_add(&c_feature_unit.list, &uac1->cs);
+
+ INIT_LIST_HEAD(&c_feature_unit.control);
+ list_add(&c_mute_control.list, &c_feature_unit.control);
+ list_add(&c_volume_control.list, &c_feature_unit.control);
+
+ c_volume_control.data[UAC__CUR] = UAC_VOLUME_CUR;
+ c_volume_control.data[UAC__MIN] = UAC_VOLUME_MIN;
+ c_volume_control.data[UAC__MAX] = UAC_VOLUME_MAX;
+ c_volume_control.data[UAC__RES] = UAC_VOLUME_RES;
- usb_put_function_instance(&opts->func_inst);
+ c_volume_control.context = &uac1->g_audio;
+ c_mute_control.context = &uac1->g_audio;
+
+ return 0;
}
static struct configfs_item_operations f_uac1_item_ops = {
- .release = f_uac1_attr_release,
-};
-
-#define UAC1_ATTRIBUTE(name) \
-static ssize_t f_uac1_opts_##name##_show( \
- struct config_item *item, \
- char *page) \
-{ \
- struct f_uac1_opts *opts = to_f_uac1_opts(item); \
- int result; \
- \
- mutex_lock(&opts->lock); \
- result = sprintf(page, "%u\n", opts->name); \
- mutex_unlock(&opts->lock); \
- \
- return result; \
-} \
- \
-static ssize_t f_uac1_opts_##name##_store( \
- struct config_item *item, \
- const char *page, size_t len) \
-{ \
- struct f_uac1_opts *opts = to_f_uac1_opts(item); \
- int ret; \
- u32 num; \
- \
- mutex_lock(&opts->lock); \
- if (opts->refcnt) { \
- ret = -EBUSY; \
- goto end; \
- } \
- \
- ret = kstrtou32(page, 0, &num); \
- if (ret) \
- goto end; \
- \
- opts->name = num; \
- ret = len; \
- \
-end: \
- mutex_unlock(&opts->lock); \
- return ret; \
-} \
- \
-CONFIGFS_ATTR(f_uac1_opts_, name)
-
-UAC1_ATTRIBUTE(c_chmask);
-UAC1_ATTRIBUTE(c_srate);
-UAC1_ATTRIBUTE(c_ssize);
-UAC1_ATTRIBUTE(p_chmask);
-UAC1_ATTRIBUTE(p_srate);
-UAC1_ATTRIBUTE(p_ssize);
-UAC1_ATTRIBUTE(req_number);
+ .release = f_uac_attr_release,
+};
+
+UAC_ATTRIBUTE(c_chmask);
+UAC_ATTRIBUTE(c_ssize);
+UAC_ATTRIBUTE(c_feature_unit);
+UAC_ATTRIBUTE(p_chmask);
+UAC_ATTRIBUTE(p_ssize);
+UAC_ATTRIBUTE(p_feature_unit);
+UAC_ATTRIBUTE(req_number);
+
+UAC_RATE_ATTRIBUTE(p_srate);
+UAC_RATE_ATTRIBUTE(c_srate);
static struct configfs_attribute *f_uac1_attrs[] = {
- &f_uac1_opts_attr_c_chmask,
- &f_uac1_opts_attr_c_srate,
- &f_uac1_opts_attr_c_ssize,
- &f_uac1_opts_attr_p_chmask,
- &f_uac1_opts_attr_p_srate,
- &f_uac1_opts_attr_p_ssize,
- &f_uac1_opts_attr_req_number,
+ &f_uac_opts_attr_c_chmask,
+ &f_uac_opts_attr_c_srate,
+ &f_uac_opts_attr_c_ssize,
+ &f_uac_opts_attr_c_feature_unit,
+ &f_uac_opts_attr_p_chmask,
+ &f_uac_opts_attr_p_srate,
+ &f_uac_opts_attr_p_ssize,
+ &f_uac_opts_attr_p_feature_unit,
+ &f_uac_opts_attr_req_number,
NULL,
};
@@ -757,15 +1278,15 @@ static const struct config_item_type f_uac1_func_type = {
static void f_audio_free_inst(struct usb_function_instance *f)
{
- struct f_uac1_opts *opts;
+ struct f_uac_opts *opts;
- opts = container_of(f, struct f_uac1_opts, func_inst);
+ opts = container_of(f, struct f_uac_opts, func_inst);
kfree(opts);
}
static struct usb_function_instance *f_audio_alloc_inst(void)
{
- struct f_uac1_opts *opts;
+ struct f_uac_opts *opts;
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
if (!opts)
@@ -777,23 +1298,27 @@ static struct usb_function_instance *f_audio_alloc_inst(void)
config_group_init_type_name(&opts->func_inst.group, "",
&f_uac1_func_type);
- opts->c_chmask = UAC1_DEF_CCHMASK;
- opts->c_srate = UAC1_DEF_CSRATE;
- opts->c_ssize = UAC1_DEF_CSSIZE;
- opts->p_chmask = UAC1_DEF_PCHMASK;
- opts->p_srate = UAC1_DEF_PSRATE;
- opts->p_ssize = UAC1_DEF_PSSIZE;
- opts->req_number = UAC1_DEF_REQ_NUM;
+ opts->c_chmask = UAC_DEF_CCHMASK;
+ opts->c_srate[0] = UAC_DEF_CSRATE;
+ opts->c_srate_active = UAC_DEF_CSRATE;
+ opts->c_ssize = UAC_DEF_CSSIZE;
+ opts->c_feature_unit = UAC_DEF_CFU;
+ opts->p_chmask = UAC_DEF_PCHMASK;
+ opts->p_srate[0] = UAC_DEF_PSRATE;
+ opts->p_srate_active = UAC_DEF_PSRATE;
+ opts->p_ssize = UAC_DEF_PSSIZE;
+ opts->p_feature_unit = UAC_DEF_PFU;
+ opts->req_number = UAC_DEF_REQ_NUM;
return &opts->func_inst;
}
static void f_audio_free(struct usb_function *f)
{
struct g_audio *audio;
- struct f_uac1_opts *opts;
+ struct f_uac_opts *opts;
audio = func_to_g_audio(f);
- opts = container_of(f->fi, struct f_uac1_opts, func_inst);
+ opts = container_of(f->fi, struct f_uac_opts, func_inst);
kfree(audio);
mutex_lock(&opts->lock);
--opts->refcnt;
@@ -812,15 +1337,15 @@ static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f)
static struct usb_function *f_audio_alloc(struct usb_function_instance *fi)
{
- struct f_uac1 *uac1;
- struct f_uac1_opts *opts;
+ struct f_uac *uac1;
+ struct f_uac_opts *opts;
/* allocate and initialize one new instance */
uac1 = kzalloc(sizeof(*uac1), GFP_KERNEL);
if (!uac1)
return ERR_PTR(-ENOMEM);
- opts = container_of(fi, struct f_uac1_opts, func_inst);
+ opts = container_of(fi, struct f_uac_opts, func_inst);
mutex_lock(&opts->lock);
++opts->refcnt;
mutex_unlock(&opts->lock);
@@ -834,6 +1359,8 @@ static struct usb_function *f_audio_alloc(struct usb_function_instance *fi)
uac1->g_audio.func.disable = f_audio_disable;
uac1->g_audio.func.free_func = f_audio_free;
+ control_selector_init(uac1);
+
return &uac1->g_audio.func;
}
diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c
index 56c724cfc..64b35a493 100644
--- a/drivers/usb/gadget/function/f_uac1_legacy.c
+++ b/drivers/usb/gadget/function/f_uac1_legacy.c
@@ -670,6 +670,11 @@ static int f_audio_get_alt(struct usb_function *f, unsigned intf)
static void f_audio_disable(struct usb_function *f)
{
+ struct f_audio *audio = func_to_audio(f);
+ struct usb_ep *out_ep = audio->out_ep;
+
+ usb_ep_disable(out_ep);
+
return;
}
@@ -970,6 +975,7 @@ static void f_audio_free(struct usb_function *f)
gaudio_cleanup(&audio->card);
opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst);
+ opts->bound = false;
kfree(audio);
mutex_lock(&opts->lock);
--opts->refcnt;
diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index 8e563f56f..34290e5e6 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -5,6 +5,7 @@
* Copyright (C) 2011
* Yadwinder Singh (yadi.brar01@gmail.com)
* Jaswinder Singh (jaswinder.singh@linaro.org)
+ * Copyright (C) 2017 Julian Scheel <julian@jusst.de>
*/
#include <linux/usb/audio.h>
@@ -12,7 +13,7 @@
#include <linux/module.h>
#include "u_audio.h"
-#include "u_uac2.h"
+#include "u_uac.h"
/*
* The driver implements a simple UAC_2 topology.
@@ -22,12 +23,8 @@
* controlled by two clock sources :
* CLK_5 := c_srate, and CLK_6 := p_srate
*/
-#define USB_OUT_IT_ID 1
-#define IO_IN_IT_ID 2
-#define IO_OUT_OT_ID 3
-#define USB_IN_OT_ID 4
-#define USB_OUT_CLK_ID 5
-#define USB_IN_CLK_ID 6
+#define USB_OUT_CLK_ID (out_clk_src_desc.bClockID)
+#define USB_IN_CLK_ID (in_clk_src_desc.bClockID)
#define CONTROL_ABSENT 0
#define CONTROL_RDONLY 1
@@ -43,23 +40,6 @@
#define UNFLW_CTRL 8
#define OVFLW_CTRL 10
-struct f_uac2 {
- struct g_audio g_audio;
- u8 ac_intf, as_in_intf, as_out_intf;
- u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */
-};
-
-static inline struct f_uac2 *func_to_uac2(struct usb_function *f)
-{
- return container_of(f, struct f_uac2, g_audio.func);
-}
-
-static inline
-struct f_uac2_opts *g_audio_to_uac2_opts(struct g_audio *agdev)
-{
- return container_of(agdev->func.fi, struct f_uac2_opts, func_inst);
-}
-
/* --------- USB Function Interface ------------- */
enum {
@@ -69,6 +49,8 @@ enum {
STR_CLKSRC_OUT,
STR_USB_IT,
STR_IO_IT,
+ STR_USB_OT_FU,
+ STR_IO_OT_FU,
STR_USB_OT,
STR_IO_OT,
STR_AS_OUT_ALT0,
@@ -77,16 +59,15 @@ enum {
STR_AS_IN_ALT1,
};
-static char clksrc_in[8];
-static char clksrc_out[8];
-
static struct usb_string strings_fn[] = {
[STR_ASSOC].s = "Source/Sink",
[STR_IF_CTRL].s = "Topology Control",
- [STR_CLKSRC_IN].s = clksrc_in,
- [STR_CLKSRC_OUT].s = clksrc_out,
+ [STR_CLKSRC_IN].s = "Input clock",
+ [STR_CLKSRC_OUT].s = "Output clock",
[STR_USB_IT].s = "USBH Out",
[STR_IO_IT].s = "USBD Out",
+ [STR_USB_OT_FU].s = "USBH In Feature Unit",
+ [STR_IO_OT_FU].s = "USBD In Feature Unit",
[STR_USB_OT].s = "USBH In",
[STR_IO_OT].s = "USBD In",
[STR_AS_OUT_ALT0].s = "Playback Inactive",
@@ -135,9 +116,9 @@ static struct uac_clock_source_descriptor in_clk_src_desc = {
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
- .bClockID = USB_IN_CLK_ID,
+ /* .bClockID = DYNAMIC */
.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
- .bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
+ .bmControls = (CONTROL_RDWR << CLK_FREQ_CTRL),
.bAssocTerminal = 0,
};
@@ -147,9 +128,9 @@ static struct uac_clock_source_descriptor out_clk_src_desc = {
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
- .bClockID = USB_OUT_CLK_ID,
+ /* .bClockID = DYNAMIC */
.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
- .bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
+ .bmControls = (CONTROL_RDWR << CLK_FREQ_CTRL),
.bAssocTerminal = 0,
};
@@ -159,10 +140,10 @@ static struct uac2_input_terminal_descriptor usb_out_it_desc = {
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_INPUT_TERMINAL,
- .bTerminalID = USB_OUT_IT_ID,
+ /* .bTerminalID = DYNAMIC */
.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
.bAssocTerminal = 0,
- .bCSourceID = USB_OUT_CLK_ID,
+ /* .bCSourceID = DYNAMIC */
.iChannelNames = 0,
.bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL),
};
@@ -173,25 +154,100 @@ static struct uac2_input_terminal_descriptor io_in_it_desc = {
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_INPUT_TERMINAL,
- .bTerminalID = IO_IN_IT_ID,
- .wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_UNDEFINED),
+ /* .bTerminalID = DYNAMIC */
+ .wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_MICROPHONE),
.bAssocTerminal = 0,
- .bCSourceID = USB_IN_CLK_ID,
+ /* .bCSourceID = DYNAMIC */
.iChannelNames = 0,
.bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL),
};
+DECLARE_UAC2_FEATURE_UNIT_DESCRIPTOR(0);
+
+/* Feature Unit for I/O-out */
+static struct uac2_feature_unit_descriptor_0 io_out_ot_fu_desc = {
+
+ .bLength = UAC2_DT_FEATURE_UNIT_SIZE(0),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+
+ .bDescriptorSubtype = UAC_FEATURE_UNIT,
+ /* .bUnitID = DYNAMIC */
+ /* .bSourceID = DYNAMIC */
+ .bmaControls[0] = (UAC2_CONTROL_BIT_RW(UAC_FU_MUTE) |
+ UAC2_CONTROL_BIT_RW(UAC_FU_VOLUME)),
+};
+
+static struct usb_audio_control c_mute_control = {
+ .list = LIST_HEAD_INIT(c_mute_control.list),
+ .name = "Capture Mute",
+ .type = UAC_FU_MUTE,
+ .set = u_audio_fu_set_cmd,
+ .get = u_audio_fu_get_cmd,
+};
+
+static struct usb_audio_control c_volume_control = {
+ .list = LIST_HEAD_INIT(c_volume_control.list),
+ .name = "Capture Volume",
+ .type = UAC_FU_VOLUME,
+ .set = u_audio_fu_set_cmd,
+ .get = u_audio_fu_get_cmd,
+};
+
+static struct usb_audio_control_selector c_feature_unit = {
+ .list = LIST_HEAD_INIT(c_feature_unit.list),
+ /* .id = DYNAMIC */
+ .name = "Capture Mute & Volume Control",
+ .type = UAC_FEATURE_UNIT,
+ .desc = (struct usb_descriptor_header *)&io_out_ot_fu_desc,
+};
+
+/* Feature Unit for USB_IN */
+static struct uac2_feature_unit_descriptor_0 usb_in_ot_fu_desc = {
+ .bLength = UAC2_DT_FEATURE_UNIT_SIZE(0),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+
+ .bDescriptorSubtype = UAC_FEATURE_UNIT,
+ /* .bUnitID = DYNAMIC */
+ /* .bSourceID = DYNAMIC */
+ .bmaControls[0] = (UAC2_CONTROL_BIT_RW(UAC_FU_MUTE) |
+ UAC2_CONTROL_BIT_RW(UAC_FU_VOLUME)),
+};
+
+static struct usb_audio_control p_mute_control = {
+ .list = LIST_HEAD_INIT(p_mute_control.list),
+ .name = "Playback Mute",
+ .type = UAC_FU_MUTE,
+ .set = u_audio_fu_set_cmd,
+ .get = u_audio_fu_get_cmd,
+};
+
+static struct usb_audio_control p_volume_control = {
+ .list = LIST_HEAD_INIT(p_volume_control.list),
+ .name = "Playback Volume",
+ .type = UAC_FU_VOLUME,
+ .set = u_audio_fu_set_cmd,
+ .get = u_audio_fu_get_cmd,
+};
+
+static struct usb_audio_control_selector p_feature_unit = {
+ .list = LIST_HEAD_INIT(p_feature_unit.list),
+ /* .id = DYNAMIC */
+ .name = "Playback Mute & Volume Control",
+ .type = UAC_FEATURE_UNIT,
+ .desc = (struct usb_descriptor_header *)&usb_in_ot_fu_desc,
+};
+
/* Ouput Terminal for USB_IN */
static struct uac2_output_terminal_descriptor usb_in_ot_desc = {
.bLength = sizeof usb_in_ot_desc,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
- .bTerminalID = USB_IN_OT_ID,
+ /* .bTerminalID = DYNAMIC */
.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
.bAssocTerminal = 0,
- .bSourceID = IO_IN_IT_ID,
- .bCSourceID = USB_IN_CLK_ID,
+ /* .bSourceID = DYNAMIC */
+ /* .bCSourceID = DYNAMIC */
.bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL),
};
@@ -201,11 +257,11 @@ static struct uac2_output_terminal_descriptor io_out_ot_desc = {
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
- .bTerminalID = IO_OUT_OT_ID,
- .wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_UNDEFINED),
+ /* .bTerminalID = DYNAMIC */
+ .wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_SPEAKER),
.bAssocTerminal = 0,
- .bSourceID = USB_OUT_IT_ID,
- .bCSourceID = USB_OUT_CLK_ID,
+ /* .bSourceID = DYNAMIC */
+ /* .bCSourceID = DYNAMIC */
.bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL),
};
@@ -213,13 +269,14 @@ static struct uac2_ac_header_descriptor ac_hdr_desc = {
.bLength = sizeof ac_hdr_desc,
.bDescriptorType = USB_DT_CS_INTERFACE,
- .bDescriptorSubtype = UAC_MS_HEADER,
+ .bDescriptorSubtype = UAC_HEADER,
.bcdADC = cpu_to_le16(0x200),
.bCategory = UAC2_FUNCTION_IO_BOX,
- .wTotalLength = cpu_to_le16(sizeof in_clk_src_desc
+ .wTotalLength = cpu_to_le16(sizeof ac_hdr_desc + sizeof in_clk_src_desc
+ sizeof out_clk_src_desc + sizeof usb_out_it_desc
+ sizeof io_in_it_desc + sizeof usb_in_ot_desc
- + sizeof io_out_ot_desc),
+ + sizeof io_out_ot_desc + sizeof usb_in_ot_fu_desc
+ + sizeof io_out_ot_fu_desc),
.bmControls = 0,
};
@@ -253,7 +310,7 @@ static struct uac2_as_header_descriptor as_out_hdr_desc = {
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_AS_GENERAL,
- .bTerminalLink = USB_OUT_IT_ID,
+ /* .bTerminalLink = DYNAMIC */
.bmControls = 0,
.bFormatType = UAC_FORMAT_TYPE_I,
.bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM),
@@ -274,7 +331,7 @@ static struct usb_endpoint_descriptor fs_epout_desc = {
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_OUT,
- .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE,
/* .wMaxPacketSize = DYNAMIC */
.bInterval = 1,
};
@@ -283,11 +340,29 @@ static struct usb_endpoint_descriptor hs_epout_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
- .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE,
/* .wMaxPacketSize = DYNAMIC */
.bInterval = 4,
};
+static struct usb_endpoint_descriptor ss_epout_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE,
+ /* .wMaxPacketSize = DYNAMIC */
+ .bInterval = 4,
+};
+
+static struct usb_ss_ep_comp_descriptor ss_epout_desc_comp = {
+ .bLength = sizeof(ss_epout_desc_comp),
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+ .bMaxBurst = 0,
+ .bmAttributes = 0,
+ /* wBytesPerInterval = DYNAMIC */
+};
+
/* CS AS ISO OUT Endpoint */
static struct uac2_iso_endpoint_descriptor as_iso_out_desc = {
.bLength = sizeof as_iso_out_desc,
@@ -330,7 +405,7 @@ static struct uac2_as_header_descriptor as_in_hdr_desc = {
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_AS_GENERAL,
- .bTerminalLink = USB_IN_OT_ID,
+ /* .bTerminalLink = DYNAMIC */
.bmControls = 0,
.bFormatType = UAC_FORMAT_TYPE_I,
.bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM),
@@ -351,7 +426,7 @@ static struct usb_endpoint_descriptor fs_epin_desc = {
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN,
- .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_SYNC,
/* .wMaxPacketSize = DYNAMIC */
.bInterval = 1,
};
@@ -360,11 +435,29 @@ static struct usb_endpoint_descriptor hs_epin_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_SYNC,
+ /* .wMaxPacketSize = DYNAMIC */
+ .bInterval = 4,
+};
+
+static struct usb_endpoint_descriptor ss_epin_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
/* .wMaxPacketSize = DYNAMIC */
.bInterval = 4,
};
+static struct usb_ss_ep_comp_descriptor ss_epin_desc_comp = {
+ .bLength = sizeof(ss_epin_desc_comp),
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+ .bMaxBurst = 0,
+ .bmAttributes = 0,
+ /* wBytesPerInterval = DYNAMIC */
+};
+
/* CS AS ISO IN Endpoint */
static struct uac2_iso_endpoint_descriptor as_iso_in_desc = {
.bLength = sizeof as_iso_in_desc,
@@ -386,6 +479,8 @@ static struct usb_descriptor_header *fs_audio_desc[] = {
(struct usb_descriptor_header *)&out_clk_src_desc,
(struct usb_descriptor_header *)&usb_out_it_desc,
(struct usb_descriptor_header *)&io_in_it_desc,
+ (struct usb_descriptor_header *)&usb_in_ot_fu_desc,
+ (struct usb_descriptor_header *)&io_out_ot_fu_desc,
(struct usb_descriptor_header *)&usb_in_ot_desc,
(struct usb_descriptor_header *)&io_out_ot_desc,
@@ -416,6 +511,8 @@ static struct usb_descriptor_header *hs_audio_desc[] = {
(struct usb_descriptor_header *)&out_clk_src_desc,
(struct usb_descriptor_header *)&usb_out_it_desc,
(struct usb_descriptor_header *)&io_in_it_desc,
+ (struct usb_descriptor_header *)&usb_in_ot_fu_desc,
+ (struct usb_descriptor_header *)&io_out_ot_fu_desc,
(struct usb_descriptor_header *)&usb_in_ot_desc,
(struct usb_descriptor_header *)&io_out_ot_desc,
@@ -437,24 +534,76 @@ static struct usb_descriptor_header *hs_audio_desc[] = {
NULL,
};
+static struct usb_descriptor_header *ss_audio_desc[] = {
+ (struct usb_descriptor_header *)&iad_desc,
+ (struct usb_descriptor_header *)&std_ac_if_desc,
+
+ (struct usb_descriptor_header *)&ac_hdr_desc,
+ (struct usb_descriptor_header *)&in_clk_src_desc,
+ (struct usb_descriptor_header *)&out_clk_src_desc,
+ (struct usb_descriptor_header *)&usb_out_it_desc,
+ (struct usb_descriptor_header *)&io_in_it_desc,
+ (struct usb_descriptor_header *)&usb_in_ot_fu_desc,
+ (struct usb_descriptor_header *)&io_out_ot_fu_desc,
+ (struct usb_descriptor_header *)&usb_in_ot_desc,
+ (struct usb_descriptor_header *)&io_out_ot_desc,
+
+ (struct usb_descriptor_header *)&std_as_out_if0_desc,
+ (struct usb_descriptor_header *)&std_as_out_if1_desc,
+
+ (struct usb_descriptor_header *)&as_out_hdr_desc,
+ (struct usb_descriptor_header *)&as_out_fmt1_desc,
+ (struct usb_descriptor_header *)&ss_epout_desc,
+ (struct usb_descriptor_header *)&ss_epout_desc_comp,
+ (struct usb_descriptor_header *)&as_iso_out_desc,
+
+ (struct usb_descriptor_header *)&std_as_in_if0_desc,
+ (struct usb_descriptor_header *)&std_as_in_if1_desc,
+
+ (struct usb_descriptor_header *)&as_in_hdr_desc,
+ (struct usb_descriptor_header *)&as_in_fmt1_desc,
+ (struct usb_descriptor_header *)&ss_epin_desc,
+ (struct usb_descriptor_header *)&ss_epin_desc_comp,
+ (struct usb_descriptor_header *)&as_iso_in_desc,
+ NULL,
+};
+
+struct cntrl_cur_lay2 {
+ __le16 dCUR;
+};
+
+struct cntrl_range_lay2 {
+ __le16 wNumSubRanges;
+ __le16 dMIN;
+ __le16 dMAX;
+ __le16 dRES;
+} __packed;
+
struct cntrl_cur_lay3 {
__le32 dCUR;
};
struct cntrl_range_lay3 {
- __le16 wNumSubRanges;
__le32 dMIN;
__le32 dMAX;
__le32 dRES;
} __packed;
-static int set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts,
+#define ranges_size(c) (sizeof(c.wNumSubRanges) + c.wNumSubRanges \
+ * sizeof(struct cntrl_range_lay3))
+struct cntrl_ranges_lay3 {
+ __u16 wNumSubRanges;
+ struct cntrl_range_lay3 r[UAC_MAX_RATES];
+} __packed;
+
+static int set_ep_max_packet_size(const struct f_uac_opts *uac2_opts,
struct usb_endpoint_descriptor *ep_desc,
enum usb_device_speed speed, bool is_playback)
{
- int chmask, srate, ssize;
+ int chmask, srate = 0, ssize;
u16 max_size_bw, max_size_ep;
unsigned int factor;
+ int i;
switch (speed) {
case USB_SPEED_FULL:
@@ -463,6 +612,7 @@ static int set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts,
break;
case USB_SPEED_HIGH:
+ case USB_SPEED_SUPER:
max_size_ep = 1024;
factor = 8000;
break;
@@ -473,11 +623,21 @@ static int set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts,
if (is_playback) {
chmask = uac2_opts->p_chmask;
- srate = uac2_opts->p_srate;
+ for (i = 0; i < UAC_MAX_RATES; i++) {
+ if (uac2_opts->p_srate[i] == 0)
+ break;
+ if (uac2_opts->p_srate[i] > srate)
+ srate = uac2_opts->p_srate[i];
+ }
ssize = uac2_opts->p_ssize;
} else {
chmask = uac2_opts->c_chmask;
- srate = uac2_opts->c_srate;
+ for (i = 0; i < UAC_MAX_RATES; i++) {
+ if (uac2_opts->c_srate[i] == 0)
+ break;
+ if (uac2_opts->c_srate[i] > srate)
+ srate = uac2_opts->c_srate[i];
+ }
ssize = uac2_opts->c_ssize;
}
@@ -489,19 +649,166 @@ static int set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts,
return 0;
}
+/* Use macro to overcome line length limitation */
+#define USBDHDR(p) (struct usb_descriptor_header *)(p)
+
+static void setup_headers(struct f_uac_opts *opts,
+ struct usb_descriptor_header **headers,
+ enum usb_device_speed speed)
+{
+ struct usb_ss_ep_comp_descriptor *epout_desc_comp = NULL;
+ struct usb_ss_ep_comp_descriptor *epin_desc_comp = NULL;
+ struct usb_endpoint_descriptor *epout_desc;
+ struct usb_endpoint_descriptor *epin_desc;
+ int i;
+
+ switch (speed) {
+ case USB_SPEED_FULL:
+ epout_desc = &fs_epout_desc;
+ epin_desc = &fs_epin_desc;
+ break;
+ case USB_SPEED_HIGH:
+ epout_desc = &hs_epout_desc;
+ epin_desc = &hs_epin_desc;
+ break;
+ default:
+ epout_desc = &ss_epout_desc;
+ epin_desc = &ss_epin_desc;
+ epout_desc_comp = &ss_epout_desc_comp;
+ epin_desc_comp = &ss_epin_desc_comp;
+ }
+
+ i = 0;
+ headers[i++] = USBDHDR(&iad_desc);
+ headers[i++] = USBDHDR(&std_ac_if_desc);
+ headers[i++] = USBDHDR(&ac_hdr_desc);
+ if (EPIN_EN(opts))
+ headers[i++] = USBDHDR(&in_clk_src_desc);
+ if (EPOUT_EN(opts)) {
+ headers[i++] = USBDHDR(&out_clk_src_desc);
+ headers[i++] = USBDHDR(&usb_out_it_desc);
+ }
+ if (EPIN_EN(opts)) {
+ headers[i++] = USBDHDR(&io_in_it_desc);
+ if (EPIN_FU(opts))
+ headers[i++] = USBDHDR(&usb_in_ot_fu_desc);
+ headers[i++] = USBDHDR(&usb_in_ot_desc);
+ }
+ if (EPOUT_EN(opts)) {
+ if (EPOUT_FU(opts))
+ headers[i++] = USBDHDR(&io_out_ot_fu_desc);
+ headers[i++] = USBDHDR(&io_out_ot_desc);
+ headers[i++] = USBDHDR(&std_as_out_if0_desc);
+ headers[i++] = USBDHDR(&std_as_out_if1_desc);
+ headers[i++] = USBDHDR(&as_out_hdr_desc);
+ headers[i++] = USBDHDR(&as_out_fmt1_desc);
+ headers[i++] = USBDHDR(epout_desc);
+ if (epout_desc_comp)
+ headers[i++] = USBDHDR(epout_desc_comp);
+
+ headers[i++] = USBDHDR(&as_iso_out_desc);
+ }
+ if (EPIN_EN(opts)) {
+ headers[i++] = USBDHDR(&std_as_in_if0_desc);
+ headers[i++] = USBDHDR(&std_as_in_if1_desc);
+ headers[i++] = USBDHDR(&as_in_hdr_desc);
+ headers[i++] = USBDHDR(&as_in_fmt1_desc);
+ headers[i++] = USBDHDR(epin_desc);
+ if (epin_desc_comp)
+ headers[i++] = USBDHDR(epin_desc_comp);
+
+ headers[i++] = USBDHDR(&as_iso_in_desc);
+ }
+ headers[i] = NULL;
+}
+
+static void setup_descriptor(struct f_uac_opts *opts)
+{
+ /* patch descriptors */
+ int i = 1; /* ID's start with 1 */
+
+ if (EPOUT_EN(opts))
+ usb_out_it_desc.bTerminalID = i++;
+ if (EPIN_EN(opts))
+ io_in_it_desc.bTerminalID = i++;
+ if (EPOUT_EN(opts) && EPOUT_FU(opts))
+ io_out_ot_fu_desc.bUnitID = i++;
+ if (EPIN_EN(opts) && EPIN_FU(opts))
+ usb_in_ot_fu_desc.bUnitID = i++;
+ if (EPOUT_EN(opts))
+ io_out_ot_desc.bTerminalID = i++;
+ if (EPIN_EN(opts))
+ usb_in_ot_desc.bTerminalID = i++;
+ if (EPOUT_EN(opts))
+ out_clk_src_desc.bClockID = i++;
+ if (EPIN_EN(opts))
+ in_clk_src_desc.bClockID = i++;
+
+ usb_out_it_desc.bCSourceID = out_clk_src_desc.bClockID;
+ if (EPIN_FU(opts)) {
+ usb_in_ot_fu_desc.bSourceID = io_in_it_desc.bTerminalID;
+ usb_in_ot_desc.bSourceID = usb_in_ot_fu_desc.bUnitID;
+ p_feature_unit.id = usb_in_ot_fu_desc.bUnitID;
+ } else {
+ usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID;
+ }
+ usb_in_ot_desc.bCSourceID = in_clk_src_desc.bClockID;
+ io_in_it_desc.bCSourceID = in_clk_src_desc.bClockID;
+ io_out_ot_desc.bCSourceID = out_clk_src_desc.bClockID;
+ if (EPOUT_FU(opts)) {
+ io_out_ot_fu_desc.bSourceID = usb_out_it_desc.bTerminalID;
+ io_out_ot_desc.bSourceID = io_out_ot_fu_desc.bUnitID;
+ c_feature_unit.id = io_out_ot_fu_desc.bUnitID;
+ } else {
+ io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID;
+ }
+ as_out_hdr_desc.bTerminalLink = usb_out_it_desc.bTerminalID;
+ as_in_hdr_desc.bTerminalLink = usb_in_ot_desc.bTerminalID;
+
+ iad_desc.bInterfaceCount = 1;
+ ac_hdr_desc.wTotalLength = sizeof ac_hdr_desc;
+
+ if (EPIN_EN(opts)) {
+ u16 len = le16_to_cpu(ac_hdr_desc.wTotalLength);
+
+ len += sizeof(in_clk_src_desc);
+ len += sizeof(usb_in_ot_desc);
+ if (EPIN_FU(opts))
+ len += sizeof(usb_in_ot_fu_desc);
+ len += sizeof(io_in_it_desc);
+ ac_hdr_desc.wTotalLength = cpu_to_le16(len);
+ iad_desc.bInterfaceCount++;
+ }
+ if (EPOUT_EN(opts)) {
+ u16 len = le16_to_cpu(ac_hdr_desc.wTotalLength);
+
+ len += sizeof(out_clk_src_desc);
+ len += sizeof(usb_out_it_desc);
+ if (EPOUT_FU(opts))
+ len += sizeof(io_out_ot_fu_desc);
+ len += sizeof(io_out_ot_desc);
+ ac_hdr_desc.wTotalLength = cpu_to_le16(len);
+ iad_desc.bInterfaceCount++;
+ }
+
+ setup_headers(opts, fs_audio_desc, USB_SPEED_FULL);
+ setup_headers(opts, hs_audio_desc, USB_SPEED_HIGH);
+ setup_headers(opts, ss_audio_desc, USB_SPEED_SUPER);
+}
+
static int
afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
{
- struct f_uac2 *uac2 = func_to_uac2(fn);
+ struct f_uac *uac2 = func_to_uac(fn);
struct g_audio *agdev = func_to_g_audio(fn);
struct usb_composite_dev *cdev = cfg->cdev;
struct usb_gadget *gadget = cdev->gadget;
struct device *dev = &gadget->dev;
- struct f_uac2_opts *uac2_opts;
+ struct f_uac_opts *uac2_opts;
struct usb_string *us;
int ret;
- uac2_opts = container_of(fn->fi, struct f_uac2_opts, func_inst);
+ uac2_opts = container_of(fn->fi, struct f_uac_opts, func_inst);
us = usb_gstrings_attach(cdev, fn_strings, ARRAY_SIZE(strings_fn));
if (IS_ERR(us))
@@ -512,6 +819,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
out_clk_src_desc.iClockSource = us[STR_CLKSRC_OUT].id;
usb_out_it_desc.iTerminal = us[STR_USB_IT].id;
io_in_it_desc.iTerminal = us[STR_IO_IT].id;
+ usb_in_ot_fu_desc.iFeature = us[STR_USB_OT_FU].id;
+ io_out_ot_fu_desc.iFeature = us[STR_IO_OT_FU].id;
usb_in_ot_desc.iTerminal = us[STR_USB_OT].id;
io_out_ot_desc.iTerminal = us[STR_IO_OT].id;
std_as_out_if0_desc.iInterface = us[STR_AS_OUT_ALT0].id;
@@ -534,9 +843,6 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
as_in_fmt1_desc.bSubslotSize = uac2_opts->p_ssize;
as_in_fmt1_desc.bBitResolution = uac2_opts->p_ssize * 8;
- snprintf(clksrc_in, sizeof(clksrc_in), "%uHz", uac2_opts->p_srate);
- snprintf(clksrc_out, sizeof(clksrc_out), "%uHz", uac2_opts->c_srate);
-
ret = usb_interface_id(cfg, fn);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
@@ -548,25 +854,29 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
uac2->ac_intf = ret;
uac2->ac_alt = 0;
- ret = usb_interface_id(cfg, fn);
- if (ret < 0) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- return ret;
+ if (EPOUT_EN(uac2_opts)) {
+ ret = usb_interface_id(cfg, fn);
+ if (ret < 0) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ return ret;
+ }
+ std_as_out_if0_desc.bInterfaceNumber = ret;
+ std_as_out_if1_desc.bInterfaceNumber = ret;
+ uac2->as_out_intf = ret;
+ uac2->as_out_alt = 0;
}
- std_as_out_if0_desc.bInterfaceNumber = ret;
- std_as_out_if1_desc.bInterfaceNumber = ret;
- uac2->as_out_intf = ret;
- uac2->as_out_alt = 0;
- ret = usb_interface_id(cfg, fn);
- if (ret < 0) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- return ret;
+ if (EPIN_EN(uac2_opts)) {
+ ret = usb_interface_id(cfg, fn);
+ if (ret < 0) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ return ret;
+ }
+ std_as_in_if0_desc.bInterfaceNumber = ret;
+ std_as_in_if1_desc.bInterfaceNumber = ret;
+ uac2->as_in_intf = ret;
+ uac2->as_in_alt = 0;
}
- std_as_in_if0_desc.bInterfaceNumber = ret;
- std_as_in_if1_desc.bInterfaceNumber = ret;
- uac2->as_in_intf = ret;
- uac2->as_in_alt = 0;
/* Calculate wMaxPacketSize according to audio bandwidth */
ret = set_ep_max_packet_size(uac2_opts, &fs_epin_desc, USB_SPEED_FULL,
@@ -597,16 +907,39 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
return ret;
}
- agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc);
- if (!agdev->out_ep) {
+ ret = set_ep_max_packet_size(uac2_opts, &ss_epin_desc, USB_SPEED_SUPER,
+ true);
+ if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- return -ENODEV;
+ return ret;
}
- agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc);
- if (!agdev->in_ep) {
+ ss_epin_desc_comp.wBytesPerInterval = ss_epin_desc.wMaxPacketSize;
+
+ ret = set_ep_max_packet_size(uac2_opts, &ss_epout_desc, USB_SPEED_SUPER,
+ false);
+ if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- return -ENODEV;
+ return ret;
+ }
+
+ ss_epout_desc_comp.wBytesPerInterval = ss_epout_desc.wMaxPacketSize;
+
+ if (EPOUT_EN(uac2_opts)) {
+ agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc);
+ if (!agdev->out_ep) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ return -ENODEV;
+ }
+ }
+
+ if (EPIN_EN(uac2_opts)) {
+ agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc);
+ if (!agdev->in_ep) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ return -ENODEV;
+ }
+ agdev->in_ep->maxpacket = usb_endpoint_maxp(&fs_epin_desc);
}
agdev->in_ep_maxpsize = max_t(u16,
@@ -616,21 +949,34 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
le16_to_cpu(fs_epout_desc.wMaxPacketSize),
le16_to_cpu(hs_epout_desc.wMaxPacketSize));
+ agdev->in_ep_maxpsize = max_t(u16, agdev->in_ep_maxpsize,
+ le16_to_cpu(ss_epin_desc.wMaxPacketSize));
+ agdev->out_ep_maxpsize = max_t(u16, agdev->out_ep_maxpsize,
+ le16_to_cpu(ss_epout_desc.wMaxPacketSize));
+
hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress;
hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress;
+ ss_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress;
+ ss_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress;
- ret = usb_assign_descriptors(fn, fs_audio_desc, hs_audio_desc, NULL,
- NULL);
+ setup_descriptor(uac2_opts);
+
+ ret = usb_assign_descriptors(fn, fs_audio_desc, hs_audio_desc, ss_audio_desc,
+ ss_audio_desc);
if (ret)
return ret;
agdev->gadget = gadget;
agdev->params.p_chmask = uac2_opts->p_chmask;
- agdev->params.p_srate = uac2_opts->p_srate;
+ memcpy(agdev->params.p_srate, uac2_opts->p_srate,
+ sizeof(agdev->params.p_srate));
+ agdev->params.p_srate_active = uac2_opts->p_srate_active;
agdev->params.p_ssize = uac2_opts->p_ssize;
agdev->params.c_chmask = uac2_opts->c_chmask;
- agdev->params.c_srate = uac2_opts->c_srate;
+ memcpy(agdev->params.c_srate, uac2_opts->c_srate,
+ sizeof(agdev->params.c_srate));
+ agdev->params.c_srate_active = uac2_opts->c_srate_active;
agdev->params.c_ssize = uac2_opts->c_ssize;
agdev->params.req_number = uac2_opts->req_number;
ret = g_audio_setup(agdev, "UAC2 PCM", "UAC2_Gadget");
@@ -648,7 +994,7 @@ static int
afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
{
struct usb_composite_dev *cdev = fn->config->cdev;
- struct f_uac2 *uac2 = func_to_uac2(fn);
+ struct f_uac *uac2 = func_to_uac(fn);
struct usb_gadget *gadget = cdev->gadget;
struct device *dev = &gadget->dev;
int ret = 0;
@@ -693,7 +1039,7 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
static int
afunc_get_alt(struct usb_function *fn, unsigned intf)
{
- struct f_uac2 *uac2 = func_to_uac2(fn);
+ struct f_uac *uac2 = func_to_uac(fn);
struct g_audio *agdev = func_to_g_audio(fn);
if (intf == uac2->ac_intf)
@@ -713,7 +1059,7 @@ afunc_get_alt(struct usb_function *fn, unsigned intf)
static void
afunc_disable(struct usb_function *fn)
{
- struct f_uac2 *uac2 = func_to_uac2(fn);
+ struct f_uac *uac2 = func_to_uac(fn);
uac2->as_in_alt = 0;
uac2->as_out_alt = 0;
@@ -722,11 +1068,11 @@ afunc_disable(struct usb_function *fn)
}
static int
-in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+in_rq_cs_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
{
struct usb_request *req = fn->config->cdev->req;
struct g_audio *agdev = func_to_g_audio(fn);
- struct f_uac2_opts *opts;
+ struct f_uac_opts *opts;
u16 w_length = le16_to_cpu(cr->wLength);
u16 w_index = le16_to_cpu(cr->wIndex);
u16 w_value = le16_to_cpu(cr->wValue);
@@ -735,9 +1081,9 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
int value = -EOPNOTSUPP;
int p_srate, c_srate;
- opts = g_audio_to_uac2_opts(agdev);
- p_srate = opts->p_srate;
- c_srate = opts->c_srate;
+ opts = g_audio_to_uac_opts(agdev);
+ p_srate = opts->p_srate_active;
+ c_srate = opts->c_srate_active;
if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
struct cntrl_cur_lay3 c;
@@ -748,6 +1094,7 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
else if (entity_id == USB_OUT_CLK_ID)
c.dCUR = cpu_to_le32(c_srate);
+ DBG(fn->config->cdev, "%s(): %d\n", __func__, c.dCUR);
value = min_t(unsigned, w_length, sizeof c);
memcpy(req->buf, &c, value);
} else if (control_selector == UAC2_CS_CONTROL_CLOCK_VALID) {
@@ -763,38 +1110,50 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
}
static int
-in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+in_rq_cs_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
{
struct usb_request *req = fn->config->cdev->req;
struct g_audio *agdev = func_to_g_audio(fn);
- struct f_uac2_opts *opts;
+ struct f_uac_opts *opts;
u16 w_length = le16_to_cpu(cr->wLength);
u16 w_index = le16_to_cpu(cr->wIndex);
u16 w_value = le16_to_cpu(cr->wValue);
u8 entity_id = (w_index >> 8) & 0xff;
u8 control_selector = w_value >> 8;
- struct cntrl_range_lay3 r;
+ struct cntrl_ranges_lay3 rs;
int value = -EOPNOTSUPP;
- int p_srate, c_srate;
+ int srate = 0;
+ int i;
- opts = g_audio_to_uac2_opts(agdev);
- p_srate = opts->p_srate;
- c_srate = opts->c_srate;
+ opts = g_audio_to_uac_opts(agdev);
if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
- if (entity_id == USB_IN_CLK_ID)
- r.dMIN = cpu_to_le32(p_srate);
- else if (entity_id == USB_OUT_CLK_ID)
- r.dMIN = cpu_to_le32(c_srate);
- else
- return -EOPNOTSUPP;
-
- r.dMAX = r.dMIN;
- r.dRES = 0;
- r.wNumSubRanges = cpu_to_le16(1);
+ rs.wNumSubRanges = 0;
+ for (i = 0; i < UAC_MAX_RATES; i++) {
+ if (entity_id == USB_IN_CLK_ID)
+ srate = opts->p_srate[i];
+ else if (entity_id == USB_OUT_CLK_ID)
+ srate = opts->c_srate[i];
+ else
+ return -EOPNOTSUPP;
+
+ if (srate == 0)
+ break;
+
+ rs.r[rs.wNumSubRanges].dMIN = srate;
+ rs.r[rs.wNumSubRanges].dMAX = srate;
+ rs.r[rs.wNumSubRanges].dRES = 0;
+ rs.wNumSubRanges++;
+ DBG(fn->config->cdev,
+ "%s(): clk %d: report rate %d. %d\n",
+ __func__, entity_id, rs.wNumSubRanges,
+ srate);
+ }
- value = min_t(unsigned, w_length, sizeof r);
- memcpy(req->buf, &r, value);
+ value = min_t(unsigned int, w_length, ranges_size(rs));
+ DBG(fn->config->cdev, "%s(): send %d rates, size %d\n",
+ __func__, rs.wNumSubRanges, value);
+ memcpy(req->buf, &rs, value);
} else {
dev_err(&agdev->gadget->dev,
"%s:%d control_selector=%d TODO!\n",
@@ -804,26 +1163,195 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
return value;
}
+static int
+in_rq_fu(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+ struct f_uac *uac2 = func_to_uac(fn);
+ struct usb_request *req = fn->config->cdev->req;
+ u16 w_length = le16_to_cpu(cr->wLength);
+ struct usb_audio_control *con = uac2->get_con;
+ u8 cmd = uac2->get_cmd;
+ char c1;
+ struct cntrl_cur_lay2 c2;
+ struct cntrl_range_lay2 r;
+ int value = -EOPNOTSUPP;
+
+ if (cmd == UAC2_CS_CUR && con->type == UAC_FU_MUTE) {
+ c1 = con->get(con, UAC__CUR);
+ value = min_t(unsigned int, w_length, 1);
+ memcpy(req->buf, &c1, value);
+ } else if (cmd == UAC2_CS_CUR && con->type == UAC_FU_VOLUME) {
+ c2.dCUR = cpu_to_le16(con->get(con, UAC__CUR));
+ value = min_t(unsigned int, w_length, sizeof(c2));
+ memcpy(req->buf, &c2, value);
+ } else if (cmd == UAC2_CS_RANGE) {
+ r.wNumSubRanges = cpu_to_le16(1);
+ r.dMIN = cpu_to_le16(con->get(con, UAC__MIN));
+ r.dMAX = cpu_to_le16(con->get(con, UAC__MAX));
+ r.dRES = cpu_to_le16(con->get(con, UAC__RES));
+ value = min_t(unsigned int, w_length, sizeof(r));
+ memcpy(req->buf, &r, value);
+ }
+
+ DBG(fn->config->cdev, "%s(): send size %d\n", __func__, value);
+
+ return value;
+}
+
+static void uac2_fu_control_complt(struct usb_ep *ep, struct usb_request *req)
+{
+ struct f_uac *uac2 = req->context;
+ struct usb_audio_control *con = uac2->set_con;
+ u8 cmd = uac2->set_cmd;
+ int status = req->status;
+ char c1;
+ struct cntrl_cur_lay2 c2;
+ struct cntrl_range_lay2 r;
+
+ switch (status) {
+ case 0: /* normal completion? */
+ if (!con)
+ break;
+
+ if (cmd == UAC2_CS_CUR && con->type == UAC_FU_MUTE) {
+ memcpy(&c1, req->buf, 1);
+ con->set(con, UAC__CUR, c1);
+ } else if (cmd == UAC2_CS_CUR && con->type == UAC_FU_VOLUME) {
+ memcpy(&c2, req->buf, sizeof(c2));
+ con->set(con, UAC__CUR, le16_to_cpu(c2.dCUR));
+ } else if (cmd == UAC2_CS_RANGE) {
+ memcpy(&r, req->buf, sizeof(r));
+ con->set(con, UAC__MIN, le16_to_cpu(r.dMIN));
+ con->set(con, UAC__MAX, le16_to_cpu(r.dMAX));
+ con->set(con, UAC__RES, le16_to_cpu(r.dRES));
+ }
+
+ uac2->set_con = NULL;
+ break;
+ default:
+ break;
+ }
+}
+
static int
ac_rq_in(struct usb_function *fn, const struct usb_ctrlrequest *cr)
{
- if (cr->bRequest == UAC2_CS_CUR)
- return in_rq_cur(fn, cr);
- else if (cr->bRequest == UAC2_CS_RANGE)
- return in_rq_range(fn, cr);
- else
- return -EOPNOTSUPP;
+ struct f_uac *uac2 = func_to_uac(fn);
+ struct usb_composite_dev *cdev = fn->config->cdev;
+ struct usb_request *req = cdev->req;
+ u8 id = ((le16_to_cpu(cr->wIndex) >> 8) & 0xFF);
+ u16 len = le16_to_cpu(cr->wLength);
+ u16 w_value = le16_to_cpu(cr->wValue);
+ u8 con_sel = (w_value >> 8) & 0xFF;
+ u8 cmd = (cr->bRequest & 0x0F);
+ struct usb_audio_control_selector *cs;
+ struct usb_audio_control *con;
+
+ DBG(cdev, "bRequest in 0x%x, w_value 0x%04x, len %d, entity %d\n",
+ cr->bRequest, w_value, len, id);
+
+ if (id == USB_OUT_CLK_ID || id == USB_IN_CLK_ID) {
+ if (cr->bRequest == UAC2_CS_CUR)
+ return in_rq_cs_cur(fn, cr);
+ else if (cr->bRequest == UAC2_CS_RANGE)
+ return in_rq_cs_range(fn, cr);
+ }
+
+ list_for_each_entry(cs, &uac2->cs, list)
+ if (cs->id == id)
+ list_for_each_entry(con, &cs->control, list)
+ if (con->type == con_sel) {
+ req->context = uac2;
+ uac2->get_con = con;
+ uac2->get_cmd = cmd;
+ req->complete = uac2_fu_control_complt;
+ return in_rq_fu(fn, cr);
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static void uac2_cs_control_sam_freq(struct usb_ep *ep, struct usb_request *req)
+{
+ struct usb_function *fn = ep->driver_data;
+ struct usb_composite_dev *cdev = fn->config->cdev;
+ struct g_audio *agdev = func_to_g_audio(fn);
+ struct f_uac *uac2 = func_to_uac(fn);
+ struct f_uac_opts *opts = g_audio_to_uac_opts(agdev);
+ u32 val;
+
+ if (req->actual != 4) {
+ WARN(cdev, "Invalid data size for UAC2_CS_CONTROL_SAM_FREQ.\n");
+ return;
+ }
+
+ val = le32_to_cpu(*((u32 *)req->buf));
+ if (uac2->ctl_id == USB_IN_CLK_ID) {
+ opts->p_srate_active = val;
+ u_audio_set_playback_srate(agdev, opts->p_srate_active);
+ } else if (uac2->ctl_id == USB_OUT_CLK_ID) {
+ opts->c_srate_active = val;
+ u_audio_set_capture_srate(agdev, opts->c_srate_active);
+ }
}
static int
-out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+out_rq_cs_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
{
+ struct usb_composite_dev *cdev = fn->config->cdev;
+ struct usb_request *req = cdev->req;
u16 w_length = le16_to_cpu(cr->wLength);
+ struct f_uac *uac2 = func_to_uac(fn);
u16 w_value = le16_to_cpu(cr->wValue);
+ u16 w_index = le16_to_cpu(cr->wIndex);
u8 control_selector = w_value >> 8;
+ u8 clock_id = w_index >> 8;
- if (control_selector == UAC2_CS_CONTROL_SAM_FREQ)
+ if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
+ DBG(cdev, "control_selector UAC2_CS_CONTROL_SAM_FREQ, clock: %d\n",
+ clock_id);
+ cdev->gadget->ep0->driver_data = fn;
+ uac2->ctl_id = clock_id;
+ req->complete = uac2_cs_control_sam_freq;
return w_length;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static int
+ac_rq_out(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+ struct f_uac *uac2 = func_to_uac(fn);
+ struct usb_composite_dev *cdev = fn->config->cdev;
+ struct usb_request *req = cdev->req;
+ u16 w_index = le16_to_cpu(cr->wIndex);
+ u16 w_value = le16_to_cpu(cr->wValue);
+ u16 w_length = le16_to_cpu(cr->wLength);
+ u8 id = (w_index >> 8) & 0xff;
+ u8 con_sel = (w_value >> 8) & 0xff;
+ u8 cmd = (cr->bRequest & 0x0f);
+ struct usb_audio_control_selector *cs;
+ struct usb_audio_control *con;
+
+ DBG(cdev, "bRequest out 0x%x, w_value 0x%04x, len %d, entity %d\n",
+ cr->bRequest, w_value, w_length, id);
+
+ if (id == USB_OUT_CLK_ID || id == USB_IN_CLK_ID) {
+ if (cr->bRequest == UAC2_CS_CUR)
+ return out_rq_cs_cur(fn, cr);
+ }
+
+ list_for_each_entry(cs, &uac2->cs, list)
+ if (cs->id == id)
+ list_for_each_entry(con, &cs->control, list)
+ if (con->type == con_sel) {
+ req->context = uac2;
+ uac2->set_con = con;
+ uac2->set_cmd = cmd;
+ req->complete = uac2_fu_control_complt;
+ return w_length;
+ }
return -EOPNOTSUPP;
}
@@ -831,7 +1359,7 @@ out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
static int
setup_rq_inf(struct usb_function *fn, const struct usb_ctrlrequest *cr)
{
- struct f_uac2 *uac2 = func_to_uac2(fn);
+ struct f_uac *uac2 = func_to_uac(fn);
struct g_audio *agdev = func_to_g_audio(fn);
u16 w_index = le16_to_cpu(cr->wIndex);
u8 intf = w_index & 0xff;
@@ -844,10 +1372,8 @@ setup_rq_inf(struct usb_function *fn, const struct usb_ctrlrequest *cr)
if (cr->bRequestType & USB_DIR_IN)
return ac_rq_in(fn, cr);
- else if (cr->bRequest == UAC2_CS_CUR)
- return out_rq_cur(fn, cr);
-
- return -EOPNOTSUPP;
+ else
+ return ac_rq_out(fn, cr);
}
static int
@@ -883,80 +1409,31 @@ afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr)
return value;
}
-static inline struct f_uac2_opts *to_f_uac2_opts(struct config_item *item)
-{
- return container_of(to_config_group(item), struct f_uac2_opts,
- func_inst.group);
-}
-
-static void f_uac2_attr_release(struct config_item *item)
-{
- struct f_uac2_opts *opts = to_f_uac2_opts(item);
-
- usb_put_function_instance(&opts->func_inst);
-}
-
static struct configfs_item_operations f_uac2_item_ops = {
- .release = f_uac2_attr_release,
+ .release = f_uac_attr_release,
};
-#define UAC2_ATTRIBUTE(name) \
-static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \
- char *page) \
-{ \
- struct f_uac2_opts *opts = to_f_uac2_opts(item); \
- int result; \
- \
- mutex_lock(&opts->lock); \
- result = sprintf(page, "%u\n", opts->name); \
- mutex_unlock(&opts->lock); \
- \
- return result; \
-} \
- \
-static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \
- const char *page, size_t len) \
-{ \
- struct f_uac2_opts *opts = to_f_uac2_opts(item); \
- int ret; \
- u32 num; \
- \
- mutex_lock(&opts->lock); \
- if (opts->refcnt) { \
- ret = -EBUSY; \
- goto end; \
- } \
- \
- ret = kstrtou32(page, 0, &num); \
- if (ret) \
- goto end; \
- \
- opts->name = num; \
- ret = len; \
- \
-end: \
- mutex_unlock(&opts->lock); \
- return ret; \
-} \
- \
-CONFIGFS_ATTR(f_uac2_opts_, name)
-
-UAC2_ATTRIBUTE(p_chmask);
-UAC2_ATTRIBUTE(p_srate);
-UAC2_ATTRIBUTE(p_ssize);
-UAC2_ATTRIBUTE(c_chmask);
-UAC2_ATTRIBUTE(c_srate);
-UAC2_ATTRIBUTE(c_ssize);
-UAC2_ATTRIBUTE(req_number);
+UAC_ATTRIBUTE(p_chmask);
+UAC_ATTRIBUTE(p_ssize);
+UAC_ATTRIBUTE(p_feature_unit);
+UAC_ATTRIBUTE(c_chmask);
+UAC_ATTRIBUTE(c_ssize);
+UAC_ATTRIBUTE(c_feature_unit);
+UAC_ATTRIBUTE(req_number);
+
+UAC_RATE_ATTRIBUTE(p_srate);
+UAC_RATE_ATTRIBUTE(c_srate);
static struct configfs_attribute *f_uac2_attrs[] = {
- &f_uac2_opts_attr_p_chmask,
- &f_uac2_opts_attr_p_srate,
- &f_uac2_opts_attr_p_ssize,
- &f_uac2_opts_attr_c_chmask,
- &f_uac2_opts_attr_c_srate,
- &f_uac2_opts_attr_c_ssize,
- &f_uac2_opts_attr_req_number,
+ &f_uac_opts_attr_p_chmask,
+ &f_uac_opts_attr_p_srate,
+ &f_uac_opts_attr_p_ssize,
+ &f_uac_opts_attr_p_feature_unit,
+ &f_uac_opts_attr_c_chmask,
+ &f_uac_opts_attr_c_srate,
+ &f_uac_opts_attr_c_ssize,
+ &f_uac_opts_attr_c_feature_unit,
+ &f_uac_opts_attr_req_number,
NULL,
};
@@ -968,15 +1445,15 @@ static const struct config_item_type f_uac2_func_type = {
static void afunc_free_inst(struct usb_function_instance *f)
{
- struct f_uac2_opts *opts;
+ struct f_uac_opts *opts;
- opts = container_of(f, struct f_uac2_opts, func_inst);
+ opts = container_of(f, struct f_uac_opts, func_inst);
kfree(opts);
}
static struct usb_function_instance *afunc_alloc_inst(void)
{
- struct f_uac2_opts *opts;
+ struct f_uac_opts *opts;
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
if (!opts)
@@ -988,23 +1465,27 @@ static struct usb_function_instance *afunc_alloc_inst(void)
config_group_init_type_name(&opts->func_inst.group, "",
&f_uac2_func_type);
- opts->p_chmask = UAC2_DEF_PCHMASK;
- opts->p_srate = UAC2_DEF_PSRATE;
- opts->p_ssize = UAC2_DEF_PSSIZE;
- opts->c_chmask = UAC2_DEF_CCHMASK;
- opts->c_srate = UAC2_DEF_CSRATE;
- opts->c_ssize = UAC2_DEF_CSSIZE;
- opts->req_number = UAC2_DEF_REQ_NUM;
+ opts->p_chmask = UAC_DEF_PCHMASK;
+ opts->p_srate[0] = UAC_DEF_PSRATE;
+ opts->p_srate_active = UAC_DEF_PSRATE;
+ opts->p_ssize = UAC_DEF_PSSIZE;
+ opts->p_feature_unit = UAC_DEF_PFU;
+ opts->c_chmask = UAC_DEF_CCHMASK;
+ opts->c_srate[0] = UAC_DEF_CSRATE;
+ opts->c_srate_active = UAC_DEF_CSRATE;
+ opts->c_ssize = UAC_DEF_CSSIZE;
+ opts->c_feature_unit = UAC_DEF_CFU;
+ opts->req_number = UAC_DEF_REQ_NUM;
return &opts->func_inst;
}
static void afunc_free(struct usb_function *f)
{
struct g_audio *agdev;
- struct f_uac2_opts *opts;
+ struct f_uac_opts *opts;
agdev = func_to_g_audio(f);
- opts = container_of(f->fi, struct f_uac2_opts, func_inst);
+ opts = container_of(f->fi, struct f_uac_opts, func_inst);
kfree(agdev);
mutex_lock(&opts->lock);
--opts->refcnt;
@@ -1021,16 +1502,55 @@ static void afunc_unbind(struct usb_configuration *c, struct usb_function *f)
agdev->gadget = NULL;
}
+/* Todo: add more control selecotor dynamically */
+static int control_selector_init(struct f_uac *uac2)
+{
+ INIT_LIST_HEAD(&uac2->cs);
+
+ /* playback feature unit */
+ list_add(&p_feature_unit.list, &uac2->cs);
+
+ INIT_LIST_HEAD(&p_feature_unit.control);
+ list_add(&p_mute_control.list, &p_feature_unit.control);
+ list_add(&p_volume_control.list, &p_feature_unit.control);
+
+ p_volume_control.data[UAC__CUR] = UAC_VOLUME_CUR;
+ p_volume_control.data[UAC__MIN] = UAC_VOLUME_MIN;
+ p_volume_control.data[UAC__MAX] = UAC_VOLUME_MAX;
+ p_volume_control.data[UAC__RES] = UAC_VOLUME_RES;
+
+ p_volume_control.context = &uac2->g_audio;
+ p_mute_control.context = &uac2->g_audio;
+
+ /* capture feature unit */
+ list_add(&c_feature_unit.list, &uac2->cs);
+
+ INIT_LIST_HEAD(&c_feature_unit.control);
+ list_add(&c_mute_control.list, &c_feature_unit.control);
+ list_add(&c_volume_control.list, &c_feature_unit.control);
+
+ c_volume_control.data[UAC__CUR] = UAC_VOLUME_CUR;
+ c_volume_control.data[UAC__MIN] = UAC_VOLUME_MIN;
+ c_volume_control.data[UAC__MAX] = UAC_VOLUME_MAX;
+ c_volume_control.data[UAC__RES] = UAC_VOLUME_RES;
+
+ c_volume_control.context = &uac2->g_audio;
+ c_mute_control.context = &uac2->g_audio;
+
+ return 0;
+}
+
+
static struct usb_function *afunc_alloc(struct usb_function_instance *fi)
{
- struct f_uac2 *uac2;
- struct f_uac2_opts *opts;
+ struct f_uac *uac2;
+ struct f_uac_opts *opts;
uac2 = kzalloc(sizeof(*uac2), GFP_KERNEL);
if (uac2 == NULL)
return ERR_PTR(-ENOMEM);
- opts = container_of(fi, struct f_uac2_opts, func_inst);
+ opts = container_of(fi, struct f_uac_opts, func_inst);
mutex_lock(&opts->lock);
++opts->refcnt;
mutex_unlock(&opts->lock);
@@ -1044,6 +1564,8 @@ static struct usb_function *afunc_alloc(struct usb_function_instance *fi)
uac2->g_audio.func.setup = afunc_setup;
uac2->g_audio.func.free_func = afunc_free;
+ control_selector_init(uac2);
+
return &uac2->g_audio.func;
}
diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index 169e73ed1..dba91c557 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -124,6 +124,18 @@ static struct usb_interface_descriptor uvc_streaming_intf_alt0 = {
.iInterface = 0,
};
+static struct usb_interface_descriptor uvc_bulk_streaming_intf_alt0 = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = UVC_INTF_VIDEO_STREAMING,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = UVC_SC_VIDEOSTREAMING,
+ .bInterfaceProtocol = 0x00,
+ .iInterface = 0,
+};
+
static struct usb_interface_descriptor uvc_streaming_intf_alt1 = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
@@ -147,6 +159,16 @@ static struct usb_endpoint_descriptor uvc_fs_streaming_ep = {
*/
};
+static struct usb_endpoint_descriptor uvc_fs_bulk_streaming_ep = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ /* The wMaxPacketSize and bInterval values will be initialized from
+ * module parameters.
+ */
+};
+
static struct usb_endpoint_descriptor uvc_hs_streaming_ep = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
@@ -158,6 +180,16 @@ static struct usb_endpoint_descriptor uvc_hs_streaming_ep = {
*/
};
+static struct usb_endpoint_descriptor uvc_hs_bulk_streaming_ep = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ /* The wMaxPacketSize and bInterval values will be initialized from
+ * module parameters.
+ */
+};
+
static struct usb_endpoint_descriptor uvc_ss_streaming_ep = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
@@ -170,6 +202,17 @@ static struct usb_endpoint_descriptor uvc_ss_streaming_ep = {
*/
};
+static struct usb_endpoint_descriptor uvc_ss_bulk_streaming_ep = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ /* The wMaxPacketSize and bInterval values will be initialized from
+ * module parameters.
+ */
+};
+
static struct usb_ss_ep_comp_descriptor uvc_ss_streaming_comp = {
.bLength = sizeof(uvc_ss_streaming_comp),
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
@@ -178,18 +221,36 @@ static struct usb_ss_ep_comp_descriptor uvc_ss_streaming_comp = {
*/
};
+static struct usb_ss_ep_comp_descriptor uvc_ss_bulk_streaming_comp = {
+ .bLength = sizeof(uvc_ss_bulk_streaming_comp),
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+ /* The bMaxBurst, bmAttributes and wBytesPerInterval values will be
+ * initialized from module parameters.
+ */
+};
+
static const struct usb_descriptor_header * const uvc_fs_streaming[] = {
(struct usb_descriptor_header *) &uvc_streaming_intf_alt1,
(struct usb_descriptor_header *) &uvc_fs_streaming_ep,
NULL,
};
+static const struct usb_descriptor_header * const uvc_fs_bulk_streaming[] = {
+ (struct usb_descriptor_header *)&uvc_fs_bulk_streaming_ep,
+ NULL,
+};
+
static const struct usb_descriptor_header * const uvc_hs_streaming[] = {
(struct usb_descriptor_header *) &uvc_streaming_intf_alt1,
(struct usb_descriptor_header *) &uvc_hs_streaming_ep,
NULL,
};
+static const struct usb_descriptor_header * const uvc_hs_bulk_streaming[] = {
+ (struct usb_descriptor_header *)&uvc_hs_bulk_streaming_ep,
+ NULL,
+};
+
static const struct usb_descriptor_header * const uvc_ss_streaming[] = {
(struct usb_descriptor_header *) &uvc_streaming_intf_alt1,
(struct usb_descriptor_header *) &uvc_ss_streaming_ep,
@@ -197,6 +258,11 @@ static const struct usb_descriptor_header * const uvc_ss_streaming[] = {
NULL,
};
+static const struct usb_descriptor_header * const uvc_ss_bulk_streaming[] = {
+ (struct usb_descriptor_header *)&uvc_ss_bulk_streaming_ep,
+ (struct usb_descriptor_header *)&uvc_ss_bulk_streaming_comp,
+ NULL,
+};
void uvc_set_trace_param(unsigned int trace)
{
uvc_gadget_trace_param = trace;
@@ -214,6 +280,10 @@ uvc_function_ep0_complete(struct usb_ep *ep, struct usb_request *req)
struct v4l2_event v4l2_event;
struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
+ uvc_trace(UVC_TRACE_CONTROL,
+ "event_setup_out %d, data len %d\n",
+ uvc->event_setup_out, req->actual);
+
if (uvc->event_setup_out) {
uvc->event_setup_out = 0;
@@ -237,6 +307,11 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
* le16_to_cpu(ctrl->wIndex), le16_to_cpu(ctrl->wLength));
*/
+ uvc_trace(UVC_TRACE_CONTROL,
+ "setup request %02x %02x value %04x index %04x %04x\n",
+ ctrl->bRequestType, ctrl->bRequest, le16_to_cpu(ctrl->wValue),
+ le16_to_cpu(ctrl->wIndex), le16_to_cpu(ctrl->wLength));
+
if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS) {
INFO(f->config->cdev, "invalid request type\n");
return -EINVAL;
@@ -271,15 +346,27 @@ static int
uvc_function_get_alt(struct usb_function *f, unsigned interface)
{
struct uvc_device *uvc = to_uvc(f);
+ struct f_uvc_opts *opts;
INFO(f->config->cdev, "uvc_function_get_alt(%u)\n", interface);
+ opts = fi_to_f_uvc_opts(f->fi);
+
if (interface == uvc->control_intf)
return 0;
else if (interface != uvc->streaming_intf)
return -EINVAL;
- else
+ else if (!opts->streaming_bulk)
return uvc->video.ep->enabled ? 1 : 0;
+ else
+ /*
+ * Alt settings in an interface are supported only for
+ * ISOC endpoints as there are different alt-settings for
+ * zero-bandwidth and full-bandwidth cases, but the same
+ * is not true for BULK endpoints, as they have a single
+ * alt-setting.
+ */
+ return 0;
}
static int
@@ -289,10 +376,13 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
struct usb_composite_dev *cdev = f->config->cdev;
struct v4l2_event v4l2_event;
struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
+ struct f_uvc_opts *opts;
int ret;
INFO(cdev, "uvc_function_set_alt(%u, %u)\n", interface, alt);
+ opts = fi_to_f_uvc_opts(f->fi);
+
if (interface == uvc->control_intf) {
if (alt)
return -EINVAL;
@@ -306,6 +396,14 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
usb_ep_enable(uvc->control_ep);
+ if (uvc->event_suspend) {
+ memset(&v4l2_event, 0, sizeof(v4l2_event));
+ v4l2_event.type = UVC_EVENT_RESUME;
+ v4l2_event_queue(&uvc->vdev, &v4l2_event);
+ uvc->event_suspend = 0;
+ uvc_trace(UVC_TRACE_SUSPEND, "send UVC_EVENT_RESUME\n");
+ }
+
if (uvc->state == UVC_STATE_DISCONNECTED) {
memset(&v4l2_event, 0, sizeof(v4l2_event));
v4l2_event.type = UVC_EVENT_CONNECT;
@@ -321,49 +419,94 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
if (interface != uvc->streaming_intf)
return -EINVAL;
- /* TODO
- if (usb_endpoint_xfer_bulk(&uvc->desc.vs_ep))
- return alt ? -EINVAL : 0;
- */
+ if (!opts->streaming_bulk) {
+ switch (alt) {
+ case 0:
+ if (uvc->state != UVC_STATE_STREAMING)
+ return 0;
- switch (alt) {
- case 0:
- if (uvc->state != UVC_STATE_STREAMING)
+ if (uvc->video.ep)
+ usb_ep_disable(uvc->video.ep);
+
+ memset(&v4l2_event, 0, sizeof(v4l2_event));
+ v4l2_event.type = UVC_EVENT_STREAMOFF;
+ v4l2_event_queue(&uvc->vdev, &v4l2_event);
+
+ uvc->state = UVC_STATE_CONNECTED;
return 0;
- if (uvc->video.ep)
- usb_ep_disable(uvc->video.ep);
+ case 1:
+ if (uvc->state != UVC_STATE_CONNECTED)
+ return 0;
- memset(&v4l2_event, 0, sizeof(v4l2_event));
- v4l2_event.type = UVC_EVENT_STREAMOFF;
- v4l2_event_queue(&uvc->vdev, &v4l2_event);
+ if (!uvc->video.ep)
+ return -EINVAL;
- uvc->state = UVC_STATE_CONNECTED;
- return 0;
+ INFO(cdev, "reset UVC\n");
+ usb_ep_disable(uvc->video.ep);
- case 1:
- if (uvc->state != UVC_STATE_CONNECTED)
- return 0;
+ ret = config_ep_by_speed(f->config->cdev->gadget,
+ &uvc->func, uvc->video.ep);
+ if (ret)
+ return ret;
+ usb_ep_enable(uvc->video.ep);
+
+ memset(&v4l2_event, 0, sizeof(v4l2_event));
+ v4l2_event.type = UVC_EVENT_STREAMON;
+ v4l2_event_queue(&uvc->vdev, &v4l2_event);
+ return USB_GADGET_DELAYED_STATUS;
- if (!uvc->video.ep)
+ default:
return -EINVAL;
+ }
+ } else {
+ switch (uvc->state) {
+ case UVC_STATE_CONNECTED:
+ if (uvc->video.ep &&
+ !uvc->video.ep->enabled) {
+ /*
+ * Enable the video streaming endpoint,
+ * but don't change the 'uvc->state'.
+ */
+ ret = config_ep_by_speed(cdev->gadget,
+ &uvc->func,
+ uvc->video.ep);
+ if (ret)
+ return ret;
+ ret = usb_ep_enable(uvc->video.ep);
+ if (ret)
+ return ret;
+ } else {
+ memset(&v4l2_event, 0, sizeof(v4l2_event));
+ v4l2_event.type = UVC_EVENT_STREAMON;
+ v4l2_event_queue(&uvc->vdev, &v4l2_event);
+
+ uvc->state = UVC_STATE_STREAMING;
+ }
+ return 0;
- INFO(cdev, "reset UVC\n");
- usb_ep_disable(uvc->video.ep);
+ case UVC_STATE_STREAMING:
+ if (!alt) {
+ INFO(cdev, "bulk streaming intf not support alt 0\n");
+ return 0;
+ }
- ret = config_ep_by_speed(f->config->cdev->gadget,
- &(uvc->func), uvc->video.ep);
- if (ret)
- return ret;
- usb_ep_enable(uvc->video.ep);
+ if (uvc->video.ep &&
+ uvc->video.ep->enabled) {
+ ret = usb_ep_disable(uvc->video.ep);
+ if (ret)
+ return ret;
+ }
- memset(&v4l2_event, 0, sizeof(v4l2_event));
- v4l2_event.type = UVC_EVENT_STREAMON;
- v4l2_event_queue(&uvc->vdev, &v4l2_event);
- return USB_GADGET_DELAYED_STATUS;
+ memset(&v4l2_event, 0, sizeof(v4l2_event));
+ v4l2_event.type = UVC_EVENT_STREAMOFF;
+ v4l2_event_queue(&uvc->vdev, &v4l2_event);
+ uvc->state = UVC_STATE_CONNECTED;
+ return 0;
- default:
- return -EINVAL;
+ default:
+ return -EINVAL;
+ }
}
}
@@ -380,11 +523,36 @@ uvc_function_disable(struct usb_function *f)
v4l2_event_queue(&uvc->vdev, &v4l2_event);
uvc->state = UVC_STATE_DISCONNECTED;
+ f->config->cdev->gadget->uvc_enabled = false;
usb_ep_disable(uvc->video.ep);
usb_ep_disable(uvc->control_ep);
}
+static void uvc_function_suspend(struct usb_function *f)
+{
+ struct uvc_device *uvc = to_uvc(f);
+ struct v4l2_event v4l2_event;
+
+ memset(&v4l2_event, 0, sizeof(v4l2_event));
+ v4l2_event.type = UVC_EVENT_SUSPEND;
+ v4l2_event_queue(&uvc->vdev, &v4l2_event);
+ uvc->event_suspend = 1;
+ uvc_trace(UVC_TRACE_SUSPEND, "send UVC_EVENT_SUSPEND\n");
+}
+
+static void uvc_function_resume(struct usb_function *f)
+{
+ struct uvc_device *uvc = to_uvc(f);
+ struct v4l2_event v4l2_event;
+
+ memset(&v4l2_event, 0, sizeof(v4l2_event));
+ v4l2_event.type = UVC_EVENT_RESUME;
+ v4l2_event_queue(&uvc->vdev, &v4l2_event);
+ uvc->event_suspend = 0;
+ uvc_trace(UVC_TRACE_SUSPEND, "send UVC_EVENT_RESUME\n");
+}
+
/* --------------------------------------------------------------------------
* Connection / disconnection
*/
@@ -395,6 +563,8 @@ uvc_function_connect(struct uvc_device *uvc)
struct usb_composite_dev *cdev = uvc->func.config->cdev;
int ret;
+ cdev->gadget->uvc_enabled = true;
+
if ((ret = usb_function_activate(&uvc->func)) < 0)
INFO(cdev, "UVC connect failed with %d\n", ret);
}
@@ -479,32 +649,45 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed)
const struct uvc_descriptor_header * const *uvc_streaming_cls;
const struct usb_descriptor_header * const *uvc_streaming_std;
const struct usb_descriptor_header * const *src;
+ struct usb_interface_descriptor *streaming_intf_alt0;
struct usb_descriptor_header **dst;
struct usb_descriptor_header **hdr;
+ struct f_uvc_opts *opts;
unsigned int control_size;
unsigned int streaming_size;
unsigned int n_desc;
unsigned int bytes;
void *mem;
+ opts = fi_to_f_uvc_opts(uvc->func.fi);
+
switch (speed) {
case USB_SPEED_SUPER:
uvc_control_desc = uvc->desc.ss_control;
uvc_streaming_cls = uvc->desc.ss_streaming;
- uvc_streaming_std = uvc_ss_streaming;
+ if (!opts->streaming_bulk)
+ uvc_streaming_std = uvc_ss_streaming;
+ else
+ uvc_streaming_std = uvc_ss_bulk_streaming;
break;
case USB_SPEED_HIGH:
uvc_control_desc = uvc->desc.fs_control;
uvc_streaming_cls = uvc->desc.hs_streaming;
- uvc_streaming_std = uvc_hs_streaming;
+ if (!opts->streaming_bulk)
+ uvc_streaming_std = uvc_hs_streaming;
+ else
+ uvc_streaming_std = uvc_hs_bulk_streaming;
break;
case USB_SPEED_FULL:
default:
uvc_control_desc = uvc->desc.fs_control;
uvc_streaming_cls = uvc->desc.fs_streaming;
- uvc_streaming_std = uvc_fs_streaming;
+ if (!opts->streaming_bulk)
+ uvc_streaming_std = uvc_fs_streaming;
+ else
+ uvc_streaming_std = uvc_fs_bulk_streaming;
break;
}
@@ -524,12 +707,17 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed)
* uvc_{fs|hs}_streaming
*/
+ if (!opts->streaming_bulk)
+ streaming_intf_alt0 = &uvc_streaming_intf_alt0;
+ else
+ streaming_intf_alt0 = &uvc_bulk_streaming_intf_alt0;
+
/* Count descriptors and compute their size. */
control_size = 0;
streaming_size = 0;
bytes = uvc_iad.bLength + uvc_control_intf.bLength
+ uvc_control_ep.bLength + uvc_control_cs_ep.bLength
- + uvc_streaming_intf_alt0.bLength;
+ + streaming_intf_alt0->bLength;
if (speed == USB_SPEED_SUPER) {
bytes += uvc_ss_control_comp.bLength;
@@ -579,7 +767,7 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed)
UVC_COPY_DESCRIPTOR(mem, dst, &uvc_ss_control_comp);
UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_cs_ep);
- UVC_COPY_DESCRIPTOR(mem, dst, &uvc_streaming_intf_alt0);
+ UVC_COPY_DESCRIPTOR(mem, dst, streaming_intf_alt0);
uvc_streaming_header = mem;
UVC_COPY_DESCRIPTORS(mem, dst,
@@ -604,15 +792,24 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
struct usb_ep *ep;
struct f_uvc_opts *opts;
int ret = -EINVAL;
+ u8 address;
INFO(cdev, "uvc_function_bind\n");
opts = fi_to_f_uvc_opts(f->fi);
/* Sanity check the streaming endpoint module parameters.
*/
- opts->streaming_interval = clamp(opts->streaming_interval, 1U, 16U);
- opts->streaming_maxpacket = clamp(opts->streaming_maxpacket, 1U, 3072U);
- opts->streaming_maxburst = min(opts->streaming_maxburst, 15U);
+ if (!opts->streaming_bulk) {
+ opts->streaming_interval = clamp(opts->streaming_interval,
+ 1U, 16U);
+ opts->streaming_maxpacket = clamp(opts->streaming_maxpacket,
+ 1U, 3072U);
+ opts->streaming_maxburst = min(opts->streaming_maxburst, 15U);
+ } else {
+ opts->streaming_maxpacket = clamp(opts->streaming_maxpacket,
+ 1U, 1024U);
+ opts->streaming_maxburst = min(opts->streaming_maxburst, 15U);
+ }
/* For SS, wMaxPacketSize has to be 1024 if bMaxBurst is not 0 */
if (opts->streaming_maxburst &&
@@ -639,26 +836,46 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
max_packet_size = opts->streaming_maxpacket / 3;
}
- uvc_fs_streaming_ep.wMaxPacketSize =
- cpu_to_le16(min(opts->streaming_maxpacket, 1023U));
- uvc_fs_streaming_ep.bInterval = opts->streaming_interval;
-
- uvc_hs_streaming_ep.wMaxPacketSize =
- cpu_to_le16(max_packet_size | ((max_packet_mult - 1) << 11));
-
- /* A high-bandwidth endpoint must specify a bInterval value of 1 */
- if (max_packet_mult > 1)
- uvc_hs_streaming_ep.bInterval = 1;
- else
- uvc_hs_streaming_ep.bInterval = opts->streaming_interval;
-
- uvc_ss_streaming_ep.wMaxPacketSize = cpu_to_le16(max_packet_size);
- uvc_ss_streaming_ep.bInterval = opts->streaming_interval;
- uvc_ss_streaming_comp.bmAttributes = max_packet_mult - 1;
- uvc_ss_streaming_comp.bMaxBurst = opts->streaming_maxburst;
- uvc_ss_streaming_comp.wBytesPerInterval =
- cpu_to_le16(max_packet_size * max_packet_mult *
- (opts->streaming_maxburst + 1));
+ if (!opts->streaming_bulk) {
+ uvc_fs_streaming_ep.wMaxPacketSize =
+ cpu_to_le16(min(opts->streaming_maxpacket, 1023U));
+ uvc_fs_streaming_ep.bInterval = opts->streaming_interval;
+
+ uvc_hs_streaming_ep.wMaxPacketSize =
+ cpu_to_le16(max_packet_size |
+ ((max_packet_mult - 1) << 11));
+
+ /* A high-bandwidth endpoint must specify a bInterval value of 1 */
+ if (max_packet_mult > 1)
+ uvc_hs_streaming_ep.bInterval = 1;
+ else
+ uvc_hs_streaming_ep.bInterval = opts->streaming_interval;
+
+ uvc_ss_streaming_ep.wMaxPacketSize =
+ cpu_to_le16(max_packet_size);
+ uvc_ss_streaming_ep.bInterval = opts->streaming_interval;
+ uvc_ss_streaming_comp.bmAttributes = max_packet_mult - 1;
+ uvc_ss_streaming_comp.bMaxBurst = opts->streaming_maxburst;
+ uvc_ss_streaming_comp.wBytesPerInterval =
+ cpu_to_le16(max_packet_size * max_packet_mult *
+ (opts->streaming_maxburst + 1));
+ } else {
+ uvc_fs_bulk_streaming_ep.wMaxPacketSize =
+ cpu_to_le16(min(opts->streaming_maxpacket, 64U));
+
+ uvc_hs_bulk_streaming_ep.wMaxPacketSize =
+ cpu_to_le16(min(opts->streaming_maxpacket, 512U));
+
+ uvc_ss_bulk_streaming_ep.wMaxPacketSize =
+ cpu_to_le16(max_packet_size);
+ uvc_ss_bulk_streaming_comp.bMaxBurst = opts->streaming_maxburst;
+ /*
+ * As per USB 3.1 spec "Table 9-26. SuperSpeed Endpoint
+ * Companion Descriptor", the wBytesPerInterval must be
+ * set to zero for bulk endpoints.
+ */
+ uvc_ss_bulk_streaming_comp.wBytesPerInterval = 0;
+ }
/* Allocate endpoints. */
ep = usb_ep_autoconfig(cdev->gadget, &uvc_control_ep);
@@ -668,23 +885,60 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
}
uvc->control_ep = ep;
- if (gadget_is_superspeed(c->cdev->gadget))
- ep = usb_ep_autoconfig_ss(cdev->gadget, &uvc_ss_streaming_ep,
- &uvc_ss_streaming_comp);
- else if (gadget_is_dualspeed(cdev->gadget))
- ep = usb_ep_autoconfig(cdev->gadget, &uvc_hs_streaming_ep);
- else
- ep = usb_ep_autoconfig(cdev->gadget, &uvc_fs_streaming_ep);
+ if (gadget_is_superspeed(c->cdev->gadget)) {
+ if (!opts->streaming_bulk)
+ ep = usb_ep_autoconfig_ss(cdev->gadget,
+ &uvc_ss_streaming_ep,
+ &uvc_ss_streaming_comp);
+ else
+ ep = usb_ep_autoconfig_ss(cdev->gadget,
+ &uvc_ss_bulk_streaming_ep,
+ &uvc_ss_bulk_streaming_comp);
+ } else if (gadget_is_dualspeed(cdev->gadget)) {
+ if (!opts->streaming_bulk) {
+ ep = usb_ep_autoconfig(cdev->gadget,
+ &uvc_hs_streaming_ep);
+ } else {
+ ep = usb_ep_autoconfig(cdev->gadget,
+ &uvc_hs_bulk_streaming_ep);
+ /*
+ * In ep_matches(), it will set wMaxPacketSize to 64
+ * bytes if ep is Bulk and ep_comp is NULL for hs/fs
+ * bulk maxpacket. So we need to set hs bulk maxpacket
+ * 512 bytes again here.
+ */
+ uvc_hs_bulk_streaming_ep.wMaxPacketSize =
+ cpu_to_le16(min(opts->streaming_maxpacket,
+ 512U));
+ }
+ } else {
+ if (!opts->streaming_bulk)
+ ep = usb_ep_autoconfig(cdev->gadget,
+ &uvc_fs_streaming_ep);
+ else
+ ep = usb_ep_autoconfig(cdev->gadget,
+ &uvc_fs_bulk_streaming_ep);
+ }
if (!ep) {
INFO(cdev, "Unable to allocate streaming EP\n");
goto error;
}
uvc->video.ep = ep;
+ address = uvc->video.ep->address;
+
+ if (!opts->streaming_bulk) {
+ uvc_fs_streaming_ep.bEndpointAddress = address;
+ uvc_hs_streaming_ep.bEndpointAddress = address;
+ uvc_ss_streaming_ep.bEndpointAddress = address;
+ } else {
+ uvc_fs_bulk_streaming_ep.bEndpointAddress = address;
+ uvc_hs_bulk_streaming_ep.bEndpointAddress = address;
+ uvc_ss_bulk_streaming_ep.bEndpointAddress = address;
+ }
- uvc_fs_streaming_ep.bEndpointAddress = uvc->video.ep->address;
- uvc_hs_streaming_ep.bEndpointAddress = uvc->video.ep->address;
- uvc_ss_streaming_ep.bEndpointAddress = uvc->video.ep->address;
+ if (opts->device_name)
+ uvc_en_us_strings[UVC_STRING_CONTROL_IDX].s = opts->device_name;
us = usb_gstrings_attach(cdev, uvc_function_strings,
ARRAY_SIZE(uvc_en_us_strings));
@@ -695,8 +949,12 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
uvc_iad.iFunction = us[UVC_STRING_CONTROL_IDX].id;
uvc_control_intf.iInterface = us[UVC_STRING_CONTROL_IDX].id;
ret = us[UVC_STRING_STREAMING_IDX].id;
- uvc_streaming_intf_alt0.iInterface = ret;
- uvc_streaming_intf_alt1.iInterface = ret;
+ if (!opts->streaming_bulk) {
+ uvc_streaming_intf_alt0.iInterface = ret;
+ uvc_streaming_intf_alt1.iInterface = ret;
+ } else {
+ uvc_bulk_streaming_intf_alt0.iInterface = ret;
+ }
/* Allocate interface IDs. */
if ((ret = usb_interface_id(c, f)) < 0)
@@ -704,12 +962,20 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
uvc_iad.bFirstInterface = ret;
uvc_control_intf.bInterfaceNumber = ret;
uvc->control_intf = ret;
+ opts->control_interface = ret;
if ((ret = usb_interface_id(c, f)) < 0)
goto error;
- uvc_streaming_intf_alt0.bInterfaceNumber = ret;
- uvc_streaming_intf_alt1.bInterfaceNumber = ret;
+
+ if (!opts->streaming_bulk) {
+ uvc_streaming_intf_alt0.bInterfaceNumber = ret;
+ uvc_streaming_intf_alt1.bInterfaceNumber = ret;
+ } else {
+ uvc_bulk_streaming_intf_alt0.bInterfaceNumber = ret;
+ }
+
uvc->streaming_intf = ret;
+ opts->streaming_interface = ret;
/* Copy descriptors */
f->fs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_FULL);
@@ -752,11 +1018,19 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
goto error;
}
+ uvc->video.async_wq = alloc_workqueue("uvcgvideo",
+ WQ_UNBOUND | WQ_HIGHPRI,
+ 0);
+ if (!uvc->video.async_wq)
+ goto error;
+
/* Initialise video. */
ret = uvcg_video_init(&uvc->video);
if (ret < 0)
goto error;
+ if (opts->streaming_bulk)
+ uvc->video.max_payload_size = uvc->video.imagesize;
/* Register a V4L2 device. */
ret = uvc_register_video(uvc);
if (ret < 0) {
@@ -786,6 +1060,13 @@ static void uvc_free_inst(struct usb_function_instance *f)
struct f_uvc_opts *opts = fi_to_f_uvc_opts(f);
mutex_destroy(&opts->lock);
+
+ if (opts->device_name_allocated) {
+ opts->device_name_allocated = false;
+ kfree(opts->device_name);
+ opts->device_name = NULL;
+ }
+
kfree(opts);
}
@@ -795,8 +1076,10 @@ static struct usb_function_instance *uvc_alloc_inst(void)
struct uvc_camera_terminal_descriptor *cd;
struct uvc_processing_unit_descriptor *pd;
struct uvc_output_terminal_descriptor *od;
+ struct UVC_EXTENSION_UNIT_DESCRIPTOR(1, 1) *ed;
struct uvc_color_matching_descriptor *md;
struct uvc_descriptor_header **ctl_cls;
+ int ret;
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
if (!opts)
@@ -842,6 +1125,34 @@ static struct usb_function_instance *uvc_alloc_inst(void)
od->bSourceID = 2;
od->iTerminal = 0;
+ ed = &opts->uvc_extension;
+ ed->bLength = UVC_DT_EXTENSION_UNIT_SIZE(1, 1);
+ ed->bDescriptorType = USB_DT_CS_INTERFACE;
+ ed->bDescriptorSubType = UVC_VC_EXTENSION_UNIT;
+ ed->bUnitID = 6;
+ ed->guidExtensionCode[0] = 0xa2;
+ ed->guidExtensionCode[1] = 0x9e;
+ ed->guidExtensionCode[2] = 0x76;
+ ed->guidExtensionCode[3] = 0x41;
+ ed->guidExtensionCode[4] = 0xde;
+ ed->guidExtensionCode[5] = 0x04;
+ ed->guidExtensionCode[6] = 0x47;
+ ed->guidExtensionCode[7] = 0xe3;
+ ed->guidExtensionCode[8] = 0x8b;
+ ed->guidExtensionCode[9] = 0x2b;
+ ed->guidExtensionCode[10] = 0xf4;
+ ed->guidExtensionCode[11] = 0x34;
+ ed->guidExtensionCode[12] = 0x1a;
+ ed->guidExtensionCode[13] = 0xff;
+ ed->guidExtensionCode[14] = 0x00;
+ ed->guidExtensionCode[15] = 0x3b;
+ ed->bNumControls = 3;
+ ed->bNrInPins = 1;
+ ed->baSourceID[0] = 2;
+ ed->bControlSize = 1;
+ ed->bmControls[0] = 7;
+ ed->iExtension = 0;
+
md = &opts->uvc_color_matching;
md->bLength = UVC_DT_COLOR_MATCHING_SIZE;
md->bDescriptorType = USB_DT_CS_INTERFACE;
@@ -856,7 +1167,8 @@ static struct usb_function_instance *uvc_alloc_inst(void)
ctl_cls[1] = (struct uvc_descriptor_header *)cd;
ctl_cls[2] = (struct uvc_descriptor_header *)pd;
ctl_cls[3] = (struct uvc_descriptor_header *)od;
- ctl_cls[4] = NULL; /* NULL-terminate */
+ ctl_cls[4] = (struct uvc_descriptor_header *)ed;
+ ctl_cls[5] = NULL; /* NULL-terminate */
opts->fs_control =
(const struct uvc_descriptor_header * const *)ctl_cls;
@@ -866,14 +1178,22 @@ static struct usb_function_instance *uvc_alloc_inst(void)
ctl_cls[1] = (struct uvc_descriptor_header *)cd;
ctl_cls[2] = (struct uvc_descriptor_header *)pd;
ctl_cls[3] = (struct uvc_descriptor_header *)od;
- ctl_cls[4] = NULL; /* NULL-terminate */
+ ctl_cls[4] = (struct uvc_descriptor_header *)ed;
+ ctl_cls[5] = NULL; /* NULL-terminate */
opts->ss_control =
(const struct uvc_descriptor_header * const *)ctl_cls;
opts->streaming_interval = 1;
opts->streaming_maxpacket = 1024;
+ opts->uvc_num_request = UVC_NUM_REQUESTS;
+ opts->pm_qos_latency = 0;
+
+ ret = uvcg_attach_configfs(opts);
+ if (ret < 0) {
+ kfree(opts);
+ return ERR_PTR(ret);
+ }
- uvcg_attach_configfs(opts);
return &opts->func_inst;
}
@@ -900,6 +1220,9 @@ static void uvc_unbind(struct usb_configuration *c, struct usb_function *f)
usb_ep_free_request(cdev->gadget->ep0, uvc->control_req);
kfree(uvc->control_buf);
+ if (uvc->video.async_wq)
+ destroy_workqueue(uvc->video.async_wq);
+
usb_free_all_descriptors(f);
}
@@ -951,6 +1274,8 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi)
uvc->func.disable = uvc_function_disable;
uvc->func.setup = uvc_function_setup;
uvc->func.free_func = uvc_free;
+ uvc->func.suspend = uvc_function_suspend;
+ uvc->func.resume = uvc_function_resume;
uvc->func.bind_deactivated = true;
return &uvc->func;
diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c
index 168303f21..347633399 100644
--- a/drivers/usb/gadget/function/u_audio.c
+++ b/drivers/usb/gadget/function/u_audio.c
@@ -13,6 +13,8 @@
*/
#include <linux/module.h>
+#include <linux/usb/audio.h>
+#include <sound/control.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@@ -23,6 +25,10 @@
#define PRD_SIZE_MAX PAGE_SIZE
#define MIN_PERIODS 4
+#define CLK_PPM_GROUP_SIZE 20
+
+static struct class *audio_class;
+
struct uac_req {
struct uac_rtd_params *pp; /* parent param */
struct usb_request *req;
@@ -260,18 +266,17 @@ static int uac_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_uac_chip *uac = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
- struct g_audio *audio_dev;
+ struct g_audio *audio_dev = uac->audio_dev;
struct uac_params *params;
int p_ssize, c_ssize;
int p_srate, c_srate;
int p_chmask, c_chmask;
- audio_dev = uac->audio_dev;
params = &audio_dev->params;
p_ssize = params->p_ssize;
c_ssize = params->c_ssize;
- p_srate = params->p_srate;
- c_srate = params->c_srate;
+ p_srate = params->p_srate_active;
+ c_srate = params->c_srate_active;
p_chmask = params->p_chmask;
c_chmask = params->c_chmask;
uac->p_residue = 0;
@@ -322,6 +327,52 @@ static int uac_pcm_open(struct snd_pcm_substream *substream)
return 0;
}
+static int uac_pcm_rate_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 324000;
+ return 0;
+}
+
+static int uac_pcm_rate_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_uac_chip *uac = snd_kcontrol_chip(kcontrol);
+ struct g_audio *audio_dev = uac->audio_dev;
+ struct uac_params *params = &audio_dev->params;
+
+ if (kcontrol->private_value == SNDRV_PCM_STREAM_CAPTURE)
+ ucontrol->value.integer.value[0] = params->c_srate_active;
+ else if (kcontrol->private_value == SNDRV_PCM_STREAM_PLAYBACK)
+ ucontrol->value.integer.value[0] = params->p_srate_active;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static struct snd_kcontrol_new uac_pcm_controls[] = {
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "Capture Rate",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = uac_pcm_rate_info,
+ .get = uac_pcm_rate_get,
+ .private_value = SNDRV_PCM_STREAM_CAPTURE,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "Playback Rate",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = uac_pcm_rate_info,
+ .get = uac_pcm_rate_get,
+ .private_value = SNDRV_PCM_STREAM_PLAYBACK,
+},
+};
+
/* ALSA cries without these function pointers */
static int uac_pcm_null(struct snd_pcm_substream *substream)
{
@@ -372,6 +423,104 @@ static inline void free_ep(struct uac_rtd_params *prm, struct usb_ep *ep)
dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__);
}
+static struct snd_kcontrol *u_audio_get_ctl(struct g_audio *audio_dev,
+ const char *name)
+{
+ struct snd_ctl_elem_id elem_id;
+
+ memset(&elem_id, 0, sizeof(elem_id));
+ elem_id.iface = SNDRV_CTL_ELEM_IFACE_PCM;
+ strlcpy(elem_id.name, name, sizeof(elem_id.name));
+ return snd_ctl_find_id(audio_dev->uac->card, &elem_id);
+}
+
+int u_audio_set_capture_srate(struct g_audio *audio_dev, int srate)
+{
+ struct snd_kcontrol *ctl = u_audio_get_ctl(audio_dev, "Capture Rate");
+ struct uac_params *params = &audio_dev->params;
+ int i;
+
+ for (i = 0; i < UAC_MAX_RATES; i++) {
+ if (params->c_srate[i] == srate) {
+ audio_dev->usb_state[SET_SAMPLE_RATE_OUT] = true;
+ schedule_work(&audio_dev->work);
+
+ params->c_srate_active = srate;
+ snd_ctl_notify(audio_dev->uac->card,
+ SNDRV_CTL_EVENT_MASK_VALUE, &ctl->id);
+ return 0;
+ }
+ if (params->c_srate[i] == 0)
+ break;
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(u_audio_set_capture_srate);
+
+static void u_audio_set_playback_pktsize(struct g_audio *audio_dev, int srate)
+{
+ struct uac_params *params = &audio_dev->params;
+ struct snd_uac_chip *uac = audio_dev->uac;
+ struct usb_gadget *gadget = audio_dev->gadget;
+ const struct usb_endpoint_descriptor *ep_desc;
+ struct uac_rtd_params *prm;
+ unsigned int factor;
+
+ prm = &uac->p_prm;
+ /* set srate before starting playback, epin is not configured */
+ if (!prm->ep_enabled)
+ return;
+
+ ep_desc = audio_dev->in_ep->desc;
+
+ /* pre-calculate the playback endpoint's interval */
+ if (gadget->speed == USB_SPEED_FULL)
+ factor = 1000;
+ else
+ factor = 8000;
+
+ /* pre-compute some values for iso_complete() */
+ uac->p_framesize = params->p_ssize *
+ num_channels(params->p_chmask);
+ uac->p_interval = factor / (1 << (ep_desc->bInterval - 1));
+ uac->p_pktsize = min_t(unsigned int,
+ uac->p_framesize *
+ (params->p_srate_active / uac->p_interval),
+ prm->max_psize);
+
+ if (uac->p_pktsize < prm->max_psize)
+ uac->p_pktsize_residue = uac->p_framesize *
+ (params->p_srate_active % uac->p_interval);
+ else
+ uac->p_pktsize_residue = 0;
+}
+
+int u_audio_set_playback_srate(struct g_audio *audio_dev, int srate)
+{
+ struct snd_kcontrol *ctl = u_audio_get_ctl(audio_dev, "Playback Rate");
+ struct uac_params *params = &audio_dev->params;
+ int i;
+
+ for (i = 0; i < UAC_MAX_RATES; i++) {
+ if (params->p_srate[i] == srate) {
+ audio_dev->usb_state[SET_SAMPLE_RATE_IN] = true;
+ schedule_work(&audio_dev->work);
+
+ params->p_srate_active = srate;
+ u_audio_set_playback_pktsize(audio_dev, srate);
+ snd_ctl_notify(audio_dev->uac->card,
+ SNDRV_CTL_EVENT_MASK_VALUE, &ctl->id);
+ return 0;
+ }
+ if (params->p_srate[i] == 0)
+ break;
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(u_audio_set_playback_srate);
+
int u_audio_start_capture(struct g_audio *audio_dev)
{
struct snd_uac_chip *uac = audio_dev->uac;
@@ -383,6 +532,19 @@ int u_audio_start_capture(struct g_audio *audio_dev)
struct uac_params *params = &audio_dev->params;
int req_len, i;
+ /*
+ * For better compatibility on some PC Hosts which
+ * failed to send SetInterface(AltSet=0) to stop
+ * capture last time. It needs to stop capture
+ * prior to start capture next time.
+ */
+ if (audio_dev->stream_state[STATE_OUT])
+ u_audio_stop_capture(audio_dev);
+
+ audio_dev->usb_state[SET_INTERFACE_OUT] = true;
+ audio_dev->stream_state[STATE_OUT] = true;
+ schedule_work(&audio_dev->work);
+
ep = audio_dev->out_ep;
prm = &uac->c_prm;
config_ep_by_speed(gadget, &audio_dev->func, ep);
@@ -420,6 +582,10 @@ void u_audio_stop_capture(struct g_audio *audio_dev)
struct snd_uac_chip *uac = audio_dev->uac;
free_ep(&uac->c_prm, audio_dev->out_ep);
+
+ audio_dev->usb_state[SET_INTERFACE_OUT] = true;
+ audio_dev->stream_state[STATE_OUT] = false;
+ schedule_work(&audio_dev->work);
}
EXPORT_SYMBOL_GPL(u_audio_stop_capture);
@@ -427,46 +593,38 @@ int u_audio_start_playback(struct g_audio *audio_dev)
{
struct snd_uac_chip *uac = audio_dev->uac;
struct usb_gadget *gadget = audio_dev->gadget;
- struct device *dev = &gadget->dev;
+ struct device *dev = audio_dev->device;
struct usb_request *req;
struct usb_ep *ep;
struct uac_rtd_params *prm;
struct uac_params *params = &audio_dev->params;
- unsigned int factor, rate;
- const struct usb_endpoint_descriptor *ep_desc;
int req_len, i;
+ /*
+ * For better compatibility on some PC Hosts which
+ * failed to send SetInterface(AltSet=0) to stop
+ * playback last time. It needs to stop playback
+ * prior to start playback next time.
+ */
+ if (audio_dev->stream_state[STATE_IN])
+ u_audio_stop_playback(audio_dev);
+
+ audio_dev->usb_state[SET_INTERFACE_IN] = true;
+ audio_dev->stream_state[STATE_IN] = true;
+ schedule_work(&audio_dev->work);
+
+ dev_dbg(dev, "start playback with rate %d\n", params->p_srate_active);
ep = audio_dev->in_ep;
prm = &uac->p_prm;
config_ep_by_speed(gadget, &audio_dev->func, ep);
- ep_desc = ep->desc;
-
- /* pre-calculate the playback endpoint's interval */
- if (gadget->speed == USB_SPEED_FULL)
- factor = 1000;
- else
- factor = 8000;
-
- /* pre-compute some values for iso_complete() */
- uac->p_framesize = params->p_ssize *
- num_channels(params->p_chmask);
- rate = params->p_srate * uac->p_framesize;
- uac->p_interval = factor / (1 << (ep_desc->bInterval - 1));
- uac->p_pktsize = min_t(unsigned int, rate / uac->p_interval,
- prm->max_psize);
-
- if (uac->p_pktsize < prm->max_psize)
- uac->p_pktsize_residue = rate % uac->p_interval;
- else
- uac->p_pktsize_residue = 0;
+ prm->ep_enabled = true;
+ usb_ep_enable(ep);
+ u_audio_set_playback_pktsize(audio_dev, params->p_srate_active);
req_len = uac->p_pktsize;
uac->p_residue = 0;
- prm->ep_enabled = true;
- usb_ep_enable(ep);
-
for (i = 0; i < params->req_number; i++) {
if (!prm->ureq[i].req) {
req = usb_ep_alloc_request(ep, GFP_ATOMIC);
@@ -496,9 +654,242 @@ void u_audio_stop_playback(struct g_audio *audio_dev)
struct snd_uac_chip *uac = audio_dev->uac;
free_ep(&uac->p_prm, audio_dev->in_ep);
+
+ audio_dev->usb_state[SET_INTERFACE_IN] = true;
+ audio_dev->stream_state[STATE_IN] = false;
+ schedule_work(&audio_dev->work);
}
EXPORT_SYMBOL_GPL(u_audio_stop_playback);
+int u_audio_fu_set_cmd(struct usb_audio_control *con, u8 cmd, int value)
+{
+ struct g_audio *audio_dev = (struct g_audio *)con->context;
+ struct uac_params *params = &audio_dev->params;
+
+ switch (cmd) {
+ case UAC_SET_CUR:
+ if (!strncmp(con->name, "Capture Mute", 12)) {
+ params->c_mute = value;
+ audio_dev->usb_state[SET_MUTE_OUT] = true;
+ } else if (!strncmp(con->name, "Capture Volume", 14)) {
+ params->c_volume = value;
+ audio_dev->usb_state[SET_VOLUME_OUT] = true;
+ } else if (!strncmp(con->name, "Playback Mute", 13)) {
+ params->p_mute = value;
+ audio_dev->usb_state[SET_MUTE_IN] = true;
+ } else if (!strncmp(con->name, "Playback Volume", 15)) {
+ params->p_volume = value;
+ audio_dev->usb_state[SET_VOLUME_IN] = true;
+ }
+ break;
+ case UAC_SET_RES:
+ /* fall through */
+ default:
+ return 0;
+ }
+
+ con->data[cmd] = value;
+ schedule_work(&audio_dev->work);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(u_audio_fu_set_cmd);
+
+int u_audio_fu_get_cmd(struct usb_audio_control *con, u8 cmd)
+{
+ struct g_audio *audio_dev = (struct g_audio *)con->context;
+
+ dev_dbg(audio_dev->device, "GET_CMD con %s cmd %d data %d\n",
+ con->name, cmd, (int16_t)con->data[cmd]);
+ return con->data[cmd];
+}
+EXPORT_SYMBOL_GPL(u_audio_fu_get_cmd);
+
+static void g_audio_work(struct work_struct *data)
+{
+ struct g_audio *audio = container_of(data, struct g_audio, work);
+ struct uac_params *params = &audio->params;
+ char *uac_event[4] = { NULL, NULL, NULL, NULL };
+ char str[19];
+ signed short volume;
+ int i;
+
+ for (i = 0; i < SET_USB_STATE_MAX; i++) {
+ if (!audio->usb_state[i])
+ continue;
+
+ switch (i) {
+ case SET_INTERFACE_OUT:
+ uac_event[0] = "USB_STATE=SET_INTERFACE";
+ uac_event[1] = "STREAM_DIRECTION=OUT";
+ uac_event[2] = audio->stream_state[STATE_OUT] ?
+ "STREAM_STATE=ON" : "STREAM_STATE=OFF";
+ break;
+ case SET_INTERFACE_IN:
+ uac_event[0] = "USB_STATE=SET_INTERFACE";
+ uac_event[1] = "STREAM_DIRECTION=IN";
+ uac_event[2] = audio->stream_state[STATE_IN] ?
+ "STREAM_STATE=ON" : "STREAM_STATE=OFF";
+ break;
+ case SET_SAMPLE_RATE_OUT:
+ uac_event[0] = "USB_STATE=SET_SAMPLE_RATE";
+ uac_event[1] = "STREAM_DIRECTION=OUT";
+ snprintf(str, sizeof(str), "SAMPLE_RATE=%d",
+ params->c_srate_active);
+ uac_event[2] = str;
+ break;
+ case SET_SAMPLE_RATE_IN:
+ uac_event[0] = "USB_STATE=SET_SAMPLE_RATE";
+ uac_event[1] = "STREAM_DIRECTION=IN";
+ snprintf(str, sizeof(str), "SAMPLE_RATE=%d",
+ params->p_srate_active);
+ uac_event[2] = str;
+ break;
+ case SET_MUTE_OUT:
+ uac_event[0] = "USB_STATE=SET_MUTE";
+ uac_event[1] = "STREAM_DIRECTION=OUT";
+ snprintf(str, sizeof(str), "MUTE=%d", params->c_mute);
+ uac_event[2] = str;
+ break;
+ case SET_MUTE_IN:
+ uac_event[0] = "USB_STATE=SET_MUTE";
+ uac_event[1] = "STREAM_DIRECTION=IN";
+ snprintf(str, sizeof(str), "MUTE=%d", params->p_mute);
+ uac_event[2] = str;
+ break;
+ case SET_VOLUME_OUT:
+ uac_event[0] = "USB_STATE=SET_VOLUME";
+ uac_event[1] = "STREAM_DIRECTION=OUT";
+ volume = (signed short)params->c_volume;
+ volume /= UAC_VOLUME_RES;
+ snprintf(str, sizeof(str), "VOLUME=%d%%", volume + 50);
+ uac_event[2] = str;
+ break;
+ case SET_VOLUME_IN:
+ uac_event[0] = "USB_STATE=SET_VOLUME";
+ uac_event[1] = "STREAM_DIRECTION=IN";
+ volume = (signed short)params->p_volume;
+ volume /= UAC_VOLUME_RES;
+ snprintf(str, sizeof(str), "VOLUME=%d%%", volume + 50);
+ uac_event[2] = str;
+ break;
+ case SET_AUDIO_CLK:
+ uac_event[0] = "USB_STATE=SET_AUDIO_CLK";
+ snprintf(str, sizeof(str), "PPM=%d", params->ppm);
+ uac_event[1] = str;
+ default:
+ break;
+ }
+
+ audio->usb_state[i] = false;
+ kobject_uevent_env(&audio->device->kobj, KOBJ_CHANGE,
+ uac_event);
+ dev_dbg(audio->device, "%s: sent uac uevent %s %s %s\n",
+ __func__, uac_event[0], uac_event[1], uac_event[2]);
+ }
+}
+
+static void ppm_calculate_work(struct work_struct *data)
+{
+ struct g_audio *g_audio = container_of(data, struct g_audio,
+ ppm_work.work);
+ struct usb_gadget *gadget = g_audio->gadget;
+ uint32_t frame_number, fn_msec, clk_msec;
+ struct frame_number_data *fn = g_audio->fn;
+ uint64_t time_now, time_msec_tmp;
+ int32_t ppm;
+ static int32_t ppms[CLK_PPM_GROUP_SIZE];
+ static int32_t ppm_sum;
+ int32_t cnt = fn->second % CLK_PPM_GROUP_SIZE;
+
+ time_now = ktime_get_raw();
+ frame_number = gadget->ops->get_frame(gadget);
+
+ if (g_audio->fn->time_last &&
+ time_now - g_audio->fn->time_last > 1500000000ULL)
+ dev_warn(g_audio->device, "PPM work scheduled too slow!\n");
+
+ g_audio->fn->time_last = time_now;
+
+ /*
+ * If usb is disconnected, the controller will not receive the
+ * SoF signal and frame number will be invalid. Because we can't
+ * get accurate time of disconnect and whether the gadget will be
+ * plugged into the same host next time or not. We must clear all
+ * statistics.
+ */
+ if (gadget->state != USB_STATE_CONFIGURED) {
+ memset(g_audio->fn, 0, sizeof(*g_audio->fn));
+ dev_dbg(g_audio->device, "Disconnect. frame number is cleared\n");
+ goto out;
+ }
+
+ /* Fist statistic to record begin frame number and system time */
+ if (!g_audio->fn->second++) {
+ g_audio->fn->time_begin = g_audio->fn->time_last;
+ g_audio->fn->fn_begin = frame_number;
+ g_audio->fn->fn_last = frame_number;
+ goto out;
+ }
+
+ /*
+ * For DWC3 Controller, only 13 bits is used to store frame(micro)
+ * number. In other words, the frame number will overflow at most
+ * 2.047 seconds. We add another registor fn_overflow the record
+ * total frame number.
+ */
+ if (frame_number <= g_audio->fn->fn_last)
+ g_audio->fn->fn_overflow++;
+ g_audio->fn->fn_last = frame_number;
+
+ if (!g_audio->fn->fn_overflow)
+ goto out;
+
+ /* The lower 3 bits represent micro number frame, we don't need it */
+ fn_msec = (((fn->fn_overflow - 1) << 14) +
+ (BIT(14) + fn->fn_last - fn->fn_begin) + BIT(2)) >> 3;
+ time_msec_tmp = fn->time_last - fn->time_begin + 500000ULL;
+ do_div(time_msec_tmp, 1000000U);
+ clk_msec = (uint32_t)time_msec_tmp;
+
+ /*
+ * According to the definition of ppm:
+ * host_clk = (1 + ppm / 1000000) * gadget_clk
+ * we can get:
+ * ppm = (host_clk - gadget_clk) * 1000000 / gadget_clk
+ */
+ ppm = (fn_msec > clk_msec) ?
+ (fn_msec - clk_msec) * 1000000L / clk_msec :
+ -((clk_msec - fn_msec) * 1000000L / clk_msec);
+
+ ppm_sum = ppm_sum - ppms[cnt] + ppm;
+ ppms[cnt] = ppm;
+
+ dev_dbg(g_audio->device,
+ "frame %u msec %u ppm_calc %d ppm_avage(%d) %d\n",
+ fn_msec, clk_msec, ppm, CLK_PPM_GROUP_SIZE,
+ ppm_sum / CLK_PPM_GROUP_SIZE);
+
+ /*
+ * We calculate the average of ppm over a period of time. If the
+ * latest frame number is too far from the average, no event will
+ * be sent.
+ */
+ if (abs(ppm_sum / CLK_PPM_GROUP_SIZE - ppm) < 3) {
+ ppm = ppm_sum > 0 ?
+ (ppm_sum + CLK_PPM_GROUP_SIZE / 2) / CLK_PPM_GROUP_SIZE :
+ (ppm_sum - CLK_PPM_GROUP_SIZE / 2) / CLK_PPM_GROUP_SIZE;
+ if (ppm != g_audio->params.ppm) {
+ g_audio->params.ppm = ppm;
+ g_audio->usb_state[SET_AUDIO_CLK] = true;
+ schedule_work(&g_audio->work);
+ }
+ }
+
+out:
+ schedule_delayed_work(&g_audio->ppm_work, 1 * HZ);
+}
+
int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
const char *card_name)
{
@@ -508,6 +899,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
struct uac_params *params;
int p_chmask, c_chmask;
int err;
+ int i;
if (!g_audio)
return -EINVAL;
@@ -522,6 +914,12 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
p_chmask = params->p_chmask;
c_chmask = params->c_chmask;
+ g_audio->fn = kzalloc(sizeof(*g_audio->fn), GFP_KERNEL);
+ if (!g_audio->fn) {
+ err = -ENOMEM;
+ goto fail;
+ }
+
if (c_chmask) {
struct uac_rtd_params *prm = &uac->c_prm;
@@ -597,7 +995,28 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX);
+ /* Add controls */
+ for (i = 0; i < ARRAY_SIZE(uac_pcm_controls); i++) {
+ err = snd_ctl_add(card,
+ snd_ctl_new1(&uac_pcm_controls[i], uac));
+ if (err < 0)
+ goto snd_fail;
+ }
+
err = snd_card_register(card);
+ if (err < 0)
+ goto snd_fail;
+
+ g_audio->device = device_create(audio_class, NULL, MKDEV(0, 0), NULL,
+ "%s", g_audio->uac->card->longname);
+ if (IS_ERR(g_audio->device)) {
+ err = PTR_ERR(g_audio->device);
+ goto snd_fail;
+ }
+
+ INIT_WORK(&g_audio->work, g_audio_work);
+ INIT_DELAYED_WORK(&g_audio->ppm_work, ppm_calculate_work);
+ ppm_calculate_work(&g_audio->ppm_work.work);
if (!err)
return 0;
@@ -610,6 +1029,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
kfree(uac->p_prm.rbuf);
kfree(uac->c_prm.rbuf);
kfree(uac);
+ kfree(g_audio->fn);
return err;
}
@@ -623,19 +1043,49 @@ void g_audio_cleanup(struct g_audio *g_audio)
if (!g_audio || !g_audio->uac)
return;
+ cancel_work_sync(&g_audio->work);
+ cancel_delayed_work_sync(&g_audio->ppm_work);
+ device_destroy(g_audio->device->class, g_audio->device->devt);
+ g_audio->device = NULL;
+
uac = g_audio->uac;
card = uac->card;
if (card)
snd_card_free(card);
+ free_ep(&uac->c_prm, g_audio->out_ep);
+ free_ep(&uac->p_prm, g_audio->in_ep);
+
kfree(uac->p_prm.ureq);
kfree(uac->c_prm.ureq);
kfree(uac->p_prm.rbuf);
kfree(uac->c_prm.rbuf);
kfree(uac);
+ kfree(g_audio->fn);
}
EXPORT_SYMBOL_GPL(g_audio_cleanup);
+static int __init u_audio_init(void)
+{
+ int err = 0;
+
+ audio_class = class_create(THIS_MODULE, "u_audio");
+ if (IS_ERR(audio_class)) {
+ err = PTR_ERR(audio_class);
+ audio_class = NULL;
+ }
+
+ return err;
+}
+module_init(u_audio_init);
+
+static void __exit u_audio_exit(void)
+{
+ if (audio_class)
+ class_destroy(audio_class);
+}
+module_exit(u_audio_exit);
+
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("USB gadget \"ALSA sound card\" utilities");
MODULE_AUTHOR("Ruslan Bilovol");
diff --git a/drivers/usb/gadget/function/u_audio.h b/drivers/usb/gadget/function/u_audio.h
index 81d3d4ed6..0178a35e0 100644
--- a/drivers/usb/gadget/function/u_audio.h
+++ b/drivers/usb/gadget/function/u_audio.h
@@ -11,21 +11,70 @@
#include <linux/usb/composite.h>
+#define UAC_VOLUME_CUR 0x0000
+#define UAC_VOLUME_RES 0x0080 /* 0.5 dB */
+#define UAC_VOLUME_MAX 0x1900 /* 25 dB */
+#define UAC_VOLUME_MIN 0xE700 /* -25 dB */
+#define UAC_VOLUME_NEGATIVE_INFINITY 0x8000
+#define UAC_MAX_RATES 10
struct uac_params {
/* playback */
+ int p_volume;
+ int p_mute;
int p_chmask; /* channel mask */
- int p_srate; /* rate in Hz */
+ int p_srate[UAC_MAX_RATES]; /* rate in Hz */
+ int p_srate_active; /* selected rate in Hz */
int p_ssize; /* sample size */
/* capture */
+ int c_volume;
+ int c_mute;
int c_chmask; /* channel mask */
- int c_srate; /* rate in Hz */
+ int c_srate[UAC_MAX_RATES]; /* rate in Hz */
+ int c_srate_active; /* selected rate in Hz */
int c_ssize; /* sample size */
+ int ppm; /* difference between audio clk and usb clk */
+
int req_number; /* number of preallocated requests */
};
+enum usb_state_index {
+ SET_INTERFACE_OUT,
+ SET_INTERFACE_IN,
+ SET_SAMPLE_RATE_OUT,
+ SET_SAMPLE_RATE_IN,
+ SET_VOLUME_OUT,
+ SET_VOLUME_IN,
+ SET_MUTE_OUT,
+ SET_MUTE_IN,
+ SET_AUDIO_CLK,
+ SET_USB_STATE_MAX,
+};
+
+enum stream_state_index {
+ STATE_OUT,
+ STATE_IN,
+};
+
+struct frame_number_data {
+ uint32_t fn_begin; /* frame number when starting statistics */
+ uint32_t fn_last; /* frame number in the latest statistics */
+ uint32_t fn_overflow; /* the time of frame number overflow */
+ uint32_t second; /* total seconds counted */
+ ktime_t time_begin; /* system time when starting statistics */
+ ktime_t time_last; /* system time in the latest statistics */
+};
+
struct g_audio {
+ struct device *device;
+ bool usb_state[SET_USB_STATE_MAX];
+ bool stream_state[2];
+ struct work_struct work;
+
+ struct frame_number_data *fn;
+ struct delayed_work ppm_work;
+
struct usb_function func;
struct usb_gadget *gadget;
@@ -81,5 +130,9 @@ int u_audio_start_capture(struct g_audio *g_audio);
void u_audio_stop_capture(struct g_audio *g_audio);
int u_audio_start_playback(struct g_audio *g_audio);
void u_audio_stop_playback(struct g_audio *g_audio);
+int u_audio_set_capture_srate(struct g_audio *audio_dev, int srate);
+int u_audio_set_playback_srate(struct g_audio *audio_dev, int srate);
+int u_audio_fu_set_cmd(struct usb_audio_control *con, u8 cmd, int value);
+int u_audio_fu_get_cmd(struct usb_audio_control *con, u8 cmd);
#endif /* __U_AUDIO_H */
diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h
deleted file mode 100644
index 6f1a9d73d..000000000
--- a/drivers/usb/gadget/function/u_uac1.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * u_uac1.h - Utility definitions for UAC1 function
- *
- * Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@gmail.com>
- */
-
-#ifndef __U_UAC1_H
-#define __U_UAC1_H
-
-#include <linux/usb/composite.h>
-
-#define UAC1_OUT_EP_MAX_PACKET_SIZE 200
-#define UAC1_DEF_CCHMASK 0x3
-#define UAC1_DEF_CSRATE 48000
-#define UAC1_DEF_CSSIZE 2
-#define UAC1_DEF_PCHMASK 0x3
-#define UAC1_DEF_PSRATE 48000
-#define UAC1_DEF_PSSIZE 2
-#define UAC1_DEF_REQ_NUM 2
-
-
-struct f_uac1_opts {
- struct usb_function_instance func_inst;
- int c_chmask;
- int c_srate;
- int c_ssize;
- int p_chmask;
- int p_srate;
- int p_ssize;
- int req_number;
- unsigned bound:1;
-
- struct mutex lock;
- int refcnt;
-};
-
-#endif /* __U_UAC1_H */
diff --git a/drivers/usb/gadget/function/u_uac1_legacy.c b/drivers/usb/gadget/function/u_uac1_legacy.c
index 5393e5c37..978ba38c5 100644
--- a/drivers/usb/gadget/function/u_uac1_legacy.c
+++ b/drivers/usb/gadget/function/u_uac1_legacy.c
@@ -119,6 +119,10 @@ static int playback_default_hw_params(struct gaudio_snd_dev *snd)
snd->channels, 0);
_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE,
snd->rate, 0);
+ _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ snd->rate / 10, 0);
+ _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+ snd->rate, 0);
snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, params);
diff --git a/drivers/usb/gadget/function/u_uac2.h b/drivers/usb/gadget/function/u_uac2.h
deleted file mode 100644
index 8362ee572..000000000
--- a/drivers/usb/gadget/function/u_uac2.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * u_uac2.h
- *
- * Utility definitions for UAC2 function
- *
- * Copyright (c) 2014 Samsung Electronics Co., Ltd.
- * http://www.samsung.com
- *
- * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
- */
-
-#ifndef U_UAC2_H
-#define U_UAC2_H
-
-#include <linux/usb/composite.h>
-
-#define UAC2_DEF_PCHMASK 0x3
-#define UAC2_DEF_PSRATE 48000
-#define UAC2_DEF_PSSIZE 2
-#define UAC2_DEF_CCHMASK 0x3
-#define UAC2_DEF_CSRATE 64000
-#define UAC2_DEF_CSSIZE 2
-#define UAC2_DEF_REQ_NUM 2
-
-struct f_uac2_opts {
- struct usb_function_instance func_inst;
- int p_chmask;
- int p_srate;
- int p_ssize;
- int c_chmask;
- int c_srate;
- int c_ssize;
- int req_number;
- bool bound;
-
- struct mutex lock;
- int refcnt;
-};
-
-#endif
diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h
index 2ed292e94..cfa81bcd2 100644
--- a/drivers/usb/gadget/function/u_uvc.h
+++ b/drivers/usb/gadget/function/u_uvc.h
@@ -18,12 +18,20 @@
#include <linux/usb/video.h>
#define fi_to_f_uvc_opts(f) container_of(f, struct f_uvc_opts, func_inst)
+DECLARE_UVC_EXTENSION_UNIT_DESCRIPTOR(1, 1);
struct f_uvc_opts {
struct usb_function_instance func_inst;
+ bool streaming_bulk;
unsigned int streaming_interval;
unsigned int streaming_maxpacket;
unsigned int streaming_maxburst;
+ bool device_name_allocated;
+ const char *device_name;
+
+ unsigned int control_interface;
+ unsigned int streaming_interface;
+ unsigned int uvc_num_request;
/*
* Control descriptors array pointers for full-/high-speed and
@@ -48,6 +56,7 @@ struct f_uvc_opts {
struct uvc_camera_terminal_descriptor uvc_camera_terminal;
struct uvc_processing_unit_descriptor uvc_processing;
struct uvc_output_terminal_descriptor uvc_output_terminal;
+ struct UVC_EXTENSION_UNIT_DESCRIPTOR(1, 1) uvc_extension;
struct uvc_color_matching_descriptor uvc_color_matching;
/*
@@ -57,8 +66,8 @@ struct f_uvc_opts {
* descriptors. Used by configfs only, must not be touched by legacy
* gadgets.
*/
- struct uvc_descriptor_header *uvc_fs_control_cls[5];
- struct uvc_descriptor_header *uvc_ss_control_cls[5];
+ struct uvc_descriptor_header *uvc_fs_control_cls[6];
+ struct uvc_descriptor_header *uvc_ss_control_cls[6];
/*
* Streaming descriptors for full-speed, high-speed and super-speed.
@@ -78,6 +87,7 @@ struct f_uvc_opts {
*/
struct mutex lock;
int refcnt;
+ int pm_qos_latency;
};
#endif /* U_UVC_H */
diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 93cf78b42..24f00aaf9 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -14,6 +14,7 @@
#include <linux/spinlock.h>
#include <linux/usb/composite.h>
#include <linux/videodev2.h>
+#include <linux/pm_qos.h>
#include <media/v4l2-device.h>
#include <media/v4l2-dev.h>
@@ -67,6 +68,7 @@ extern unsigned int uvc_gadget_trace_param;
#define UVC_NUM_REQUESTS 4
#define UVC_MAX_REQUEST_SIZE 64
#define UVC_MAX_EVENTS 4
+#define UVC_MAX_NUM_REQUESTS 8
/* ------------------------------------------------------------------------
* Structures
@@ -74,6 +76,8 @@ extern unsigned int uvc_gadget_trace_param;
struct uvc_video {
struct usb_ep *ep;
+ struct work_struct pump;
+ struct workqueue_struct *async_wq;
/* Frame parameters */
u8 bpp;
@@ -85,8 +89,8 @@ struct uvc_video {
/* Requests */
unsigned int req_size;
- struct usb_request *req[UVC_NUM_REQUESTS];
- __u8 *req_buffer[UVC_NUM_REQUESTS];
+ struct usb_request *req[UVC_MAX_NUM_REQUESTS];
+ __u8 *req_buffer[UVC_MAX_NUM_REQUESTS];
struct list_head req_free;
spinlock_t req_lock;
@@ -113,6 +117,8 @@ struct uvc_device {
enum uvc_state state;
struct usb_function func;
struct uvc_video video;
+ /* for creating and issuing QoS requests */
+ struct pm_qos_request pm_qos;
/* Descriptors */
struct {
@@ -133,6 +139,7 @@ struct uvc_device {
/* Events */
unsigned int event_length;
unsigned int event_setup_out : 1;
+ unsigned int event_suspend : 1;
};
static inline struct uvc_device *to_uvc(struct usb_function *f)
diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c
index 2e4c0391b..2b34a7333 100644
--- a/drivers/usb/gadget/function/uvc_configfs.c
+++ b/drivers/usb/gadget/function/uvc_configfs.c
@@ -12,9 +12,14 @@
#include <linux/sort.h>
+#include "uvc.h"
#include "u_uvc.h"
#include "uvc_configfs.h"
+/* -----------------------------------------------------------------------------
+ * Global Utility Structures and Macros
+ */
+
#define UVCG_STREAMING_CONTROL_SIZE 1
#define UVC_ATTR(prefix, cname, aname) \
@@ -34,6 +39,9 @@ static struct configfs_attribute prefix##attr_##cname = { \
.show = prefix##cname##_show, \
}
+#define le8_to_cpu(x) (x)
+#define cpu_to_le8(x) (x)
+
static int uvcg_config_compare_u32(const void *l, const void *r)
{
u32 li = *(const u32 *)l;
@@ -48,7 +56,76 @@ static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item)
func_inst.group);
}
-/* control/header/<NAME> */
+struct uvcg_config_group_type {
+ struct config_item_type type;
+ const char *name;
+ const struct uvcg_config_group_type **children;
+ int (*create_children)(struct config_group *group);
+};
+
+static void uvcg_config_item_release(struct config_item *item)
+{
+ struct config_group *group = to_config_group(item);
+
+ kfree(group);
+}
+
+static struct configfs_item_operations uvcg_config_item_ops = {
+ .release = uvcg_config_item_release,
+};
+
+static int uvcg_config_create_group(struct config_group *parent,
+ const struct uvcg_config_group_type *type);
+
+static int uvcg_config_create_children(struct config_group *group,
+ const struct uvcg_config_group_type *type)
+{
+ const struct uvcg_config_group_type **child;
+ int ret;
+
+ if (type->create_children)
+ return type->create_children(group);
+
+ for (child = type->children; child && *child; ++child) {
+ ret = uvcg_config_create_group(group, *child);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int uvcg_config_create_group(struct config_group *parent,
+ const struct uvcg_config_group_type *type)
+{
+ struct config_group *group;
+
+ group = kzalloc(sizeof(*group), GFP_KERNEL);
+ if (!group)
+ return -ENOMEM;
+
+ config_group_init_type_name(group, type->name, &type->type);
+ configfs_add_default_group(group, parent);
+
+ return uvcg_config_create_children(group, type);
+}
+
+static void uvcg_config_remove_children(struct config_group *group)
+{
+ struct config_group *child, *n;
+
+ list_for_each_entry_safe(child, n, &group->default_groups, group_entry) {
+ list_del(&child->group_entry);
+ uvcg_config_remove_children(child);
+ config_item_put(&child->cg_item);
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * control/header/<NAME>
+ * control/header
+ */
+
DECLARE_UVC_HEADER_DESCRIPTOR(1);
struct uvcg_control_header {
@@ -62,9 +139,9 @@ static struct uvcg_control_header *to_uvcg_control_header(struct config_item *it
return container_of(item, struct uvcg_control_header, item);
}
-#define UVCG_CTRL_HDR_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit) \
+#define UVCG_CTRL_HDR_ATTR(cname, aname, bits, limit) \
static ssize_t uvcg_control_header_##cname##_show( \
- struct config_item *item, char *page) \
+ struct config_item *item, char *page) \
{ \
struct uvcg_control_header *ch = to_uvcg_control_header(item); \
struct f_uvc_opts *opts; \
@@ -78,7 +155,7 @@ static ssize_t uvcg_control_header_##cname##_show( \
opts = to_f_uvc_opts(opts_item); \
\
mutex_lock(&opts->lock); \
- result = sprintf(page, "%d\n", conv(ch->desc.aname)); \
+ result = sprintf(page, "%u\n", le##bits##_to_cpu(ch->desc.aname));\
mutex_unlock(&opts->lock); \
\
mutex_unlock(su_mutex); \
@@ -94,7 +171,7 @@ uvcg_control_header_##cname##_store(struct config_item *item, \
struct config_item *opts_item; \
struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;\
int ret; \
- uxx num; \
+ u##bits num; \
\
mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
\
@@ -107,7 +184,7 @@ uvcg_control_header_##cname##_store(struct config_item *item, \
goto end; \
} \
\
- ret = str2u(page, 0, &num); \
+ ret = kstrtou##bits(page, 0, &num); \
if (ret) \
goto end; \
\
@@ -115,7 +192,7 @@ uvcg_control_header_##cname##_store(struct config_item *item, \
ret = -EINVAL; \
goto end; \
} \
- ch->desc.aname = vnoc(num); \
+ ch->desc.aname = cpu_to_le##bits(num); \
ret = len; \
end: \
mutex_unlock(&opts->lock); \
@@ -125,11 +202,9 @@ end: \
\
UVC_ATTR(uvcg_control_header_, cname, aname)
-UVCG_CTRL_HDR_ATTR(bcd_uvc, bcdUVC, le16_to_cpu, kstrtou16, u16, cpu_to_le16,
- 0xffff);
+UVCG_CTRL_HDR_ATTR(bcd_uvc, bcdUVC, 16, 0xffff);
-UVCG_CTRL_HDR_ATTR(dw_clock_frequency, dwClockFrequency, le32_to_cpu, kstrtou32,
- u32, cpu_to_le32, 0x7fffffff);
+UVCG_CTRL_HDR_ATTR(dw_clock_frequency, dwClockFrequency, 32, 0x7fffffff);
#undef UVCG_CTRL_HDR_ATTR
@@ -140,6 +215,7 @@ static struct configfs_attribute *uvcg_control_header_attrs[] = {
};
static const struct config_item_type uvcg_control_header_type = {
+ .ct_item_ops = &uvcg_config_item_ops,
.ct_attrs = uvcg_control_header_attrs,
.ct_owner = THIS_MODULE,
};
@@ -164,60 +240,42 @@ static struct config_item *uvcg_control_header_make(struct config_group *group,
return &h->item;
}
-static void uvcg_control_header_drop(struct config_group *group,
- struct config_item *item)
-{
- struct uvcg_control_header *h = to_uvcg_control_header(item);
-
- kfree(h);
-}
-
-/* control/header */
-static struct uvcg_control_header_grp {
- struct config_group group;
-} uvcg_control_header_grp;
-
static struct configfs_group_operations uvcg_control_header_grp_ops = {
.make_item = uvcg_control_header_make,
- .drop_item = uvcg_control_header_drop,
};
-static const struct config_item_type uvcg_control_header_grp_type = {
- .ct_group_ops = &uvcg_control_header_grp_ops,
- .ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_control_header_grp_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_group_ops = &uvcg_control_header_grp_ops,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "header",
};
-/* control/processing/default */
-static struct uvcg_default_processing {
- struct config_group group;
-} uvcg_default_processing;
-
-static inline struct uvcg_default_processing
-*to_uvcg_default_processing(struct config_item *item)
-{
- return container_of(to_config_group(item),
- struct uvcg_default_processing, group);
-}
+/* -----------------------------------------------------------------------------
+ * control/processing/default
+ */
-#define UVCG_DEFAULT_PROCESSING_ATTR(cname, aname, conv) \
+#define UVCG_DEFAULT_PROCESSING_ATTR(cname, aname, bits) \
static ssize_t uvcg_default_processing_##cname##_show( \
struct config_item *item, char *page) \
{ \
- struct uvcg_default_processing *dp = to_uvcg_default_processing(item); \
+ struct config_group *group = to_config_group(item); \
struct f_uvc_opts *opts; \
struct config_item *opts_item; \
- struct mutex *su_mutex = &dp->group.cg_subsys->su_mutex; \
+ struct mutex *su_mutex = &group->cg_subsys->su_mutex; \
struct uvc_processing_unit_descriptor *pd; \
int result; \
\
mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
\
- opts_item = dp->group.cg_item.ci_parent->ci_parent->ci_parent; \
+ opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; \
opts = to_f_uvc_opts(opts_item); \
pd = &opts->uvc_processing; \
\
mutex_lock(&opts->lock); \
- result = sprintf(page, "%d\n", conv(pd->aname)); \
+ result = sprintf(page, "%u\n", le##bits##_to_cpu(pd->aname)); \
mutex_unlock(&opts->lock); \
\
mutex_unlock(su_mutex); \
@@ -226,37 +284,33 @@ static ssize_t uvcg_default_processing_##cname##_show( \
\
UVC_ATTR_RO(uvcg_default_processing_, cname, aname)
-#define identity_conv(x) (x)
-
-UVCG_DEFAULT_PROCESSING_ATTR(b_unit_id, bUnitID, identity_conv);
-UVCG_DEFAULT_PROCESSING_ATTR(b_source_id, bSourceID, identity_conv);
-UVCG_DEFAULT_PROCESSING_ATTR(w_max_multiplier, wMaxMultiplier, le16_to_cpu);
-UVCG_DEFAULT_PROCESSING_ATTR(i_processing, iProcessing, identity_conv);
-
-#undef identity_conv
+UVCG_DEFAULT_PROCESSING_ATTR(b_unit_id, bUnitID, 8);
+UVCG_DEFAULT_PROCESSING_ATTR(b_source_id, bSourceID, 8);
+UVCG_DEFAULT_PROCESSING_ATTR(w_max_multiplier, wMaxMultiplier, 16);
+UVCG_DEFAULT_PROCESSING_ATTR(i_processing, iProcessing, 8);
#undef UVCG_DEFAULT_PROCESSING_ATTR
static ssize_t uvcg_default_processing_bm_controls_show(
struct config_item *item, char *page)
{
- struct uvcg_default_processing *dp = to_uvcg_default_processing(item);
+ struct config_group *group = to_config_group(item);
struct f_uvc_opts *opts;
struct config_item *opts_item;
- struct mutex *su_mutex = &dp->group.cg_subsys->su_mutex;
+ struct mutex *su_mutex = &group->cg_subsys->su_mutex;
struct uvc_processing_unit_descriptor *pd;
int result, i;
char *pg = page;
mutex_lock(su_mutex); /* for navigating configfs hierarchy */
- opts_item = dp->group.cg_item.ci_parent->ci_parent->ci_parent;
+ opts_item = group->cg_item.ci_parent->ci_parent->ci_parent;
opts = to_f_uvc_opts(opts_item);
pd = &opts->uvc_processing;
mutex_lock(&opts->lock);
for (result = 0, i = 0; i < pd->bControlSize; ++i) {
- result += sprintf(pg, "%d\n", pd->bmControls[i]);
+ result += sprintf(pg, "%u\n", pd->bmControls[i]);
pg = page + result;
}
mutex_unlock(&opts->lock);
@@ -277,54 +331,55 @@ static struct configfs_attribute *uvcg_default_processing_attrs[] = {
NULL,
};
-static const struct config_item_type uvcg_default_processing_type = {
- .ct_attrs = uvcg_default_processing_attrs,
- .ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_default_processing_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_attrs = uvcg_default_processing_attrs,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "default",
};
-/* struct uvcg_processing {}; */
-
-/* control/processing */
-static struct uvcg_processing_grp {
- struct config_group group;
-} uvcg_processing_grp;
+/* -----------------------------------------------------------------------------
+ * control/processing
+ */
-static const struct config_item_type uvcg_processing_grp_type = {
- .ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_processing_grp_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "processing",
+ .children = (const struct uvcg_config_group_type*[]) {
+ &uvcg_default_processing_type,
+ NULL,
+ },
};
-/* control/terminal/camera/default */
-static struct uvcg_default_camera {
- struct config_group group;
-} uvcg_default_camera;
-
-static inline struct uvcg_default_camera
-*to_uvcg_default_camera(struct config_item *item)
-{
- return container_of(to_config_group(item),
- struct uvcg_default_camera, group);
-}
+/* -----------------------------------------------------------------------------
+ * control/terminal/camera/default
+ */
-#define UVCG_DEFAULT_CAMERA_ATTR(cname, aname, conv) \
+#define UVCG_DEFAULT_CAMERA_ATTR(cname, aname, bits) \
static ssize_t uvcg_default_camera_##cname##_show( \
struct config_item *item, char *page) \
{ \
- struct uvcg_default_camera *dc = to_uvcg_default_camera(item); \
+ struct config_group *group = to_config_group(item); \
struct f_uvc_opts *opts; \
struct config_item *opts_item; \
- struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex; \
+ struct mutex *su_mutex = &group->cg_subsys->su_mutex; \
struct uvc_camera_terminal_descriptor *cd; \
int result; \
\
mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
\
- opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent-> \
+ opts_item = group->cg_item.ci_parent->ci_parent->ci_parent-> \
ci_parent; \
opts = to_f_uvc_opts(opts_item); \
cd = &opts->uvc_camera_terminal; \
\
mutex_lock(&opts->lock); \
- result = sprintf(page, "%d\n", conv(cd->aname)); \
+ result = sprintf(page, "%u\n", le##bits##_to_cpu(cd->aname)); \
mutex_unlock(&opts->lock); \
\
mutex_unlock(su_mutex); \
@@ -334,44 +389,40 @@ static ssize_t uvcg_default_camera_##cname##_show( \
\
UVC_ATTR_RO(uvcg_default_camera_, cname, aname)
-#define identity_conv(x) (x)
-
-UVCG_DEFAULT_CAMERA_ATTR(b_terminal_id, bTerminalID, identity_conv);
-UVCG_DEFAULT_CAMERA_ATTR(w_terminal_type, wTerminalType, le16_to_cpu);
-UVCG_DEFAULT_CAMERA_ATTR(b_assoc_terminal, bAssocTerminal, identity_conv);
-UVCG_DEFAULT_CAMERA_ATTR(i_terminal, iTerminal, identity_conv);
+UVCG_DEFAULT_CAMERA_ATTR(b_terminal_id, bTerminalID, 8);
+UVCG_DEFAULT_CAMERA_ATTR(w_terminal_type, wTerminalType, 16);
+UVCG_DEFAULT_CAMERA_ATTR(b_assoc_terminal, bAssocTerminal, 8);
+UVCG_DEFAULT_CAMERA_ATTR(i_terminal, iTerminal, 8);
UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_min, wObjectiveFocalLengthMin,
- le16_to_cpu);
+ 16);
UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_max, wObjectiveFocalLengthMax,
- le16_to_cpu);
+ 16);
UVCG_DEFAULT_CAMERA_ATTR(w_ocular_focal_length, wOcularFocalLength,
- le16_to_cpu);
-
-#undef identity_conv
+ 16);
#undef UVCG_DEFAULT_CAMERA_ATTR
static ssize_t uvcg_default_camera_bm_controls_show(
struct config_item *item, char *page)
{
- struct uvcg_default_camera *dc = to_uvcg_default_camera(item);
+ struct config_group *group = to_config_group(item);
struct f_uvc_opts *opts;
struct config_item *opts_item;
- struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex;
+ struct mutex *su_mutex = &group->cg_subsys->su_mutex;
struct uvc_camera_terminal_descriptor *cd;
int result, i;
char *pg = page;
mutex_lock(su_mutex); /* for navigating configfs hierarchy */
- opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent->
+ opts_item = group->cg_item.ci_parent->ci_parent->ci_parent->
ci_parent;
opts = to_f_uvc_opts(opts_item);
cd = &opts->uvc_camera_terminal;
mutex_lock(&opts->lock);
for (result = 0, i = 0; i < cd->bControlSize; ++i) {
- result += sprintf(pg, "%d\n", cd->bmControls[i]);
+ result += sprintf(pg, "%u\n", cd->bmControls[i]);
pg = page + result;
}
mutex_unlock(&opts->lock);
@@ -394,54 +445,55 @@ static struct configfs_attribute *uvcg_default_camera_attrs[] = {
NULL,
};
-static const struct config_item_type uvcg_default_camera_type = {
- .ct_attrs = uvcg_default_camera_attrs,
- .ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_default_camera_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_attrs = uvcg_default_camera_attrs,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "default",
};
-/* struct uvcg_camera {}; */
-
-/* control/terminal/camera */
-static struct uvcg_camera_grp {
- struct config_group group;
-} uvcg_camera_grp;
+/* -----------------------------------------------------------------------------
+ * control/terminal/camera
+ */
-static const struct config_item_type uvcg_camera_grp_type = {
- .ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_camera_grp_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "camera",
+ .children = (const struct uvcg_config_group_type*[]) {
+ &uvcg_default_camera_type,
+ NULL,
+ },
};
-/* control/terminal/output/default */
-static struct uvcg_default_output {
- struct config_group group;
-} uvcg_default_output;
-
-static inline struct uvcg_default_output
-*to_uvcg_default_output(struct config_item *item)
-{
- return container_of(to_config_group(item),
- struct uvcg_default_output, group);
-}
+/* -----------------------------------------------------------------------------
+ * control/terminal/output/default
+ */
-#define UVCG_DEFAULT_OUTPUT_ATTR(cname, aname, conv) \
+#define UVCG_DEFAULT_OUTPUT_ATTR(cname, aname, bits) \
static ssize_t uvcg_default_output_##cname##_show( \
- struct config_item *item, char *page) \
+ struct config_item *item, char *page) \
{ \
- struct uvcg_default_output *dout = to_uvcg_default_output(item); \
+ struct config_group *group = to_config_group(item); \
struct f_uvc_opts *opts; \
struct config_item *opts_item; \
- struct mutex *su_mutex = &dout->group.cg_subsys->su_mutex; \
+ struct mutex *su_mutex = &group->cg_subsys->su_mutex; \
struct uvc_output_terminal_descriptor *cd; \
int result; \
\
mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
\
- opts_item = dout->group.cg_item.ci_parent->ci_parent-> \
+ opts_item = group->cg_item.ci_parent->ci_parent-> \
ci_parent->ci_parent; \
opts = to_f_uvc_opts(opts_item); \
cd = &opts->uvc_output_terminal; \
\
mutex_lock(&opts->lock); \
- result = sprintf(page, "%d\n", conv(cd->aname)); \
+ result = sprintf(page, "%u\n", le##bits##_to_cpu(cd->aname)); \
mutex_unlock(&opts->lock); \
\
mutex_unlock(su_mutex); \
@@ -451,15 +503,11 @@ static ssize_t uvcg_default_output_##cname##_show( \
\
UVC_ATTR_RO(uvcg_default_output_, cname, aname)
-#define identity_conv(x) (x)
-
-UVCG_DEFAULT_OUTPUT_ATTR(b_terminal_id, bTerminalID, identity_conv);
-UVCG_DEFAULT_OUTPUT_ATTR(w_terminal_type, wTerminalType, le16_to_cpu);
-UVCG_DEFAULT_OUTPUT_ATTR(b_assoc_terminal, bAssocTerminal, identity_conv);
-UVCG_DEFAULT_OUTPUT_ATTR(b_source_id, bSourceID, identity_conv);
-UVCG_DEFAULT_OUTPUT_ATTR(i_terminal, iTerminal, identity_conv);
-
-#undef identity_conv
+UVCG_DEFAULT_OUTPUT_ATTR(b_terminal_id, bTerminalID, 8);
+UVCG_DEFAULT_OUTPUT_ATTR(w_terminal_type, wTerminalType, 16);
+UVCG_DEFAULT_OUTPUT_ATTR(b_assoc_terminal, bAssocTerminal, 8);
+UVCG_DEFAULT_OUTPUT_ATTR(b_source_id, bSourceID, 8);
+UVCG_DEFAULT_OUTPUT_ATTR(i_terminal, iTerminal, 8);
#undef UVCG_DEFAULT_OUTPUT_ATTR
@@ -472,47 +520,68 @@ static struct configfs_attribute *uvcg_default_output_attrs[] = {
NULL,
};
-static const struct config_item_type uvcg_default_output_type = {
- .ct_attrs = uvcg_default_output_attrs,
- .ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_default_output_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_attrs = uvcg_default_output_attrs,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "default",
};
-/* struct uvcg_output {}; */
-
-/* control/terminal/output */
-static struct uvcg_output_grp {
- struct config_group group;
-} uvcg_output_grp;
+/* -----------------------------------------------------------------------------
+ * control/terminal/output
+ */
-static const struct config_item_type uvcg_output_grp_type = {
- .ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_output_grp_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "output",
+ .children = (const struct uvcg_config_group_type*[]) {
+ &uvcg_default_output_type,
+ NULL,
+ },
};
-/* control/terminal */
-static struct uvcg_terminal_grp {
- struct config_group group;
-} uvcg_terminal_grp;
+/* -----------------------------------------------------------------------------
+ * control/terminal
+ */
-static const struct config_item_type uvcg_terminal_grp_type = {
- .ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_terminal_grp_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "terminal",
+ .children = (const struct uvcg_config_group_type*[]) {
+ &uvcg_camera_grp_type,
+ &uvcg_output_grp_type,
+ NULL,
+ },
};
-/* control/class/{fs} */
-static struct uvcg_control_class {
- struct config_group group;
-} uvcg_control_class_fs, uvcg_control_class_ss;
+/* -----------------------------------------------------------------------------
+ * control/class/{fs|ss}
+ */
+struct uvcg_control_class_group {
+ struct config_group group;
+ const char *name;
+};
static inline struct uvc_descriptor_header
**uvcg_get_ctl_class_arr(struct config_item *i, struct f_uvc_opts *o)
{
- struct uvcg_control_class *cl = container_of(to_config_group(i),
- struct uvcg_control_class, group);
+ struct uvcg_control_class_group *group =
+ container_of(i, struct uvcg_control_class_group,
+ group.cg_item);
- if (cl == &uvcg_control_class_fs)
+ if (!strcmp(group->name, "fs"))
return o->uvc_fs_control_cls;
- if (cl == &uvcg_control_class_ss)
+ if (!strcmp(group->name, "ss"))
return o->uvc_ss_control_cls;
return NULL;
@@ -596,6 +665,7 @@ static void uvcg_control_class_drop_link(struct config_item *src,
}
static struct configfs_item_operations uvcg_control_class_item_ops = {
+ .release = uvcg_config_item_release,
.allow_link = uvcg_control_class_allow_link,
.drop_link = uvcg_control_class_drop_link,
};
@@ -605,42 +675,106 @@ static const struct config_item_type uvcg_control_class_type = {
.ct_owner = THIS_MODULE,
};
-/* control/class */
-static struct uvcg_control_class_grp {
- struct config_group group;
-} uvcg_control_class_grp;
+/* -----------------------------------------------------------------------------
+ * control/class
+ */
+
+static int uvcg_control_class_create_children(struct config_group *parent)
+{
+ static const char * const names[] = { "fs", "ss" };
+ unsigned int i;
-static const struct config_item_type uvcg_control_class_grp_type = {
- .ct_owner = THIS_MODULE,
+ for (i = 0; i < ARRAY_SIZE(names); ++i) {
+ struct uvcg_control_class_group *group;
+
+ group = kzalloc(sizeof(*group), GFP_KERNEL);
+ if (!group)
+ return -ENOMEM;
+
+ group->name = names[i];
+
+ config_group_init_type_name(&group->group, group->name,
+ &uvcg_control_class_type);
+ configfs_add_default_group(&group->group, parent);
+ }
+
+ return 0;
+}
+
+static const struct uvcg_config_group_type uvcg_control_class_grp_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "class",
+ .create_children = uvcg_control_class_create_children,
};
-/* control */
-static struct uvcg_control_grp {
- struct config_group group;
-} uvcg_control_grp;
+/* -----------------------------------------------------------------------------
+ * control
+ */
+
+static ssize_t uvcg_default_control_b_interface_number_show(
+ struct config_item *item, char *page)
+{
+ struct config_group *group = to_config_group(item);
+ struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+ struct config_item *opts_item;
+ struct f_uvc_opts *opts;
+ int result = 0;
-static const struct config_item_type uvcg_control_grp_type = {
- .ct_owner = THIS_MODULE,
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+ opts_item = item->ci_parent;
+ opts = to_f_uvc_opts(opts_item);
+
+ mutex_lock(&opts->lock);
+ result += sprintf(page, "%u\n", opts->control_interface);
+ mutex_unlock(&opts->lock);
+
+ mutex_unlock(su_mutex);
+
+ return result;
+}
+
+UVC_ATTR_RO(uvcg_default_control_, b_interface_number, bInterfaceNumber);
+
+static struct configfs_attribute *uvcg_default_control_attrs[] = {
+ &uvcg_default_control_attr_b_interface_number,
+ NULL,
};
-/* streaming/uncompressed */
-static struct uvcg_uncompressed_grp {
- struct config_group group;
-} uvcg_uncompressed_grp;
+static const struct uvcg_config_group_type uvcg_control_grp_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_attrs = uvcg_default_control_attrs,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "control",
+ .children = (const struct uvcg_config_group_type*[]) {
+ &uvcg_control_header_grp_type,
+ &uvcg_processing_grp_type,
+ &uvcg_terminal_grp_type,
+ &uvcg_control_class_grp_type,
+ NULL,
+ },
+};
-/* streaming/mjpeg */
-static struct uvcg_mjpeg_grp {
- struct config_group group;
-} uvcg_mjpeg_grp;
+/* -----------------------------------------------------------------------------
+ * streaming/uncompressed
+ * streaming/mjpeg
+ */
-static struct config_item *fmt_parent[] = {
- &uvcg_uncompressed_grp.group.cg_item,
- &uvcg_mjpeg_grp.group.cg_item,
+static const char * const uvcg_format_names[] = {
+ "uncompressed",
+ "mjpeg",
+ "framebased",
};
enum uvcg_format_type {
UVCG_UNCOMPRESSED = 0,
UVCG_MJPEG,
+ UVCG_FRAMEBASED,
};
struct uvcg_format {
@@ -719,7 +853,11 @@ struct uvcg_format_ptr {
struct list_head entry;
};
-/* streaming/header/<NAME> */
+/* -----------------------------------------------------------------------------
+ * streaming/header/<NAME>
+ * streaming/header
+ */
+
struct uvcg_streaming_header {
struct config_item item;
struct uvc_input_header_descriptor desc;
@@ -733,6 +871,8 @@ static struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item
return container_of(item, struct uvcg_streaming_header, item);
}
+static void uvcg_format_set_indices(struct config_group *fmt);
+
static int uvcg_streaming_header_allow_link(struct config_item *src,
struct config_item *target)
{
@@ -757,10 +897,22 @@ static int uvcg_streaming_header_allow_link(struct config_item *src,
goto out;
}
- for (i = 0; i < ARRAY_SIZE(fmt_parent); ++i)
- if (target->ci_parent == fmt_parent[i])
+ /*
+ * Linking is only allowed to direct children of the format nodes
+ * (streaming/uncompressed or streaming/mjpeg nodes). First check that
+ * the grand-parent of the target matches the grand-parent of the source
+ * (the streaming node), and then verify that the target parent is a
+ * format node.
+ */
+ if (src->ci_parent->ci_parent != target->ci_parent->ci_parent)
+ goto out;
+
+ for (i = 0; i < ARRAY_SIZE(uvcg_format_names); ++i) {
+ if (!strcmp(target->ci_parent->ci_name, uvcg_format_names[i]))
break;
- if (i == ARRAY_SIZE(fmt_parent))
+ }
+
+ if (i == ARRAY_SIZE(uvcg_format_names))
goto out;
target_fmt = container_of(to_config_group(target), struct uvcg_format,
@@ -768,6 +920,8 @@ static int uvcg_streaming_header_allow_link(struct config_item *src,
if (!target_fmt)
goto out;
+ uvcg_format_set_indices(to_config_group(target));
+
format_ptr = kzalloc(sizeof(*format_ptr), GFP_KERNEL);
if (!format_ptr) {
ret = -ENOMEM;
@@ -823,13 +977,14 @@ static void uvcg_streaming_header_drop_link(struct config_item *src,
}
static struct configfs_item_operations uvcg_streaming_header_item_ops = {
- .allow_link = uvcg_streaming_header_allow_link,
- .drop_link = uvcg_streaming_header_drop_link,
+ .release = uvcg_config_item_release,
+ .allow_link = uvcg_streaming_header_allow_link,
+ .drop_link = uvcg_streaming_header_drop_link,
};
-#define UVCG_STREAMING_HEADER_ATTR(cname, aname, conv) \
+#define UVCG_STREAMING_HEADER_ATTR(cname, aname, bits) \
static ssize_t uvcg_streaming_header_##cname##_show( \
- struct config_item *item, char *page) \
+ struct config_item *item, char *page) \
{ \
struct uvcg_streaming_header *sh = to_uvcg_streaming_header(item); \
struct f_uvc_opts *opts; \
@@ -843,7 +998,7 @@ static ssize_t uvcg_streaming_header_##cname##_show( \
opts = to_f_uvc_opts(opts_item); \
\
mutex_lock(&opts->lock); \
- result = sprintf(page, "%d\n", conv(sh->desc.aname)); \
+ result = sprintf(page, "%u\n", le##bits##_to_cpu(sh->desc.aname));\
mutex_unlock(&opts->lock); \
\
mutex_unlock(su_mutex); \
@@ -852,16 +1007,11 @@ static ssize_t uvcg_streaming_header_##cname##_show( \
\
UVC_ATTR_RO(uvcg_streaming_header_, cname, aname)
-#define identity_conv(x) (x)
-
-UVCG_STREAMING_HEADER_ATTR(bm_info, bmInfo, identity_conv);
-UVCG_STREAMING_HEADER_ATTR(b_terminal_link, bTerminalLink, identity_conv);
-UVCG_STREAMING_HEADER_ATTR(b_still_capture_method, bStillCaptureMethod,
- identity_conv);
-UVCG_STREAMING_HEADER_ATTR(b_trigger_support, bTriggerSupport, identity_conv);
-UVCG_STREAMING_HEADER_ATTR(b_trigger_usage, bTriggerUsage, identity_conv);
-
-#undef identity_conv
+UVCG_STREAMING_HEADER_ATTR(bm_info, bmInfo, 8);
+UVCG_STREAMING_HEADER_ATTR(b_terminal_link, bTerminalLink, 8);
+UVCG_STREAMING_HEADER_ATTR(b_still_capture_method, bStillCaptureMethod, 8);
+UVCG_STREAMING_HEADER_ATTR(b_trigger_support, bTriggerSupport, 8);
+UVCG_STREAMING_HEADER_ATTR(b_trigger_usage, bTriggerUsage, 8);
#undef UVCG_STREAMING_HEADER_ATTR
@@ -900,31 +1050,26 @@ static struct config_item
return &h->item;
}
-static void uvcg_streaming_header_drop(struct config_group *group,
- struct config_item *item)
-{
- struct uvcg_streaming_header *h = to_uvcg_streaming_header(item);
-
- kfree(h);
-}
-
-/* streaming/header */
-static struct uvcg_streaming_header_grp {
- struct config_group group;
-} uvcg_streaming_header_grp;
-
static struct configfs_group_operations uvcg_streaming_header_grp_ops = {
.make_item = uvcg_streaming_header_make,
- .drop_item = uvcg_streaming_header_drop,
};
-static const struct config_item_type uvcg_streaming_header_grp_type = {
- .ct_group_ops = &uvcg_streaming_header_grp_ops,
- .ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_streaming_header_grp_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_group_ops = &uvcg_streaming_header_grp_ops,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "header",
};
-/* streaming/<mode>/<format>/<NAME> */
+/* -----------------------------------------------------------------------------
+ * streaming/<mode>/<format>/<NAME>
+ */
+
struct uvcg_frame {
+ struct config_item item;
+ enum uvcg_format_type fmt_type;
struct {
u8 b_length;
u8 b_descriptor_type;
@@ -935,13 +1080,17 @@ struct uvcg_frame {
u16 w_height;
u32 dw_min_bit_rate;
u32 dw_max_bit_rate;
+ /*
+ * dw_max_video_frame_buffer_size is only for uncompressed and
+ * mjpeg format
+ */
u32 dw_max_video_frame_buffer_size;
u32 dw_default_frame_interval;
u8 b_frame_interval_type;
+ /* dw_bytes_perline is only for framebased format */
+ u32 dw_bytes_perline;
} __attribute__((packed)) frame;
u32 *dw_frame_interval;
- enum uvcg_format_type fmt_type;
- struct config_item item;
};
static struct uvcg_frame *to_uvcg_frame(struct config_item *item)
@@ -949,7 +1098,7 @@ static struct uvcg_frame *to_uvcg_frame(struct config_item *item)
return container_of(item, struct uvcg_frame, item);
}
-#define UVCG_FRAME_ATTR(cname, aname, to_cpu_endian, to_little_endian, bits) \
+#define UVCG_FRAME_ATTR(cname, aname, bits) \
static ssize_t uvcg_frame_##cname##_show(struct config_item *item, char *page)\
{ \
struct uvcg_frame *f = to_uvcg_frame(item); \
@@ -964,7 +1113,7 @@ static ssize_t uvcg_frame_##cname##_show(struct config_item *item, char *page)\
opts = to_f_uvc_opts(opts_item); \
\
mutex_lock(&opts->lock); \
- result = sprintf(page, "%d\n", to_cpu_endian(f->frame.cname)); \
+ result = sprintf(page, "%u\n", f->frame.cname); \
mutex_unlock(&opts->lock); \
\
mutex_unlock(su_mutex); \
@@ -979,8 +1128,8 @@ static ssize_t uvcg_frame_##cname##_store(struct config_item *item, \
struct config_item *opts_item; \
struct uvcg_format *fmt; \
struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\
+ typeof(f->frame.cname) num; \
int ret; \
- u##bits num; \
\
ret = kstrtou##bits(page, 0, &num); \
if (ret) \
@@ -998,7 +1147,7 @@ static ssize_t uvcg_frame_##cname##_store(struct config_item *item, \
goto end; \
} \
\
- f->frame.cname = to_little_endian(num); \
+ f->frame.cname = num; \
ret = len; \
end: \
mutex_unlock(&opts->lock); \
@@ -1008,20 +1157,49 @@ end: \
\
UVC_ATTR(uvcg_frame_, cname, aname);
-#define noop_conversion(x) (x)
+static ssize_t uvcg_frame_b_frame_index_show(struct config_item *item,
+ char *page)
+{
+ struct uvcg_frame *f = to_uvcg_frame(item);
+ struct uvcg_format *fmt;
+ struct f_uvc_opts *opts;
+ struct config_item *opts_item;
+ struct config_item *fmt_item;
+ struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;
+ int result;
-UVCG_FRAME_ATTR(bm_capabilities, bmCapabilities, noop_conversion,
- noop_conversion, 8);
-UVCG_FRAME_ATTR(w_width, wWidth, le16_to_cpu, cpu_to_le16, 16);
-UVCG_FRAME_ATTR(w_height, wHeight, le16_to_cpu, cpu_to_le16, 16);
-UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, le32_to_cpu, cpu_to_le32, 32);
-UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, le32_to_cpu, cpu_to_le32, 32);
-UVCG_FRAME_ATTR(dw_max_video_frame_buffer_size, dwMaxVideoFrameBufferSize,
- le32_to_cpu, cpu_to_le32, 32);
-UVCG_FRAME_ATTR(dw_default_frame_interval, dwDefaultFrameInterval,
- le32_to_cpu, cpu_to_le32, 32);
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */
-#undef noop_conversion
+ fmt_item = f->item.ci_parent;
+ fmt = to_uvcg_format(fmt_item);
+
+ if (!fmt->linked) {
+ result = -EBUSY;
+ goto out;
+ }
+
+ opts_item = fmt_item->ci_parent->ci_parent->ci_parent;
+ opts = to_f_uvc_opts(opts_item);
+
+ mutex_lock(&opts->lock);
+ result = sprintf(page, "%u\n", f->frame.b_frame_index);
+ mutex_unlock(&opts->lock);
+
+out:
+ mutex_unlock(su_mutex);
+ return result;
+}
+
+UVC_ATTR_RO(uvcg_frame_, b_frame_index, bFrameIndex);
+
+UVCG_FRAME_ATTR(bm_capabilities, bmCapabilities, 8);
+UVCG_FRAME_ATTR(w_width, wWidth, 16);
+UVCG_FRAME_ATTR(w_height, wHeight, 16);
+UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, 32);
+UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, 32);
+UVCG_FRAME_ATTR(dw_max_video_frame_buffer_size, dwMaxVideoFrameBufferSize, 32);
+UVCG_FRAME_ATTR(dw_default_frame_interval, dwDefaultFrameInterval, 32);
+UVCG_FRAME_ATTR(dw_bytes_perline, dwBytesPerLine, 32);
#undef UVCG_FRAME_ATTR
@@ -1042,8 +1220,7 @@ static ssize_t uvcg_frame_dw_frame_interval_show(struct config_item *item,
mutex_lock(&opts->lock);
for (result = 0, i = 0; i < frm->frame.b_frame_interval_type; ++i) {
- result += sprintf(pg, "%d\n",
- le32_to_cpu(frm->dw_frame_interval[i]));
+ result += sprintf(pg, "%u\n", frm->dw_frame_interval[i]);
pg = page + result;
}
mutex_unlock(&opts->lock);
@@ -1068,7 +1245,7 @@ static inline int __uvcg_fill_frm_intrv(char *buf, void *priv)
return ret;
interv = priv;
- **interv = cpu_to_le32(num);
+ **interv = num;
++*interv;
return 0;
@@ -1157,7 +1334,8 @@ static ssize_t uvcg_frame_dw_frame_interval_store(struct config_item *item,
UVC_ATTR(uvcg_frame_, dw_frame_interval, dwFrameInterval);
-static struct configfs_attribute *uvcg_frame_attrs[] = {
+static struct configfs_attribute *uvcg_frame_attrs1[] = {
+ &uvcg_frame_attr_b_frame_index,
&uvcg_frame_attr_bm_capabilities,
&uvcg_frame_attr_w_width,
&uvcg_frame_attr_w_height,
@@ -1169,8 +1347,28 @@ static struct configfs_attribute *uvcg_frame_attrs[] = {
NULL,
};
-static const struct config_item_type uvcg_frame_type = {
- .ct_attrs = uvcg_frame_attrs,
+static struct configfs_attribute *uvcg_frame_attrs2[] = {
+ &uvcg_frame_attr_b_frame_index,
+ &uvcg_frame_attr_bm_capabilities,
+ &uvcg_frame_attr_w_width,
+ &uvcg_frame_attr_w_height,
+ &uvcg_frame_attr_dw_min_bit_rate,
+ &uvcg_frame_attr_dw_max_bit_rate,
+ &uvcg_frame_attr_dw_default_frame_interval,
+ &uvcg_frame_attr_dw_frame_interval,
+ &uvcg_frame_attr_dw_bytes_perline,
+ NULL,
+};
+
+static const struct config_item_type uvcg_frame_type1 = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_attrs = uvcg_frame_attrs1,
+ .ct_owner = THIS_MODULE,
+};
+
+static const struct config_item_type uvcg_frame_type2 = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_attrs = uvcg_frame_attrs2,
.ct_owner = THIS_MODULE,
};
@@ -1188,12 +1386,13 @@ static struct config_item *uvcg_frame_make(struct config_group *group,
h->frame.b_descriptor_type = USB_DT_CS_INTERFACE;
h->frame.b_frame_index = 1;
- h->frame.w_width = cpu_to_le16(640);
- h->frame.w_height = cpu_to_le16(360);
- h->frame.dw_min_bit_rate = cpu_to_le32(18432000);
- h->frame.dw_max_bit_rate = cpu_to_le32(55296000);
- h->frame.dw_max_video_frame_buffer_size = cpu_to_le32(460800);
- h->frame.dw_default_frame_interval = cpu_to_le32(666666);
+ h->frame.w_width = 640;
+ h->frame.w_height = 360;
+ h->frame.dw_min_bit_rate = 18432000;
+ h->frame.dw_max_bit_rate = 55296000;
+ h->frame.dw_max_video_frame_buffer_size = 460800;
+ h->frame.dw_default_frame_interval = 666666;
+ h->frame.dw_bytes_perline = 0;
opts_item = group->cg_item.ci_parent->ci_parent->ci_parent;
opts = to_f_uvc_opts(opts_item);
@@ -1206,6 +1405,9 @@ static struct config_item *uvcg_frame_make(struct config_group *group,
} else if (fmt->type == UVCG_MJPEG) {
h->frame.b_descriptor_subtype = UVC_VS_FRAME_MJPEG;
h->fmt_type = UVCG_MJPEG;
+ } else if (fmt->type == UVCG_FRAMEBASED) {
+ h->frame.b_descriptor_subtype = UVC_VS_FRAME_FRAME_BASED;
+ h->fmt_type = UVCG_FRAMEBASED;
} else {
mutex_unlock(&opts->lock);
kfree(h);
@@ -1214,14 +1416,16 @@ static struct config_item *uvcg_frame_make(struct config_group *group,
++fmt->num_frames;
mutex_unlock(&opts->lock);
- config_item_init_type_name(&h->item, name, &uvcg_frame_type);
+ if (fmt->type == UVCG_FRAMEBASED)
+ config_item_init_type_name(&h->item, name, &uvcg_frame_type2);
+ else
+ config_item_init_type_name(&h->item, name, &uvcg_frame_type1);
return &h->item;
}
static void uvcg_frame_drop(struct config_group *group, struct config_item *item)
{
- struct uvcg_frame *h = to_uvcg_frame(item);
struct uvcg_format *fmt;
struct f_uvc_opts *opts;
struct config_item *opts_item;
@@ -1232,11 +1436,32 @@ static void uvcg_frame_drop(struct config_group *group, struct config_item *item
mutex_lock(&opts->lock);
fmt = to_uvcg_format(&group->cg_item);
--fmt->num_frames;
- kfree(h);
mutex_unlock(&opts->lock);
+
+ config_item_put(item);
}
-/* streaming/uncompressed/<NAME> */
+static void uvcg_format_set_indices(struct config_group *fmt)
+{
+ struct config_item *ci;
+ unsigned int i = 1;
+
+ list_for_each_entry(ci, &fmt->cg_children, ci_entry) {
+ struct uvcg_frame *frm;
+
+ if (ci->ci_type != &uvcg_frame_type1 &&
+ ci->ci_type != &uvcg_frame_type2)
+ continue;
+
+ frm = to_uvcg_frame(ci);
+ frm->frame.b_frame_index = i++;
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * streaming/uncompressed/<NAME>
+ */
+
struct uvcg_uncompressed {
struct uvcg_format fmt;
struct uvc_format_uncompressed desc;
@@ -1308,7 +1533,7 @@ static ssize_t uvcg_uncompressed_guid_format_store(struct config_item *item,
UVC_ATTR(uvcg_uncompressed_, guid_format, guidFormat);
-#define UVCG_UNCOMPRESSED_ATTR_RO(cname, aname, conv) \
+#define UVCG_UNCOMPRESSED_ATTR_RO(cname, aname, bits) \
static ssize_t uvcg_uncompressed_##cname##_show( \
struct config_item *item, char *page) \
{ \
@@ -1324,7 +1549,7 @@ static ssize_t uvcg_uncompressed_##cname##_show( \
opts = to_f_uvc_opts(opts_item); \
\
mutex_lock(&opts->lock); \
- result = sprintf(page, "%d\n", conv(u->desc.aname)); \
+ result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\
mutex_unlock(&opts->lock); \
\
mutex_unlock(su_mutex); \
@@ -1333,7 +1558,7 @@ static ssize_t uvcg_uncompressed_##cname##_show( \
\
UVC_ATTR_RO(uvcg_uncompressed_, cname, aname);
-#define UVCG_UNCOMPRESSED_ATTR(cname, aname, conv) \
+#define UVCG_UNCOMPRESSED_ATTR(cname, aname, bits) \
static ssize_t uvcg_uncompressed_##cname##_show( \
struct config_item *item, char *page) \
{ \
@@ -1349,7 +1574,7 @@ static ssize_t uvcg_uncompressed_##cname##_show( \
opts = to_f_uvc_opts(opts_item); \
\
mutex_lock(&opts->lock); \
- result = sprintf(page, "%d\n", conv(u->desc.aname)); \
+ result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\
mutex_unlock(&opts->lock); \
\
mutex_unlock(su_mutex); \
@@ -1396,16 +1621,12 @@ end: \
\
UVC_ATTR(uvcg_uncompressed_, cname, aname);
-#define identity_conv(x) (x)
-
-UVCG_UNCOMPRESSED_ATTR(b_bits_per_pixel, bBitsPerPixel, identity_conv);
-UVCG_UNCOMPRESSED_ATTR(b_default_frame_index, bDefaultFrameIndex,
- identity_conv);
-UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, identity_conv);
-UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, identity_conv);
-UVCG_UNCOMPRESSED_ATTR_RO(bm_interface_flags, bmInterfaceFlags, identity_conv);
-
-#undef identity_conv
+UVCG_UNCOMPRESSED_ATTR_RO(b_format_index, bFormatIndex, 8);
+UVCG_UNCOMPRESSED_ATTR(b_bits_per_pixel, bBitsPerPixel, 8);
+UVCG_UNCOMPRESSED_ATTR(b_default_frame_index, bDefaultFrameIndex, 8);
+UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, 8);
+UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, 8);
+UVCG_UNCOMPRESSED_ATTR_RO(bm_interface_flags, bmInterfaceFlags, 8);
#undef UVCG_UNCOMPRESSED_ATTR
#undef UVCG_UNCOMPRESSED_ATTR_RO
@@ -1428,6 +1649,7 @@ uvcg_uncompressed_bma_controls_store(struct config_item *item,
UVC_ATTR(uvcg_uncompressed_, bma_controls, bmaControls);
static struct configfs_attribute *uvcg_uncompressed_attrs[] = {
+ &uvcg_uncompressed_attr_b_format_index,
&uvcg_uncompressed_attr_guid_format,
&uvcg_uncompressed_attr_b_bits_per_pixel,
&uvcg_uncompressed_attr_b_default_frame_index,
@@ -1439,6 +1661,7 @@ static struct configfs_attribute *uvcg_uncompressed_attrs[] = {
};
static const struct config_item_type uvcg_uncompressed_type = {
+ .ct_item_ops = &uvcg_config_item_ops,
.ct_group_ops = &uvcg_uncompressed_group_ops,
.ct_attrs = uvcg_uncompressed_attrs,
.ct_owner = THIS_MODULE,
@@ -1475,25 +1698,23 @@ static struct config_group *uvcg_uncompressed_make(struct config_group *group,
return &h->fmt.group;
}
-static void uvcg_uncompressed_drop(struct config_group *group,
- struct config_item *item)
-{
- struct uvcg_uncompressed *h = to_uvcg_uncompressed(item);
-
- kfree(h);
-}
-
static struct configfs_group_operations uvcg_uncompressed_grp_ops = {
.make_group = uvcg_uncompressed_make,
- .drop_item = uvcg_uncompressed_drop,
};
-static const struct config_item_type uvcg_uncompressed_grp_type = {
- .ct_group_ops = &uvcg_uncompressed_grp_ops,
- .ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_uncompressed_grp_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_group_ops = &uvcg_uncompressed_grp_ops,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "uncompressed",
};
-/* streaming/mjpeg/<NAME> */
+/* -----------------------------------------------------------------------------
+ * streaming/mjpeg/<NAME>
+ */
+
struct uvcg_mjpeg {
struct uvcg_format fmt;
struct uvc_format_mjpeg desc;
@@ -1511,7 +1732,7 @@ static struct configfs_group_operations uvcg_mjpeg_group_ops = {
.drop_item = uvcg_frame_drop,
};
-#define UVCG_MJPEG_ATTR_RO(cname, aname, conv) \
+#define UVCG_MJPEG_ATTR_RO(cname, aname, bits) \
static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\
{ \
struct uvcg_mjpeg *u = to_uvcg_mjpeg(item); \
@@ -1526,7 +1747,7 @@ static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\
opts = to_f_uvc_opts(opts_item); \
\
mutex_lock(&opts->lock); \
- result = sprintf(page, "%d\n", conv(u->desc.aname)); \
+ result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\
mutex_unlock(&opts->lock); \
\
mutex_unlock(su_mutex); \
@@ -1535,7 +1756,7 @@ static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\
\
UVC_ATTR_RO(uvcg_mjpeg_, cname, aname)
-#define UVCG_MJPEG_ATTR(cname, aname, conv) \
+#define UVCG_MJPEG_ATTR(cname, aname, bits) \
static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\
{ \
struct uvcg_mjpeg *u = to_uvcg_mjpeg(item); \
@@ -1550,7 +1771,7 @@ static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\
opts = to_f_uvc_opts(opts_item); \
\
mutex_lock(&opts->lock); \
- result = sprintf(page, "%d\n", conv(u->desc.aname)); \
+ result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\
mutex_unlock(&opts->lock); \
\
mutex_unlock(su_mutex); \
@@ -1597,16 +1818,12 @@ end: \
\
UVC_ATTR(uvcg_mjpeg_, cname, aname)
-#define identity_conv(x) (x)
-
-UVCG_MJPEG_ATTR(b_default_frame_index, bDefaultFrameIndex,
- identity_conv);
-UVCG_MJPEG_ATTR_RO(bm_flags, bmFlags, identity_conv);
-UVCG_MJPEG_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, identity_conv);
-UVCG_MJPEG_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, identity_conv);
-UVCG_MJPEG_ATTR_RO(bm_interface_flags, bmInterfaceFlags, identity_conv);
-
-#undef identity_conv
+UVCG_MJPEG_ATTR_RO(b_format_index, bFormatIndex, 8);
+UVCG_MJPEG_ATTR(b_default_frame_index, bDefaultFrameIndex, 8);
+UVCG_MJPEG_ATTR_RO(bm_flags, bmFlags, 8);
+UVCG_MJPEG_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, 8);
+UVCG_MJPEG_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, 8);
+UVCG_MJPEG_ATTR_RO(bm_interface_flags, bmInterfaceFlags, 8);
#undef UVCG_MJPEG_ATTR
#undef UVCG_MJPEG_ATTR_RO
@@ -1629,6 +1846,7 @@ uvcg_mjpeg_bma_controls_store(struct config_item *item,
UVC_ATTR(uvcg_mjpeg_, bma_controls, bmaControls);
static struct configfs_attribute *uvcg_mjpeg_attrs[] = {
+ &uvcg_mjpeg_attr_b_format_index,
&uvcg_mjpeg_attr_b_default_frame_index,
&uvcg_mjpeg_attr_bm_flags,
&uvcg_mjpeg_attr_b_aspect_ratio_x,
@@ -1639,6 +1857,7 @@ static struct configfs_attribute *uvcg_mjpeg_attrs[] = {
};
static const struct config_item_type uvcg_mjpeg_type = {
+ .ct_item_ops = &uvcg_config_item_ops,
.ct_group_ops = &uvcg_mjpeg_group_ops,
.ct_attrs = uvcg_mjpeg_attrs,
.ct_owner = THIS_MODULE,
@@ -1669,56 +1888,297 @@ static struct config_group *uvcg_mjpeg_make(struct config_group *group,
return &h->fmt.group;
}
-static void uvcg_mjpeg_drop(struct config_group *group,
- struct config_item *item)
+static struct configfs_group_operations uvcg_mjpeg_grp_ops = {
+ .make_group = uvcg_mjpeg_make,
+};
+
+static const struct uvcg_config_group_type uvcg_mjpeg_grp_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_group_ops = &uvcg_mjpeg_grp_ops,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "mjpeg",
+};
+
+/* -----------------------------------------------------------------------------
+ * streaming/framebased/<NAME>
+ */
+
+struct uvcg_framebased {
+ struct uvcg_format fmt;
+ struct uvc_format_framebased desc;
+};
+
+static struct uvcg_framebased *to_uvcg_framebased(struct config_item *item)
{
- struct uvcg_mjpeg *h = to_uvcg_mjpeg(item);
+ return container_of(
+ container_of(to_config_group(item), struct uvcg_format, group),
+ struct uvcg_framebased, fmt);
+}
+
+static struct configfs_group_operations uvcg_framebased_group_ops = {
+ .make_item = uvcg_frame_make,
+ .drop_item = uvcg_frame_drop,
+};
- kfree(h);
+#define UVCG_FRAMEBASED_ATTR_RO(cname, aname, bits) \
+static ssize_t uvcg_framebased_##cname##_show(struct config_item *item,\
+ char *page) \
+{ \
+ struct uvcg_framebased *u = to_uvcg_framebased(item); \
+ struct f_uvc_opts *opts; \
+ struct config_item *opts_item; \
+ struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \
+ int result; \
+ \
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
+ \
+ opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
+ opts = to_f_uvc_opts(opts_item); \
+ \
+ mutex_lock(&opts->lock); \
+ result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\
+ mutex_unlock(&opts->lock); \
+ \
+ mutex_unlock(su_mutex); \
+ return result; \
+} \
+ \
+UVC_ATTR_RO(uvcg_framebased_, cname, aname)
+
+#define UVCG_FRAMEBASED_ATTR(cname, aname, bits) \
+static ssize_t uvcg_framebased_##cname##_show(struct config_item *item,\
+ char *page)\
+{ \
+ struct uvcg_framebased *u = to_uvcg_framebased(item); \
+ struct f_uvc_opts *opts; \
+ struct config_item *opts_item; \
+ struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \
+ int result; \
+ \
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
+ \
+ opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
+ opts = to_f_uvc_opts(opts_item); \
+ \
+ mutex_lock(&opts->lock); \
+ result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\
+ mutex_unlock(&opts->lock); \
+ \
+ mutex_unlock(su_mutex); \
+ return result; \
+} \
+ \
+static ssize_t \
+uvcg_framebased_##cname##_store(struct config_item *item, \
+ const char *page, size_t len) \
+{ \
+ struct uvcg_framebased *u = to_uvcg_framebased(item); \
+ struct f_uvc_opts *opts; \
+ struct config_item *opts_item; \
+ struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \
+ int ret; \
+ u8 num; \
+ \
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
+ \
+ opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
+ opts = to_f_uvc_opts(opts_item); \
+ \
+ mutex_lock(&opts->lock); \
+ if (u->fmt.linked || opts->refcnt) { \
+ ret = -EBUSY; \
+ goto end; \
+ } \
+ \
+ ret = kstrtou8(page, 0, &num); \
+ if (ret) \
+ goto end; \
+ \
+ if (num > 255) { \
+ ret = -EINVAL; \
+ goto end; \
+ } \
+ u->desc.aname = num; \
+ ret = len; \
+end: \
+ mutex_unlock(&opts->lock); \
+ mutex_unlock(su_mutex); \
+ return ret; \
+} \
+ \
+UVC_ATTR(uvcg_framebased_, cname, aname)
+
+UVCG_FRAMEBASED_ATTR_RO(b_format_index, bFormatIndex, 8);
+UVCG_FRAMEBASED_ATTR_RO(b_bits_per_pixel, bBitsPerPixel, 8);
+UVCG_FRAMEBASED_ATTR(b_default_frame_index, bDefaultFrameIndex, 8);
+UVCG_FRAMEBASED_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, 8);
+UVCG_FRAMEBASED_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, 8);
+UVCG_FRAMEBASED_ATTR_RO(bm_interface_flags, bmInterfaceFlags, 8);
+
+#undef UVCG_FRAMEBASED_ATTR
+#undef UVCG_FRAMEBASED_ATTR_RO
+
+static ssize_t uvcg_framebased_guid_format_show(struct config_item *item,
+ char *page)
+{
+ struct uvcg_framebased *ch = to_uvcg_framebased(item);
+ struct f_uvc_opts *opts;
+ struct config_item *opts_item;
+ struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex;
+
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+ opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;
+ opts = to_f_uvc_opts(opts_item);
+
+ mutex_lock(&opts->lock);
+ memcpy(page, ch->desc.guidFormat, sizeof(ch->desc.guidFormat));
+ mutex_unlock(&opts->lock);
+
+ mutex_unlock(su_mutex);
+
+ return sizeof(ch->desc.guidFormat);
}
-static struct configfs_group_operations uvcg_mjpeg_grp_ops = {
- .make_group = uvcg_mjpeg_make,
- .drop_item = uvcg_mjpeg_drop,
+static ssize_t uvcg_framebased_guid_format_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct uvcg_framebased *ch = to_uvcg_framebased(item);
+ struct f_uvc_opts *opts;
+ struct config_item *opts_item;
+ struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex;
+ int ret;
+
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+ opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;
+ opts = to_f_uvc_opts(opts_item);
+
+ mutex_lock(&opts->lock);
+ if (ch->fmt.linked || opts->refcnt) {
+ ret = -EBUSY;
+ goto end;
+ }
+
+ memcpy(ch->desc.guidFormat, page,
+ min(sizeof(ch->desc.guidFormat), len));
+ ret = sizeof(ch->desc.guidFormat);
+
+end:
+ mutex_unlock(&opts->lock);
+ mutex_unlock(su_mutex);
+ return ret;
+}
+
+UVC_ATTR(uvcg_framebased_, guid_format, guidFormat);
+
+static inline ssize_t
+uvcg_framebased_bma_controls_show(struct config_item *item, char *page)
+{
+ struct uvcg_framebased *u = to_uvcg_framebased(item);
+
+ return uvcg_format_bma_controls_show(&u->fmt, page);
+}
+
+static inline ssize_t
+uvcg_framebased_bma_controls_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct uvcg_framebased *u = to_uvcg_framebased(item);
+
+ return uvcg_format_bma_controls_store(&u->fmt, page, len);
+}
+
+UVC_ATTR(uvcg_framebased_, bma_controls, bmaControls);
+
+static struct configfs_attribute *uvcg_framebased_attrs[] = {
+ &uvcg_framebased_attr_b_format_index,
+ &uvcg_framebased_attr_b_default_frame_index,
+ &uvcg_framebased_attr_b_bits_per_pixel,
+ &uvcg_framebased_attr_b_aspect_ratio_x,
+ &uvcg_framebased_attr_b_aspect_ratio_y,
+ &uvcg_framebased_attr_bm_interface_flags,
+ &uvcg_framebased_attr_bma_controls,
+ &uvcg_framebased_attr_guid_format,
+ NULL,
};
-static const struct config_item_type uvcg_mjpeg_grp_type = {
- .ct_group_ops = &uvcg_mjpeg_grp_ops,
+static const struct config_item_type uvcg_framebased_type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_group_ops = &uvcg_framebased_group_ops,
+ .ct_attrs = uvcg_framebased_attrs,
.ct_owner = THIS_MODULE,
};
-/* streaming/color_matching/default */
-static struct uvcg_default_color_matching {
- struct config_group group;
-} uvcg_default_color_matching;
-
-static inline struct uvcg_default_color_matching
-*to_uvcg_default_color_matching(struct config_item *item)
+static struct config_group *uvcg_framebased_make(struct config_group *group,
+ const char *name)
{
- return container_of(to_config_group(item),
- struct uvcg_default_color_matching, group);
+ static char guid[] = { /*Declear frame frame based as H264*/
+ 'H', '2', '6', '4', 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
+ };
+ struct uvcg_framebased *f;
+
+ f = kzalloc(sizeof(*f), GFP_KERNEL);
+ if (!f)
+ return ERR_PTR(-ENOMEM);
+
+ f->desc.bLength = UVC_DT_FORMAT_FRAMEBASED_SIZE;
+ f->desc.bDescriptorType = USB_DT_CS_INTERFACE;
+ f->desc.bDescriptorSubType = UVC_VS_FORMAT_FRAME_BASED;
+ memcpy(f->desc.guidFormat, guid, sizeof(guid));
+ f->desc.bBitsPerPixel = 16;
+ f->desc.bDefaultFrameIndex = 1;
+ f->desc.bAspectRatioX = 0;
+ f->desc.bAspectRatioY = 0;
+ f->desc.bmInterfaceFlags = 0;
+ f->desc.bCopyProtect = 0;
+ f->desc.bVariableSize = 1;
+
+ f->fmt.type = UVCG_FRAMEBASED;
+ config_group_init_type_name(&f->fmt.group, name,
+ &uvcg_framebased_type);
+
+ return &f->fmt.group;
}
-#define UVCG_DEFAULT_COLOR_MATCHING_ATTR(cname, aname, conv) \
+static struct configfs_group_operations uvcg_framebased_grp_ops = {
+ .make_group = uvcg_framebased_make,
+};
+static const struct uvcg_config_group_type uvcg_framebased_grp_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_group_ops = &uvcg_framebased_grp_ops,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "framebased",
+};
+
+/* -----------------------------------------------------------------------------
+ * streaming/color_matching/default
+ */
+
+#define UVCG_DEFAULT_COLOR_MATCHING_ATTR(cname, aname, bits) \
static ssize_t uvcg_default_color_matching_##cname##_show( \
- struct config_item *item, char *page) \
+ struct config_item *item, char *page) \
{ \
- struct uvcg_default_color_matching *dc = \
- to_uvcg_default_color_matching(item); \
+ struct config_group *group = to_config_group(item); \
struct f_uvc_opts *opts; \
struct config_item *opts_item; \
- struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex; \
+ struct mutex *su_mutex = &group->cg_subsys->su_mutex; \
struct uvc_color_matching_descriptor *cd; \
int result; \
\
mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
\
- opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent; \
+ opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; \
opts = to_f_uvc_opts(opts_item); \
cd = &opts->uvc_color_matching; \
\
mutex_lock(&opts->lock); \
- result = sprintf(page, "%d\n", conv(cd->aname)); \
+ result = sprintf(page, "%u\n", le##bits##_to_cpu(cd->aname)); \
mutex_unlock(&opts->lock); \
\
mutex_unlock(su_mutex); \
@@ -1727,16 +2187,10 @@ static ssize_t uvcg_default_color_matching_##cname##_show( \
\
UVC_ATTR_RO(uvcg_default_color_matching_, cname, aname)
-#define identity_conv(x) (x)
-
-UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_color_primaries, bColorPrimaries,
- identity_conv);
+UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_color_primaries, bColorPrimaries, 8);
UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_transfer_characteristics,
- bTransferCharacteristics, identity_conv);
-UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_matrix_coefficients, bMatrixCoefficients,
- identity_conv);
-
-#undef identity_conv
+ bTransferCharacteristics, 8);
+UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_matrix_coefficients, bMatrixCoefficients, 8);
#undef UVCG_DEFAULT_COLOR_MATCHING_ATTR
@@ -1747,41 +2201,54 @@ static struct configfs_attribute *uvcg_default_color_matching_attrs[] = {
NULL,
};
-static const struct config_item_type uvcg_default_color_matching_type = {
- .ct_attrs = uvcg_default_color_matching_attrs,
- .ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_default_color_matching_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_attrs = uvcg_default_color_matching_attrs,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "default",
};
-/* struct uvcg_color_matching {}; */
-
-/* streaming/color_matching */
-static struct uvcg_color_matching_grp {
- struct config_group group;
-} uvcg_color_matching_grp;
+/* -----------------------------------------------------------------------------
+ * streaming/color_matching
+ */
-static const struct config_item_type uvcg_color_matching_grp_type = {
- .ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_color_matching_grp_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "color_matching",
+ .children = (const struct uvcg_config_group_type*[]) {
+ &uvcg_default_color_matching_type,
+ NULL,
+ },
};
-/* streaming/class/{fs|hs|ss} */
-static struct uvcg_streaming_class {
- struct config_group group;
-} uvcg_streaming_class_fs, uvcg_streaming_class_hs, uvcg_streaming_class_ss;
+/* -----------------------------------------------------------------------------
+ * streaming/class/{fs|hs|ss}
+ */
+struct uvcg_streaming_class_group {
+ struct config_group group;
+ const char *name;
+};
static inline struct uvc_descriptor_header
***__uvcg_get_stream_class_arr(struct config_item *i, struct f_uvc_opts *o)
{
- struct uvcg_streaming_class *cl = container_of(to_config_group(i),
- struct uvcg_streaming_class, group);
+ struct uvcg_streaming_class_group *group =
+ container_of(i, struct uvcg_streaming_class_group,
+ group.cg_item);
- if (cl == &uvcg_streaming_class_fs)
+ if (!strcmp(group->name, "fs"))
return &o->uvc_fs_streaming_cls;
- if (cl == &uvcg_streaming_class_hs)
+ if (!strcmp(group->name, "hs"))
return &o->uvc_hs_streaming_cls;
- if (cl == &uvcg_streaming_class_ss)
+ if (!strcmp(group->name, "ss"))
return &o->uvc_ss_streaming_cls;
return NULL;
@@ -1882,6 +2349,10 @@ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n,
container_of(fmt, struct uvcg_mjpeg, fmt);
*size += sizeof(m->desc);
+ } else if (fmt->type == UVCG_FRAMEBASED) {
+ struct uvcg_framebased *f =
+ container_of(fmt, struct uvcg_framebased, fmt);
+ *size += sizeof(f->desc);
} else {
return -EINVAL;
}
@@ -1892,6 +2363,11 @@ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n,
int sz = sizeof(frm->dw_frame_interval);
*size += sizeof(frm->frame);
+ /*
+ * framebased has duplicate member with uncompressed and
+ * mjpeg, so minus it
+ */
+ *size -= sizeof(u32);
*size += frm->frame.b_frame_interval_type * sz;
}
break;
@@ -1902,6 +2378,27 @@ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n,
return 0;
}
+static int __uvcg_copy_framebased_desc(void *dest, struct uvcg_frame *frm,
+ int sz)
+{
+ struct uvc_frame_framebased *desc = dest;
+
+ desc->bLength = frm->frame.b_length;
+ desc->bDescriptorType = frm->frame.b_descriptor_type;
+ desc->bDescriptorSubType = frm->frame.b_descriptor_subtype;
+ desc->bFrameIndex = frm->frame.b_frame_index;
+ desc->bmCapabilities = frm->frame.bm_capabilities;
+ desc->wWidth = frm->frame.w_width;
+ desc->wHeight = frm->frame.w_height;
+ desc->dwMinBitRate = frm->frame.dw_min_bit_rate;
+ desc->dwMaxBitRate = frm->frame.dw_max_bit_rate;
+ desc->dwDefaultFrameInterval = frm->frame.dw_default_frame_interval;
+ desc->bFrameIntervalType = frm->frame.b_frame_interval_type;
+ desc->dwBytesPerLine = frm->frame.dw_bytes_perline;
+
+ return 0;
+}
+
/*
* Fill an array of streaming descriptors.
*
@@ -1940,24 +2437,31 @@ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n,
struct uvcg_format *fmt = priv1;
if (fmt->type == UVCG_UNCOMPRESSED) {
- struct uvc_format_uncompressed *unc = *dest;
struct uvcg_uncompressed *u =
container_of(fmt, struct uvcg_uncompressed,
fmt);
+ u->desc.bFormatIndex = n + 1;
+ u->desc.bNumFrameDescriptors = fmt->num_frames;
memcpy(*dest, &u->desc, sizeof(u->desc));
*dest += sizeof(u->desc);
- unc->bNumFrameDescriptors = fmt->num_frames;
- unc->bFormatIndex = n + 1;
} else if (fmt->type == UVCG_MJPEG) {
- struct uvc_format_mjpeg *mjp = *dest;
struct uvcg_mjpeg *m =
container_of(fmt, struct uvcg_mjpeg, fmt);
+ m->desc.bFormatIndex = n + 1;
+ m->desc.bNumFrameDescriptors = fmt->num_frames;
memcpy(*dest, &m->desc, sizeof(m->desc));
*dest += sizeof(m->desc);
- mjp->bNumFrameDescriptors = fmt->num_frames;
- mjp->bFormatIndex = n + 1;
+ } else if (fmt->type == UVCG_FRAMEBASED) {
+ struct uvcg_framebased *f =
+ container_of(fmt, struct uvcg_framebased,
+ fmt);
+
+ f->desc.bFormatIndex = n + 1;
+ f->desc.bNumFrameDescriptors = fmt->num_frames;
+ memcpy(*dest, &f->desc, sizeof(f->desc));
+ *dest += sizeof(f->desc);
} else {
return -EINVAL;
}
@@ -1967,8 +2471,11 @@ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n,
struct uvcg_frame *frm = priv1;
struct uvc_descriptor_header *h = *dest;
- sz = sizeof(frm->frame);
- memcpy(*dest, &frm->frame, sz);
+ sz = sizeof(frm->frame) - 4;
+ if (frm->fmt_type != UVCG_FRAMEBASED)
+ memcpy(*dest, &frm->frame, sz);
+ else
+ __uvcg_copy_framebased_desc(*dest, frm, sz);
*dest += sz;
sz = frm->frame.b_frame_interval_type *
sizeof(*frm->dw_frame_interval);
@@ -1980,6 +2487,9 @@ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n,
else if (frm->fmt_type == UVCG_MJPEG)
h->bLength = UVC_DT_FRAME_MJPEG_SIZE(
frm->frame.b_frame_interval_type);
+ else if (frm->fmt_type == UVCG_FRAMEBASED)
+ h->bLength = UVC_DT_FRAME_FRAMEBASED_SIZE(
+ frm->frame.b_frame_interval_type);
}
break;
}
@@ -2102,6 +2612,7 @@ static void uvcg_streaming_class_drop_link(struct config_item *src,
}
static struct configfs_item_operations uvcg_streaming_class_item_ops = {
+ .release = uvcg_config_item_release,
.allow_link = uvcg_streaming_class_allow_link,
.drop_link = uvcg_streaming_class_drop_link,
};
@@ -2111,36 +2622,110 @@ static const struct config_item_type uvcg_streaming_class_type = {
.ct_owner = THIS_MODULE,
};
-/* streaming/class */
-static struct uvcg_streaming_class_grp {
- struct config_group group;
-} uvcg_streaming_class_grp;
+/* -----------------------------------------------------------------------------
+ * streaming/class
+ */
+
+static int uvcg_streaming_class_create_children(struct config_group *parent)
+{
+ static const char * const names[] = { "fs", "hs", "ss" };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(names); ++i) {
+ struct uvcg_streaming_class_group *group;
+
+ group = kzalloc(sizeof(*group), GFP_KERNEL);
+ if (!group)
+ return -ENOMEM;
-static const struct config_item_type uvcg_streaming_class_grp_type = {
- .ct_owner = THIS_MODULE,
+ group->name = names[i];
+
+ config_group_init_type_name(&group->group, group->name,
+ &uvcg_streaming_class_type);
+ configfs_add_default_group(&group->group, parent);
+ }
+
+ return 0;
+}
+
+static const struct uvcg_config_group_type uvcg_streaming_class_grp_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "class",
+ .create_children = uvcg_streaming_class_create_children,
};
-/* streaming */
-static struct uvcg_streaming_grp {
- struct config_group group;
-} uvcg_streaming_grp;
+/* -----------------------------------------------------------------------------
+ * streaming
+ */
-static const struct config_item_type uvcg_streaming_grp_type = {
- .ct_owner = THIS_MODULE,
+static ssize_t uvcg_default_streaming_b_interface_number_show(
+ struct config_item *item, char *page)
+{
+ struct config_group *group = to_config_group(item);
+ struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+ struct config_item *opts_item;
+ struct f_uvc_opts *opts;
+ int result = 0;
+
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+ opts_item = item->ci_parent;
+ opts = to_f_uvc_opts(opts_item);
+
+ mutex_lock(&opts->lock);
+ result += sprintf(page, "%u\n", opts->streaming_interface);
+ mutex_unlock(&opts->lock);
+
+ mutex_unlock(su_mutex);
+
+ return result;
+}
+
+UVC_ATTR_RO(uvcg_default_streaming_, b_interface_number, bInterfaceNumber);
+
+static struct configfs_attribute *uvcg_default_streaming_attrs[] = {
+ &uvcg_default_streaming_attr_b_interface_number,
+ NULL,
+};
+
+static const struct uvcg_config_group_type uvcg_streaming_grp_type = {
+ .type = {
+ .ct_item_ops = &uvcg_config_item_ops,
+ .ct_attrs = uvcg_default_streaming_attrs,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "streaming",
+ .children = (const struct uvcg_config_group_type*[]) {
+ &uvcg_streaming_header_grp_type,
+ &uvcg_uncompressed_grp_type,
+ &uvcg_mjpeg_grp_type,
+ &uvcg_framebased_grp_type,
+ &uvcg_color_matching_grp_type,
+ &uvcg_streaming_class_grp_type,
+ NULL,
+ },
};
-static void uvc_attr_release(struct config_item *item)
+/* -----------------------------------------------------------------------------
+ * UVC function
+ */
+
+static void uvc_func_item_release(struct config_item *item)
{
struct f_uvc_opts *opts = to_f_uvc_opts(item);
+ uvcg_config_remove_children(to_config_group(item));
usb_put_function_instance(&opts->func_inst);
}
-static struct configfs_item_operations uvc_item_ops = {
- .release = uvc_attr_release,
+static struct configfs_item_operations uvc_func_item_ops = {
+ .release = uvc_func_item_release,
};
-#define UVCG_OPTS_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit) \
+#define UVCG_OPTS_ATTR(cname, aname, limit) \
static ssize_t f_uvc_opts_##cname##_show( \
struct config_item *item, char *page) \
{ \
@@ -2148,7 +2733,7 @@ static ssize_t f_uvc_opts_##cname##_show( \
int result; \
\
mutex_lock(&opts->lock); \
- result = sprintf(page, "%d\n", conv(opts->cname)); \
+ result = sprintf(page, "%u\n", opts->cname); \
mutex_unlock(&opts->lock); \
\
return result; \
@@ -2159,8 +2744,8 @@ f_uvc_opts_##cname##_store(struct config_item *item, \
const char *page, size_t len) \
{ \
struct f_uvc_opts *opts = to_f_uvc_opts(item); \
+ unsigned int num; \
int ret; \
- uxx num; \
\
mutex_lock(&opts->lock); \
if (opts->refcnt) { \
@@ -2168,7 +2753,7 @@ f_uvc_opts_##cname##_store(struct config_item *item, \
goto end; \
} \
\
- ret = str2u(page, 0, &num); \
+ ret = kstrtouint(page, 0, &num); \
if (ret) \
goto end; \
\
@@ -2176,7 +2761,7 @@ f_uvc_opts_##cname##_store(struct config_item *item, \
ret = -EINVAL; \
goto end; \
} \
- opts->cname = vnoc(num); \
+ opts->cname = num; \
ret = len; \
end: \
mutex_unlock(&opts->lock); \
@@ -2185,143 +2770,105 @@ end: \
\
UVC_ATTR(f_uvc_opts_, cname, cname)
-#define identity_conv(x) (x)
+UVCG_OPTS_ATTR(streaming_bulk, streaming_bulk, 1);
+UVCG_OPTS_ATTR(streaming_interval, streaming_interval, 16);
+UVCG_OPTS_ATTR(streaming_maxpacket, streaming_maxpacket, 3072);
+UVCG_OPTS_ATTR(streaming_maxburst, streaming_maxburst, 15);
+UVCG_OPTS_ATTR(uvc_num_request, uvc_num_request, UVC_MAX_NUM_REQUESTS);
+UVCG_OPTS_ATTR(pm_qos_latency, pm_qos_latency, PM_QOS_LATENCY_ANY);
-UVCG_OPTS_ATTR(streaming_interval, streaming_interval, identity_conv,
- kstrtou8, u8, identity_conv, 16);
-UVCG_OPTS_ATTR(streaming_maxpacket, streaming_maxpacket, le16_to_cpu,
- kstrtou16, u16, le16_to_cpu, 3072);
-UVCG_OPTS_ATTR(streaming_maxburst, streaming_maxburst, identity_conv,
- kstrtou8, u8, identity_conv, 15);
+#undef UVCG_OPTS_ATTR
-#undef identity_conv
+static ssize_t f_uvc_opts_device_name_show(struct config_item *item,
+ char *page)
+{
+ struct f_uvc_opts *opts = to_f_uvc_opts(item);
+ int ret;
-#undef UVCG_OPTS_ATTR
+ mutex_lock(&opts->lock);
+ ret = sprintf(page, "%s\n", opts->device_name ?: "");
+ mutex_unlock(&opts->lock);
+
+ return ret;
+}
+
+static ssize_t f_uvc_opts_device_name_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct f_uvc_opts *opts = to_f_uvc_opts(item);
+ const char *old_name;
+ char *name;
+ int ret;
+
+ if (strlen(page) < len)
+ return -EOVERFLOW;
+
+ mutex_lock(&opts->lock);
+ if (opts->refcnt) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ name = kstrdup(page, GFP_KERNEL);
+ if (!name) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ if (name[len - 1] == '\n')
+ name[len - 1] = '\0';
+
+ old_name = opts->device_name;
+ opts->device_name = name;
+
+ if (opts->device_name_allocated)
+ kfree(old_name);
+
+ opts->device_name_allocated = true;
+ ret = len;
+unlock:
+ mutex_unlock(&opts->lock);
+
+ return ret;
+}
+UVC_ATTR(f_uvc_opts_, device_name, device_name);
static struct configfs_attribute *uvc_attrs[] = {
+ &f_uvc_opts_attr_streaming_bulk,
&f_uvc_opts_attr_streaming_interval,
&f_uvc_opts_attr_streaming_maxpacket,
&f_uvc_opts_attr_streaming_maxburst,
+ &f_uvc_opts_attr_uvc_num_request,
+ &f_uvc_opts_attr_pm_qos_latency,
+ &f_uvc_opts_attr_device_name,
NULL,
};
-static const struct config_item_type uvc_func_type = {
- .ct_item_ops = &uvc_item_ops,
- .ct_attrs = uvc_attrs,
- .ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvc_func_type = {
+ .type = {
+ .ct_item_ops = &uvc_func_item_ops,
+ .ct_attrs = uvc_attrs,
+ .ct_owner = THIS_MODULE,
+ },
+ .name = "",
+ .children = (const struct uvcg_config_group_type*[]) {
+ &uvcg_control_grp_type,
+ &uvcg_streaming_grp_type,
+ NULL,
+ },
};
int uvcg_attach_configfs(struct f_uvc_opts *opts)
{
- config_group_init_type_name(&uvcg_control_header_grp.group,
- "header",
- &uvcg_control_header_grp_type);
-
- config_group_init_type_name(&uvcg_default_processing.group,
- "default", &uvcg_default_processing_type);
- config_group_init_type_name(&uvcg_processing_grp.group,
- "processing", &uvcg_processing_grp_type);
- configfs_add_default_group(&uvcg_default_processing.group,
- &uvcg_processing_grp.group);
-
- config_group_init_type_name(&uvcg_default_camera.group,
- "default", &uvcg_default_camera_type);
- config_group_init_type_name(&uvcg_camera_grp.group,
- "camera", &uvcg_camera_grp_type);
- configfs_add_default_group(&uvcg_default_camera.group,
- &uvcg_camera_grp.group);
-
- config_group_init_type_name(&uvcg_default_output.group,
- "default", &uvcg_default_output_type);
- config_group_init_type_name(&uvcg_output_grp.group,
- "output", &uvcg_output_grp_type);
- configfs_add_default_group(&uvcg_default_output.group,
- &uvcg_output_grp.group);
-
- config_group_init_type_name(&uvcg_terminal_grp.group,
- "terminal", &uvcg_terminal_grp_type);
- configfs_add_default_group(&uvcg_camera_grp.group,
- &uvcg_terminal_grp.group);
- configfs_add_default_group(&uvcg_output_grp.group,
- &uvcg_terminal_grp.group);
-
- config_group_init_type_name(&uvcg_control_class_fs.group,
- "fs", &uvcg_control_class_type);
- config_group_init_type_name(&uvcg_control_class_ss.group,
- "ss", &uvcg_control_class_type);
- config_group_init_type_name(&uvcg_control_class_grp.group,
- "class",
- &uvcg_control_class_grp_type);
- configfs_add_default_group(&uvcg_control_class_fs.group,
- &uvcg_control_class_grp.group);
- configfs_add_default_group(&uvcg_control_class_ss.group,
- &uvcg_control_class_grp.group);
-
- config_group_init_type_name(&uvcg_control_grp.group,
- "control",
- &uvcg_control_grp_type);
- configfs_add_default_group(&uvcg_control_header_grp.group,
- &uvcg_control_grp.group);
- configfs_add_default_group(&uvcg_processing_grp.group,
- &uvcg_control_grp.group);
- configfs_add_default_group(&uvcg_terminal_grp.group,
- &uvcg_control_grp.group);
- configfs_add_default_group(&uvcg_control_class_grp.group,
- &uvcg_control_grp.group);
-
- config_group_init_type_name(&uvcg_streaming_header_grp.group,
- "header",
- &uvcg_streaming_header_grp_type);
- config_group_init_type_name(&uvcg_uncompressed_grp.group,
- "uncompressed",
- &uvcg_uncompressed_grp_type);
- config_group_init_type_name(&uvcg_mjpeg_grp.group,
- "mjpeg",
- &uvcg_mjpeg_grp_type);
- config_group_init_type_name(&uvcg_default_color_matching.group,
- "default",
- &uvcg_default_color_matching_type);
- config_group_init_type_name(&uvcg_color_matching_grp.group,
- "color_matching",
- &uvcg_color_matching_grp_type);
- configfs_add_default_group(&uvcg_default_color_matching.group,
- &uvcg_color_matching_grp.group);
-
- config_group_init_type_name(&uvcg_streaming_class_fs.group,
- "fs", &uvcg_streaming_class_type);
- config_group_init_type_name(&uvcg_streaming_class_hs.group,
- "hs", &uvcg_streaming_class_type);
- config_group_init_type_name(&uvcg_streaming_class_ss.group,
- "ss", &uvcg_streaming_class_type);
- config_group_init_type_name(&uvcg_streaming_class_grp.group,
- "class", &uvcg_streaming_class_grp_type);
- configfs_add_default_group(&uvcg_streaming_class_fs.group,
- &uvcg_streaming_class_grp.group);
- configfs_add_default_group(&uvcg_streaming_class_hs.group,
- &uvcg_streaming_class_grp.group);
- configfs_add_default_group(&uvcg_streaming_class_ss.group,
- &uvcg_streaming_class_grp.group);
-
- config_group_init_type_name(&uvcg_streaming_grp.group,
- "streaming", &uvcg_streaming_grp_type);
- configfs_add_default_group(&uvcg_streaming_header_grp.group,
- &uvcg_streaming_grp.group);
- configfs_add_default_group(&uvcg_uncompressed_grp.group,
- &uvcg_streaming_grp.group);
- configfs_add_default_group(&uvcg_mjpeg_grp.group,
- &uvcg_streaming_grp.group);
- configfs_add_default_group(&uvcg_color_matching_grp.group,
- &uvcg_streaming_grp.group);
- configfs_add_default_group(&uvcg_streaming_class_grp.group,
- &uvcg_streaming_grp.group);
-
- config_group_init_type_name(&opts->func_inst.group,
- "",
- &uvc_func_type);
- configfs_add_default_group(&uvcg_control_grp.group,
- &opts->func_inst.group);
- configfs_add_default_group(&uvcg_streaming_grp.group,
- &opts->func_inst.group);
+ int ret;
- return 0;
+ config_group_init_type_name(&opts->func_inst.group, uvc_func_type.name,
+ &uvc_func_type.type);
+
+ ret = uvcg_config_create_children(&opts->func_inst.group,
+ &uvc_func_type);
+ if (ret < 0)
+ config_group_put(&opts->func_inst.group);
+
+ return ret;
}
diff --git a/drivers/usb/gadget/function/uvc_queue.c b/drivers/usb/gadget/function/uvc_queue.c
index 9e33d5206..a404108c3 100644
--- a/drivers/usb/gadget/function/uvc_queue.c
+++ b/drivers/usb/gadget/function/uvc_queue.c
@@ -124,6 +124,14 @@ int uvcg_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
queue->queue.mem_ops = &vb2_vmalloc_memops;
queue->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
| V4L2_BUF_FLAG_TSTAMP_SRC_EOF;
+ /*
+ * For rockchip platform, the userspace uvc application
+ * use bytesused == 0 as a way to indicate that the data
+ * is all zero and unused.
+ */
+#ifdef CONFIG_ARCH_ROCKCHIP
+ queue->queue.allow_zero_bytesused = 1;
+#endif
ret = vb2_queue_init(&queue->queue);
if (ret)
return ret;
diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 7f1ca3b57..9d2356eef 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -41,6 +41,7 @@ uvc_send_response(struct uvc_device *uvc, struct uvc_request_data *data)
req->length = min_t(unsigned int, uvc->event_length, data->length);
req->zero = data->length < uvc->event_length;
+ uvc_trace(UVC_TRACE_CONTROL, "%s: req len %d\n", __func__, req->length);
memcpy(req->buf, data->data, req->length);
return usb_ep_queue(cdev->gadget->ep0, req, GFP_KERNEL);
@@ -57,7 +58,10 @@ struct uvc_format {
static struct uvc_format uvc_formats[] = {
{ 16, V4L2_PIX_FMT_YUYV },
+ { 12, V4L2_PIX_FMT_NV12 },
{ 0, V4L2_PIX_FMT_MJPEG },
+ { 0, V4L2_PIX_FMT_H264 },
+ { 0, V4L2_PIX_FMT_H265 },
};
static int
@@ -173,7 +177,9 @@ uvc_v4l2_qbuf(struct file *file, void *fh, struct v4l2_buffer *b)
if (ret < 0)
return ret;
- return uvcg_video_pump(video);
+ queue_work(video->async_wq, &video->pump);
+
+ return ret;
}
static int
@@ -197,17 +203,30 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
if (type != video->queue.queue.type)
return -EINVAL;
+ if (uvc->state != UVC_STATE_CONNECTED)
+ return -ENODEV;
+
/* Enable UVC video. */
ret = uvcg_video_enable(video, 1);
if (ret < 0)
return ret;
/*
- * Complete the alternate setting selection setup phase now that
- * userspace is ready to provide video frames.
+ * Alt settings in an interface are supported only
+ * for ISOC endpoints as there are different alt-
+ * settings for zero-bandwidth and full-bandwidth
+ * cases, but the same is not true for BULK endpoints,
+ * as they have a single alt-setting.
*/
- uvc_function_setup_continue(uvc);
- uvc->state = UVC_STATE_STREAMING;
+ if (!usb_endpoint_xfer_bulk(video->ep->desc)) {
+ /*
+ * Complete the alternate setting selection
+ * setup phase now that userspace is ready
+ * to provide video frames.
+ */
+ uvc_function_setup_continue(uvc);
+ uvc->state = UVC_STATE_STREAMING;
+ }
return 0;
}
@@ -356,6 +375,9 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
.open = uvc_v4l2_open,
.release = uvc_v4l2_release,
.unlocked_ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl32 = video_ioctl2,
+#endif
.mmap = uvc_v4l2_mmap,
.poll = uvc_v4l2_poll,
#ifndef CONFIG_MMU
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 2c9821ec8..809259d61 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -12,12 +12,14 @@
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/video.h>
+#include <linux/pm_qos.h>
#include <media/v4l2-dev.h>
#include "uvc.h"
#include "uvc_queue.h"
#include "uvc_video.h"
+#include "u_uvc.h"
/* --------------------------------------------------------------------------
* Video codecs
@@ -87,6 +89,7 @@ uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video,
video->fid ^= UVC_STREAM_FID;
video->payload_size = 0;
+ req->zero = 1;
}
if (video->payload_size == video->max_payload_size ||
@@ -133,51 +136,19 @@ static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
if (ret < 0) {
printk(KERN_INFO "Failed to queue request (%d).\n", ret);
/* Isochronous endpoints can't be halted. */
- if (usb_endpoint_xfer_bulk(video->ep->desc))
+ if (video->ep->desc && usb_endpoint_xfer_bulk(video->ep->desc))
usb_ep_set_halt(video->ep);
}
return ret;
}
-/*
- * I somehow feel that synchronisation won't be easy to achieve here. We have
- * three events that control USB requests submission:
- *
- * - USB request completion: the completion handler will resubmit the request
- * if a video buffer is available.
- *
- * - USB interface setting selection: in response to a SET_INTERFACE request,
- * the handler will start streaming if a video buffer is available and if
- * video is not currently streaming.
- *
- * - V4L2 buffer queueing: the driver will start streaming if video is not
- * currently streaming.
- *
- * Race conditions between those 3 events might lead to deadlocks or other
- * nasty side effects.
- *
- * The "video currently streaming" condition can't be detected by the irqqueue
- * being empty, as a request can still be in flight. A separate "queue paused"
- * flag is thus needed.
- *
- * The paused flag will be set when we try to retrieve the irqqueue head if the
- * queue is empty, and cleared when we queue a buffer.
- *
- * The USB request completion handler will get the buffer at the irqqueue head
- * under protection of the queue spinlock. If the queue is empty, the streaming
- * paused flag will be set. Right after releasing the spinlock a userspace
- * application can queue a buffer. The flag will then cleared, and the ioctl
- * handler will restart the video stream.
- */
static void
uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
{
struct uvc_video *video = req->context;
struct uvc_video_queue *queue = &video->queue;
- struct uvc_buffer *buf;
unsigned long flags;
- int ret;
switch (req->status) {
case 0:
@@ -186,46 +157,33 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
case -ESHUTDOWN: /* disconnect from host. */
printk(KERN_DEBUG "VS request cancelled.\n");
uvcg_queue_cancel(queue, 1);
- goto requeue;
+ break;
default:
printk(KERN_INFO "VS request completed with status %d.\n",
req->status);
uvcg_queue_cancel(queue, 0);
- goto requeue;
- }
-
- spin_lock_irqsave(&video->queue.irqlock, flags);
- buf = uvcg_queue_head(&video->queue);
- if (buf == NULL) {
- spin_unlock_irqrestore(&video->queue.irqlock, flags);
- goto requeue;
- }
-
- video->encode(req, video, buf);
-
- ret = uvcg_video_ep_queue(video, req);
- spin_unlock_irqrestore(&video->queue.irqlock, flags);
-
- if (ret < 0) {
- uvcg_queue_cancel(queue, 0);
- goto requeue;
+ break;
}
- return;
-
-requeue:
spin_lock_irqsave(&video->req_lock, flags);
list_add_tail(&req->list, &video->req_free);
spin_unlock_irqrestore(&video->req_lock, flags);
+
+ queue_work(video->async_wq, &video->pump);
}
static int
uvc_video_free_requests(struct uvc_video *video)
{
unsigned int i;
+ struct uvc_device *uvc;
+ struct f_uvc_opts *opts;
+
+ uvc = container_of(video, struct uvc_device, video);
+ opts = fi_to_f_uvc_opts(uvc->func.fi);
- for (i = 0; i < UVC_NUM_REQUESTS; ++i) {
+ for (i = 0; i < opts->uvc_num_request; ++i) {
if (video->req[i]) {
usb_ep_free_request(video->ep, video->req[i]);
video->req[i] = NULL;
@@ -248,14 +206,24 @@ uvc_video_alloc_requests(struct uvc_video *video)
unsigned int req_size;
unsigned int i;
int ret = -ENOMEM;
+ struct uvc_device *uvc;
+ struct f_uvc_opts *opts;
+
+ uvc = container_of(video, struct uvc_device, video);
+ opts = fi_to_f_uvc_opts(uvc->func.fi);
BUG_ON(video->req_size);
- req_size = video->ep->maxpacket
- * max_t(unsigned int, video->ep->maxburst, 1)
- * (video->ep->mult);
+ if (!usb_endpoint_xfer_bulk(video->ep->desc)) {
+ req_size = video->ep->maxpacket
+ * max_t(unsigned int, video->ep->maxburst, 1)
+ * (video->ep->mult);
+ } else {
+ req_size = video->ep->maxpacket
+ * max_t(unsigned int, video->ep->maxburst, 1);
+ }
- for (i = 0; i < UVC_NUM_REQUESTS; ++i) {
+ for (i = 0; i < opts->uvc_num_request; ++i) {
video->req_buffer[i] = kmalloc(req_size, GFP_KERNEL);
if (video->req_buffer[i] == NULL)
goto error;
@@ -291,18 +259,15 @@ uvc_video_alloc_requests(struct uvc_video *video)
* This function fills the available USB requests (listed in req_free) with
* video data from the queued buffers.
*/
-int uvcg_video_pump(struct uvc_video *video)
+static void uvcg_video_pump(struct work_struct *work)
{
+ struct uvc_video *video = container_of(work, struct uvc_video, pump);
struct uvc_video_queue *queue = &video->queue;
struct usb_request *req;
struct uvc_buffer *buf;
unsigned long flags;
int ret;
- /* FIXME TODO Race between uvcg_video_pump and requests completion
- * handler ???
- */
-
while (1) {
/* Retrieve the first available USB request, protected by the
* request lock.
@@ -310,7 +275,7 @@ int uvcg_video_pump(struct uvc_video *video)
spin_lock_irqsave(&video->req_lock, flags);
if (list_empty(&video->req_free)) {
spin_unlock_irqrestore(&video->req_lock, flags);
- return 0;
+ return;
}
req = list_first_entry(&video->req_free, struct usb_request,
list);
@@ -342,7 +307,7 @@ int uvcg_video_pump(struct uvc_video *video)
spin_lock_irqsave(&video->req_lock, flags);
list_add_tail(&req->list, &video->req_free);
spin_unlock_irqrestore(&video->req_lock, flags);
- return 0;
+ return;
}
/*
@@ -352,6 +317,8 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
{
unsigned int i;
int ret;
+ struct uvc_device *uvc;
+ struct f_uvc_opts *opts;
if (video->ep == NULL) {
printk(KERN_INFO "Video enable failed, device is "
@@ -359,16 +326,25 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
return -ENODEV;
}
+ uvc = container_of(video, struct uvc_device, video);
+ opts = fi_to_f_uvc_opts(uvc->func.fi);
+
if (!enable) {
- for (i = 0; i < UVC_NUM_REQUESTS; ++i)
+ cancel_work_sync(&video->pump);
+ uvcg_queue_cancel(&video->queue, 0);
+ for (i = 0; i < opts->uvc_num_request; ++i)
if (video->req[i])
usb_ep_dequeue(video->ep, video->req[i]);
uvc_video_free_requests(video);
uvcg_queue_enable(&video->queue, 0);
+ if (pm_qos_request_active(&uvc->pm_qos))
+ pm_qos_remove_request(&uvc->pm_qos);
return 0;
}
+ pm_qos_add_request(&uvc->pm_qos, PM_QOS_CPU_DMA_LATENCY,
+ opts->pm_qos_latency);
if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
return ret;
@@ -381,7 +357,9 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
} else
video->encode = uvc_video_encode_isoc;
- return uvcg_video_pump(video);
+ queue_work(video->async_wq, &video->pump);
+
+ return ret;
}
/*
@@ -391,6 +369,7 @@ int uvcg_video_init(struct uvc_video *video)
{
INIT_LIST_HEAD(&video->req_free);
spin_lock_init(&video->req_lock);
+ INIT_WORK(&video->pump, uvcg_video_pump);
video->fcc = V4L2_PIX_FMT_YUYV;
video->bpp = 16;
diff --git a/drivers/usb/gadget/function/uvc_video.h b/drivers/usb/gadget/function/uvc_video.h
index 7d77122b0..fc1e2ebd7 100644
--- a/drivers/usb/gadget/function/uvc_video.h
+++ b/drivers/usb/gadget/function/uvc_video.h
@@ -14,8 +14,6 @@
struct uvc_video;
-int uvcg_video_pump(struct uvc_video *video);
-
int uvcg_video_enable(struct uvc_video *video, int enable);
int uvcg_video_init(struct uvc_video *video);
diff --git a/drivers/usb/gadget/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig
index 784bf86da..21bceaa68 100644
--- a/drivers/usb/gadget/legacy/Kconfig
+++ b/drivers/usb/gadget/legacy/Kconfig
@@ -1,3 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
#
# USB Gadget support on a system involves
# (a) a peripheral controller, and
@@ -13,6 +14,32 @@
# both kinds of controller can also support "USB On-the-Go" (CONFIG_USB_OTG).
#
+choice
+ tristate "USB Gadget precomposed configurations"
+ default USB_ETH
+ optional
+ help
+ A Linux "Gadget Driver" talks to the USB Peripheral Controller
+ driver through the abstract "gadget" API. Some other operating
+ systems call these "client" drivers, of which "class drivers"
+ are a subset (implementing a USB device class specification).
+ A gadget driver implements one or more USB functions using
+ the peripheral hardware.
+
+ Gadget drivers are hardware-neutral, or "platform independent",
+ except that they sometimes must understand quirks or limitations
+ of the particular controllers they work with. For example, when
+ a controller doesn't support alternate configurations or provide
+ enough of the right types of endpoints, the gadget driver might
+ not be able work with that controller, or might need to implement
+ a less common variant of a device class protocol.
+
+ The available choices each represent a single precomposed USB
+ gadget configuration. In the device model, each option contains
+ both the device instantiation as a child for a USB gadget
+ controller, and the relevant drivers for each function declared
+ by the device.
+
config USB_ZERO
tristate "Gadget Zero (DEVELOPMENT)"
select USB_LIBCOMPOSITE
@@ -490,3 +517,16 @@ config USB_G_WEBCAM
Say "y" to link the driver statically, or "m" to build a
dynamically linked module called "g_webcam".
+
+config USB_RAW_GADGET
+ tristate "USB Raw Gadget"
+ help
+ USB Raw Gadget is a kernel module that provides a userspace interface
+ for the USB Gadget subsystem. Essentially it allows to emulate USB
+ devices from userspace. See Documentation/usb/raw-gadget.rst for
+ details.
+
+ Say "y" to link the driver statically, or "m" to build a
+ dynamically linked module called "raw_gadget".
+
+endchoice
diff --git a/drivers/usb/gadget/legacy/Makefile b/drivers/usb/gadget/legacy/Makefile
index abd0c3e66..4d864bf82 100644
--- a/drivers/usb/gadget/legacy/Makefile
+++ b/drivers/usb/gadget/legacy/Makefile
@@ -43,3 +43,4 @@ obj-$(CONFIG_USB_G_WEBCAM) += g_webcam.o
obj-$(CONFIG_USB_G_NCM) += g_ncm.o
obj-$(CONFIG_USB_G_ACM_MS) += g_acm_ms.o
obj-$(CONFIG_USB_GADGET_TARGET) += tcm_usb_gadget.o
+obj-$(CONFIG_USB_RAW_GADGET) += raw_gadget.o
diff --git a/drivers/usb/gadget/legacy/audio.c b/drivers/usb/gadget/legacy/audio.c
index a748ed084..104d1c4ce 100644
--- a/drivers/usb/gadget/legacy/audio.c
+++ b/drivers/usb/gadget/legacy/audio.c
@@ -17,69 +17,36 @@
USB_GADGET_COMPOSITE_OPTIONS();
-#ifndef CONFIG_GADGET_UAC1
-#include "u_uac2.h"
+#if !defined(CONFIG_GADGET_UAC1) || !defined(CONFIG_GADGET_UAC1_LEGACY)
+#include "u_uac.h"
/* Playback(USB-IN) Default Stereo - Fl/Fr */
-static int p_chmask = UAC2_DEF_PCHMASK;
+static int p_chmask = UAC_DEF_PCHMASK;
module_param(p_chmask, uint, S_IRUGO);
MODULE_PARM_DESC(p_chmask, "Playback Channel Mask");
/* Playback Default 48 KHz */
-static int p_srate = UAC2_DEF_PSRATE;
+static int p_srate = UAC_DEF_PSRATE;
module_param(p_srate, uint, S_IRUGO);
MODULE_PARM_DESC(p_srate, "Playback Sampling Rate");
/* Playback Default 16bits/sample */
-static int p_ssize = UAC2_DEF_PSSIZE;
+static int p_ssize = UAC_DEF_PSSIZE;
module_param(p_ssize, uint, S_IRUGO);
MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)");
/* Capture(USB-OUT) Default Stereo - Fl/Fr */
-static int c_chmask = UAC2_DEF_CCHMASK;
+static int c_chmask = UAC_DEF_CCHMASK;
module_param(c_chmask, uint, S_IRUGO);
MODULE_PARM_DESC(c_chmask, "Capture Channel Mask");
/* Capture Default 64 KHz */
-static int c_srate = UAC2_DEF_CSRATE;
+static int c_srate = UAC_DEF_CSRATE;
module_param(c_srate, uint, S_IRUGO);
MODULE_PARM_DESC(c_srate, "Capture Sampling Rate");
/* Capture Default 16bits/sample */
-static int c_ssize = UAC2_DEF_CSSIZE;
-module_param(c_ssize, uint, S_IRUGO);
-MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)");
-#else
-#ifndef CONFIG_GADGET_UAC1_LEGACY
-#include "u_uac1.h"
-
-/* Playback(USB-IN) Default Stereo - Fl/Fr */
-static int p_chmask = UAC1_DEF_PCHMASK;
-module_param(p_chmask, uint, S_IRUGO);
-MODULE_PARM_DESC(p_chmask, "Playback Channel Mask");
-
-/* Playback Default 48 KHz */
-static int p_srate = UAC1_DEF_PSRATE;
-module_param(p_srate, uint, S_IRUGO);
-MODULE_PARM_DESC(p_srate, "Playback Sampling Rate");
-
-/* Playback Default 16bits/sample */
-static int p_ssize = UAC1_DEF_PSSIZE;
-module_param(p_ssize, uint, S_IRUGO);
-MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)");
-
-/* Capture(USB-OUT) Default Stereo - Fl/Fr */
-static int c_chmask = UAC1_DEF_CCHMASK;
-module_param(c_chmask, uint, S_IRUGO);
-MODULE_PARM_DESC(c_chmask, "Capture Channel Mask");
-
-/* Capture Default 48 KHz */
-static int c_srate = UAC1_DEF_CSRATE;
-module_param(c_srate, uint, S_IRUGO);
-MODULE_PARM_DESC(c_srate, "Capture Sampling Rate");
-
-/* Capture Default 16bits/sample */
-static int c_ssize = UAC1_DEF_CSSIZE;
+static int c_ssize = UAC_DEF_CSSIZE;
module_param(c_ssize, uint, S_IRUGO);
MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)");
#else /* CONFIG_GADGET_UAC1_LEGACY */
@@ -109,7 +76,6 @@ static int audio_buf_size = UAC1_AUDIO_BUF_SIZE;
module_param(audio_buf_size, int, S_IRUGO);
MODULE_PARM_DESC(audio_buf_size, "Audio buffer size");
#endif /* CONFIG_GADGET_UAC1_LEGACY */
-#endif
/* string IDs are assigned dynamically */
@@ -235,14 +201,10 @@ static struct usb_configuration audio_config_driver = {
static int audio_bind(struct usb_composite_dev *cdev)
{
-#ifndef CONFIG_GADGET_UAC1
- struct f_uac2_opts *uac2_opts;
-#else
-#ifndef CONFIG_GADGET_UAC1_LEGACY
- struct f_uac1_opts *uac1_opts;
+#if !defined(CONFIG_GADGET_UAC1) || !defined(CONFIG_GADGET_UAC1_LEGACY)
+ struct f_uac_opts *uac_opts;
#else
struct f_uac1_legacy_opts *uac1_opts;
-#endif
#endif
int status;
@@ -250,9 +212,11 @@ static int audio_bind(struct usb_composite_dev *cdev)
fi_uac2 = usb_get_function_instance("uac2");
if (IS_ERR(fi_uac2))
return PTR_ERR(fi_uac2);
+ uac_opts = container_of(fi_uac2, struct f_uac_opts, func_inst);
#else
#ifndef CONFIG_GADGET_UAC1_LEGACY
fi_uac1 = usb_get_function_instance("uac1");
+ uac_opts = container_of(fi_uac1, struct f_uac_opts, func_inst);
#else
fi_uac1 = usb_get_function_instance("uac1_legacy");
#endif
@@ -260,25 +224,17 @@ static int audio_bind(struct usb_composite_dev *cdev)
return PTR_ERR(fi_uac1);
#endif
-#ifndef CONFIG_GADGET_UAC1
- uac2_opts = container_of(fi_uac2, struct f_uac2_opts, func_inst);
- uac2_opts->p_chmask = p_chmask;
- uac2_opts->p_srate = p_srate;
- uac2_opts->p_ssize = p_ssize;
- uac2_opts->c_chmask = c_chmask;
- uac2_opts->c_srate = c_srate;
- uac2_opts->c_ssize = c_ssize;
- uac2_opts->req_number = UAC2_DEF_REQ_NUM;
-#else
-#ifndef CONFIG_GADGET_UAC1_LEGACY
- uac1_opts = container_of(fi_uac1, struct f_uac1_opts, func_inst);
- uac1_opts->p_chmask = p_chmask;
- uac1_opts->p_srate = p_srate;
- uac1_opts->p_ssize = p_ssize;
- uac1_opts->c_chmask = c_chmask;
- uac1_opts->c_srate = c_srate;
- uac1_opts->c_ssize = c_ssize;
- uac1_opts->req_number = UAC1_DEF_REQ_NUM;
+#if !defined(CONFIG_GADGET_UAC1) || !defined(CONFIG_GADGET_UAC1_LEGACY)
+ memset(uac_opts, 0x0, sizeof(*uac_opts));
+ uac_opts->p_chmask = p_chmask;
+ uac_opts->p_srate[0] = p_srate;
+ uac_opts->p_srate_active = p_srate;
+ uac_opts->p_ssize = p_ssize;
+ uac_opts->c_chmask = c_chmask;
+ uac_opts->c_srate[0] = c_srate;
+ uac_opts->c_srate_active = c_srate;
+ uac_opts->c_ssize = c_ssize;
+ uac_opts->req_number = UAC_DEF_REQ_NUM;
#else /* CONFIG_GADGET_UAC1_LEGACY */
uac1_opts = container_of(fi_uac1, struct f_uac1_legacy_opts, func_inst);
uac1_opts->fn_play = fn_play;
@@ -288,7 +244,6 @@ static int audio_bind(struct usb_composite_dev *cdev)
uac1_opts->req_count = req_count;
uac1_opts->audio_buf_size = audio_buf_size;
#endif /* CONFIG_GADGET_UAC1_LEGACY */
-#endif
status = usb_string_ids_tab(cdev, strings_dev);
if (status < 0)
diff --git a/drivers/usb/gadget/legacy/webcam.c b/drivers/usb/gadget/legacy/webcam.c
index a9f8eb8e1..e68d921b3 100644
--- a/drivers/usb/gadget/legacy/webcam.c
+++ b/drivers/usb/gadget/legacy/webcam.c
@@ -382,6 +382,8 @@ webcam_bind(struct usb_composite_dev *cdev)
uvc_opts->fs_streaming = uvc_fs_streaming_cls;
uvc_opts->hs_streaming = uvc_hs_streaming_cls;
uvc_opts->ss_streaming = uvc_ss_streaming_cls;
+ uvc_opts->uvc_num_request = UVC_NUM_REQUESTS;
+ uvc_opts->pm_qos_latency = 0;
/* Allocate string descriptor numbers ... note that string contents
* can be overridden by the composite_dev glue.
diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig
index d83d93c6e..e070bcaec 100644
--- a/drivers/usb/gadget/udc/Kconfig
+++ b/drivers/usb/gadget/udc/Kconfig
@@ -1,3 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
#
# USB Gadget support on a system involves
# (a) a peripheral controller, and
diff --git a/drivers/usb/gadget/udc/bdc/Kconfig b/drivers/usb/gadget/udc/bdc/Kconfig
index 051091bd2..fb01ff47b 100644
--- a/drivers/usb/gadget/udc/bdc/Kconfig
+++ b/drivers/usb/gadget/udc/bdc/Kconfig
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
config USB_BDC_UDC
tristate "Broadcom USB3.0 device controller IP driver(BDC)"
depends on USB_GADGET && HAS_DMA
diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c
index e3dc74cdb..d8c9586a3 100644
--- a/drivers/usb/gadget/udc/core.c
+++ b/drivers/usb/gadget/udc/core.c
@@ -506,6 +506,43 @@ int usb_gadget_wakeup(struct usb_gadget *gadget)
}
EXPORT_SYMBOL_GPL(usb_gadget_wakeup);
+/**
+ * usb_gsi_ep_op - performs operation on GSI accelerated EP based on EP op code
+ *
+ * Operations such as EP configuration, TRB allocation, StartXfer etc.
+ * See gsi_ep_op for more details.
+ */
+int usb_gsi_ep_op(struct usb_ep *ep,
+ struct usb_gsi_request *req, enum gsi_ep_op op)
+{
+ if (ep && ep->ops && ep->ops->gsi_ep_op)
+ return ep->ops->gsi_ep_op(ep, req, op);
+
+ return -EOPNOTSUPP;
+}
+EXPORT_SYMBOL_GPL(usb_gsi_ep_op);
+
+/**
+ * usb_gadget_func_wakeup - send a function remote wakeup up notification
+ * to the host connected to this gadget
+ * @gadget: controller used to wake up the host
+ * @interface_id: the interface which triggered the remote wakeup event
+ *
+ * Returns zero on success. Otherwise, negative error code is returned.
+ */
+int usb_gadget_func_wakeup(struct usb_gadget *gadget,
+ int interface_id)
+{
+ if (!gadget || (gadget->speed != USB_SPEED_SUPER))
+ return -EOPNOTSUPP;
+
+ if (!gadget->ops || !gadget->ops->func_wakeup)
+ return -EOPNOTSUPP;
+
+ return gadget->ops->func_wakeup(gadget, interface_id);
+}
+EXPORT_SYMBOL_GPL(usb_gadget_func_wakeup);
+
/**
* usb_gadget_set_selfpowered - sets the device selfpowered feature.
* @gadget:the device being declared as self-powered
@@ -747,10 +784,10 @@ int usb_gadget_deactivate(struct usb_gadget *gadget)
{
int ret = 0;
- if (gadget->deactivated)
+ if (!gadget || gadget->deactivated)
goto out;
- if (gadget->connected) {
+ if (gadget->connected && !gadget->uvc_enabled) {
ret = usb_gadget_disconnect(gadget);
if (ret)
goto out;
diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c
index d25d25da6..3259899b2 100644
--- a/drivers/usb/gadget/udc/dummy_hcd.c
+++ b/drivers/usb/gadget/udc/dummy_hcd.c
@@ -187,31 +187,31 @@ static const struct {
USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)),
/* and now some generic EPs so we have enough in multi config */
- EP_INFO("ep3out",
+ EP_INFO("ep-aout",
USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)),
- EP_INFO("ep4in",
+ EP_INFO("ep-bin",
USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_IN)),
- EP_INFO("ep5out",
+ EP_INFO("ep-cout",
USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)),
- EP_INFO("ep6out",
+ EP_INFO("ep-dout",
USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)),
- EP_INFO("ep7in",
+ EP_INFO("ep-ein",
USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_IN)),
- EP_INFO("ep8out",
+ EP_INFO("ep-fout",
USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)),
- EP_INFO("ep9in",
+ EP_INFO("ep-gin",
USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_IN)),
- EP_INFO("ep10out",
+ EP_INFO("ep-hout",
USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)),
- EP_INFO("ep11out",
+ EP_INFO("ep-iout",
USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)),
- EP_INFO("ep12in",
+ EP_INFO("ep-jin",
USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_IN)),
- EP_INFO("ep13out",
+ EP_INFO("ep-kout",
USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)),
- EP_INFO("ep14in",
+ EP_INFO("ep-lin",
USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_IN)),
- EP_INFO("ep15out",
+ EP_INFO("ep-mout",
USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)),
#undef EP_INFO
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 1a4ea98ca..353fa0bec 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -1,3 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
#
# USB Host Controller Drivers
#
diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c
index f4195822e..01d133525 100644
--- a/drivers/usb/host/ehci-platform.c
+++ b/drivers/usb/host/ehci-platform.c
@@ -28,6 +28,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/sys_soc.h>
#include <linux/timer.h>
@@ -53,6 +54,18 @@ struct ehci_platform_priv {
static const char hcd_name[] = "ehci-platform";
+static void ehci_rockchip_usic_init(struct usb_hcd *hcd)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+ ehci_writel(ehci, USIC_ENABLE,
+ hcd->regs + USIC_ENABLE_OFFSET);
+ ehci_writel(ehci, USIC_MICROFRAME_COUNT,
+ hcd->regs + USIC_MICROFRAME_OFFSET);
+ ehci_writel(ehci, USIC_SCALE_DOWN,
+ hcd->regs + USIC_SCALE_DOWN_OFFSET);
+}
+
static int ehci_platform_reset(struct usb_hcd *hcd)
{
struct platform_device *pdev = to_platform_device(hcd->self.controller);
@@ -297,6 +310,10 @@ static int ehci_platform_probe(struct platform_device *dev)
if (soc_device_match(quirk_poll_match))
priv->quirk_poll = true;
+ if (of_property_read_bool(dev->dev.of_node,
+ "rockchip-has-usic"))
+ ehci->has_usic = 1;
+
for (clk = 0; clk < EHCI_MAX_CLKS; clk++) {
priv->clks[clk] = of_clk_get(dev->dev.of_node, clk);
if (IS_ERR(priv->clks[clk])) {
@@ -347,6 +364,9 @@ static int ehci_platform_probe(struct platform_device *dev)
}
#endif
+ pm_runtime_set_active(&dev->dev);
+ pm_runtime_enable(&dev->dev);
+ pm_runtime_get_sync(&dev->dev);
if (pdata->power_on) {
err = pdata->power_on(dev);
if (err < 0)
@@ -366,6 +386,9 @@ static int ehci_platform_probe(struct platform_device *dev)
if (err)
goto err_power;
+ if (ehci->has_usic)
+ ehci_rockchip_usic_init(hcd);
+
device_wakeup_enable(hcd->self.controller);
device_enable_async_suspend(hcd->self.controller);
platform_set_drvdata(dev, hcd);
@@ -379,6 +402,8 @@ static int ehci_platform_probe(struct platform_device *dev)
if (pdata->power_off)
pdata->power_off(dev);
err_reset:
+ pm_runtime_put_sync(&dev->dev);
+ pm_runtime_disable(&dev->dev);
reset_control_assert(priv->rsts);
err_put_clks:
while (--clk >= 0)
@@ -414,6 +439,9 @@ static int ehci_platform_remove(struct platform_device *dev)
usb_put_hcd(hcd);
+ pm_runtime_put_sync(&dev->dev);
+ pm_runtime_disable(&dev->dev);
+
if (pdata == &ehci_platform_defaults)
dev->dev.platform_data = NULL;
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index 0101018be..38d28929e 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -220,6 +220,13 @@ struct ehci_hcd { /* one per controller */
unsigned imx28_write_fix:1; /* For Freescale i.MX28 */
unsigned is_aspeed:1;
unsigned ignore_oc:1;
+ unsigned has_usic:1;
+ #define USIC_MICROFRAME_OFFSET 0x90
+ #define USIC_SCALE_DOWN_OFFSET 0xa0
+ #define USIC_ENABLE_OFFSET 0xb0
+ #define USIC_ENABLE BIT(0)
+ #define USIC_SCALE_DOWN BIT(2)
+ #define USIC_MICROFRAME_COUNT 0x1d4d
/* required for usb32 quirk */
#define OHCI_CTRL_HCFS (3 << 6)
diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c
index 65a1c3fdc..e11eb9c81 100644
--- a/drivers/usb/host/ohci-platform.c
+++ b/drivers/usb/host/ohci-platform.c
@@ -32,7 +32,7 @@
#include "ohci.h"
#define DRIVER_DESC "OHCI generic platform driver"
-#define OHCI_MAX_CLKS 3
+#define OHCI_MAX_CLKS 4
#define hcd_to_ohci_priv(h) ((struct ohci_platform_priv *)hcd_to_ohci(h)->priv)
struct ohci_platform_priv {
@@ -96,7 +96,7 @@ static int ohci_platform_probe(struct platform_device *dev)
struct ohci_hcd *ohci;
int err, irq, clk = 0;
- if (usb_disabled())
+ if (usb_disabled() || of_machine_is_compatible("rockchip,rk3288"))
return -ENODEV;
/*
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index 1bb48e534..b7c378b5c 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -1571,7 +1571,21 @@ int xhci_bus_suspend(struct usb_hcd *hcd)
t2 &= ~PORT_PLS_MASK;
t2 |= PORT_LINK_STROBE | XDEV_U3;
set_bit(port_index, &bus_state->bus_suspended);
+ } else if ((xhci->quirks & XHCI_U2_BROKEN_SUSPEND) &&
+ (hcd->speed < HCD_USB3) &&
+ (t1 & PORT_PLS_MASK) == XDEV_U3) {
+ /*
+ * Rockchip SNPS xHC 3.0 set USB 2.0 PHY enter
+ * suspend mode from DWC3 core if the suspend
+ * conditions are valid. In this case, it need
+ * to set the bus_suspended bit for USB 2.0, so
+ * that in xhci_bus_resume, it can set the xHC
+ * link state to XDEV_RESUME and send USB resume
+ * signal to USB 2.0 device.
+ */
+ set_bit(port_index, &bus_state->bus_suspended);
}
+
/* USB core sets remote wake mask for USB 3.0 hubs,
* including the USB 3.0 roothub, but only if CONFIG_PM
* is enabled, so also enable remote wake here.
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index 798823ce2..276c6fc9f 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -1837,19 +1837,145 @@ void xhci_free_erst(struct xhci_hcd *xhci, struct xhci_erst *erst)
erst->entries = NULL;
}
-void xhci_mem_cleanup(struct xhci_hcd *xhci)
+void xhci_handle_sec_intr_events(struct xhci_hcd *xhci, int intr_num)
+{
+ union xhci_trb *erdp_trb, *current_trb;
+ struct xhci_segment *seg;
+ u64 erdp_reg;
+ u32 iman_reg;
+ dma_addr_t deq;
+ unsigned long segment_offset;
+
+ /* disable irq, ack pending interrupt and ack all pending events */
+
+ iman_reg =
+ readl_relaxed(&xhci->sec_ir_set[intr_num]->irq_pending);
+ iman_reg &= ~IMAN_IE;
+ writel_relaxed(iman_reg,
+ &xhci->sec_ir_set[intr_num]->irq_pending);
+ iman_reg =
+ readl_relaxed(&xhci->sec_ir_set[intr_num]->irq_pending);
+ if (iman_reg & IMAN_IP)
+ writel_relaxed(iman_reg,
+ &xhci->sec_ir_set[intr_num]->irq_pending);
+
+ /* last acked event trb is in erdp reg */
+ erdp_reg =
+ xhci_read_64(xhci, &xhci->sec_ir_set[intr_num]->erst_dequeue);
+ deq = (dma_addr_t)(erdp_reg & ~ERST_PTR_MASK);
+ if (!deq) {
+ pr_debug("%s: event ring handling not required\n", __func__);
+ return;
+ }
+
+ seg = xhci->sec_event_ring[intr_num]->first_seg;
+ segment_offset = deq - seg->dma;
+
+ /* find out virtual address of the last acked event trb */
+ erdp_trb = current_trb = &seg->trbs[0] +
+ (segment_offset/sizeof(*current_trb));
+
+ /* read cycle state of the last acked trb to find out CCS */
+ xhci->sec_event_ring[intr_num]->cycle_state =
+ (current_trb->event_cmd.flags & TRB_CYCLE);
+
+ while (1) {
+ /* last trb of the event ring: toggle cycle state */
+ if (current_trb == &seg->trbs[TRBS_PER_SEGMENT - 1]) {
+ xhci->sec_event_ring[intr_num]->cycle_state ^= 1;
+ current_trb = &seg->trbs[0];
+ } else {
+ current_trb++;
+ }
+
+ /* cycle state transition */
+ if ((le32_to_cpu(current_trb->event_cmd.flags) & TRB_CYCLE) !=
+ xhci->sec_event_ring[intr_num]->cycle_state)
+ break;
+ }
+
+ if (erdp_trb != current_trb) {
+ deq =
+ xhci_trb_virt_to_dma(xhci->sec_event_ring[intr_num]->deq_seg,
+ current_trb);
+ if (deq == 0)
+ xhci_warn(xhci,
+ "WARN invalid SW event ring dequeue ptr.\n");
+ /* Update HC event ring dequeue pointer */
+ erdp_reg &= ERST_PTR_MASK;
+ erdp_reg |= ((u64) deq & (u64) ~ERST_PTR_MASK);
+ }
+
+ /* Clear the event handler busy flag (RW1C); event ring is empty. */
+ erdp_reg |= ERST_EHB;
+ xhci_write_64(xhci, erdp_reg,
+ &xhci->sec_ir_set[intr_num]->erst_dequeue);
+}
+
+int xhci_sec_event_ring_cleanup(struct usb_hcd *hcd, unsigned int intr_num)
{
+ int size;
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
- int i, j, num_ports;
- cancel_delayed_work_sync(&xhci->cmd_timer);
+ if (intr_num >= xhci->max_interrupters) {
+ xhci_err(xhci, "invalid secondary interrupter num %d\n",
+ intr_num);
+ return -EINVAL;
+ }
- xhci_free_erst(xhci, &xhci->erst);
+ size =
+ sizeof(struct xhci_erst_entry)*(xhci->sec_erst[intr_num].num_entries);
+ if (xhci->sec_erst[intr_num].entries) {
+ xhci_handle_sec_intr_events(xhci, intr_num);
+ dma_free_coherent(dev, size, xhci->sec_erst[intr_num].entries,
+ xhci->sec_erst[intr_num].erst_dma_addr);
+ xhci->sec_erst[intr_num].entries = NULL;
+ }
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed SEC ERST#%d",
+ intr_num);
+ if (xhci->sec_event_ring[intr_num])
+ xhci_ring_free(xhci, xhci->sec_event_ring[intr_num]);
+
+ xhci->sec_event_ring[intr_num] = NULL;
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init,
+ "Freed sec event ring");
+
+ return 0;
+}
+
+void xhci_event_ring_cleanup(struct xhci_hcd *xhci)
+{
+ unsigned int i;
+
+ /* sec event ring clean up */
+ for (i = 1; i < xhci->max_interrupters; i++)
+ xhci_sec_event_ring_cleanup(xhci_to_hcd(xhci), i);
+ kfree(xhci->sec_ir_set);
+ xhci->sec_ir_set = NULL;
+ kfree(xhci->sec_erst);
+ xhci->sec_erst = NULL;
+ kfree(xhci->sec_event_ring);
+ xhci->sec_event_ring = NULL;
+
+ /* primary event ring clean up */
+ xhci_free_erst(xhci, &xhci->erst);
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed primary ERST");
if (xhci->event_ring)
xhci_ring_free(xhci, xhci->event_ring);
xhci->event_ring = NULL;
- xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed event ring");
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed priamry event ring");
+}
+
+void xhci_mem_cleanup(struct xhci_hcd *xhci)
+{
+ struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
+ int i, j, num_ports;
+
+ cancel_delayed_work_sync(&xhci->cmd_timer);
+
+ xhci_event_ring_cleanup(xhci);
if (xhci->lpm_command)
xhci_free_command(xhci, xhci->lpm_command);
@@ -2095,30 +2221,6 @@ static int xhci_check_trb_in_td_math(struct xhci_hcd *xhci)
return 0;
}
-static void xhci_set_hc_event_deq(struct xhci_hcd *xhci)
-{
- u64 temp;
- dma_addr_t deq;
-
- deq = xhci_trb_virt_to_dma(xhci->event_ring->deq_seg,
- xhci->event_ring->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->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_dbg_trace(xhci, trace_xhci_dbg_init,
- "// Write event ring dequeue pointer, "
- "preserving EHB bit");
- xhci_write_64(xhci, ((u64) deq & (u64) ~ERST_PTR_MASK) | temp,
- &xhci->ir_set->erst_dequeue);
-}
-
static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports,
__le32 __iomem *addr, int max_caps)
{
@@ -2391,6 +2493,154 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags)
return 0;
}
+int xhci_event_ring_setup(struct xhci_hcd *xhci, struct xhci_ring **er,
+ struct xhci_intr_reg __iomem *ir_set, struct xhci_erst *erst,
+ unsigned int intr_num, gfp_t flags)
+{
+ dma_addr_t deq;
+ u64 val_64;
+ unsigned int val;
+ int ret;
+
+ *er = xhci_ring_alloc(xhci, ERST_NUM_SEGS, 1, TYPE_EVENT, 0, flags);
+ if (!*er)
+ return -ENOMEM;
+
+ ret = xhci_alloc_erst(xhci, *er, erst, flags);
+ if (ret)
+ return ret;
+
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init,
+ "intr# %d: num segs = %i, virt addr = %pK, dma addr = 0x%llx",
+ intr_num,
+ erst->num_entries,
+ erst->entries,
+ (unsigned long long)erst->erst_dma_addr);
+
+ /* set ERST count with the number of entries in the segment table */
+ val = readl_relaxed(&ir_set->erst_size);
+ val &= ERST_SIZE_MASK;
+ val |= ERST_NUM_SEGS;
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init,
+ "Write ERST size = %i to ir_set %d (some bits preserved)", val,
+ intr_num);
+ writel_relaxed(val, &ir_set->erst_size);
+
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init,
+ "intr# %d: Set ERST entries to point to event ring.",
+ intr_num);
+ /* set the segment table base address */
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init,
+ "Set ERST base address for ir_set %d = 0x%llx",
+ intr_num,
+ (unsigned long long)erst->erst_dma_addr);
+ val_64 = xhci_read_64(xhci, &ir_set->erst_base);
+ val_64 &= ERST_PTR_MASK;
+ val_64 |= (erst->erst_dma_addr & (u64) ~ERST_PTR_MASK);
+ xhci_write_64(xhci, val_64, &ir_set->erst_base);
+
+ /* Set the event ring dequeue address */
+ deq = xhci_trb_virt_to_dma((*er)->deq_seg, (*er)->dequeue);
+ if (deq == 0 && !in_interrupt())
+ xhci_warn(xhci,
+ "intr# %d:WARN something wrong with SW event ring deq ptr.\n",
+ intr_num);
+ /* Update HC event ring dequeue pointer */
+ val_64 = xhci_read_64(xhci, &ir_set->erst_dequeue);
+ val_64 &= ERST_PTR_MASK;
+ /* Don't clear the EHB bit (which is RW1C) because
+ * there might be more events to service.
+ */
+ val_64 &= ~ERST_EHB;
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init,
+ "intr# %d:Write event ring dequeue pointer, preserving EHB bit",
+ intr_num);
+ xhci_write_64(xhci, ((u64) deq & (u64) ~ERST_PTR_MASK) | val_64,
+ &ir_set->erst_dequeue);
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init,
+ "Wrote ERST address to ir_set %d.", intr_num);
+
+ return 0;
+}
+
+int xhci_sec_event_ring_setup(struct usb_hcd *hcd, unsigned int intr_num)
+{
+ int ret;
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+ if ((xhci->xhc_state & XHCI_STATE_HALTED) || !xhci->sec_ir_set
+ || !xhci->sec_event_ring || !xhci->sec_erst ||
+ intr_num >= xhci->max_interrupters) {
+ xhci_err(xhci,
+ "%s:state %x ir_set %pK evt_ring %pK erst %pK intr# %d\n",
+ __func__, xhci->xhc_state, xhci->sec_ir_set,
+ xhci->sec_event_ring, xhci->sec_erst, intr_num);
+ return -EINVAL;
+ }
+
+ if (xhci->sec_event_ring && xhci->sec_event_ring[intr_num]
+ && xhci->sec_event_ring[intr_num]->first_seg)
+ goto done;
+
+ xhci->sec_ir_set[intr_num] = &xhci->run_regs->ir_set[intr_num];
+ ret = xhci_event_ring_setup(xhci,
+ &xhci->sec_event_ring[intr_num],
+ xhci->sec_ir_set[intr_num],
+ &xhci->sec_erst[intr_num],
+ intr_num, GFP_KERNEL);
+ if (ret) {
+ xhci_err(xhci, "sec event ring setup failed inter#%d\n",
+ intr_num);
+ return ret;
+ }
+done:
+ return 0;
+}
+
+int xhci_event_ring_init(struct xhci_hcd *xhci, gfp_t flags)
+{
+ int ret = 0;
+
+ /* primary + secondary */
+ xhci->max_interrupters = HCS_MAX_INTRS(xhci->hcs_params1);
+
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init,
+ "// Allocating primary event ring");
+
+ /* Set ir_set to interrupt register set 0 */
+ xhci->ir_set = &xhci->run_regs->ir_set[0];
+ ret = xhci_event_ring_setup(xhci, &xhci->event_ring, xhci->ir_set,
+ &xhci->erst, 0, flags);
+ if (ret) {
+ xhci_err(xhci, "failed to setup primary event ring\n");
+ goto fail;
+ }
+
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init,
+ "// Allocating sec event ring related pointers");
+
+ xhci->sec_ir_set = kcalloc(xhci->max_interrupters,
+ sizeof(*xhci->sec_ir_set), flags);
+ if (!xhci->sec_ir_set) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ xhci->sec_event_ring = kcalloc(xhci->max_interrupters,
+ sizeof(*xhci->sec_event_ring), flags);
+ if (!xhci->sec_event_ring) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ xhci->sec_erst = kcalloc(xhci->max_interrupters,
+ sizeof(*xhci->sec_erst), flags);
+ if (!xhci->sec_erst)
+ ret = -ENOMEM;
+fail:
+ return ret;
+}
+
int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
{
dma_addr_t dma;
@@ -2398,7 +2648,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
unsigned int val, val2;
u64 val_64;
u32 page_size, temp;
- int i, ret;
+ int i;
INIT_LIST_HEAD(&xhci->cmd_list);
@@ -2519,50 +2769,17 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
"// Doorbell array is located at offset 0x%x"
" from cap regs base addr", val);
xhci->dba = (void __iomem *) xhci->cap_regs + val;
- /* Set ir_set to interrupt register set 0 */
- xhci->ir_set = &xhci->run_regs->ir_set[0];
/*
* Event ring setup: Allocate a normal ring, but also setup
* the event ring segment table (ERST). Section 4.9.3.
*/
- xhci_dbg_trace(xhci, trace_xhci_dbg_init, "// Allocating event ring");
- xhci->event_ring = xhci_ring_alloc(xhci, ERST_NUM_SEGS, 1, TYPE_EVENT,
- 0, flags);
- if (!xhci->event_ring)
- goto fail;
- if (xhci_check_trb_in_td_math(xhci) < 0)
+ if (xhci_event_ring_init(xhci, GFP_KERNEL))
goto fail;
- ret = xhci_alloc_erst(xhci, xhci->event_ring, &xhci->erst, flags);
- if (ret)
+ if (xhci_check_trb_in_td_math(xhci) < 0)
goto fail;
- /* set ERST count with the number of entries in the segment table */
- val = readl(&xhci->ir_set->erst_size);
- val &= ERST_SIZE_MASK;
- val |= ERST_NUM_SEGS;
- xhci_dbg_trace(xhci, trace_xhci_dbg_init,
- "// Write ERST size = %i to ir_set 0 (some bits preserved)",
- val);
- writel(val, &xhci->ir_set->erst_size);
-
- xhci_dbg_trace(xhci, trace_xhci_dbg_init,
- "// Set ERST entries to point to event ring.");
- /* set the segment table base address */
- xhci_dbg_trace(xhci, trace_xhci_dbg_init,
- "// Set ERST base address for ir_set 0 = 0x%llx",
- (unsigned long long)xhci->erst.erst_dma_addr);
- val_64 = xhci_read_64(xhci, &xhci->ir_set->erst_base);
- val_64 &= ERST_PTR_MASK;
- val_64 |= (xhci->erst.erst_dma_addr & (u64) ~ERST_PTR_MASK);
- xhci_write_64(xhci, val_64, &xhci->ir_set->erst_base);
-
- /* Set the event ring dequeue address */
- xhci_set_hc_event_deq(xhci);
- xhci_dbg_trace(xhci, trace_xhci_dbg_init,
- "Wrote ERST address to ir_set 0.");
-
/*
* XXX: Might need to set the Interrupter Moderation Register to
* something other than the default (~1ms minimum between interrupts).
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index adc437ca8..688b968f1 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -288,6 +288,23 @@ static int xhci_plat_probe(struct platform_device *pdev)
if (device_property_read_bool(tmpdev, "quirk-broken-port-ped"))
xhci->quirks |= XHCI_BROKEN_PORT_PED;
+ if (device_property_read_bool(tmpdev, "xhci-slow-suspend"))
+ xhci->quirks |= XHCI_SLOW_SUSPEND;
+
+ if (device_property_read_bool(tmpdev, "xhci-trb-ent-quirk"))
+ xhci->quirks |= XHCI_TRB_ENT_QUIRK;
+
+ if (device_property_read_bool(tmpdev, "usb3-dis-autosuspend"))
+ xhci->quirks |= XHCI_DIS_AUTOSUSPEND;
+
+ if (device_property_read_bool(tmpdev,
+ "xhci-warm-reset-on-suspend"))
+ xhci->quirks |= XHCI_WARM_RESET_ON_SUSPEND;
+
+ if (device_property_read_bool(tmpdev,
+ "xhci-u2-broken-suspend"))
+ xhci->quirks |= XHCI_U2_BROKEN_SUSPEND;
+
device_property_read_u32(tmpdev, "imod-interval-ns",
&xhci->imod_interval);
}
@@ -305,6 +322,15 @@ static int xhci_plat_probe(struct platform_device *pdev)
hcd->skip_phy_initialization = 1;
}
+ xhci->shared_hcd->usb_phy = devm_usb_get_phy(sysdev,
+ USB_PHY_TYPE_USB3);
+ if (IS_ERR(xhci->shared_hcd->usb_phy)) {
+ ret = PTR_ERR(xhci->shared_hcd->usb_phy);
+ if (ret == -EPROBE_DEFER)
+ goto put_usb3_hcd;
+ xhci->shared_hcd->usb_phy = NULL;
+ }
+
ret = usb_add_hcd(hcd, irq, IRQF_SHARED);
if (ret)
goto disable_usb_phy;
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index f5bd91752..66d6709eb 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -3272,6 +3272,7 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
bool more_trbs_coming = true;
bool need_zero_pkt = false;
bool first_trb = true;
+ bool en_trb_ent = !!(xhci->quirks & XHCI_TRB_ENT_QUIRK);
unsigned int num_trbs;
unsigned int start_cycle, num_sgs = 0;
unsigned int enqd_len, block_len, trb_buff_len, full_len;
@@ -3308,6 +3309,13 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
if (urb->transfer_flags & URB_ZERO_PACKET && urb_priv->num_tds > 1)
need_zero_pkt = true;
+ /*
+ * Don't enable the ENT flag in the TRB if
+ * the EP support bulk streaming protocol.
+ */
+ if (urb->stream_id)
+ en_trb_ent = false;
+
td = &urb_priv->td[0];
/*
@@ -3339,11 +3347,22 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
} else
field |= ring->cycle_state;
+ /*
+ * Don't enable the ENT flag in the TRB if the
+ * transfer length of the TRB isn't an integer
+ * multiple of the EP maxpacket.
+ */
+ if (en_trb_ent &&
+ (trb_buff_len % usb_endpoint_maxp(&urb->ep->desc)))
+ en_trb_ent = false;
+
/* Chain all the TRBs together; clear the chain bit in the last
* TRB to indicate it's the last TRB in the chain.
*/
if (enqd_len + trb_buff_len < full_len) {
field |= TRB_CHAIN;
+ if (en_trb_ent)
+ field |= TRB_ENT;
if (trb_is_link(ring->enqueue + 1)) {
if (xhci_align_td(xhci, urb, enqd_len,
&trb_buff_len,
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index ab255144e..134307bde 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -17,6 +17,7 @@
#include <linux/slab.h>
#include <linux/dmi.h>
#include <linux/dma-mapping.h>
+#include <linux/usb/quirks.h>
#include "xhci.h"
#include "xhci-trace.h"
@@ -775,6 +776,15 @@ void xhci_shutdown(struct usb_hcd *hcd)
{
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ if (!hcd->rh_registered)
+ return;
+
+ /* Don't poll the roothubs on shutdown */
+ clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
+ del_timer_sync(&hcd->rh_timer);
+ clear_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags);
+ del_timer_sync(&xhci->shared_hcd->rh_timer);
+
if (xhci->quirks & XHCI_SPURIOUS_REBOOT)
usb_disable_xhci_ports(to_pci_dev(hcd->self.sysdev));
@@ -953,6 +963,26 @@ static bool xhci_pending_portevent(struct xhci_hcd *xhci)
return false;
}
+static void xhci_warm_port_reset_quirk(struct xhci_hcd *xhci)
+{
+ struct xhci_port **ports;
+ int port_index;
+ u32 portsc;
+
+ port_index = xhci->usb3_rhub.num_ports;
+ ports = xhci->usb3_rhub.ports;
+ while (port_index--) {
+ portsc = readl(ports[port_index]->addr);
+ /* Do warm port reset if no USB3 device connected */
+ if (!(portsc & PORT_CONNECT)) {
+ portsc |= PORT_WR;
+ writel(portsc, ports[port_index]->addr);
+ /* flush write */
+ readl(ports[port_index]->addr);
+ }
+ }
+}
+
/*
* Stop HC (not bus-specific)
*
@@ -978,6 +1008,13 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup)
if (!do_wakeup)
xhci_disable_port_wake_on_bits(xhci);
+ /*
+ * Do a warm reset for USB3 port to resets the USB3 link,
+ * forcing the link to enter the Rx.Detect state.
+ */
+ if (xhci->quirks & XHCI_WARM_RESET_ON_SUSPEND)
+ xhci_warm_port_reset_quirk(xhci);
+
if (!HCD_HW_ACCESSIBLE(hcd))
return 0;
@@ -5149,6 +5186,9 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
/* xHCI private pointer was set in xhci_pci_probe for the second
* registered roothub.
*/
+ if (xhci->quirks & XHCI_DIS_AUTOSUSPEND)
+ xhci->shared_hcd->self.root_hub->quirks |=
+ USB_QUIRK_AUTO_SUSPEND;
return 0;
}
@@ -5235,6 +5275,136 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
}
EXPORT_SYMBOL_GPL(xhci_gen_setup);
+static phys_addr_t xhci_get_sec_event_ring_phys_addr(struct usb_hcd *hcd,
+ unsigned int intr_num, dma_addr_t *dma)
+{
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ struct device *dev = hcd->self.sysdev;
+ struct sg_table sgt;
+ phys_addr_t pa;
+
+ if (intr_num > xhci->max_interrupters) {
+ xhci_err(xhci, "intr num %d > max intrs %d\n", intr_num,
+ xhci->max_interrupters);
+ return 0;
+ }
+
+ if (!(xhci->xhc_state & XHCI_STATE_HALTED) &&
+ xhci->sec_event_ring && xhci->sec_event_ring[intr_num]
+ && xhci->sec_event_ring[intr_num]->first_seg) {
+
+ dma_get_sgtable(dev, &sgt,
+ xhci->sec_event_ring[intr_num]->first_seg->trbs,
+ xhci->sec_event_ring[intr_num]->first_seg->dma,
+ TRB_SEGMENT_SIZE);
+
+ *dma = xhci->sec_event_ring[intr_num]->first_seg->dma;
+
+ pa = page_to_phys(sg_page(sgt.sgl));
+ sg_free_table(&sgt);
+
+ return pa;
+ }
+
+ return 0;
+}
+
+static phys_addr_t xhci_get_xfer_ring_phys_addr(struct usb_hcd *hcd,
+ struct usb_device *udev, struct usb_host_endpoint *ep, dma_addr_t *dma)
+{
+ int ret;
+ unsigned int ep_index;
+ struct xhci_virt_device *virt_dev;
+ struct device *dev = hcd->self.sysdev;
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ struct sg_table sgt;
+ phys_addr_t pa;
+
+ ret = xhci_check_args(hcd, udev, ep, 1, true, __func__);
+ if (ret <= 0) {
+ xhci_err(xhci, "%s: invalid args\n", __func__);
+ return 0;
+ }
+
+ virt_dev = xhci->devs[udev->slot_id];
+ ep_index = xhci_get_endpoint_index(&ep->desc);
+
+ if (virt_dev->eps[ep_index].ring &&
+ virt_dev->eps[ep_index].ring->first_seg) {
+
+ dma_get_sgtable(dev, &sgt,
+ virt_dev->eps[ep_index].ring->first_seg->trbs,
+ virt_dev->eps[ep_index].ring->first_seg->dma,
+ TRB_SEGMENT_SIZE);
+
+ *dma = virt_dev->eps[ep_index].ring->first_seg->dma;
+
+ pa = page_to_phys(sg_page(sgt.sgl));
+ sg_free_table(&sgt);
+
+ return pa;
+ }
+
+ return 0;
+}
+
+static int xhci_stop_endpoint(struct usb_hcd *hcd,
+ struct usb_device *udev, struct usb_host_endpoint *ep)
+{
+ struct xhci_hcd *xhci;
+ unsigned int ep_index;
+ struct xhci_virt_device *virt_dev;
+ struct xhci_command *cmd;
+ unsigned long flags;
+ int ret = 0;
+
+ if (!hcd || !udev || !ep)
+ return -EINVAL;
+
+ xhci = hcd_to_xhci(hcd);
+ cmd = xhci_alloc_command(xhci, true, GFP_NOIO);
+ if (!cmd)
+ return -ENOMEM;
+
+ spin_lock_irqsave(&xhci->lock, flags);
+ virt_dev = xhci->devs[udev->slot_id];
+ if (!virt_dev) {
+ ret = -ENODEV;
+ goto err;
+ }
+
+ ep_index = xhci_get_endpoint_index(&ep->desc);
+ if (virt_dev->eps[ep_index].ring &&
+ virt_dev->eps[ep_index].ring->dequeue) {
+ ret = xhci_queue_stop_endpoint(xhci, cmd, udev->slot_id,
+ ep_index, 0);
+ if (ret)
+ goto err;
+
+ xhci_ring_cmd_db(xhci);
+ spin_unlock_irqrestore(&xhci->lock, flags);
+
+ /* Wait for stop endpoint command to finish */
+ wait_for_completion(cmd->completion);
+
+ if (cmd->status == COMP_COMMAND_ABORTED ||
+ cmd->status == COMP_STOPPED) {
+ xhci_warn(xhci,
+ "stop endpoint command timeout for ep%d%s\n",
+ usb_endpoint_num(&ep->desc),
+ usb_endpoint_dir_in(&ep->desc) ? "in" : "out");
+ ret = -ETIME;
+ }
+ goto free_cmd;
+ }
+
+err:
+ spin_unlock_irqrestore(&xhci->lock, flags);
+free_cmd:
+ xhci_free_command(xhci, cmd);
+ return ret;
+}
+
static const struct hc_driver xhci_hc_driver = {
.description = "xhci-hcd",
.product_desc = "xHCI Host Controller",
@@ -5295,6 +5465,11 @@ static const struct hc_driver xhci_hc_driver = {
.enable_usb3_lpm_timeout = xhci_enable_usb3_lpm_timeout,
.disable_usb3_lpm_timeout = xhci_disable_usb3_lpm_timeout,
.find_raw_port_number = xhci_find_raw_port_number,
+ .sec_event_ring_setup = xhci_sec_event_ring_setup,
+ .sec_event_ring_cleanup = xhci_sec_event_ring_cleanup,
+ .get_sec_event_ring_phys_addr = xhci_get_sec_event_ring_phys_addr,
+ .get_xfer_ring_phys_addr = xhci_get_xfer_ring_phys_addr,
+ .stop_endpoint = xhci_stop_endpoint,
};
void xhci_init_driver(struct hc_driver *drv,
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 3e6cb257f..dcf91c5cb 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1512,7 +1512,11 @@ static inline const char *xhci_trb_type_string(u8 type)
#define TRB_SEGMENT_SIZE (TRBS_PER_SEGMENT*16)
#define TRB_SEGMENT_SHIFT (ilog2(TRB_SEGMENT_SIZE))
/* TRB buffer pointers can't cross 64KB boundaries */
+#ifdef CONFIG_ARCH_ROCKCHIP
+#define TRB_MAX_BUFF_SHIFT 13 // set to 16 if we use snps,parkmode-disable-ss-quirk;
+#else
#define TRB_MAX_BUFF_SHIFT 16
+#endif
#define TRB_MAX_BUFF_SIZE (1 << TRB_MAX_BUFF_SHIFT)
/* How much data is left before the 64KB boundary? */
#define TRB_BUFF_LEN_UP_TO_BOUNDARY(addr) (TRB_MAX_BUFF_SIZE - \
@@ -1745,6 +1749,10 @@ struct xhci_hcd {
struct xhci_doorbell_array __iomem *dba;
/* Our HCD's current interrupter register set */
struct xhci_intr_reg __iomem *ir_set;
+ /* secondary interrupter */
+ struct xhci_intr_reg __iomem **sec_ir_set;
+
+ int core_id;
/* Cached register copies of read-only HC data */
__u32 hcs_params1;
@@ -1788,6 +1796,11 @@ struct xhci_hcd {
struct xhci_command *current_cmd;
struct xhci_ring *event_ring;
struct xhci_erst erst;
+
+ /* secondary event ring and erst */
+ struct xhci_ring **sec_event_ring;
+ struct xhci_erst *sec_erst;
+
/* Scratchpad */
struct xhci_scratchpad *scratchpad;
/* Store LPM test failed devices' information */
@@ -1876,7 +1889,11 @@ struct xhci_hcd {
#define XHCI_ZERO_64B_REGS BIT_ULL(32)
#define XHCI_RESET_PLL_ON_DISCONNECT BIT_ULL(34)
#define XHCI_SNPS_BROKEN_SUSPEND BIT_ULL(35)
+#define XHCI_TRB_ENT_QUIRK BIT_ULL(36)
+#define XHCI_DIS_AUTOSUSPEND BIT_ULL(37)
#define XHCI_DISABLE_SPARSE BIT_ULL(38)
+#define XHCI_WARM_RESET_ON_SUSPEND BIT_ULL(39)
+#define XHCI_U2_BROKEN_SUSPEND BIT_ULL(40)
unsigned int num_active_eps;
unsigned int limit_active_eps;
@@ -2057,6 +2074,8 @@ struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci,
int type, gfp_t flags);
void xhci_free_container_ctx(struct xhci_hcd *xhci,
struct xhci_container_ctx *ctx);
+int xhci_sec_event_ring_setup(struct usb_hcd *hcd, unsigned int intr_num);
+int xhci_sec_event_ring_cleanup(struct usb_hcd *hcd, unsigned int intr_num);
/* xHCI host controller glue */
typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *);
diff --git a/include/linux/usb.h b/include/linux/usb.h
index ff010d1fd..7723b82c2 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -402,6 +402,8 @@ struct usb_host_bos {
struct usb_ssp_cap_descriptor *ssp_cap;
struct usb_ss_container_id_descriptor *ss_id;
struct usb_ptm_cap_descriptor *ptm_cap;
+ struct usb_config_summary_descriptor *config_summary;
+ unsigned int num_config_summary_desc;
};
int __usb_get_extra_descriptor(char *buffer, unsigned size,
@@ -466,6 +468,15 @@ struct usb_bus {
struct mon_bus *mon_bus; /* non-null when associated */
int monitored; /* non-zero when monitored */
#endif
+ unsigned skip_resume:1; /* All USB devices are brought into full
+ * power state after system resume. It
+ * is desirable for some buses to keep
+ * their devices in suspend state even
+ * after system resume. The devices
+ * are resumed later when a remote
+ * wakeup is detected or an interface
+ * driver starts I/O.
+ */
};
struct usb_dev_state;
diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h
index 2040696d7..e8362991c 100644
--- a/include/linux/usb/composite.h
+++ b/include/linux/usb/composite.h
@@ -118,6 +118,7 @@ struct usb_os_desc_table {
/**
* struct usb_function - describes one function of a configuration
* @name: For diagnostics, identifies the function.
+ * @intf_id: Interface ID
* @strings: tables of strings, keyed by identifiers assigned during bind()
* and by language IDs provided in control requests
* @fs_descriptors: Table of full (or low) speed descriptors, using interface and
@@ -163,6 +164,13 @@ struct usb_os_desc_table {
* GetStatus() request when the recipient is Interface.
* @func_suspend: callback to be called when
* SetFeature(FUNCTION_SUSPEND) is reseived
+ * @func_is_suspended: Tells whether the function is currently in
+ * Function Suspend state (used in Super Speed mode only).
+ * @func_wakeup_allowed: Tells whether Function Remote Wakeup has been allowed
+ * by the USB host (used in Super Speed mode only).
+ * @func_wakeup_pending: Marks that the function has issued a Function Wakeup
+ * while the USB bus was suspended and therefore a Function Wakeup
+ * notification needs to be sent once the USB bus is resumed.
*
* A single USB function uses one or more interfaces, and should in most
* cases support operation at both full and high speeds. Each function is
@@ -190,6 +198,7 @@ struct usb_os_desc_table {
struct usb_function {
const char *name;
+ int intf_id;
struct usb_gadget_strings **strings;
struct usb_descriptor_header **fs_descriptors;
struct usb_descriptor_header **hs_descriptors;
@@ -233,6 +242,9 @@ struct usb_function {
int (*get_status)(struct usb_function *);
int (*func_suspend)(struct usb_function *,
u8 suspend_opt);
+ unsigned func_is_suspended:1;
+ unsigned func_wakeup_allowed:1;
+ unsigned func_wakeup_pending:1;
/* private: */
/* internals */
struct list_head list;
@@ -248,6 +260,9 @@ int usb_function_deactivate(struct usb_function *);
int usb_function_activate(struct usb_function *);
int usb_interface_id(struct usb_configuration *, struct usb_function *);
+int usb_func_wakeup(struct usb_function *func);
+
+int usb_get_func_interface_id(struct usb_function *func);
int config_ep_by_speed_and_alt(struct usb_gadget *g, struct usb_function *f,
struct usb_ep *_ep, u8 alt);
@@ -590,6 +605,7 @@ struct usb_function_instance {
struct config_group group;
struct list_head cfs_list;
struct usb_function_driver *fd;
+ struct usb_function *f;
int (*set_inst_name)(struct usb_function_instance *inst,
const char *name);
void (*free_func_inst)(struct usb_function_instance *inst);
diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h
index e5cd84a0f..bdfb54af0 100644
--- a/include/linux/usb/gadget.h
+++ b/include/linux/usb/gadget.h
@@ -61,6 +61,8 @@ struct usb_ep;
* invalidated by the error may first be dequeued.
* @context: For use by the completion callback
* @list: For use by the gadget driver.
+ * @frame_number: Reports the interval number in (micro)frame in which the
+ * isochronous transfer was transmitted or received.
* @status: Reports completion code, zero or a negative errno.
* Normally, faults block the transfer queue from advancing until
* the completion callback returns.
@@ -73,6 +75,7 @@ struct usb_ep;
* Note that for writes (IN transfers) some data bytes may still
* reside in a device-side FIFO when the request is reported as
* complete.
+ * @udc_priv: Vendor private data in usage by the UDC.
*
* These are allocated/freed through the endpoint they're used with. The
* hardware's driver can add extra per-request data to the memory it returns,
@@ -112,8 +115,60 @@ struct usb_request {
void *context;
struct list_head list;
+ unsigned frame_number; /* ISO ONLY */
+
int status;
unsigned actual;
+ unsigned int udc_priv;
+};
+
+/*
+ * @buf_base_addr: Base pointer to buffer allocated for each GSI enabled EP.
+ * TRBs point to buffers that are split from this pool. The size of the
+ * buffer is num_bufs times buf_len. num_bufs and buf_len are determined
+ based on desired performance and aggregation size.
+ * @dma: DMA address corresponding to buf_base_addr.
+ * @num_bufs: Number of buffers associated with the GSI enabled EP. This
+ * corresponds to the number of non-zlp TRBs allocated for the EP.
+ * The value is determined based on desired performance for the EP.
+ * @buf_len: Size of each individual buffer is determined based on aggregation
+ * negotiated as per the protocol. In case of no aggregation supported by
+ * the protocol, we use default values.
+ * @db_reg_phs_addr_lsb: IPA channel doorbell register's physical address LSB
+ * @mapped_db_reg_phs_addr_lsb: doorbell LSB IOVA address mapped with IOMMU
+ * @db_reg_phs_addr_msb: IPA channel doorbell register's physical address MSB
+ * @sgt_trb_xfer_ring: USB TRB ring related sgtable entries
+ * @sgt_data_buff: Data buffer related sgtable entries
+ * @dev: pointer to the DMA-capable dwc device
+ */
+struct usb_gsi_request {
+ void *buf_base_addr;
+ dma_addr_t dma;
+ size_t num_bufs;
+ size_t buf_len;
+ u32 db_reg_phs_addr_lsb;
+ dma_addr_t mapped_db_reg_phs_addr_lsb;
+ u32 db_reg_phs_addr_msb;
+ struct sg_table sgt_trb_xfer_ring;
+ struct sg_table sgt_data_buff;
+ struct device *dev;
+};
+
+enum gsi_ep_op {
+ GSI_EP_OP_CONFIG = 0,
+ GSI_EP_OP_STARTXFER,
+ GSI_EP_OP_STORE_DBL_INFO,
+ GSI_EP_OP_ENABLE_GSI,
+ GSI_EP_OP_UPDATEXFER,
+ GSI_EP_OP_RING_DB,
+ GSI_EP_OP_ENDXFER,
+ GSI_EP_OP_GET_CH_INFO,
+ GSI_EP_OP_GET_XFER_IDX,
+ GSI_EP_OP_PREPARE_TRBS,
+ GSI_EP_OP_FREE_TRBS,
+ GSI_EP_OP_SET_CLR_BLOCK_DBL,
+ GSI_EP_OP_CHECK_FOR_SUSPEND,
+ GSI_EP_OP_DISABLE,
};
/*-------------------------------------------------------------------------*/
@@ -144,6 +199,9 @@ struct usb_ep_ops {
int (*fifo_status) (struct usb_ep *ep);
void (*fifo_flush) (struct usb_ep *ep);
+ int (*gsi_ep_op) (struct usb_ep *ep, void *op_data,
+ enum gsi_ep_op op);
+
};
/**
@@ -184,6 +242,11 @@ struct usb_ep_caps {
.dir_out = !!(_dir & USB_EP_CAPS_DIR_OUT), \
}
+enum ep_type {
+ EP_TYPE_NORMAL = 0,
+ EP_TYPE_GSI,
+};
+
/**
* struct usb_ep - device side representation of USB endpoint
* @name:identifier for the endpoint, such as "ep-a" or "ep9in-bulk"
@@ -209,6 +272,12 @@ struct usb_ep_caps {
* enabled and remains valid until the endpoint is disabled.
* @comp_desc: In case of SuperSpeed support, this is the endpoint companion
* descriptor that is used to configure the endpoint
+ * @ep_type: Used to specify type of EP eg. normal vs h/w accelerated.
+ * @ep_num: Used EP number
+ * @ep_intr_num: Interrupter number for EP.
+ * @endless: In case where endless transfer is being initiated, this is set
+ * to disable usb event interrupt for few events.
+ * @transfer_type: Used to specify transfer type of EP.
*
* the bus controller driver lists all the general purpose endpoints in
* gadget->ep_list. the control endpoint (gadget->ep0) is not in that list,
@@ -232,6 +301,13 @@ struct usb_ep {
u8 address;
const struct usb_endpoint_descriptor *desc;
const struct usb_ss_ep_comp_descriptor *comp_desc;
+ enum ep_type ep_type;
+ u8 ep_num;
+ u8 ep_intr_num;
+ bool endless;
+#ifdef CONFIG_ARCH_ROCKCHIP
+ u8 transfer_type;
+#endif
};
/*-------------------------------------------------------------------------*/
@@ -249,6 +325,8 @@ int usb_ep_clear_halt(struct usb_ep *ep);
int usb_ep_set_wedge(struct usb_ep *ep);
int usb_ep_fifo_status(struct usb_ep *ep);
void usb_ep_fifo_flush(struct usb_ep *ep);
+int usb_gsi_ep_op(struct usb_ep *ep,
+ struct usb_gsi_request *req, enum gsi_ep_op op);
#else
static inline void usb_ep_set_maxpacket_limit(struct usb_ep *ep,
unsigned maxpacket_limit)
@@ -278,6 +356,10 @@ static inline int usb_ep_fifo_status(struct usb_ep *ep)
{ return 0; }
static inline void usb_ep_fifo_flush(struct usb_ep *ep)
{ }
+
+static inline int usb_gsi_ep_op(struct usb_ep *ep,
+ struct usb_gsi_request *req, enum gsi_ep_op op)
+{ return 0; }
#endif /* USB_GADGET */
/*-------------------------------------------------------------------------*/
@@ -300,6 +382,7 @@ struct usb_udc;
struct usb_gadget_ops {
int (*get_frame)(struct usb_gadget *);
int (*wakeup)(struct usb_gadget *);
+ int (*func_wakeup)(struct usb_gadget *g, int interface_id);
int (*set_selfpowered) (struct usb_gadget *, int is_selfpowered);
int (*vbus_session) (struct usb_gadget *, int is_active);
int (*vbus_draw) (struct usb_gadget *, unsigned mA);
@@ -314,6 +397,7 @@ struct usb_gadget_ops {
struct usb_ep *(*match_ep)(struct usb_gadget *,
struct usb_endpoint_descriptor *,
struct usb_ss_ep_comp_descriptor *);
+ int (*restart)(struct usb_gadget *g);
};
/**
@@ -365,6 +449,9 @@ struct usb_gadget_ops {
* @connected: True if gadget is connected.
* @lpm_capable: If the gadget max_speed is FULL or HIGH, this flag
* indicates that it supports LPM as per the LPM ECN & errata.
+ * @remote_wakeup: Indicates if the host has enabled the remote_wakeup
+ * feature.
+ * @uvc_enabled: True if uvc function is enabled.
*
* Gadgets have a mostly-portable "gadget driver" implementing device
* functions, handling all usb configurations and interfaces. Gadget
@@ -419,6 +506,8 @@ struct usb_gadget {
unsigned deactivated:1;
unsigned connected:1;
unsigned lpm_capable:1;
+ unsigned remote_wakeup:1;
+ unsigned uvc_enabled:1;
};
#define work_to_gadget(w) (container_of((w), struct usb_gadget, work))
@@ -552,6 +641,7 @@ static inline int gadget_is_otg(struct usb_gadget *g)
#if IS_ENABLED(CONFIG_USB_GADGET)
int usb_gadget_frame_number(struct usb_gadget *gadget);
int usb_gadget_wakeup(struct usb_gadget *gadget);
+int usb_gadget_func_wakeup(struct usb_gadget *gadget, int interface_id);
int usb_gadget_set_selfpowered(struct usb_gadget *gadget);
int usb_gadget_clear_selfpowered(struct usb_gadget *gadget);
int usb_gadget_vbus_connect(struct usb_gadget *gadget);
@@ -566,6 +656,9 @@ static inline int usb_gadget_frame_number(struct usb_gadget *gadget)
{ return 0; }
static inline int usb_gadget_wakeup(struct usb_gadget *gadget)
{ return 0; }
+static inline int usb_gadget_func_wakeup(struct usb_gadget *gadget,
+ int interface_id)
+{ return 0; }
static inline int usb_gadget_set_selfpowered(struct usb_gadget *gadget)
{ return 0; }
static inline int usb_gadget_clear_selfpowered(struct usb_gadget *gadget)
@@ -804,6 +897,11 @@ int usb_otg_descriptor_init(struct usb_gadget *gadget,
struct usb_descriptor_header *otg_desc);
/*-------------------------------------------------------------------------*/
+int usb_func_ep_queue(struct usb_function *func, struct usb_ep *ep,
+ struct usb_request *req, gfp_t gfp_flags);
+
+/*-------------------------------------------------------------------------*/
+
/* utility to simplify map/unmap of usb_requests to/from DMA */
#ifdef CONFIG_HAS_DMA
@@ -883,5 +981,8 @@ extern struct usb_ep *usb_ep_autoconfig_ss(struct usb_gadget *,
extern void usb_ep_autoconfig_release(struct usb_ep *);
extern void usb_ep_autoconfig_reset(struct usb_gadget *);
+extern struct usb_ep *usb_ep_autoconfig_by_name(struct usb_gadget *gadget,
+ struct usb_endpoint_descriptor *desc,
+ const char *ep_name);
#endif /* __LINUX_USB_GADGET_H */
diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h
index 3adbcc15f..b65d0b305 100644
--- a/include/linux/usb/hcd.h
+++ b/include/linux/usb/hcd.h
@@ -407,6 +407,17 @@ struct hc_driver {
/* Call for power on/off the port if necessary */
int (*port_power)(struct usb_hcd *hcd, int portnum, bool enable);
+ int (*sec_event_ring_setup)(struct usb_hcd *hcd, unsigned int intr_num);
+ int (*sec_event_ring_cleanup)(struct usb_hcd *hcd,
+ unsigned int intr_num);
+ phys_addr_t (*get_sec_event_ring_phys_addr)(struct usb_hcd *hcd,
+ unsigned int intr_num, dma_addr_t *dma);
+ phys_addr_t (*get_xfer_ring_phys_addr)(struct usb_hcd *hcd,
+ struct usb_device *udev, struct usb_host_endpoint *ep,
+ dma_addr_t *dma);
+ int (*get_core_id)(struct usb_hcd *hcd);
+ int (*stop_endpoint)(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep);
};
static inline int hcd_giveback_urb_in_bh(struct usb_hcd *hcd)
@@ -445,6 +456,17 @@ extern int usb_hcd_alloc_bandwidth(struct usb_device *udev,
struct usb_host_interface *old_alt,
struct usb_host_interface *new_alt);
extern int usb_hcd_get_frame_number(struct usb_device *udev);
+extern int usb_hcd_sec_event_ring_setup(struct usb_device *udev,
+ unsigned int intr_num);
+extern int usb_hcd_sec_event_ring_cleanup(struct usb_device *udev,
+ unsigned int intr_num);
+extern phys_addr_t usb_hcd_get_sec_event_ring_phys_addr(
+ struct usb_device *udev, unsigned int intr_num, dma_addr_t *dma);
+extern phys_addr_t usb_hcd_get_xfer_ring_phys_addr(
+ struct usb_device *udev, struct usb_host_endpoint *ep, dma_addr_t *dma);
+extern int usb_hcd_get_controller_id(struct usb_device *udev);
+extern int usb_hcd_stop_endpoint(struct usb_device *udev,
+ struct usb_host_endpoint *ep);
struct usb_hcd *__usb_create_hcd(const struct hc_driver *driver,
struct device *sysdev, struct device *dev, const char *bus_name,
diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h
index 69f1b6328..5113ab6ad 100644
--- a/include/linux/usb/otg.h
+++ b/include/linux/usb/otg.h
@@ -118,6 +118,7 @@ enum usb_dr_mode {
USB_DR_MODE_HOST,
USB_DR_MODE_PERIPHERAL,
USB_DR_MODE_OTG,
+ USB_DR_MODE_DRD,
};
/**
diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h
index 22c1f579a..ff7a8053e 100644
--- a/include/linux/usb/quirks.h
+++ b/include/linux/usb/quirks.h
@@ -72,4 +72,7 @@
/* device has blacklisted endpoints */
#define USB_QUIRK_ENDPOINT_BLACKLIST BIT(15)
+/* device can't support auto suspend function */
+#define USB_QUIRK_AUTO_SUSPEND BIT(16)
+
#endif /* __LINUX_USB_QUIRKS_H */
diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h
index 3f376688b..9fa1a51b2 100644
--- a/include/uapi/linux/usb/ch9.h
+++ b/include/uapi/linux/usb/ch9.h
@@ -1084,6 +1084,26 @@ struct usb_ptm_cap_descriptor {
*/
#define USB_DT_USB_SSP_CAP_SIZE(ssac) (12 + (ssac + 1) * 4)
+/*
+ * Configuration Summary descriptors: Defines a list of device preferred
+ * configurations. This descriptor may be used by Host software to decide
+ * which Configuration to use to obtain the desired functionality.
+ */
+#define USB_CAP_TYPE_CONFIG_SUMMARY 0x10
+#define USB_CONFIG_SUMMARY_DESC_REV 0x100
+
+struct usb_config_summary_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDevCapabilityType;
+ __u16 bcdVersion;
+ __u8 bClass;
+ __u8 bSubClass;
+ __u8 bProtocol;
+ __u8 bConfigurationCount;
+ __u8 bConfigurationIndex[];
+} __attribute__((packed));
+
/*-------------------------------------------------------------------------*/
/* USB_DT_WIRELESS_ENDPOINT_COMP: companion descriptor associated with
--
2.37.0 (Apple Git-136)