13884 lines
438 KiB
Diff
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, ¶ms);
|
|
+
|
|
+ /* 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)
|
|
|