usb: changes for v3.18 merge window

Quite big pull request this time. Audio and UVC gadgets
 can now be used with our configfs-based binding. We have
 three PHY drivers being removed because a new one has been
 added using new PHY framework.
 
 Gadget framework got a new ->reset callback preparing for
 some other changes to come on next merge window.
 
 A few new drivers came in as well; among those we have a
 new UDC driver from Xilinx and two new glue layers for
 DWC3 (ST and Qualcomm).
 
 DWC3 also learned about tracepoints which will help debugging
 quite a bit.
 
 Other than that, a big series of non-critical fixes and
 cleanups.
 
 All patches have been on linux-next for quite a bit of time
 and I boot tested these changes on platforms I have access
 to and work with mainline.
 
 Signed-of-by: Felipe Balbi <balbi@ti.com>
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABAgAGBQJUGu+JAAoJEIaOsuA1yqREyekP/3uIW2Y5Z1/51Zk7LZGBaqXJ
 ODxfwe9rE2ZQkN6OfwVzLKbFKmP6Fc+1FFBahza8hmyqgW7V9KaoRiAWhjkwkpDj
 5qgmLVbXIYLlLUWCGgQnx0sV3S46ubE8XZDnyHlz0mT66+ee915qGpbbWa83hI7S
 lbPr/AybpldbBwu0tD7yCTMx5Q+92KH0ZGzzNH9lNpzV8X2qA5+9IsnlRhcRCKBp
 GZnZSHLkOBQitrCK+NTjM+2XJ5WFOmlfZd13w7YPnPWz23yImJlRp697bqaKGHDX
 BnPMsoSNVtT7uMcge+LRcyJ0RgWiaIL9TODVLPQLm/tV1gp0OwF53HF+0o8usf4z
 9D+NXC3BVxCAkjv5HFqFaEAyZ0km0m+1/VE8opAvT0u8ySNwE6uXXczBLN2GCMdJ
 N2djwR/9eMRQsbDVweZoGOgO2FEqKx2nEfsZfkX1xmJoRkvPl2ssFejdSZw2oD3N
 lJpb1rg6Ko+TURxKDaqZY/0Uu/8zz6Waqs2AmhV4QHlB2RZWxBW8MrPe2hIB5e97
 UJjgOdHBBEzXlj0kLwbRojWyDb4JokpyyQx3ZuO1A3r3m1hrGgM0GRcW7k09IxM9
 W6UQow1B4l3tfMpOOFO8xe7nUzN8ekj85lIdgUl3BTBGpxexvViv6APiGVt8p8lS
 7jbEldb3DyAnqJldepdQ
 =VHzN
 -----END PGP SIGNATURE-----

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

Felipe writes:

usb: changes for v3.18 merge window

Quite big pull request this time. Audio and UVC gadgets
can now be used with our configfs-based binding. We have
three PHY drivers being removed because a new one has been
added using new PHY framework.

Gadget framework got a new ->reset callback preparing for
some other changes to come on next merge window.

A few new drivers came in as well; among those we have a
new UDC driver from Xilinx and two new glue layers for
DWC3 (ST and Qualcomm).

DWC3 also learned about tracepoints which will help debugging
quite a bit.

Other than that, a big series of non-critical fixes and
cleanups.

All patches have been on linux-next for quite a bit of time
and I boot tested these changes on platforms I have access
to and work with mainline.

Signed-of-by: Felipe Balbi <balbi@ti.com>
This commit is contained in:
Greg Kroah-Hartman 2014-09-19 15:18:00 -07:00
commit 213db49399
87 changed files with 6014 additions and 2581 deletions

View File

@ -0,0 +1,12 @@
What: /config/usb-gadget/gadget/functions/uac1.name
Date: Sep 2014
KernelVersion: 3.18
Description:
The attributes:
audio_buf_size - audio buffer size
fn_cap - capture pcm device file name
fn_cntl - control device file name
fn_play - playback pcm device file name
req_buf_size - ISO OUT endpoint request buffer size
req_count - ISO OUT endpoint request count

View File

@ -0,0 +1,12 @@
What: /config/usb-gadget/gadget/functions/uac2.name
Date: Sep 2014
KernelVersion: 3.18
Description:
The attributes:
c_chmask - capture channel mask
c_srate - capture sampling rate
c_ssize - capture sample size (bytes)
p_chmask - playback channel mask
p_srate - playback sampling rate
p_ssize - playback sample size (bytes)

View File

@ -0,0 +1,39 @@
Qualcomm DWC3 HS AND SS PHY CONTROLLER
--------------------------------------
DWC3 PHY nodes are defined to describe on-chip Synopsis Physical layer
controllers. Each DWC3 PHY controller should have its own node.
Required properties:
- compatible: should contain one of the following:
- "qcom,dwc3-hs-usb-phy" for High Speed Synopsis PHY controller
- "qcom,dwc3-ss-usb-phy" for Super Speed Synopsis PHY controller
- reg: offset and length of the DWC3 PHY controller register set
- #phy-cells: must be zero
- clocks: a list of phandles and clock-specifier pairs, one for each entry in
clock-names.
- clock-names: Should contain "ref" for the PHY reference clock
Optional clocks:
"xo" External reference clock
Example:
phy@100f8800 {
compatible = "qcom,dwc3-hs-usb-phy";
reg = <0x100f8800 0x30>;
clocks = <&gcc USB30_0_UTMI_CLK>;
clock-names = "ref";
#phy-cells = <0>;
status = "ok";
};
phy@100f8830 {
compatible = "qcom,dwc3-ss-usb-phy";
reg = <0x100f8830 0x30>;
clocks = <&gcc USB30_0_MASTER_CLK>;
clock-names = "ref";
#phy-cells = <0>;
status = "ok";
};

View File

@ -0,0 +1,68 @@
ST DWC3 glue logic
This file documents the parameters for the dwc3-st driver.
This driver controls the glue logic used to configure the dwc3 core on
STiH407 based platforms.
Required properties:
- compatible : must be "st,stih407-dwc3"
- reg : glue logic base address and USB syscfg ctrl register offset
- reg-names : should be "reg-glue" and "syscfg-reg"
- st,syscon : should be phandle to system configuration node which
encompasses the glue registers
- resets : list of phandle and reset specifier pairs. There should be two entries, one
for the powerdown and softreset lines of the usb3 IP
- reset-names : list of reset signal names. Names should be "powerdown" and "softreset"
See: Documentation/devicetree/bindings/reset/st,sti-powerdown.txt
See: Documentation/devicetree/bindings/reset/reset.txt
- #address-cells, #size-cells : should be '1' if the device has sub-nodes
with 'reg' property
- pinctl-names : A pinctrl state named "default" must be defined
See: Documentation/devicetree/bindings/pinctrl/pinctrl-binding.txt
- pinctrl-0 : Pin control group
See: Documentation/devicetree/bindings/pinctrl/pinctrl-binding.txt
- ranges : allows valid 1:1 translation between child's address space and
parent's address space
Sub-nodes:
The dwc3 core should be added as subnode to ST DWC3 glue as shown in the
example below. The DT binding details of dwc3 can be found in:
Documentation/devicetree/bindings/usb/dwc3.txt
NB: The dr_mode property described in [1] is NOT optional for this driver, as the default value
is "otg", which isn't supported by this SoC. Valid dr_mode values for dwc3-st are either "host"
or "device".
[1] Documentation/devicetree/bindings/usb/generic.txt
Example:
st_dwc3: dwc3@8f94000 {
status = "disabled";
compatible = "st,stih407-dwc3";
reg = <0x08f94000 0x1000>, <0x110 0x4>;
reg-names = "reg-glue", "syscfg-reg";
st,syscfg = <&syscfg_core>;
resets = <&powerdown STIH407_USB3_POWERDOWN>,
<&softreset STIH407_MIPHY2_SOFTRESET>;
reset-names = "powerdown",
"softreset";
#address-cells = <1>;
#size-cells = <1>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_usb3>;
ranges;
dwc3: dwc3@9900000 {
compatible = "snps,dwc3";
reg = <0x09900000 0x100000>;
interrupts = <GIC_SPI 155 IRQ_TYPE_NONE>;
dr_mode = "host";
phys-names = "usb2-phy", "usb3-phy";
phys = <&usb2_picophy2>, <&phy_port2 MIPHY_TYPE_USB>;
};
};

View File

@ -5,6 +5,7 @@ Required properties:
* "fsl,imx23-usbphy" for imx23 and imx28 * "fsl,imx23-usbphy" for imx23 and imx28
* "fsl,imx6q-usbphy" for imx6dq and imx6dl * "fsl,imx6q-usbphy" for imx6dq and imx6dl
* "fsl,imx6sl-usbphy" for imx6sl * "fsl,imx6sl-usbphy" for imx6sl
* "fsl,vf610-usbphy" for Vybrid vf610
* "fsl,imx6sx-usbphy" for imx6sx * "fsl,imx6sx-usbphy" for imx6sx
"fsl,imx23-usbphy" is still a fallback for other strings "fsl,imx23-usbphy" is still a fallback for other strings
- reg: Should contain registers location and length - reg: Should contain registers location and length

View File

@ -0,0 +1,66 @@
Qualcomm SuperSpeed DWC3 USB SoC controller
Required properties:
- compatible: should contain "qcom,dwc3"
- clocks: A list of phandle + clock-specifier pairs for the
clocks listed in clock-names
- clock-names: Should contain the following:
"core" Master/Core clock, have to be >= 125 MHz for SS
operation and >= 60MHz for HS operation
Optional clocks:
"iface" System bus AXI clock. Not present on all platforms
"sleep" Sleep clock, used when USB3 core goes into low
power mode (U3).
Required child node:
A child node must exist to represent the core DWC3 IP block. The name of
the node is not important. The content of the node is defined in dwc3.txt.
Phy documentation is provided in the following places:
Documentation/devicetree/bindings/phy/qcom,dwc3-usb-phy.txt
Example device nodes:
hs_phy: phy@100f8800 {
compatible = "qcom,dwc3-hs-usb-phy";
reg = <0x100f8800 0x30>;
clocks = <&gcc USB30_0_UTMI_CLK>;
clock-names = "ref";
#phy-cells = <0>;
status = "ok";
};
ss_phy: phy@100f8830 {
compatible = "qcom,dwc3-ss-usb-phy";
reg = <0x100f8830 0x30>;
clocks = <&gcc USB30_0_MASTER_CLK>;
clock-names = "ref";
#phy-cells = <0>;
status = "ok";
};
usb3_0: usb30@0 {
compatible = "qcom,dwc3";
#address-cells = <1>;
#size-cells = <1>;
clocks = <&gcc USB30_0_MASTER_CLK>;
clock-names = "core";
ranges;
status = "ok";
dwc3@10000000 {
compatible = "snps,dwc3";
reg = <0x10000000 0xcd00>;
interrupts = <0 205 0x4>;
phys = <&hs_phy>, <&ss_phy>;
phy-names = "usb2-phy", "usb3-phy";
tx-fifo-resize;
dr_mode = "host";
};
};

View File

@ -0,0 +1,24 @@
Renesas Electronics USBHS driver
Required properties:
- compatible: Must contain one of the following:
- "renesas,usbhs-r8a7790"
- "renesas,usbhs-r8a7791"
- reg: Base address and length of the register for the USBHS
- interrupts: Interrupt specifier for the USBHS
- clocks: A list of phandle + clock specifier pairs
Optional properties:
- renesas,buswait: Integer to use BUSWAIT register
- renesas,enable-gpio: A gpio specifier to check GPIO determining if USB
function should be enabled
- phys: phandle + phy specifier pair
- phy-names: must be "usb"
Example:
usbhs: usb@e6590000 {
compatible = "renesas,usbhs-r8a7790";
reg = <0 0xe6590000 0 0x100>;
interrupts = <0 107 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&mstp7_clks R8A7790_CLK_HSUSB>;
};

View File

@ -0,0 +1,18 @@
Xilinx USB2 device controller
Required properties:
- compatible : Should be "xlnx,usb2-device-4.00.a"
- reg : Physical base address and size of the USB2
device registers map.
- interrupts : Should contain single irq line of USB2 device
controller
- xlnx,has-builtin-dma : if DMA is included
Example:
axi-usb2-device@42e00000 {
compatible = "xlnx,usb2-device-4.00.a";
interrupts = <0x0 0x39 0x1>;
reg = <0x42e00000 0x10000>;
xlnx,has-builtin-dma;
};

View File

@ -1398,6 +1398,7 @@ F: drivers/media/rc/st_rc.c
F: drivers/i2c/busses/i2c-st.c F: drivers/i2c/busses/i2c-st.c
F: drivers/tty/serial/st-asc.c F: drivers/tty/serial/st-asc.c
F: drivers/mmc/host/sdhci-st.c F: drivers/mmc/host/sdhci-st.c
F: drivers/usb/dwc3/dwc3-st.c
ARM/TECHNOLOGIC SYSTEMS TS7250 MACHINE SUPPORT ARM/TECHNOLOGIC SYSTEMS TS7250 MACHINE SUPPORT
M: Lennert Buytenhek <kernel@wantstofly.org> M: Lennert Buytenhek <kernel@wantstofly.org>
@ -9683,7 +9684,7 @@ USB WEBCAM GADGET
M: Laurent Pinchart <laurent.pinchart@ideasonboard.com> M: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
L: linux-usb@vger.kernel.org L: linux-usb@vger.kernel.org
S: Maintained S: Maintained
F: drivers/usb/gadget/function/*uvc*.c F: drivers/usb/gadget/function/*uvc*
F: drivers/usb/gadget/legacy/webcam.c F: drivers/usb/gadget/legacy/webcam.c
USB WIRELESS RNDIS DRIVER (rndis_wlan) USB WIRELESS RNDIS DRIVER (rndis_wlan)

View File

@ -80,6 +80,23 @@ config USB_DWC3_KEYSTONE
Support of USB2/3 functionality in TI Keystone2 platforms. Support of USB2/3 functionality in TI Keystone2 platforms.
Say 'Y' or 'M' here if you have one such device Say 'Y' or 'M' here if you have one such device
config USB_DWC3_ST
tristate "STMicroelectronics Platforms"
depends on ARCH_STI && OF
default USB_DWC3
help
STMicroelectronics SoCs with one DesignWare Core USB3 IP
inside (i.e. STiH407).
Say 'Y' or 'M' if you have one such device.
config USB_DWC3_QCOM
tristate "Qualcomm Platforms"
depends on ARCH_QCOM || COMPILE_TEST
default USB_DWC3
help
Recent Qualcomm SoCs ship with one DesignWare Core USB3 IP inside,
say 'Y' or 'M' if you have one such device.
comment "Debugging features" comment "Debugging features"
config USB_DWC3_DEBUG config USB_DWC3_DEBUG

View File

@ -1,9 +1,12 @@
# define_trace.h needs to know how to find our header
CFLAGS_trace.o := -I$(src)
ccflags-$(CONFIG_USB_DWC3_DEBUG) := -DDEBUG ccflags-$(CONFIG_USB_DWC3_DEBUG) := -DDEBUG
ccflags-$(CONFIG_USB_DWC3_VERBOSE) += -DVERBOSE_DEBUG ccflags-$(CONFIG_USB_DWC3_VERBOSE) += -DVERBOSE_DEBUG
obj-$(CONFIG_USB_DWC3) += dwc3.o obj-$(CONFIG_USB_DWC3) += dwc3.o
dwc3-y := core.o dwc3-y := core.o debug.o trace.o
ifneq ($(filter y,$(CONFIG_USB_DWC3_HOST) $(CONFIG_USB_DWC3_DUAL_ROLE)),) ifneq ($(filter y,$(CONFIG_USB_DWC3_HOST) $(CONFIG_USB_DWC3_DUAL_ROLE)),)
dwc3-y += host.o dwc3-y += host.o
@ -33,3 +36,5 @@ obj-$(CONFIG_USB_DWC3_OMAP) += dwc3-omap.o
obj-$(CONFIG_USB_DWC3_EXYNOS) += dwc3-exynos.o obj-$(CONFIG_USB_DWC3_EXYNOS) += dwc3-exynos.o
obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o
obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o
obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o
obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o

View File

@ -186,10 +186,8 @@ static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length)
dwc->ev_buffs = devm_kzalloc(dwc->dev, sizeof(*dwc->ev_buffs) * num, dwc->ev_buffs = devm_kzalloc(dwc->dev, sizeof(*dwc->ev_buffs) * num,
GFP_KERNEL); GFP_KERNEL);
if (!dwc->ev_buffs) { if (!dwc->ev_buffs)
dev_err(dwc->dev, "can't allocate event buffers array\n");
return -ENOMEM; return -ENOMEM;
}
for (i = 0; i < num; i++) { for (i = 0; i < num; i++) {
struct dwc3_event_buffer *evt; struct dwc3_event_buffer *evt;
@ -639,10 +637,9 @@ static int dwc3_probe(struct platform_device *pdev)
void *mem; void *mem;
mem = devm_kzalloc(dev, sizeof(*dwc) + DWC3_ALIGN_MASK, GFP_KERNEL); mem = devm_kzalloc(dev, sizeof(*dwc) + DWC3_ALIGN_MASK, GFP_KERNEL);
if (!mem) { if (!mem)
dev_err(dev, "not enough memory\n");
return -ENOMEM; return -ENOMEM;
}
dwc = PTR_ALIGN(mem, DWC3_ALIGN_MASK + 1); dwc = PTR_ALIGN(mem, DWC3_ALIGN_MASK + 1);
dwc->mem = mem; dwc->mem = mem;
dwc->dev = dev; dwc->dev = dev;

View File

@ -33,6 +33,8 @@
#include <linux/phy/phy.h> #include <linux/phy/phy.h>
#define DWC3_MSG_MAX 500
/* Global constants */ /* Global constants */
#define DWC3_EP0_BOUNCE_SIZE 512 #define DWC3_EP0_BOUNCE_SIZE 512
#define DWC3_ENDPOINTS_NUM 32 #define DWC3_ENDPOINTS_NUM 32
@ -938,7 +940,7 @@ int dwc3_gadget_get_link_state(struct dwc3 *dwc);
int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state); int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state);
int dwc3_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep, int dwc3_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep,
unsigned cmd, struct dwc3_gadget_ep_cmd_params *params); unsigned cmd, struct dwc3_gadget_ep_cmd_params *params);
int dwc3_send_gadget_generic_command(struct dwc3 *dwc, int cmd, u32 param); int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param);
#else #else
static inline int dwc3_gadget_init(struct dwc3 *dwc) static inline int dwc3_gadget_init(struct dwc3 *dwc)
{ return 0; } { return 0; }

32
drivers/usb/dwc3/debug.c Normal file
View File

@ -0,0 +1,32 @@
/**
* debug.c - DesignWare USB3 DRD Controller Debug/Trace Support
*
* Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com
*
* Author: Felipe Balbi <balbi@ti.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 of
* the License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "debug.h"
void dwc3_trace(void (*trace)(struct va_format *), const char *fmt, ...)
{
struct va_format vaf;
va_list args;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
trace(&vaf);
va_end(args);
}

View File

@ -16,8 +16,206 @@
* GNU General Public License for more details. * GNU General Public License for more details.
*/ */
#ifndef __DWC3_DEBUG_H
#define __DWC3_DEBUG_H
#include "core.h" #include "core.h"
/**
* dwc3_gadget_ep_cmd_string - returns endpoint command string
* @cmd: command code
*/
static inline const char *
dwc3_gadget_ep_cmd_string(u8 cmd)
{
switch (cmd) {
case DWC3_DEPCMD_DEPSTARTCFG:
return "Start New Configuration";
case DWC3_DEPCMD_ENDTRANSFER:
return "End Transfer";
case DWC3_DEPCMD_UPDATETRANSFER:
return "Update Transfer";
case DWC3_DEPCMD_STARTTRANSFER:
return "Start Transfer";
case DWC3_DEPCMD_CLEARSTALL:
return "Clear Stall";
case DWC3_DEPCMD_SETSTALL:
return "Set Stall";
case DWC3_DEPCMD_GETEPSTATE:
return "Get Endpoint State";
case DWC3_DEPCMD_SETTRANSFRESOURCE:
return "Set Endpoint Transfer Resource";
case DWC3_DEPCMD_SETEPCONFIG:
return "Set Endpoint Configuration";
default:
return "UNKNOWN command";
}
}
/**
* dwc3_gadget_generic_cmd_string - returns generic command string
* @cmd: command code
*/
static inline const char *
dwc3_gadget_generic_cmd_string(u8 cmd)
{
switch (cmd) {
case DWC3_DGCMD_SET_LMP:
return "Set LMP";
case DWC3_DGCMD_SET_PERIODIC_PAR:
return "Set Periodic Parameters";
case DWC3_DGCMD_XMIT_FUNCTION:
return "Transmit Function Wake Device Notification";
case DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO:
return "Set Scratchpad Buffer Array Address Lo";
case DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI:
return "Set Scratchpad Buffer Array Address Hi";
case DWC3_DGCMD_SELECTED_FIFO_FLUSH:
return "Selected FIFO Flush";
case DWC3_DGCMD_ALL_FIFO_FLUSH:
return "All FIFO Flush";
case DWC3_DGCMD_SET_ENDPOINT_NRDY:
return "Set Endpoint NRDY";
case DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK:
return "Run SoC Bus Loopback Test";
default:
return "UNKNOWN";
}
}
/**
* dwc3_gadget_link_string - returns link name
* @link_state: link state code
*/
static inline const char *
dwc3_gadget_link_string(enum dwc3_link_state link_state)
{
switch (link_state) {
case DWC3_LINK_STATE_U0:
return "U0";
case DWC3_LINK_STATE_U1:
return "U1";
case DWC3_LINK_STATE_U2:
return "U2";
case DWC3_LINK_STATE_U3:
return "U3";
case DWC3_LINK_STATE_SS_DIS:
return "SS.Disabled";
case DWC3_LINK_STATE_RX_DET:
return "RX.Detect";
case DWC3_LINK_STATE_SS_INACT:
return "SS.Inactive";
case DWC3_LINK_STATE_POLL:
return "Polling";
case DWC3_LINK_STATE_RECOV:
return "Recovery";
case DWC3_LINK_STATE_HRESET:
return "Hot Reset";
case DWC3_LINK_STATE_CMPLY:
return "Compliance";
case DWC3_LINK_STATE_LPBK:
return "Loopback";
case DWC3_LINK_STATE_RESET:
return "Reset";
case DWC3_LINK_STATE_RESUME:
return "Resume";
default:
return "UNKNOWN link state\n";
}
}
/**
* dwc3_gadget_event_string - returns event name
* @event: the event code
*/
static inline const char *dwc3_gadget_event_string(u8 event)
{
switch (event) {
case DWC3_DEVICE_EVENT_DISCONNECT:
return "Disconnect";
case DWC3_DEVICE_EVENT_RESET:
return "Reset";
case DWC3_DEVICE_EVENT_CONNECT_DONE:
return "Connection Done";
case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE:
return "Link Status Change";
case DWC3_DEVICE_EVENT_WAKEUP:
return "WakeUp";
case DWC3_DEVICE_EVENT_EOPF:
return "End-Of-Frame";
case DWC3_DEVICE_EVENT_SOF:
return "Start-Of-Frame";
case DWC3_DEVICE_EVENT_ERRATIC_ERROR:
return "Erratic Error";
case DWC3_DEVICE_EVENT_CMD_CMPL:
return "Command Complete";
case DWC3_DEVICE_EVENT_OVERFLOW:
return "Overflow";
}
return "UNKNOWN";
}
/**
* dwc3_ep_event_string - returns event name
* @event: then event code
*/
static inline const char *dwc3_ep_event_string(u8 event)
{
switch (event) {
case DWC3_DEPEVT_XFERCOMPLETE:
return "Transfer Complete";
case DWC3_DEPEVT_XFERINPROGRESS:
return "Transfer In-Progress";
case DWC3_DEPEVT_XFERNOTREADY:
return "Transfer Not Ready";
case DWC3_DEPEVT_RXTXFIFOEVT:
return "FIFO";
case DWC3_DEPEVT_STREAMEVT:
return "Stream";
case DWC3_DEPEVT_EPCMDCMPLT:
return "Endpoint Command Complete";
}
return "UNKNOWN";
}
/**
* dwc3_gadget_event_type_string - return event name
* @event: the event code
*/
static inline const char *dwc3_gadget_event_type_string(u8 event)
{
switch (event) {
case DWC3_DEVICE_EVENT_DISCONNECT:
return "Disconnect";
case DWC3_DEVICE_EVENT_RESET:
return "Reset";
case DWC3_DEVICE_EVENT_CONNECT_DONE:
return "Connect Done";
case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE:
return "Link Status Change";
case DWC3_DEVICE_EVENT_WAKEUP:
return "Wake-Up";
case DWC3_DEVICE_EVENT_HIBER_REQ:
return "Hibernation";
case DWC3_DEVICE_EVENT_EOPF:
return "End of Periodic Frame";
case DWC3_DEVICE_EVENT_SOF:
return "Start of Frame";
case DWC3_DEVICE_EVENT_ERRATIC_ERROR:
return "Erratic Error";
case DWC3_DEVICE_EVENT_CMD_CMPL:
return "Command Complete";
case DWC3_DEVICE_EVENT_OVERFLOW:
return "Overflow";
default:
return "UNKNOWN";
}
}
void dwc3_trace(void (*trace)(struct va_format *), const char *fmt, ...);
#ifdef CONFIG_DEBUG_FS #ifdef CONFIG_DEBUG_FS
extern int dwc3_debugfs_init(struct dwc3 *); extern int dwc3_debugfs_init(struct dwc3 *);
extern void dwc3_debugfs_exit(struct dwc3 *); extern void dwc3_debugfs_exit(struct dwc3 *);
@ -27,4 +225,4 @@ static inline int dwc3_debugfs_init(struct dwc3 *d)
static inline void dwc3_debugfs_exit(struct dwc3 *d) static inline void dwc3_debugfs_exit(struct dwc3 *d)
{ } { }
#endif #endif
#endif /* __DWC3_DEBUG_H */

View File

@ -113,10 +113,8 @@ static int dwc3_exynos_probe(struct platform_device *pdev)
int ret; int ret;
exynos = devm_kzalloc(dev, sizeof(*exynos), GFP_KERNEL); exynos = devm_kzalloc(dev, sizeof(*exynos), GFP_KERNEL);
if (!exynos) { if (!exynos)
dev_err(dev, "not enough memory\n");
return -ENOMEM; return -ENOMEM;
}
/* /*
* Right now device-tree probed devices don't get dma_mask set. * Right now device-tree probed devices don't get dma_mask set.

View File

@ -481,10 +481,8 @@ static int dwc3_omap_probe(struct platform_device *pdev)
} }
omap = devm_kzalloc(dev, sizeof(*omap), GFP_KERNEL); omap = devm_kzalloc(dev, sizeof(*omap), GFP_KERNEL);
if (!omap) { if (!omap)
dev_err(dev, "not enough memory\n");
return -ENOMEM; return -ENOMEM;
}
platform_set_drvdata(pdev, omap); platform_set_drvdata(pdev, omap);

View File

@ -103,10 +103,8 @@ static int dwc3_pci_probe(struct pci_dev *pci,
struct device *dev = &pci->dev; struct device *dev = &pci->dev;
glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL); glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL);
if (!glue) { if (!glue)
dev_err(dev, "not enough memory\n");
return -ENOMEM; return -ENOMEM;
}
glue->dev = dev; glue->dev = dev;

View File

@ -0,0 +1,131 @@
/* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
struct dwc3_qcom {
struct device *dev;
struct clk *core_clk;
struct clk *iface_clk;
struct clk *sleep_clk;
};
static int dwc3_qcom_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct dwc3_qcom *qdwc;
int ret;
qdwc = devm_kzalloc(&pdev->dev, sizeof(*qdwc), GFP_KERNEL);
if (!qdwc)
return -ENOMEM;
platform_set_drvdata(pdev, qdwc);
qdwc->dev = &pdev->dev;
qdwc->core_clk = devm_clk_get(qdwc->dev, "core");
if (IS_ERR(qdwc->core_clk)) {
dev_err(qdwc->dev, "failed to get core clock\n");
return PTR_ERR(qdwc->core_clk);
}
qdwc->iface_clk = devm_clk_get(qdwc->dev, "iface");
if (IS_ERR(qdwc->iface_clk)) {
dev_dbg(qdwc->dev, "failed to get optional iface clock\n");
qdwc->iface_clk = NULL;
}
qdwc->sleep_clk = devm_clk_get(qdwc->dev, "sleep");
if (IS_ERR(qdwc->sleep_clk)) {
dev_dbg(qdwc->dev, "failed to get optional sleep clock\n");
qdwc->sleep_clk = NULL;
}
ret = clk_prepare_enable(qdwc->core_clk);
if (ret) {
dev_err(qdwc->dev, "failed to enable core clock\n");
goto err_core;
}
ret = clk_prepare_enable(qdwc->iface_clk);
if (ret) {
dev_err(qdwc->dev, "failed to enable optional iface clock\n");
goto err_iface;
}
ret = clk_prepare_enable(qdwc->sleep_clk);
if (ret) {
dev_err(qdwc->dev, "failed to enable optional sleep clock\n");
goto err_sleep;
}
ret = of_platform_populate(node, NULL, NULL, qdwc->dev);
if (ret) {
dev_err(qdwc->dev, "failed to register core - %d\n", ret);
goto err_clks;
}
return 0;
err_clks:
clk_disable_unprepare(qdwc->sleep_clk);
err_sleep:
clk_disable_unprepare(qdwc->iface_clk);
err_iface:
clk_disable_unprepare(qdwc->core_clk);
err_core:
return ret;
}
static int dwc3_qcom_remove(struct platform_device *pdev)
{
struct dwc3_qcom *qdwc = platform_get_drvdata(pdev);
of_platform_depopulate(&pdev->dev);
clk_disable_unprepare(qdwc->sleep_clk);
clk_disable_unprepare(qdwc->iface_clk);
clk_disable_unprepare(qdwc->core_clk);
return 0;
}
static const struct of_device_id of_dwc3_match[] = {
{ .compatible = "qcom,dwc3" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, of_dwc3_match);
static struct platform_driver dwc3_qcom_driver = {
.probe = dwc3_qcom_probe,
.remove = dwc3_qcom_remove,
.driver = {
.name = "qcom-dwc3",
.owner = THIS_MODULE,
.of_match_table = of_dwc3_match,
},
};
module_platform_driver(dwc3_qcom_driver);
MODULE_ALIAS("platform:qcom-dwc3");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("DesignWare USB3 QCOM Glue Layer");
MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>");

367
drivers/usb/dwc3/dwc3-st.c Normal file
View File

@ -0,0 +1,367 @@
/**
* dwc3-st.c Support for dwc3 platform devices on ST Microelectronics platforms
*
* This is a small driver for the dwc3 to provide the glue logic
* to configure the controller. Tested on STi platforms.
*
* Copyright (C) 2014 Stmicroelectronics
*
* Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
* Contributors: Aymen Bouattay <aymen.bouattay@st.com>
* Peter Griffin <peter.griffin@linaro.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Inspired by dwc3-omap.c and dwc3-exynos.c.
*/
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/usb/of.h>
#include "core.h"
#include "io.h"
/* glue registers */
#define CLKRST_CTRL 0x00
#define AUX_CLK_EN BIT(0)
#define SW_PIPEW_RESET_N BIT(4)
#define EXT_CFG_RESET_N BIT(8)
/*
* 1'b0 : The host controller complies with the xHCI revision 0.96
* 1'b1 : The host controller complies with the xHCI revision 1.0
*/
#define XHCI_REVISION BIT(12)
#define USB2_VBUS_MNGMNT_SEL1 0x2C
/*
* For all fields in USB2_VBUS_MNGMNT_SEL1
* 2b00 : Override value from Reg 0x30 is selected
* 2b01 : utmiotg_<signal_name> from usb3_top is selected
* 2b10 : pipew_<signal_name> from PIPEW instance is selected
* 2b11 : value is 1'b0
*/
#define USB2_VBUS_REG30 0x0
#define USB2_VBUS_UTMIOTG 0x1
#define USB2_VBUS_PIPEW 0x2
#define USB2_VBUS_ZERO 0x3
#define SEL_OVERRIDE_VBUSVALID(n) (n << 0)
#define SEL_OVERRIDE_POWERPRESENT(n) (n << 4)
#define SEL_OVERRIDE_BVALID(n) (n << 8)
/* Static DRD configuration */
#define USB3_CONTROL_MASK 0xf77
#define USB3_DEVICE_NOT_HOST BIT(0)
#define USB3_FORCE_VBUSVALID BIT(1)
#define USB3_DELAY_VBUSVALID BIT(2)
#define USB3_SEL_FORCE_OPMODE BIT(4)
#define USB3_FORCE_OPMODE(n) (n << 5)
#define USB3_SEL_FORCE_DPPULLDOWN2 BIT(8)
#define USB3_FORCE_DPPULLDOWN2 BIT(9)
#define USB3_SEL_FORCE_DMPULLDOWN2 BIT(10)
#define USB3_FORCE_DMPULLDOWN2 BIT(11)
/**
* struct st_dwc3 - dwc3-st driver private structure
* @dev: device pointer
* @glue_base: ioaddr for the glue registers
* @regmap: regmap pointer for getting syscfg
* @syscfg_reg_off: usb syscfg control offset
* @dr_mode: drd static host/device config
* @rstc_pwrdn: rest controller for powerdown signal
* @rstc_rst: reset controller for softreset signal
*/
struct st_dwc3 {
struct device *dev;
void __iomem *glue_base;
struct regmap *regmap;
int syscfg_reg_off;
enum usb_dr_mode dr_mode;
struct reset_control *rstc_pwrdn;
struct reset_control *rstc_rst;
};
static inline u32 st_dwc3_readl(void __iomem *base, u32 offset)
{
return readl_relaxed(base + offset);
}
static inline void st_dwc3_writel(void __iomem *base, u32 offset, u32 value)
{
writel_relaxed(value, base + offset);
}
/**
* st_dwc3_drd_init: program the port
* @dwc3_data: driver private structure
* Description: this function is to program the port as either host or device
* according to the static configuration passed from devicetree.
* OTG and dual role are not yet supported!
*/
static int st_dwc3_drd_init(struct st_dwc3 *dwc3_data)
{
u32 val;
int err;
err = regmap_read(dwc3_data->regmap, dwc3_data->syscfg_reg_off, &val);
if (err)
return err;
val &= USB3_CONTROL_MASK;
switch (dwc3_data->dr_mode) {
case USB_DR_MODE_PERIPHERAL:
val &= ~(USB3_FORCE_VBUSVALID | USB3_DELAY_VBUSVALID
| USB3_SEL_FORCE_OPMODE | USB3_FORCE_OPMODE(0x3)
| USB3_SEL_FORCE_DPPULLDOWN2 | USB3_FORCE_DPPULLDOWN2
| USB3_SEL_FORCE_DMPULLDOWN2 | USB3_FORCE_DMPULLDOWN2);
val |= USB3_DEVICE_NOT_HOST;
dev_dbg(dwc3_data->dev, "Configuring as Device\n");
break;
case USB_DR_MODE_HOST:
val &= ~(USB3_DEVICE_NOT_HOST | USB3_FORCE_VBUSVALID
| USB3_SEL_FORCE_OPMODE | USB3_FORCE_OPMODE(0x3)
| USB3_SEL_FORCE_DPPULLDOWN2 | USB3_FORCE_DPPULLDOWN2
| USB3_SEL_FORCE_DMPULLDOWN2 | USB3_FORCE_DMPULLDOWN2);
/*
* USB3_DELAY_VBUSVALID is ANDed with USB_C_VBUSVALID. Thus,
* when set to 0, it can delay the arrival of VBUSVALID
* information to VBUSVLDEXT2 input of the pico PHY.
* We don't want to do that so we set the bit to '1'.
*/
val |= USB3_DELAY_VBUSVALID;
dev_dbg(dwc3_data->dev, "Configuring as Host\n");
break;
default:
dev_err(dwc3_data->dev, "Unsupported mode of operation %d\n",
dwc3_data->dr_mode);
return -EINVAL;
}
return regmap_write(dwc3_data->regmap, dwc3_data->syscfg_reg_off, val);
}
/**
* st_dwc3_init: init the controller via glue logic
* @dwc3_data: driver private structure
*/
static void st_dwc3_init(struct st_dwc3 *dwc3_data)
{
u32 reg = st_dwc3_readl(dwc3_data->glue_base, CLKRST_CTRL);
reg |= AUX_CLK_EN | EXT_CFG_RESET_N | XHCI_REVISION;
reg &= ~SW_PIPEW_RESET_N;
st_dwc3_writel(dwc3_data->glue_base, CLKRST_CTRL, reg);
/* configure mux for vbus, powerpresent and bvalid signals */
reg = st_dwc3_readl(dwc3_data->glue_base, USB2_VBUS_MNGMNT_SEL1);
reg |= SEL_OVERRIDE_VBUSVALID(USB2_VBUS_UTMIOTG) |
SEL_OVERRIDE_POWERPRESENT(USB2_VBUS_UTMIOTG) |
SEL_OVERRIDE_BVALID(USB2_VBUS_UTMIOTG);
st_dwc3_writel(dwc3_data->glue_base, USB2_VBUS_MNGMNT_SEL1, reg);
reg = st_dwc3_readl(dwc3_data->glue_base, CLKRST_CTRL);
reg |= SW_PIPEW_RESET_N;
st_dwc3_writel(dwc3_data->glue_base, CLKRST_CTRL, reg);
}
static int st_dwc3_probe(struct platform_device *pdev)
{
struct st_dwc3 *dwc3_data;
struct resource *res;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node, *child;
struct regmap *regmap;
int ret;
dwc3_data = devm_kzalloc(dev, sizeof(*dwc3_data), GFP_KERNEL);
if (!dwc3_data)
return -ENOMEM;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "reg-glue");
dwc3_data->glue_base = devm_ioremap_resource(dev, res);
if (IS_ERR(dwc3_data->glue_base))
return PTR_ERR(dwc3_data->glue_base);
regmap = syscon_regmap_lookup_by_phandle(node, "st,syscfg");
if (IS_ERR(regmap))
return PTR_ERR(regmap);
dma_set_coherent_mask(dev, dev->coherent_dma_mask);
dwc3_data->dev = dev;
dwc3_data->regmap = regmap;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "syscfg-reg");
if (!res) {
ret = -ENXIO;
goto undo_platform_dev_alloc;
}
dwc3_data->syscfg_reg_off = res->start;
dev_vdbg(&pdev->dev, "glue-logic addr 0x%p, syscfg-reg offset 0x%x\n",
dwc3_data->glue_base, dwc3_data->syscfg_reg_off);
dwc3_data->rstc_pwrdn = devm_reset_control_get(dev, "powerdown");
if (IS_ERR(dwc3_data->rstc_pwrdn)) {
dev_err(&pdev->dev, "could not get power controller\n");
ret = PTR_ERR(dwc3_data->rstc_pwrdn);
goto undo_platform_dev_alloc;
}
/* Manage PowerDown */
reset_control_deassert(dwc3_data->rstc_pwrdn);
dwc3_data->rstc_rst = devm_reset_control_get(dev, "softreset");
if (IS_ERR(dwc3_data->rstc_rst)) {
dev_err(&pdev->dev, "could not get reset controller\n");
ret = PTR_ERR(dwc3_data->rstc_pwrdn);
goto undo_powerdown;
}
/* Manage SoftReset */
reset_control_deassert(dwc3_data->rstc_rst);
child = of_get_child_by_name(node, "dwc3");
if (!child) {
dev_err(&pdev->dev, "failed to find dwc3 core node\n");
ret = -ENODEV;
goto undo_softreset;
}
dwc3_data->dr_mode = of_usb_get_dr_mode(child);
/* Allocate and initialize the core */
ret = of_platform_populate(node, NULL, NULL, dev);
if (ret) {
dev_err(dev, "failed to add dwc3 core\n");
goto undo_softreset;
}
/*
* Configure the USB port as device or host according to the static
* configuration passed from DT.
* DRD is the only mode currently supported so this will be enhanced
* as soon as OTG is available.
*/
ret = st_dwc3_drd_init(dwc3_data);
if (ret) {
dev_err(dev, "drd initialisation failed\n");
goto undo_softreset;
}
/* ST glue logic init */
st_dwc3_init(dwc3_data);
platform_set_drvdata(pdev, dwc3_data);
return 0;
undo_softreset:
reset_control_assert(dwc3_data->rstc_rst);
undo_powerdown:
reset_control_assert(dwc3_data->rstc_pwrdn);
undo_platform_dev_alloc:
platform_device_put(pdev);
return ret;
}
static int st_dwc3_remove(struct platform_device *pdev)
{
struct st_dwc3 *dwc3_data = platform_get_drvdata(pdev);
of_platform_depopulate(&pdev->dev);
reset_control_assert(dwc3_data->rstc_pwrdn);
reset_control_assert(dwc3_data->rstc_rst);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int st_dwc3_suspend(struct device *dev)
{
struct st_dwc3 *dwc3_data = dev_get_drvdata(dev);
reset_control_assert(dwc3_data->rstc_pwrdn);
reset_control_assert(dwc3_data->rstc_rst);
pinctrl_pm_select_sleep_state(dev);
return 0;
}
static int st_dwc3_resume(struct device *dev)
{
struct st_dwc3 *dwc3_data = dev_get_drvdata(dev);
int ret;
pinctrl_pm_select_default_state(dev);
reset_control_deassert(dwc3_data->rstc_pwrdn);
reset_control_deassert(dwc3_data->rstc_rst);
ret = st_dwc3_drd_init(dwc3_data);
if (ret) {
dev_err(dev, "drd initialisation failed\n");
return ret;
}
/* ST glue logic init */
st_dwc3_init(dwc3_data);
return 0;
}
#endif /* CONFIG_PM_SLEEP */
static SIMPLE_DEV_PM_OPS(st_dwc3_dev_pm_ops, st_dwc3_suspend, st_dwc3_resume);
static const struct of_device_id st_dwc3_match[] = {
{ .compatible = "st,stih407-dwc3" },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, st_dwc3_match);
static struct platform_driver st_dwc3_driver = {
.probe = st_dwc3_probe,
.remove = st_dwc3_remove,
.driver = {
.name = "usb-st-dwc3",
.of_match_table = st_dwc3_match,
.pm = &st_dwc3_dev_pm_ops,
},
};
module_platform_driver(st_dwc3_driver);
MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>");
MODULE_DESCRIPTION("DesignWare USB3 STi Glue Layer");
MODULE_LICENSE("GPL v2");

View File

@ -31,6 +31,7 @@
#include <linux/usb/composite.h> #include <linux/usb/composite.h>
#include "core.h" #include "core.h"
#include "debug.h"
#include "gadget.h" #include "gadget.h"
#include "io.h" #include "io.h"
@ -65,7 +66,7 @@ static int dwc3_ep0_start_trans(struct dwc3 *dwc, u8 epnum, dma_addr_t buf_dma,
dep = dwc->eps[epnum]; dep = dwc->eps[epnum];
if (dep->flags & DWC3_EP_BUSY) { if (dep->flags & DWC3_EP_BUSY) {
dev_vdbg(dwc->dev, "%s: still busy\n", dep->name); dwc3_trace(trace_dwc3_ep0, "%s still busy", dep->name);
return 0; return 0;
} }
@ -88,7 +89,8 @@ static int dwc3_ep0_start_trans(struct dwc3 *dwc, u8 epnum, dma_addr_t buf_dma,
ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, ret = dwc3_send_gadget_ep_cmd(dwc, dep->number,
DWC3_DEPCMD_STARTTRANSFER, &params); DWC3_DEPCMD_STARTTRANSFER, &params);
if (ret < 0) { if (ret < 0) {
dev_dbg(dwc->dev, "failed to send STARTTRANSFER command\n"); dwc3_trace(trace_dwc3_ep0, "%s STARTTRANSFER failed",
dep->name);
return ret; return ret;
} }
@ -153,7 +155,8 @@ static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep,
if (dwc->ep0state == EP0_STATUS_PHASE) if (dwc->ep0state == EP0_STATUS_PHASE)
__dwc3_ep0_do_control_status(dwc, dwc->eps[direction]); __dwc3_ep0_do_control_status(dwc, dwc->eps[direction]);
else else
dev_dbg(dwc->dev, "too early for delayed status\n"); dwc3_trace(trace_dwc3_ep0,
"too early for delayed status");
return 0; return 0;
} }
@ -217,7 +220,8 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request,
spin_lock_irqsave(&dwc->lock, flags); spin_lock_irqsave(&dwc->lock, flags);
if (!dep->endpoint.desc) { if (!dep->endpoint.desc) {
dev_dbg(dwc->dev, "trying to queue request %p to disabled %s\n", dwc3_trace(trace_dwc3_ep0,
"trying to queue request %p to disabled %s",
request, dep->name); request, dep->name);
ret = -ESHUTDOWN; ret = -ESHUTDOWN;
goto out; goto out;
@ -229,7 +233,8 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request,
goto out; goto out;
} }
dev_vdbg(dwc->dev, "queueing request %p to %s length %d, state '%s'\n", dwc3_trace(trace_dwc3_ep0,
"queueing request %p to %s length %d state '%s'",
request, dep->name, request->length, request, dep->name, request->length,
dwc3_ep0_state_string(dwc->ep0state)); dwc3_ep0_state_string(dwc->ep0state));
@ -485,12 +490,13 @@ static int dwc3_ep0_set_address(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl)
addr = le16_to_cpu(ctrl->wValue); addr = le16_to_cpu(ctrl->wValue);
if (addr > 127) { if (addr > 127) {
dev_dbg(dwc->dev, "invalid device address %d\n", addr); dwc3_trace(trace_dwc3_ep0, "invalid device address %d", addr);
return -EINVAL; return -EINVAL;
} }
if (state == USB_STATE_CONFIGURED) { if (state == USB_STATE_CONFIGURED) {
dev_dbg(dwc->dev, "trying to set address when configured\n"); dwc3_trace(trace_dwc3_ep0,
"trying to set address when configured");
return -EINVAL; return -EINVAL;
} }
@ -556,7 +562,7 @@ static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl)
dwc3_writel(dwc->regs, DWC3_DCTL, reg); dwc3_writel(dwc->regs, DWC3_DCTL, reg);
dwc->resize_fifos = true; dwc->resize_fifos = true;
dev_dbg(dwc->dev, "resize fifos flag SET\n"); dwc3_trace(trace_dwc3_ep0, "resize FIFOs flag SET");
} }
break; break;
@ -680,35 +686,35 @@ static int dwc3_ep0_std_request(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl)
switch (ctrl->bRequest) { switch (ctrl->bRequest) {
case USB_REQ_GET_STATUS: case USB_REQ_GET_STATUS:
dev_vdbg(dwc->dev, "USB_REQ_GET_STATUS\n"); dwc3_trace(trace_dwc3_ep0, "USB_REQ_GET_STATUS\n");
ret = dwc3_ep0_handle_status(dwc, ctrl); ret = dwc3_ep0_handle_status(dwc, ctrl);
break; break;
case USB_REQ_CLEAR_FEATURE: case USB_REQ_CLEAR_FEATURE:
dev_vdbg(dwc->dev, "USB_REQ_CLEAR_FEATURE\n"); dwc3_trace(trace_dwc3_ep0, "USB_REQ_CLEAR_FEATURE\n");
ret = dwc3_ep0_handle_feature(dwc, ctrl, 0); ret = dwc3_ep0_handle_feature(dwc, ctrl, 0);
break; break;
case USB_REQ_SET_FEATURE: case USB_REQ_SET_FEATURE:
dev_vdbg(dwc->dev, "USB_REQ_SET_FEATURE\n"); dwc3_trace(trace_dwc3_ep0, "USB_REQ_SET_FEATURE\n");
ret = dwc3_ep0_handle_feature(dwc, ctrl, 1); ret = dwc3_ep0_handle_feature(dwc, ctrl, 1);
break; break;
case USB_REQ_SET_ADDRESS: case USB_REQ_SET_ADDRESS:
dev_vdbg(dwc->dev, "USB_REQ_SET_ADDRESS\n"); dwc3_trace(trace_dwc3_ep0, "USB_REQ_SET_ADDRESS\n");
ret = dwc3_ep0_set_address(dwc, ctrl); ret = dwc3_ep0_set_address(dwc, ctrl);
break; break;
case USB_REQ_SET_CONFIGURATION: case USB_REQ_SET_CONFIGURATION:
dev_vdbg(dwc->dev, "USB_REQ_SET_CONFIGURATION\n"); dwc3_trace(trace_dwc3_ep0, "USB_REQ_SET_CONFIGURATION\n");
ret = dwc3_ep0_set_config(dwc, ctrl); ret = dwc3_ep0_set_config(dwc, ctrl);
break; break;
case USB_REQ_SET_SEL: case USB_REQ_SET_SEL:
dev_vdbg(dwc->dev, "USB_REQ_SET_SEL\n"); dwc3_trace(trace_dwc3_ep0, "USB_REQ_SET_SEL\n");
ret = dwc3_ep0_set_sel(dwc, ctrl); ret = dwc3_ep0_set_sel(dwc, ctrl);
break; break;
case USB_REQ_SET_ISOCH_DELAY: case USB_REQ_SET_ISOCH_DELAY:
dev_vdbg(dwc->dev, "USB_REQ_SET_ISOCH_DELAY\n"); dwc3_trace(trace_dwc3_ep0, "USB_REQ_SET_ISOCH_DELAY\n");
ret = dwc3_ep0_set_isoch_delay(dwc, ctrl); ret = dwc3_ep0_set_isoch_delay(dwc, ctrl);
break; break;
default: default:
dev_vdbg(dwc->dev, "Forwarding to gadget driver\n"); dwc3_trace(trace_dwc3_ep0, "Forwarding to gadget driver\n");
ret = dwc3_ep0_delegate_req(dwc, ctrl); ret = dwc3_ep0_delegate_req(dwc, ctrl);
break; break;
} }
@ -726,6 +732,8 @@ static void dwc3_ep0_inspect_setup(struct dwc3 *dwc,
if (!dwc->gadget_driver) if (!dwc->gadget_driver)
goto out; goto out;
trace_dwc3_ctrl_req(ctrl);
len = le16_to_cpu(ctrl->wLength); len = le16_to_cpu(ctrl->wLength);
if (!len) { if (!len) {
dwc->three_stage_setup = false; dwc->three_stage_setup = false;
@ -774,7 +782,7 @@ static void dwc3_ep0_complete_data(struct dwc3 *dwc,
status = DWC3_TRB_SIZE_TRBSTS(trb->size); status = DWC3_TRB_SIZE_TRBSTS(trb->size);
if (status == DWC3_TRBSTS_SETUP_PENDING) { if (status == DWC3_TRBSTS_SETUP_PENDING) {
dev_dbg(dwc->dev, "Setup Pending received\n"); dwc3_trace(trace_dwc3_ep0, "Setup Pending received");
if (r) if (r)
dwc3_gadget_giveback(ep0, r, -ECONNRESET); dwc3_gadget_giveback(ep0, r, -ECONNRESET);
@ -834,7 +842,7 @@ static void dwc3_ep0_complete_status(struct dwc3 *dwc,
ret = dwc3_gadget_set_test_mode(dwc, dwc->test_mode_nr); ret = dwc3_gadget_set_test_mode(dwc, dwc->test_mode_nr);
if (ret < 0) { if (ret < 0) {
dev_dbg(dwc->dev, "Invalid Test #%d\n", dwc3_trace(trace_dwc3_ep0, "Invalid Test #%d",
dwc->test_mode_nr); dwc->test_mode_nr);
dwc3_ep0_stall_and_restart(dwc); dwc3_ep0_stall_and_restart(dwc);
return; return;
@ -843,7 +851,7 @@ static void dwc3_ep0_complete_status(struct dwc3 *dwc,
status = DWC3_TRB_SIZE_TRBSTS(trb->size); status = DWC3_TRB_SIZE_TRBSTS(trb->size);
if (status == DWC3_TRBSTS_SETUP_PENDING) if (status == DWC3_TRBSTS_SETUP_PENDING)
dev_dbg(dwc->dev, "Setup Pending received\n"); dwc3_trace(trace_dwc3_ep0, "Setup Pending received\n");
dwc->ep0state = EP0_SETUP_PHASE; dwc->ep0state = EP0_SETUP_PHASE;
dwc3_ep0_out_start(dwc); dwc3_ep0_out_start(dwc);
@ -860,17 +868,17 @@ static void dwc3_ep0_xfer_complete(struct dwc3 *dwc,
switch (dwc->ep0state) { switch (dwc->ep0state) {
case EP0_SETUP_PHASE: case EP0_SETUP_PHASE:
dev_vdbg(dwc->dev, "Inspecting Setup Bytes\n"); dwc3_trace(trace_dwc3_ep0, "Setup Phase");
dwc3_ep0_inspect_setup(dwc, event); dwc3_ep0_inspect_setup(dwc, event);
break; break;
case EP0_DATA_PHASE: case EP0_DATA_PHASE:
dev_vdbg(dwc->dev, "Data Phase\n"); dwc3_trace(trace_dwc3_ep0, "Data Phase");
dwc3_ep0_complete_data(dwc, event); dwc3_ep0_complete_data(dwc, event);
break; break;
case EP0_STATUS_PHASE: case EP0_STATUS_PHASE:
dev_vdbg(dwc->dev, "Status Phase\n"); dwc3_trace(trace_dwc3_ep0, "Status Phase");
dwc3_ep0_complete_status(dwc, event); dwc3_ep0_complete_status(dwc, event);
break; break;
default: default:
@ -946,7 +954,7 @@ static int dwc3_ep0_start_control_status(struct dwc3_ep *dep)
static void __dwc3_ep0_do_control_status(struct dwc3 *dwc, struct dwc3_ep *dep) static void __dwc3_ep0_do_control_status(struct dwc3 *dwc, struct dwc3_ep *dep)
{ {
if (dwc->resize_fifos) { if (dwc->resize_fifos) {
dev_dbg(dwc->dev, "starting to resize fifos\n"); dwc3_trace(trace_dwc3_ep0, "Resizing FIFOs");
dwc3_gadget_resize_tx_fifos(dwc); dwc3_gadget_resize_tx_fifos(dwc);
dwc->resize_fifos = 0; dwc->resize_fifos = 0;
} }
@ -987,7 +995,7 @@ static void dwc3_ep0_xfernotready(struct dwc3 *dwc,
switch (event->status) { switch (event->status) {
case DEPEVT_STATUS_CONTROL_DATA: case DEPEVT_STATUS_CONTROL_DATA:
dev_vdbg(dwc->dev, "Control Data\n"); dwc3_trace(trace_dwc3_ep0, "Control Data");
/* /*
* We already have a DATA transfer in the controller's cache, * We already have a DATA transfer in the controller's cache,
@ -1001,7 +1009,8 @@ static void dwc3_ep0_xfernotready(struct dwc3 *dwc,
if (dwc->ep0_expect_in != event->endpoint_number) { if (dwc->ep0_expect_in != event->endpoint_number) {
struct dwc3_ep *dep = dwc->eps[dwc->ep0_expect_in]; struct dwc3_ep *dep = dwc->eps[dwc->ep0_expect_in];
dev_vdbg(dwc->dev, "Wrong direction for Data phase\n"); dwc3_trace(trace_dwc3_ep0,
"Wrong direction for Data phase");
dwc3_ep0_end_control_data(dwc, dep); dwc3_ep0_end_control_data(dwc, dep);
dwc3_ep0_stall_and_restart(dwc); dwc3_ep0_stall_and_restart(dwc);
return; return;
@ -1013,13 +1022,13 @@ static void dwc3_ep0_xfernotready(struct dwc3 *dwc,
if (dwc->ep0_next_event != DWC3_EP0_NRDY_STATUS) if (dwc->ep0_next_event != DWC3_EP0_NRDY_STATUS)
return; return;
dev_vdbg(dwc->dev, "Control Status\n"); dwc3_trace(trace_dwc3_ep0, "Control Status");
dwc->ep0state = EP0_STATUS_PHASE; dwc->ep0state = EP0_STATUS_PHASE;
if (dwc->delayed_status) { if (dwc->delayed_status) {
WARN_ON_ONCE(event->endpoint_number != 1); WARN_ON_ONCE(event->endpoint_number != 1);
dev_vdbg(dwc->dev, "Mass Storage delayed status\n"); dwc3_trace(trace_dwc3_ep0, "Delayed Status");
return; return;
} }
@ -1032,7 +1041,7 @@ void dwc3_ep0_interrupt(struct dwc3 *dwc,
{ {
u8 epnum = event->endpoint_number; u8 epnum = event->endpoint_number;
dev_dbg(dwc->dev, "%s while ep%d%s in state '%s'\n", dwc3_trace(trace_dwc3_ep0, "%s while ep%d%s in state '%s'",
dwc3_ep_event_string(event->endpoint_event), dwc3_ep_event_string(event->endpoint_event),
epnum >> 1, (epnum & 1) ? "in" : "out", epnum >> 1, (epnum & 1) ? "in" : "out",
dwc3_ep0_state_string(dwc->ep0state)); dwc3_ep0_state_string(dwc->ep0state));

View File

@ -30,6 +30,7 @@
#include <linux/usb/ch9.h> #include <linux/usb/ch9.h>
#include <linux/usb/gadget.h> #include <linux/usb/gadget.h>
#include "debug.h"
#include "core.h" #include "core.h"
#include "gadget.h" #include "gadget.h"
#include "io.h" #include "io.h"
@ -266,107 +267,19 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
dev_dbg(dwc->dev, "request %p from %s completed %d/%d ===> %d\n", dev_dbg(dwc->dev, "request %p from %s completed %d/%d ===> %d\n",
req, dep->name, req->request.actual, req, dep->name, req->request.actual,
req->request.length, status); req->request.length, status);
trace_dwc3_gadget_giveback(req);
spin_unlock(&dwc->lock); spin_unlock(&dwc->lock);
req->request.complete(&dep->endpoint, &req->request); req->request.complete(&dep->endpoint, &req->request);
spin_lock(&dwc->lock); spin_lock(&dwc->lock);
} }
static const char *dwc3_gadget_ep_cmd_string(u8 cmd) int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param)
{
switch (cmd) {
case DWC3_DEPCMD_DEPSTARTCFG:
return "Start New Configuration";
case DWC3_DEPCMD_ENDTRANSFER:
return "End Transfer";
case DWC3_DEPCMD_UPDATETRANSFER:
return "Update Transfer";
case DWC3_DEPCMD_STARTTRANSFER:
return "Start Transfer";
case DWC3_DEPCMD_CLEARSTALL:
return "Clear Stall";
case DWC3_DEPCMD_SETSTALL:
return "Set Stall";
case DWC3_DEPCMD_GETEPSTATE:
return "Get Endpoint State";
case DWC3_DEPCMD_SETTRANSFRESOURCE:
return "Set Endpoint Transfer Resource";
case DWC3_DEPCMD_SETEPCONFIG:
return "Set Endpoint Configuration";
default:
return "UNKNOWN command";
}
}
static const char *dwc3_gadget_generic_cmd_string(u8 cmd)
{
switch (cmd) {
case DWC3_DGCMD_SET_LMP:
return "Set LMP";
case DWC3_DGCMD_SET_PERIODIC_PAR:
return "Set Periodic Parameters";
case DWC3_DGCMD_XMIT_FUNCTION:
return "Transmit Function Wake Device Notification";
case DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO:
return "Set Scratchpad Buffer Array Address Lo";
case DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI:
return "Set Scratchpad Buffer Array Address Hi";
case DWC3_DGCMD_SELECTED_FIFO_FLUSH:
return "Selected FIFO Flush";
case DWC3_DGCMD_ALL_FIFO_FLUSH:
return "All FIFO Flush";
case DWC3_DGCMD_SET_ENDPOINT_NRDY:
return "Set Endpoint NRDY";
case DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK:
return "Run SoC Bus Loopback Test";
default:
return "UNKNOWN";
}
}
static const char *dwc3_gadget_link_string(enum dwc3_link_state link_state)
{
switch (link_state) {
case DWC3_LINK_STATE_U0:
return "U0";
case DWC3_LINK_STATE_U1:
return "U1";
case DWC3_LINK_STATE_U2:
return "U2";
case DWC3_LINK_STATE_U3:
return "U3";
case DWC3_LINK_STATE_SS_DIS:
return "SS.Disabled";
case DWC3_LINK_STATE_RX_DET:
return "RX.Detect";
case DWC3_LINK_STATE_SS_INACT:
return "SS.Inactive";
case DWC3_LINK_STATE_POLL:
return "Polling";
case DWC3_LINK_STATE_RECOV:
return "Recovery";
case DWC3_LINK_STATE_HRESET:
return "Hot Reset";
case DWC3_LINK_STATE_CMPLY:
return "Compliance";
case DWC3_LINK_STATE_LPBK:
return "Loopback";
case DWC3_LINK_STATE_RESET:
return "Reset";
case DWC3_LINK_STATE_RESUME:
return "Resume";
default:
return "UNKNOWN link state\n";
}
}
int dwc3_send_gadget_generic_command(struct dwc3 *dwc, int cmd, u32 param)
{ {
u32 timeout = 500; u32 timeout = 500;
u32 reg; u32 reg;
dev_vdbg(dwc->dev, "generic cmd '%s' [%d] param %08x\n", trace_dwc3_gadget_generic_cmd(cmd, param);
dwc3_gadget_generic_cmd_string(cmd), cmd, param);
dwc3_writel(dwc->regs, DWC3_DGCMDPAR, param); dwc3_writel(dwc->regs, DWC3_DGCMDPAR, param);
dwc3_writel(dwc->regs, DWC3_DGCMD, cmd | DWC3_DGCMD_CMDACT); dwc3_writel(dwc->regs, DWC3_DGCMD, cmd | DWC3_DGCMD_CMDACT);
@ -397,10 +310,7 @@ int dwc3_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep,
u32 timeout = 500; u32 timeout = 500;
u32 reg; u32 reg;
dev_vdbg(dwc->dev, "%s: cmd '%s' [%d] params %08x %08x %08x\n", trace_dwc3_gadget_ep_cmd(dep, cmd, params);
dep->name,
dwc3_gadget_ep_cmd_string(cmd), cmd, params->param0,
params->param1, params->param2);
dwc3_writel(dwc->regs, DWC3_DEPCMDPAR0(ep), params->param0); dwc3_writel(dwc->regs, DWC3_DEPCMDPAR0(ep), params->param0);
dwc3_writel(dwc->regs, DWC3_DEPCMDPAR1(ep), params->param1); dwc3_writel(dwc->regs, DWC3_DEPCMDPAR1(ep), params->param1);
@ -789,17 +699,16 @@ static struct usb_request *dwc3_gadget_ep_alloc_request(struct usb_ep *ep,
{ {
struct dwc3_request *req; struct dwc3_request *req;
struct dwc3_ep *dep = to_dwc3_ep(ep); struct dwc3_ep *dep = to_dwc3_ep(ep);
struct dwc3 *dwc = dep->dwc;
req = kzalloc(sizeof(*req), gfp_flags); req = kzalloc(sizeof(*req), gfp_flags);
if (!req) { if (!req)
dev_err(dwc->dev, "not enough memory\n");
return NULL; return NULL;
}
req->epnum = dep->number; req->epnum = dep->number;
req->dep = dep; req->dep = dep;
trace_dwc3_alloc_request(req);
return &req->request; return &req->request;
} }
@ -808,6 +717,7 @@ static void dwc3_gadget_ep_free_request(struct usb_ep *ep,
{ {
struct dwc3_request *req = to_dwc3_request(request); struct dwc3_request *req = to_dwc3_request(request);
trace_dwc3_free_request(req);
kfree(req); kfree(req);
} }
@ -889,6 +799,8 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep,
trb->ctrl |= DWC3_TRB_CTRL_SID_SOFN(req->request.stream_id); trb->ctrl |= DWC3_TRB_CTRL_SID_SOFN(req->request.stream_id);
trb->ctrl |= DWC3_TRB_CTRL_HWO; trb->ctrl |= DWC3_TRB_CTRL_HWO;
trace_dwc3_prepare_trb(dep, trb);
} }
/* /*
@ -1235,6 +1147,7 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request,
dev_vdbg(dwc->dev, "queing request %p to %s length %d\n", dev_vdbg(dwc->dev, "queing request %p to %s length %d\n",
request, ep->name, request->length); request, ep->name, request->length);
trace_dwc3_ep_queue(req);
ret = __dwc3_gadget_ep_queue(dep, req); ret = __dwc3_gadget_ep_queue(dep, req);
spin_unlock_irqrestore(&dwc->lock, flags); spin_unlock_irqrestore(&dwc->lock, flags);
@ -1254,6 +1167,8 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep,
unsigned long flags; unsigned long flags;
int ret = 0; int ret = 0;
trace_dwc3_ep_dequeue(req);
spin_lock_irqsave(&dwc->lock, flags); spin_lock_irqsave(&dwc->lock, flags);
list_for_each_entry(r, &dep->request_list, list) { list_for_each_entry(r, &dep->request_list, list) {
@ -1744,11 +1659,8 @@ static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc,
u8 epnum = (i << 1) | (!!direction); u8 epnum = (i << 1) | (!!direction);
dep = kzalloc(sizeof(*dep), GFP_KERNEL); dep = kzalloc(sizeof(*dep), GFP_KERNEL);
if (!dep) { if (!dep)
dev_err(dwc->dev, "can't allocate endpoint %d\n",
epnum);
return -ENOMEM; return -ENOMEM;
}
dep->dwc = dwc; dep->dwc = dwc;
dep->number = epnum; dep->number = epnum;
@ -1847,6 +1759,8 @@ static int __dwc3_cleanup_done_trbs(struct dwc3 *dwc, struct dwc3_ep *dep,
unsigned int s_pkt = 0; unsigned int s_pkt = 0;
unsigned int trb_status; unsigned int trb_status;
trace_dwc3_complete_trb(dep, trb);
if ((trb->ctrl & DWC3_TRB_CTRL_HWO) && status != -ESHUTDOWN) if ((trb->ctrl & DWC3_TRB_CTRL_HWO) && status != -ESHUTDOWN)
/* /*
* We continue despite the error. There is not much we * We continue despite the error. There is not much we
@ -2021,9 +1935,6 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
if (!(dep->flags & DWC3_EP_ENABLED)) if (!(dep->flags & DWC3_EP_ENABLED))
return; return;
dev_vdbg(dwc->dev, "%s: %s\n", dep->name,
dwc3_ep_event_string(event->endpoint_event));
if (epnum == 0 || epnum == 1) { if (epnum == 0 || epnum == 1) {
dwc3_ep0_interrupt(dwc, event); dwc3_ep0_interrupt(dwc, event);
return; return;
@ -2210,8 +2121,6 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc)
{ {
int reg; int reg;
dev_vdbg(dwc->dev, "%s\n", __func__);
reg = dwc3_readl(dwc->regs, DWC3_DCTL); reg = dwc3_readl(dwc->regs, DWC3_DCTL);
reg &= ~DWC3_DCTL_INITU1ENA; reg &= ~DWC3_DCTL_INITU1ENA;
dwc3_writel(dwc->regs, DWC3_DCTL, reg); dwc3_writel(dwc->regs, DWC3_DCTL, reg);
@ -2230,8 +2139,6 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc)
{ {
u32 reg; u32 reg;
dev_vdbg(dwc->dev, "%s\n", __func__);
/* /*
* WORKAROUND: DWC3 revisions <1.88a have an issue which * WORKAROUND: DWC3 revisions <1.88a have an issue which
* would cause a missing Disconnect Event if there's a * would cause a missing Disconnect Event if there's a
@ -2316,8 +2223,6 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc)
u32 reg; u32 reg;
u8 speed; u8 speed;
dev_vdbg(dwc->dev, "%s\n", __func__);
reg = dwc3_readl(dwc->regs, DWC3_DSTS); reg = dwc3_readl(dwc->regs, DWC3_DSTS);
speed = reg & DWC3_DSTS_CONNECTSPD; speed = reg & DWC3_DSTS_CONNECTSPD;
dwc->speed = speed; dwc->speed = speed;
@ -2415,8 +2320,6 @@ 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)
{ {
dev_vdbg(dwc->dev, "%s\n", __func__);
/* /*
* TODO take core out of low power mode when that's * TODO take core out of low power mode when that's
* implemented. * implemented.
@ -2521,10 +2424,6 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,
break; break;
} }
dev_vdbg(dwc->dev, "link change: %s [%d] -> %s [%d]\n",
dwc3_gadget_link_string(dwc->link_state),
dwc->link_state, dwc3_gadget_link_string(next), next);
dwc->link_state = next; dwc->link_state = next;
} }
@ -2601,6 +2500,8 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc,
static void dwc3_process_event_entry(struct dwc3 *dwc, static void dwc3_process_event_entry(struct dwc3 *dwc,
const union dwc3_event *event) const union dwc3_event *event)
{ {
trace_dwc3_event(event->raw);
/* Endpoint IRQ, handle it and return early */ /* Endpoint IRQ, handle it and return early */
if (event->type.is_devspec == 0) { if (event->type.is_devspec == 0) {
/* depevt */ /* depevt */
@ -2754,7 +2655,6 @@ int dwc3_gadget_init(struct dwc3 *dwc)
dwc->setup_buf = kzalloc(DWC3_EP0_BOUNCE_SIZE, GFP_KERNEL); dwc->setup_buf = kzalloc(DWC3_EP0_BOUNCE_SIZE, GFP_KERNEL);
if (!dwc->setup_buf) { if (!dwc->setup_buf) {
dev_err(dwc->dev, "failed to allocate setup buffer\n");
ret = -ENOMEM; ret = -ENOMEM;
goto err2; goto err2;
} }

View File

@ -103,60 +103,4 @@ static inline u32 dwc3_gadget_ep_get_transfer_index(struct dwc3 *dwc, u8 number)
return DWC3_DEPCMD_GET_RSC_IDX(res_id); return DWC3_DEPCMD_GET_RSC_IDX(res_id);
} }
/**
* dwc3_gadget_event_string - returns event name
* @event: the event code
*/
static inline const char *dwc3_gadget_event_string(u8 event)
{
switch (event) {
case DWC3_DEVICE_EVENT_DISCONNECT:
return "Disconnect";
case DWC3_DEVICE_EVENT_RESET:
return "Reset";
case DWC3_DEVICE_EVENT_CONNECT_DONE:
return "Connection Done";
case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE:
return "Link Status Change";
case DWC3_DEVICE_EVENT_WAKEUP:
return "WakeUp";
case DWC3_DEVICE_EVENT_EOPF:
return "End-Of-Frame";
case DWC3_DEVICE_EVENT_SOF:
return "Start-Of-Frame";
case DWC3_DEVICE_EVENT_ERRATIC_ERROR:
return "Erratic Error";
case DWC3_DEVICE_EVENT_CMD_CMPL:
return "Command Complete";
case DWC3_DEVICE_EVENT_OVERFLOW:
return "Overflow";
}
return "UNKNOWN";
}
/**
* dwc3_ep_event_string - returns event name
* @event: then event code
*/
static inline const char *dwc3_ep_event_string(u8 event)
{
switch (event) {
case DWC3_DEPEVT_XFERCOMPLETE:
return "Transfer Complete";
case DWC3_DEPEVT_XFERINPROGRESS:
return "Transfer In-Progress";
case DWC3_DEPEVT_XFERNOTREADY:
return "Transfer Not Ready";
case DWC3_DEPEVT_RXTXFIFOEVT:
return "FIFO";
case DWC3_DEPEVT_STREAMEVT:
return "Stream";
case DWC3_DEPEVT_EPCMDCMPLT:
return "Endpoint Command Complete";
}
return "UNKNOWN";
}
#endif /* __DRIVERS_USB_DWC3_GADGET_H */ #endif /* __DRIVERS_USB_DWC3_GADGET_H */

View File

@ -20,27 +20,51 @@
#define __DRIVERS_USB_DWC3_IO_H #define __DRIVERS_USB_DWC3_IO_H
#include <linux/io.h> #include <linux/io.h>
#include "trace.h"
#include "debug.h"
#include "core.h" #include "core.h"
static inline u32 dwc3_readl(void __iomem *base, u32 offset) static inline u32 dwc3_readl(void __iomem *base, u32 offset)
{ {
u32 offs = offset - DWC3_GLOBALS_REGS_START;
u32 value;
/* /*
* We requested the mem region starting from the Globals address * We requested the mem region starting from the Globals address
* space, see dwc3_probe in core.c. * space, see dwc3_probe in core.c.
* However, the offsets are given starting from xHCI address space. * However, the offsets are given starting from xHCI address space.
*/ */
return readl(base + (offset - DWC3_GLOBALS_REGS_START)); value = readl(base + offs);
/*
* When tracing we want to make it easy to find the correct address on
* documentation, so we revert it back to the proper addresses, the
* same way they are described on SNPS documentation
*/
dwc3_trace(trace_dwc3_readl, "addr %p value %08x",
base - DWC3_GLOBALS_REGS_START + offset, value);
return value;
} }
static inline void dwc3_writel(void __iomem *base, u32 offset, u32 value) static inline void dwc3_writel(void __iomem *base, u32 offset, u32 value)
{ {
u32 offs = offset - DWC3_GLOBALS_REGS_START;
/* /*
* We requested the mem region starting from the Globals address * We requested the mem region starting from the Globals address
* space, see dwc3_probe in core.c. * space, see dwc3_probe in core.c.
* However, the offsets are given starting from xHCI address space. * However, the offsets are given starting from xHCI address space.
*/ */
writel(value, base + (offset - DWC3_GLOBALS_REGS_START)); writel(value, base + offs);
/*
* When tracing we want to make it easy to find the correct address on
* documentation, so we revert it back to the proper addresses, the
* same way they are described on SNPS documentation
*/
dwc3_trace(trace_dwc3_writel, "addr %p value %08x",
base - DWC3_GLOBALS_REGS_START + offset, value);
} }
#endif /* __DRIVERS_USB_DWC3_IO_H */ #endif /* __DRIVERS_USB_DWC3_IO_H */

19
drivers/usb/dwc3/trace.c Normal file
View File

@ -0,0 +1,19 @@
/**
* trace.c - DesignWare USB3 DRD Controller Trace Support
*
* Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com
*
* Author: Felipe Balbi <balbi@ti.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 of
* the License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define CREATE_TRACE_POINTS
#include "trace.h"

220
drivers/usb/dwc3/trace.h Normal file
View File

@ -0,0 +1,220 @@
/**
* trace.h - DesignWare USB3 DRD Controller Trace Support
*
* Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com
*
* Author: Felipe Balbi <balbi@ti.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 of
* the License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM dwc3
#if !defined(__DWC3_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define __DWC3_TRACE_H
#include <linux/types.h>
#include <linux/tracepoint.h>
#include <asm/byteorder.h>
#include "core.h"
#include "debug.h"
DECLARE_EVENT_CLASS(dwc3_log_msg,
TP_PROTO(struct va_format *vaf),
TP_ARGS(vaf),
TP_STRUCT__entry(__dynamic_array(char, msg, DWC3_MSG_MAX)),
TP_fast_assign(
vsnprintf(__get_str(msg), DWC3_MSG_MAX, vaf->fmt, *vaf->va);
),
TP_printk("%s", __get_str(msg))
);
DEFINE_EVENT(dwc3_log_msg, dwc3_readl,
TP_PROTO(struct va_format *vaf),
TP_ARGS(vaf)
);
DEFINE_EVENT(dwc3_log_msg, dwc3_writel,
TP_PROTO(struct va_format *vaf),
TP_ARGS(vaf)
);
DEFINE_EVENT(dwc3_log_msg, dwc3_ep0,
TP_PROTO(struct va_format *vaf),
TP_ARGS(vaf)
);
DECLARE_EVENT_CLASS(dwc3_log_event,
TP_PROTO(u32 event),
TP_ARGS(event),
TP_STRUCT__entry(
__field(u32, event)
),
TP_fast_assign(
__entry->event = event;
),
TP_printk("event %08x\n", __entry->event)
);
DEFINE_EVENT(dwc3_log_event, dwc3_event,
TP_PROTO(u32 event),
TP_ARGS(event)
);
DECLARE_EVENT_CLASS(dwc3_log_ctrl,
TP_PROTO(struct usb_ctrlrequest *ctrl),
TP_ARGS(ctrl),
TP_STRUCT__entry(
__field(struct usb_ctrlrequest *, ctrl)
),
TP_fast_assign(
__entry->ctrl = ctrl;
),
TP_printk("bRequestType %02x bRequest %02x wValue %04x wIndex %04x wLength %d",
__entry->ctrl->bRequestType, __entry->ctrl->bRequest,
le16_to_cpu(__entry->ctrl->wValue), le16_to_cpu(__entry->ctrl->wIndex),
le16_to_cpu(__entry->ctrl->wLength)
)
);
DEFINE_EVENT(dwc3_log_ctrl, dwc3_ctrl_req,
TP_PROTO(struct usb_ctrlrequest *ctrl),
TP_ARGS(ctrl)
);
DECLARE_EVENT_CLASS(dwc3_log_request,
TP_PROTO(struct dwc3_request *req),
TP_ARGS(req),
TP_STRUCT__entry(
__field(struct dwc3_request *, req)
),
TP_fast_assign(
__entry->req = req;
),
TP_printk("%s: req %p length %u/%u ==> %d",
__entry->req->dep->name, __entry->req,
__entry->req->request.actual, __entry->req->request.length,
__entry->req->request.status
)
);
DEFINE_EVENT(dwc3_log_request, dwc3_alloc_request,
TP_PROTO(struct dwc3_request *req),
TP_ARGS(req)
);
DEFINE_EVENT(dwc3_log_request, dwc3_free_request,
TP_PROTO(struct dwc3_request *req),
TP_ARGS(req)
);
DEFINE_EVENT(dwc3_log_request, dwc3_ep_queue,
TP_PROTO(struct dwc3_request *req),
TP_ARGS(req)
);
DEFINE_EVENT(dwc3_log_request, dwc3_ep_dequeue,
TP_PROTO(struct dwc3_request *req),
TP_ARGS(req)
);
DEFINE_EVENT(dwc3_log_request, dwc3_gadget_giveback,
TP_PROTO(struct dwc3_request *req),
TP_ARGS(req)
);
DECLARE_EVENT_CLASS(dwc3_log_generic_cmd,
TP_PROTO(unsigned int cmd, u32 param),
TP_ARGS(cmd, param),
TP_STRUCT__entry(
__field(unsigned int, cmd)
__field(u32, param)
),
TP_fast_assign(
__entry->cmd = cmd;
__entry->param = param;
),
TP_printk("cmd '%s' [%d] param %08x\n",
dwc3_gadget_generic_cmd_string(__entry->cmd),
__entry->cmd, __entry->param
)
);
DEFINE_EVENT(dwc3_log_generic_cmd, dwc3_gadget_generic_cmd,
TP_PROTO(unsigned int cmd, u32 param),
TP_ARGS(cmd, param)
);
DECLARE_EVENT_CLASS(dwc3_log_gadget_ep_cmd,
TP_PROTO(struct dwc3_ep *dep, unsigned int cmd,
struct dwc3_gadget_ep_cmd_params *params),
TP_ARGS(dep, cmd, params),
TP_STRUCT__entry(
__field(struct dwc3_ep *, dep)
__field(unsigned int, cmd)
__field(struct dwc3_gadget_ep_cmd_params *, params)
),
TP_fast_assign(
__entry->dep = dep;
__entry->cmd = cmd;
__entry->params = params;
),
TP_printk("%s: cmd '%s' [%d] params %08x %08x %08x\n",
__entry->dep->name, dwc3_gadget_ep_cmd_string(__entry->cmd),
__entry->cmd, __entry->params->param0,
__entry->params->param1, __entry->params->param2
)
);
DEFINE_EVENT(dwc3_log_gadget_ep_cmd, dwc3_gadget_ep_cmd,
TP_PROTO(struct dwc3_ep *dep, unsigned int cmd,
struct dwc3_gadget_ep_cmd_params *params),
TP_ARGS(dep, cmd, params)
);
DECLARE_EVENT_CLASS(dwc3_log_trb,
TP_PROTO(struct dwc3_ep *dep, struct dwc3_trb *trb),
TP_ARGS(dep, trb),
TP_STRUCT__entry(
__field(struct dwc3_ep *, dep)
__field(struct dwc3_trb *, trb)
),
TP_fast_assign(
__entry->dep = dep;
__entry->trb = trb;
),
TP_printk("%s: trb %p bph %08x bpl %08x size %08x ctrl %08x\n",
__entry->dep->name, __entry->trb, __entry->trb->bph,
__entry->trb->bpl, __entry->trb->size, __entry->trb->ctrl
)
);
DEFINE_EVENT(dwc3_log_trb, dwc3_prepare_trb,
TP_PROTO(struct dwc3_ep *dep, struct dwc3_trb *trb),
TP_ARGS(dep, trb)
);
DEFINE_EVENT(dwc3_log_trb, dwc3_complete_trb,
TP_PROTO(struct dwc3_ep *dep, struct dwc3_trb *trb),
TP_ARGS(dep, trb)
);
#endif /* __DWC3_TRACE_H */
/* this part has to be here */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace
#include <trace/define_trace.h>

View File

@ -181,6 +181,15 @@ config USB_F_MASS_STORAGE
config USB_F_FS config USB_F_FS
tristate tristate
config USB_F_UAC1
tristate
config USB_F_UAC2
tristate
config USB_F_UVC
tristate
choice choice
tristate "USB Gadget Drivers" tristate "USB Gadget Drivers"
default USB_ETH default USB_ETH

View File

@ -3,7 +3,7 @@
# #
subdir-ccflags-$(CONFIG_USB_GADGET_DEBUG) := -DDEBUG subdir-ccflags-$(CONFIG_USB_GADGET_DEBUG) := -DDEBUG
subdir-ccflags-$(CONFIG_USB_GADGET_VERBOSE) += -DVERBOSE_DEBUG subdir-ccflags-$(CONFIG_USB_GADGET_VERBOSE) += -DVERBOSE_DEBUG
ccflags-y += -Idrivers/usb/gadget/udc ccflags-y += -I$(srctree)/drivers/usb/gadget/udc
obj-$(CONFIG_USB_LIBCOMPOSITE) += libcomposite.o obj-$(CONFIG_USB_LIBCOMPOSITE) += libcomposite.o
libcomposite-y := usbstring.o config.o epautoconf.o libcomposite-y := usbstring.o config.o epautoconf.o

View File

@ -2073,6 +2073,7 @@ static const struct usb_gadget_driver composite_driver_template = {
.unbind = composite_unbind, .unbind = composite_unbind,
.setup = composite_setup, .setup = composite_setup,
.reset = composite_disconnect,
.disconnect = composite_disconnect, .disconnect = composite_disconnect,
.suspend = composite_suspend, .suspend = composite_suspend,

View File

@ -1450,6 +1450,7 @@ static const struct usb_gadget_driver configfs_driver_template = {
.unbind = configfs_composite_unbind, .unbind = configfs_composite_unbind,
.setup = composite_setup, .setup = composite_setup,
.reset = composite_disconnect,
.disconnect = composite_disconnect, .disconnect = composite_disconnect,
.max_speed = USB_SPEED_SUPER, .max_speed = USB_SPEED_SUPER,

View File

@ -2,8 +2,8 @@
# USB peripheral controller drivers # USB peripheral controller drivers
# #
ccflags-y := -Idrivers/usb/gadget/ ccflags-y := -I$(srctree)/drivers/usb/gadget/
ccflags-y += -Idrivers/usb/gadget/udc/ ccflags-y += -I$(srctree)/drivers/usb/gadget/udc/
# USB Functions # USB Functions
usb_f_acm-y := f_acm.o usb_f_acm-y := f_acm.o
@ -32,3 +32,9 @@ usb_f_mass_storage-y := f_mass_storage.o storage_common.o
obj-$(CONFIG_USB_F_MASS_STORAGE)+= usb_f_mass_storage.o obj-$(CONFIG_USB_F_MASS_STORAGE)+= usb_f_mass_storage.o
usb_f_fs-y := f_fs.o usb_f_fs-y := f_fs.o
obj-$(CONFIG_USB_F_FS) += usb_f_fs.o obj-$(CONFIG_USB_F_FS) += usb_f_fs.o
usb_f_uac1-y := f_uac1.o u_uac1.o
obj-$(CONFIG_USB_F_UAC1) += usb_f_uac1.o
usb_f_uac2-y := f_uac2.o
obj-$(CONFIG_USB_F_UAC2) += usb_f_uac2.o
usb_f_uvc-y := f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o
obj-$(CONFIG_USB_F_UVC) += usb_f_uvc.o

View File

@ -313,15 +313,15 @@ static void acm_complete_set_line_coding(struct usb_ep *ep,
struct usb_composite_dev *cdev = acm->port.func.config->cdev; struct usb_composite_dev *cdev = acm->port.func.config->cdev;
if (req->status != 0) { if (req->status != 0) {
DBG(cdev, "acm ttyGS%d completion, err %d\n", dev_dbg(&cdev->gadget->dev, "acm ttyGS%d completion, err %d\n",
acm->port_num, req->status); acm->port_num, req->status);
return; return;
} }
/* normal completion */ /* normal completion */
if (req->actual != sizeof(acm->port_line_coding)) { if (req->actual != sizeof(acm->port_line_coding)) {
DBG(cdev, "acm ttyGS%d short resp, len %d\n", dev_dbg(&cdev->gadget->dev, "acm ttyGS%d short resp, len %d\n",
acm->port_num, req->actual); acm->port_num, req->actual);
usb_ep_set_halt(ep); usb_ep_set_halt(ep);
} else { } else {
struct usb_cdc_line_coding *value = req->buf; struct usb_cdc_line_coding *value = req->buf;
@ -397,14 +397,16 @@ static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
default: default:
invalid: invalid:
VDBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", dev_vdbg(&cdev->gadget->dev,
ctrl->bRequestType, ctrl->bRequest, "invalid control req%02x.%02x v%04x i%04x l%d\n",
w_value, w_index, w_length); ctrl->bRequestType, ctrl->bRequest,
w_value, w_index, w_length);
} }
/* respond with data transfer or status phase? */ /* respond with data transfer or status phase? */
if (value >= 0) { if (value >= 0) {
DBG(cdev, "acm ttyGS%d req%02x.%02x v%04x i%04x l%d\n", dev_dbg(&cdev->gadget->dev,
"acm ttyGS%d req%02x.%02x v%04x i%04x l%d\n",
acm->port_num, ctrl->bRequestType, ctrl->bRequest, acm->port_num, ctrl->bRequestType, ctrl->bRequest,
w_value, w_index, w_length); w_value, w_index, w_length);
req->zero = 0; req->zero = 0;
@ -428,10 +430,12 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
if (intf == acm->ctrl_id) { if (intf == acm->ctrl_id) {
if (acm->notify->driver_data) { if (acm->notify->driver_data) {
VDBG(cdev, "reset acm control interface %d\n", intf); dev_vdbg(&cdev->gadget->dev,
"reset acm control interface %d\n", intf);
usb_ep_disable(acm->notify); usb_ep_disable(acm->notify);
} else { } else {
VDBG(cdev, "init acm ctrl interface %d\n", intf); dev_vdbg(&cdev->gadget->dev,
"init acm ctrl interface %d\n", intf);
if (config_ep_by_speed(cdev->gadget, f, acm->notify)) if (config_ep_by_speed(cdev->gadget, f, acm->notify))
return -EINVAL; return -EINVAL;
} }
@ -440,11 +444,13 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
} else if (intf == acm->data_id) { } else if (intf == acm->data_id) {
if (acm->port.in->driver_data) { if (acm->port.in->driver_data) {
DBG(cdev, "reset acm ttyGS%d\n", acm->port_num); dev_dbg(&cdev->gadget->dev,
"reset acm ttyGS%d\n", acm->port_num);
gserial_disconnect(&acm->port); gserial_disconnect(&acm->port);
} }
if (!acm->port.in->desc || !acm->port.out->desc) { if (!acm->port.in->desc || !acm->port.out->desc) {
DBG(cdev, "activate acm ttyGS%d\n", acm->port_num); dev_dbg(&cdev->gadget->dev,
"activate acm ttyGS%d\n", acm->port_num);
if (config_ep_by_speed(cdev->gadget, f, if (config_ep_by_speed(cdev->gadget, f,
acm->port.in) || acm->port.in) ||
config_ep_by_speed(cdev->gadget, f, config_ep_by_speed(cdev->gadget, f,
@ -467,7 +473,7 @@ static void acm_disable(struct usb_function *f)
struct f_acm *acm = func_to_acm(f); struct f_acm *acm = func_to_acm(f);
struct usb_composite_dev *cdev = f->config->cdev; struct usb_composite_dev *cdev = f->config->cdev;
DBG(cdev, "acm ttyGS%d deactivated\n", acm->port_num); dev_dbg(&cdev->gadget->dev, "acm ttyGS%d deactivated\n", acm->port_num);
gserial_disconnect(&acm->port); gserial_disconnect(&acm->port);
usb_ep_disable(acm->notify); usb_ep_disable(acm->notify);
acm->notify->driver_data = NULL; acm->notify->driver_data = NULL;
@ -537,8 +543,8 @@ static int acm_notify_serial_state(struct f_acm *acm)
spin_lock(&acm->lock); spin_lock(&acm->lock);
if (acm->notify_req) { if (acm->notify_req) {
DBG(cdev, "acm ttyGS%d serial state %04x\n", dev_dbg(&cdev->gadget->dev, "acm ttyGS%d serial state %04x\n",
acm->port_num, acm->serial_state); acm->port_num, acm->serial_state);
status = acm_cdc_notify(acm, USB_CDC_NOTIFY_SERIAL_STATE, status = acm_cdc_notify(acm, USB_CDC_NOTIFY_SERIAL_STATE,
0, &acm->serial_state, sizeof(acm->serial_state)); 0, &acm->serial_state, sizeof(acm->serial_state));
} else { } else {
@ -691,12 +697,13 @@ acm_bind(struct usb_configuration *c, struct usb_function *f)
if (status) if (status)
goto fail; goto fail;
DBG(cdev, "acm ttyGS%d: %s speed IN/%s OUT/%s NOTIFY/%s\n", dev_dbg(&cdev->gadget->dev,
acm->port_num, "acm ttyGS%d: %s speed IN/%s OUT/%s NOTIFY/%s\n",
gadget_is_superspeed(c->cdev->gadget) ? "super" : acm->port_num,
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", gadget_is_superspeed(c->cdev->gadget) ? "super" :
acm->port.in->name, acm->port.out->name, gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
acm->notify->name); acm->port.in->name, acm->port.out->name,
acm->notify->name);
return 0; return 0;
fail: fail:

View File

@ -1032,6 +1032,29 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
case FUNCTIONFS_ENDPOINT_REVMAP: case FUNCTIONFS_ENDPOINT_REVMAP:
ret = epfile->ep->num; ret = epfile->ep->num;
break; break;
case FUNCTIONFS_ENDPOINT_DESC:
{
int desc_idx;
struct usb_endpoint_descriptor *desc;
switch (epfile->ffs->gadget->speed) {
case USB_SPEED_SUPER:
desc_idx = 2;
break;
case USB_SPEED_HIGH:
desc_idx = 1;
break;
default:
desc_idx = 0;
}
desc = epfile->ep->descs[desc_idx];
spin_unlock_irq(&epfile->ffs->eps_lock);
ret = copy_to_user((void *)value, desc, sizeof(*desc));
if (ret)
ret = -EFAULT;
return ret;
}
default: default:
ret = -ENOTTY; ret = -ENOTTY;
} }
@ -1534,7 +1557,10 @@ static int ffs_epfiles_create(struct ffs_data *ffs)
epfile->ffs = ffs; epfile->ffs = ffs;
mutex_init(&epfile->mutex); mutex_init(&epfile->mutex);
init_waitqueue_head(&epfile->wait); init_waitqueue_head(&epfile->wait);
sprintf(epfiles->name, "ep%u", i); if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)
sprintf(epfiles->name, "ep%02x", ffs->eps_addrmap[i]);
else
sprintf(epfiles->name, "ep%u", i);
if (!unlikely(ffs_sb_create_file(ffs->sb, epfiles->name, epfile, if (!unlikely(ffs_sb_create_file(ffs->sb, epfiles->name, epfile,
&ffs_epfile_operations, &ffs_epfile_operations,
&epfile->dentry))) { &epfile->dentry))) {
@ -2083,10 +2109,12 @@ static int __ffs_data_got_descs(struct ffs_data *ffs,
break; break;
case FUNCTIONFS_DESCRIPTORS_MAGIC_V2: case FUNCTIONFS_DESCRIPTORS_MAGIC_V2:
flags = get_unaligned_le32(data + 8); flags = get_unaligned_le32(data + 8);
ffs->user_flags = flags;
if (flags & ~(FUNCTIONFS_HAS_FS_DESC | if (flags & ~(FUNCTIONFS_HAS_FS_DESC |
FUNCTIONFS_HAS_HS_DESC | FUNCTIONFS_HAS_HS_DESC |
FUNCTIONFS_HAS_SS_DESC | FUNCTIONFS_HAS_SS_DESC |
FUNCTIONFS_HAS_MS_OS_DESC)) { FUNCTIONFS_HAS_MS_OS_DESC |
FUNCTIONFS_VIRTUAL_ADDR)) {
ret = -ENOSYS; ret = -ENOSYS;
goto error; goto error;
} }
@ -2346,7 +2374,8 @@ static void __ffs_event_add(struct ffs_data *ffs,
break; break;
default: default:
BUG(); WARN(1, "%d: unknown event, this should not happen\n", type);
return;
} }
{ {
@ -2393,7 +2422,8 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep,
struct usb_endpoint_descriptor *ds = (void *)desc; struct usb_endpoint_descriptor *ds = (void *)desc;
struct ffs_function *func = priv; struct ffs_function *func = priv;
struct ffs_ep *ffs_ep; struct ffs_ep *ffs_ep;
unsigned ep_desc_id, idx; unsigned ep_desc_id;
int idx;
static const char *speed_names[] = { "full", "high", "super" }; static const char *speed_names[] = { "full", "high", "super" };
if (type != FFS_DESCRIPTOR) if (type != FFS_DESCRIPTOR)
@ -2441,7 +2471,13 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep,
} else { } else {
struct usb_request *req; struct usb_request *req;
struct usb_ep *ep; struct usb_ep *ep;
u8 bEndpointAddress;
/*
* We back up bEndpointAddress because autoconfig overwrites
* it with physical endpoint address.
*/
bEndpointAddress = ds->bEndpointAddress;
pr_vdebug("autoconfig\n"); pr_vdebug("autoconfig\n");
ep = usb_ep_autoconfig(func->gadget, ds); ep = usb_ep_autoconfig(func->gadget, ds);
if (unlikely(!ep)) if (unlikely(!ep))
@ -2456,6 +2492,12 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep,
ffs_ep->req = req; ffs_ep->req = req;
func->eps_revmap[ds->bEndpointAddress & func->eps_revmap[ds->bEndpointAddress &
USB_ENDPOINT_NUMBER_MASK] = idx + 1; USB_ENDPOINT_NUMBER_MASK] = idx + 1;
/*
* If we use virtual address mapping, we restore
* original bEndpointAddress value.
*/
if (func->ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)
ds->bEndpointAddress = bEndpointAddress;
} }
ffs_dump_mem(": Rewritten ep desc", ds, ds->bLength); ffs_dump_mem(": Rewritten ep desc", ds, ds->bLength);
@ -2900,6 +2942,8 @@ static int ffs_func_setup(struct usb_function *f,
ret = ffs_func_revmap_ep(func, le16_to_cpu(creq->wIndex)); ret = ffs_func_revmap_ep(func, le16_to_cpu(creq->wIndex));
if (unlikely(ret < 0)) if (unlikely(ret < 0))
return ret; return ret;
if (func->ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)
ret = func->ffs->eps_addrmap[ret];
break; break;
default: default:

View File

@ -298,7 +298,8 @@ static void disable_loopback(struct f_loopback *loop)
struct usb_composite_dev *cdev; struct usb_composite_dev *cdev;
cdev = loop->function.config->cdev; cdev = loop->function.config->cdev;
disable_endpoints(cdev, loop->in_ep, loop->out_ep, NULL, NULL); disable_endpoints(cdev, loop->in_ep, loop->out_ep, NULL, NULL, NULL,
NULL);
VDBG(cdev, "%s disabled\n", loop->function.name); VDBG(cdev, "%s disabled\n", loop->function.name);
} }

View File

@ -566,22 +566,22 @@ static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
*pbusy = 1; *pbusy = 1;
*state = BUF_STATE_BUSY; *state = BUF_STATE_BUSY;
spin_unlock_irq(&fsg->common->lock); spin_unlock_irq(&fsg->common->lock);
rc = usb_ep_queue(ep, req, GFP_KERNEL); rc = usb_ep_queue(ep, req, GFP_KERNEL);
if (rc != 0) { if (rc == 0)
*pbusy = 0; return; /* All good, we're done */
*state = BUF_STATE_EMPTY;
/* We can't do much more than wait for a reset */ *pbusy = 0;
*state = BUF_STATE_EMPTY;
/* /* We can't do much more than wait for a reset */
* Note: currently the net2280 driver fails zero-length
* submissions if DMA is enabled. /*
*/ * Note: currently the net2280 driver fails zero-length
if (rc != -ESHUTDOWN && * submissions if DMA is enabled.
!(rc == -EOPNOTSUPP && req->length == 0)) */
WARNING(fsg, "error in submission: %s --> %d\n", if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP && req->length == 0))
ep->name, rc); WARNING(fsg, "error in submission: %s --> %d\n", ep->name, rc);
}
} }
static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh) static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh)
@ -3665,4 +3665,3 @@ void fsg_config_from_params(struct fsg_config *cfg,
cfg->fsg_num_buffers = fsg_num_buffers; cfg->fsg_num_buffers = fsg_num_buffers;
} }
EXPORT_SYMBOL_GPL(fsg_config_from_params); EXPORT_SYMBOL_GPL(fsg_config_from_params);

View File

@ -200,19 +200,22 @@ static int obex_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
if (alt != 0) if (alt != 0)
goto fail; goto fail;
/* NOP */ /* NOP */
DBG(cdev, "reset obex ttyGS%d control\n", obex->port_num); dev_dbg(&cdev->gadget->dev,
"reset obex ttyGS%d control\n", obex->port_num);
} else if (intf == obex->data_id) { } else if (intf == obex->data_id) {
if (alt > 1) if (alt > 1)
goto fail; goto fail;
if (obex->port.in->driver_data) { if (obex->port.in->driver_data) {
DBG(cdev, "reset obex ttyGS%d\n", obex->port_num); dev_dbg(&cdev->gadget->dev,
"reset obex ttyGS%d\n", obex->port_num);
gserial_disconnect(&obex->port); gserial_disconnect(&obex->port);
} }
if (!obex->port.in->desc || !obex->port.out->desc) { if (!obex->port.in->desc || !obex->port.out->desc) {
DBG(cdev, "init obex ttyGS%d\n", obex->port_num); dev_dbg(&cdev->gadget->dev,
"init obex ttyGS%d\n", obex->port_num);
if (config_ep_by_speed(cdev->gadget, f, if (config_ep_by_speed(cdev->gadget, f,
obex->port.in) || obex->port.in) ||
config_ep_by_speed(cdev->gadget, f, config_ep_by_speed(cdev->gadget, f,
@ -224,7 +227,8 @@ static int obex_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
} }
if (alt == 1) { if (alt == 1) {
DBG(cdev, "activate obex ttyGS%d\n", obex->port_num); dev_dbg(&cdev->gadget->dev,
"activate obex ttyGS%d\n", obex->port_num);
gserial_connect(&obex->port, obex->port_num); gserial_connect(&obex->port, obex->port_num);
} }
@ -252,7 +256,7 @@ static void obex_disable(struct usb_function *f)
struct f_obex *obex = func_to_obex(f); struct f_obex *obex = func_to_obex(f);
struct usb_composite_dev *cdev = f->config->cdev; struct usb_composite_dev *cdev = f->config->cdev;
DBG(cdev, "obex ttyGS%d disable\n", obex->port_num); dev_dbg(&cdev->gadget->dev, "obex ttyGS%d disable\n", obex->port_num);
gserial_disconnect(&obex->port); gserial_disconnect(&obex->port);
} }
@ -269,7 +273,8 @@ static void obex_connect(struct gserial *g)
status = usb_function_activate(&g->func); status = usb_function_activate(&g->func);
if (status) if (status)
DBG(cdev, "obex ttyGS%d function activate --> %d\n", dev_dbg(&cdev->gadget->dev,
"obex ttyGS%d function activate --> %d\n",
obex->port_num, status); obex->port_num, status);
} }
@ -284,7 +289,8 @@ static void obex_disconnect(struct gserial *g)
status = usb_function_deactivate(&g->func); status = usb_function_deactivate(&g->func);
if (status) if (status)
DBG(cdev, "obex ttyGS%d function deactivate --> %d\n", dev_dbg(&cdev->gadget->dev,
"obex ttyGS%d function deactivate --> %d\n",
obex->port_num, status); obex->port_num, status);
} }
@ -383,10 +389,10 @@ static int obex_bind(struct usb_configuration *c, struct usb_function *f)
obex->can_activate = true; obex->can_activate = true;
DBG(cdev, "obex ttyGS%d: %s speed IN/%s OUT/%s\n", dev_dbg(&cdev->gadget->dev, "obex ttyGS%d: %s speed IN/%s OUT/%s\n",
obex->port_num, obex->port_num,
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
obex->port.in->name, obex->port.out->name); obex->port.in->name, obex->port.out->name);
return 0; return 0;

View File

@ -155,11 +155,13 @@ static int gser_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
/* we know alt == 0, so this is an activation or a reset */ /* we know alt == 0, so this is an activation or a reset */
if (gser->port.in->driver_data) { if (gser->port.in->driver_data) {
DBG(cdev, "reset generic ttyGS%d\n", gser->port_num); dev_dbg(&cdev->gadget->dev,
"reset generic ttyGS%d\n", gser->port_num);
gserial_disconnect(&gser->port); gserial_disconnect(&gser->port);
} }
if (!gser->port.in->desc || !gser->port.out->desc) { if (!gser->port.in->desc || !gser->port.out->desc) {
DBG(cdev, "activate generic ttyGS%d\n", gser->port_num); dev_dbg(&cdev->gadget->dev,
"activate generic ttyGS%d\n", gser->port_num);
if (config_ep_by_speed(cdev->gadget, f, gser->port.in) || if (config_ep_by_speed(cdev->gadget, f, gser->port.in) ||
config_ep_by_speed(cdev->gadget, f, gser->port.out)) { config_ep_by_speed(cdev->gadget, f, gser->port.out)) {
gser->port.in->desc = NULL; gser->port.in->desc = NULL;
@ -176,7 +178,8 @@ static void gser_disable(struct usb_function *f)
struct f_gser *gser = func_to_gser(f); struct f_gser *gser = func_to_gser(f);
struct usb_composite_dev *cdev = f->config->cdev; struct usb_composite_dev *cdev = f->config->cdev;
DBG(cdev, "generic ttyGS%d deactivated\n", gser->port_num); dev_dbg(&cdev->gadget->dev,
"generic ttyGS%d deactivated\n", gser->port_num);
gserial_disconnect(&gser->port); gserial_disconnect(&gser->port);
} }
@ -239,11 +242,11 @@ static int gser_bind(struct usb_configuration *c, struct usb_function *f)
gser_ss_function); gser_ss_function);
if (status) if (status)
goto fail; goto fail;
DBG(cdev, "generic ttyGS%d: %s speed IN/%s OUT/%s\n", dev_dbg(&cdev->gadget->dev, "generic ttyGS%d: %s speed IN/%s OUT/%s\n",
gser->port_num, gser->port_num,
gadget_is_superspeed(c->cdev->gadget) ? "super" : gadget_is_superspeed(c->cdev->gadget) ? "super" :
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
gser->port.in->name, gser->port.out->name); gser->port.in->name, gser->port.out->name);
return 0; return 0;
fail: fail:

View File

@ -23,6 +23,15 @@
#include "gadget_chips.h" #include "gadget_chips.h"
#include "u_f.h" #include "u_f.h"
#define USB_MS_TO_SS_INTERVAL(x) USB_MS_TO_HS_INTERVAL(x)
enum eptype {
EP_CONTROL = 0,
EP_BULK,
EP_ISOC,
EP_INTERRUPT,
};
/* /*
* SOURCE/SINK FUNCTION ... a primary testing vehicle for USB peripheral * SOURCE/SINK FUNCTION ... a primary testing vehicle for USB peripheral
* controller drivers. * controller drivers.
@ -55,6 +64,8 @@ struct f_sourcesink {
struct usb_ep *out_ep; struct usb_ep *out_ep;
struct usb_ep *iso_in_ep; struct usb_ep *iso_in_ep;
struct usb_ep *iso_out_ep; struct usb_ep *iso_out_ep;
struct usb_ep *int_in_ep;
struct usb_ep *int_out_ep;
int cur_alt; int cur_alt;
}; };
@ -68,6 +79,10 @@ static unsigned isoc_interval;
static unsigned isoc_maxpacket; static unsigned isoc_maxpacket;
static unsigned isoc_mult; static unsigned isoc_mult;
static unsigned isoc_maxburst; static unsigned isoc_maxburst;
static unsigned int_interval; /* In ms */
static unsigned int_maxpacket;
static unsigned int_mult;
static unsigned int_maxburst;
static unsigned buflen; static unsigned buflen;
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
@ -92,6 +107,16 @@ static struct usb_interface_descriptor source_sink_intf_alt1 = {
/* .iInterface = DYNAMIC */ /* .iInterface = DYNAMIC */
}; };
static struct usb_interface_descriptor source_sink_intf_alt2 = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bAlternateSetting = 2,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
/* .iInterface = DYNAMIC */
};
/* full speed support: */ /* full speed support: */
static struct usb_endpoint_descriptor fs_source_desc = { static struct usb_endpoint_descriptor fs_source_desc = {
@ -130,6 +155,26 @@ static struct usb_endpoint_descriptor fs_iso_sink_desc = {
.bInterval = 4, .bInterval = 4,
}; };
static struct usb_endpoint_descriptor fs_int_source_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = cpu_to_le16(64),
.bInterval = GZERO_INT_INTERVAL,
};
static struct usb_endpoint_descriptor fs_int_sink_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_OUT,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = cpu_to_le16(64),
.bInterval = GZERO_INT_INTERVAL,
};
static struct usb_descriptor_header *fs_source_sink_descs[] = { static struct usb_descriptor_header *fs_source_sink_descs[] = {
(struct usb_descriptor_header *) &source_sink_intf_alt0, (struct usb_descriptor_header *) &source_sink_intf_alt0,
(struct usb_descriptor_header *) &fs_sink_desc, (struct usb_descriptor_header *) &fs_sink_desc,
@ -140,6 +185,10 @@ static struct usb_descriptor_header *fs_source_sink_descs[] = {
(struct usb_descriptor_header *) &fs_source_desc, (struct usb_descriptor_header *) &fs_source_desc,
(struct usb_descriptor_header *) &fs_iso_sink_desc, (struct usb_descriptor_header *) &fs_iso_sink_desc,
(struct usb_descriptor_header *) &fs_iso_source_desc, (struct usb_descriptor_header *) &fs_iso_source_desc,
(struct usb_descriptor_header *) &source_sink_intf_alt2,
#define FS_ALT_IFC_2_OFFSET 8
(struct usb_descriptor_header *) &fs_int_sink_desc,
(struct usb_descriptor_header *) &fs_int_source_desc,
NULL, NULL,
}; };
@ -179,6 +228,24 @@ static struct usb_endpoint_descriptor hs_iso_sink_desc = {
.bInterval = 4, .bInterval = 4,
}; };
static struct usb_endpoint_descriptor hs_int_source_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = cpu_to_le16(1024),
.bInterval = USB_MS_TO_HS_INTERVAL(GZERO_INT_INTERVAL),
};
static struct usb_endpoint_descriptor hs_int_sink_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = cpu_to_le16(1024),
.bInterval = USB_MS_TO_HS_INTERVAL(GZERO_INT_INTERVAL),
};
static struct usb_descriptor_header *hs_source_sink_descs[] = { static struct usb_descriptor_header *hs_source_sink_descs[] = {
(struct usb_descriptor_header *) &source_sink_intf_alt0, (struct usb_descriptor_header *) &source_sink_intf_alt0,
(struct usb_descriptor_header *) &hs_source_desc, (struct usb_descriptor_header *) &hs_source_desc,
@ -189,6 +256,10 @@ static struct usb_descriptor_header *hs_source_sink_descs[] = {
(struct usb_descriptor_header *) &hs_sink_desc, (struct usb_descriptor_header *) &hs_sink_desc,
(struct usb_descriptor_header *) &hs_iso_source_desc, (struct usb_descriptor_header *) &hs_iso_source_desc,
(struct usb_descriptor_header *) &hs_iso_sink_desc, (struct usb_descriptor_header *) &hs_iso_sink_desc,
(struct usb_descriptor_header *) &source_sink_intf_alt2,
#define HS_ALT_IFC_2_OFFSET 8
(struct usb_descriptor_header *) &hs_int_source_desc,
(struct usb_descriptor_header *) &hs_int_sink_desc,
NULL, NULL,
}; };
@ -264,6 +335,42 @@ static struct usb_ss_ep_comp_descriptor ss_iso_sink_comp_desc = {
.wBytesPerInterval = cpu_to_le16(1024), .wBytesPerInterval = cpu_to_le16(1024),
}; };
static struct usb_endpoint_descriptor ss_int_source_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = cpu_to_le16(1024),
.bInterval = USB_MS_TO_SS_INTERVAL(GZERO_INT_INTERVAL),
};
struct usb_ss_ep_comp_descriptor ss_int_source_comp_desc = {
.bLength = USB_DT_SS_EP_COMP_SIZE,
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
.bMaxBurst = 0,
.bmAttributes = 0,
.wBytesPerInterval = cpu_to_le16(1024),
};
static struct usb_endpoint_descriptor ss_int_sink_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = cpu_to_le16(1024),
.bInterval = USB_MS_TO_SS_INTERVAL(GZERO_INT_INTERVAL),
};
struct usb_ss_ep_comp_descriptor ss_int_sink_comp_desc = {
.bLength = USB_DT_SS_EP_COMP_SIZE,
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
.bMaxBurst = 0,
.bmAttributes = 0,
.wBytesPerInterval = cpu_to_le16(1024),
};
static struct usb_descriptor_header *ss_source_sink_descs[] = { static struct usb_descriptor_header *ss_source_sink_descs[] = {
(struct usb_descriptor_header *) &source_sink_intf_alt0, (struct usb_descriptor_header *) &source_sink_intf_alt0,
(struct usb_descriptor_header *) &ss_source_desc, (struct usb_descriptor_header *) &ss_source_desc,
@ -280,6 +387,12 @@ static struct usb_descriptor_header *ss_source_sink_descs[] = {
(struct usb_descriptor_header *) &ss_iso_source_comp_desc, (struct usb_descriptor_header *) &ss_iso_source_comp_desc,
(struct usb_descriptor_header *) &ss_iso_sink_desc, (struct usb_descriptor_header *) &ss_iso_sink_desc,
(struct usb_descriptor_header *) &ss_iso_sink_comp_desc, (struct usb_descriptor_header *) &ss_iso_sink_comp_desc,
(struct usb_descriptor_header *) &source_sink_intf_alt2,
#define SS_ALT_IFC_2_OFFSET 14
(struct usb_descriptor_header *) &ss_int_source_desc,
(struct usb_descriptor_header *) &ss_int_source_comp_desc,
(struct usb_descriptor_header *) &ss_int_sink_desc,
(struct usb_descriptor_header *) &ss_int_sink_comp_desc,
NULL, NULL,
}; };
@ -301,6 +414,21 @@ static struct usb_gadget_strings *sourcesink_strings[] = {
}; };
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
static const char *get_ep_string(enum eptype ep_type)
{
switch (ep_type) {
case EP_ISOC:
return "ISOC-";
case EP_INTERRUPT:
return "INTERRUPT-";
case EP_CONTROL:
return "CTRL-";
case EP_BULK:
return "BULK-";
default:
return "UNKNOWN-";
}
}
static inline struct usb_request *ss_alloc_ep_req(struct usb_ep *ep, int len) static inline struct usb_request *ss_alloc_ep_req(struct usb_ep *ep, int len)
{ {
@ -328,7 +456,8 @@ static void disable_ep(struct usb_composite_dev *cdev, struct usb_ep *ep)
void disable_endpoints(struct usb_composite_dev *cdev, void disable_endpoints(struct usb_composite_dev *cdev,
struct usb_ep *in, struct usb_ep *out, struct usb_ep *in, struct usb_ep *out,
struct usb_ep *iso_in, struct usb_ep *iso_out) struct usb_ep *iso_in, struct usb_ep *iso_out,
struct usb_ep *int_in, struct usb_ep *int_out)
{ {
disable_ep(cdev, in); disable_ep(cdev, in);
disable_ep(cdev, out); disable_ep(cdev, out);
@ -336,6 +465,10 @@ void disable_endpoints(struct usb_composite_dev *cdev,
disable_ep(cdev, iso_in); disable_ep(cdev, iso_in);
if (iso_out) if (iso_out)
disable_ep(cdev, iso_out); disable_ep(cdev, iso_out);
if (int_in)
disable_ep(cdev, int_in);
if (int_out)
disable_ep(cdev, int_out);
} }
static int static int
@ -352,6 +485,7 @@ sourcesink_bind(struct usb_configuration *c, struct usb_function *f)
return id; return id;
source_sink_intf_alt0.bInterfaceNumber = id; source_sink_intf_alt0.bInterfaceNumber = id;
source_sink_intf_alt1.bInterfaceNumber = id; source_sink_intf_alt1.bInterfaceNumber = id;
source_sink_intf_alt2.bInterfaceNumber = id;
/* allocate bulk endpoints */ /* allocate bulk endpoints */
ss->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_source_desc); ss->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_source_desc);
@ -412,14 +546,55 @@ no_iso:
if (isoc_maxpacket > 1024) if (isoc_maxpacket > 1024)
isoc_maxpacket = 1024; isoc_maxpacket = 1024;
/* sanity check the interrupt module parameters */
if (int_interval < 1)
int_interval = 1;
if (int_interval > 4096)
int_interval = 4096;
if (int_mult > 2)
int_mult = 2;
if (int_maxburst > 15)
int_maxburst = 15;
/* fill in the FS interrupt descriptors from the module parameters */
fs_int_source_desc.wMaxPacketSize = int_maxpacket > 64 ?
64 : int_maxpacket;
fs_int_source_desc.bInterval = int_interval > 255 ?
255 : int_interval;
fs_int_sink_desc.wMaxPacketSize = int_maxpacket > 64 ?
64 : int_maxpacket;
fs_int_sink_desc.bInterval = int_interval > 255 ?
255 : int_interval;
/* allocate int endpoints */
ss->int_in_ep = usb_ep_autoconfig(cdev->gadget, &fs_int_source_desc);
if (!ss->int_in_ep)
goto no_int;
ss->int_in_ep->driver_data = cdev; /* claim */
ss->int_out_ep = usb_ep_autoconfig(cdev->gadget, &fs_int_sink_desc);
if (ss->int_out_ep) {
ss->int_out_ep->driver_data = cdev; /* claim */
} else {
ss->int_in_ep->driver_data = NULL;
ss->int_in_ep = NULL;
no_int:
fs_source_sink_descs[FS_ALT_IFC_2_OFFSET] = NULL;
hs_source_sink_descs[HS_ALT_IFC_2_OFFSET] = NULL;
ss_source_sink_descs[SS_ALT_IFC_2_OFFSET] = NULL;
}
if (int_maxpacket > 1024)
int_maxpacket = 1024;
/* support high speed hardware */ /* support high speed hardware */
hs_source_desc.bEndpointAddress = fs_source_desc.bEndpointAddress; hs_source_desc.bEndpointAddress = fs_source_desc.bEndpointAddress;
hs_sink_desc.bEndpointAddress = fs_sink_desc.bEndpointAddress; hs_sink_desc.bEndpointAddress = fs_sink_desc.bEndpointAddress;
/* /*
* Fill in the HS isoc descriptors from the module parameters. * Fill in the HS isoc and interrupt descriptors from the module
* We assume that the user knows what they are doing and won't * parameters. We assume that the user knows what they are doing and
* give parameters that their UDC doesn't support. * won't give parameters that their UDC doesn't support.
*/ */
hs_iso_source_desc.wMaxPacketSize = isoc_maxpacket; hs_iso_source_desc.wMaxPacketSize = isoc_maxpacket;
hs_iso_source_desc.wMaxPacketSize |= isoc_mult << 11; hs_iso_source_desc.wMaxPacketSize |= isoc_mult << 11;
@ -432,6 +607,17 @@ no_iso:
hs_iso_sink_desc.bInterval = isoc_interval; hs_iso_sink_desc.bInterval = isoc_interval;
hs_iso_sink_desc.bEndpointAddress = fs_iso_sink_desc.bEndpointAddress; hs_iso_sink_desc.bEndpointAddress = fs_iso_sink_desc.bEndpointAddress;
hs_int_source_desc.wMaxPacketSize = int_maxpacket;
hs_int_source_desc.wMaxPacketSize |= int_mult << 11;
hs_int_source_desc.bInterval = USB_MS_TO_HS_INTERVAL(int_interval);
hs_int_source_desc.bEndpointAddress =
fs_int_source_desc.bEndpointAddress;
hs_int_sink_desc.wMaxPacketSize = int_maxpacket;
hs_int_sink_desc.wMaxPacketSize |= int_mult << 11;
hs_int_sink_desc.bInterval = USB_MS_TO_HS_INTERVAL(int_interval);
hs_int_sink_desc.bEndpointAddress = fs_int_sink_desc.bEndpointAddress;
/* support super speed hardware */ /* support super speed hardware */
ss_source_desc.bEndpointAddress = ss_source_desc.bEndpointAddress =
fs_source_desc.bEndpointAddress; fs_source_desc.bEndpointAddress;
@ -439,9 +625,9 @@ no_iso:
fs_sink_desc.bEndpointAddress; fs_sink_desc.bEndpointAddress;
/* /*
* Fill in the SS isoc descriptors from the module parameters. * Fill in the SS isoc and interrupt descriptors from the module
* We assume that the user knows what they are doing and won't * parameters. We assume that the user knows what they are doing and
* give parameters that their UDC doesn't support. * won't give parameters that their UDC doesn't support.
*/ */
ss_iso_source_desc.wMaxPacketSize = isoc_maxpacket; ss_iso_source_desc.wMaxPacketSize = isoc_maxpacket;
ss_iso_source_desc.bInterval = isoc_interval; ss_iso_source_desc.bInterval = isoc_interval;
@ -460,17 +646,37 @@ no_iso:
isoc_maxpacket * (isoc_mult + 1) * (isoc_maxburst + 1); isoc_maxpacket * (isoc_mult + 1) * (isoc_maxburst + 1);
ss_iso_sink_desc.bEndpointAddress = fs_iso_sink_desc.bEndpointAddress; ss_iso_sink_desc.bEndpointAddress = fs_iso_sink_desc.bEndpointAddress;
ss_int_source_desc.wMaxPacketSize = int_maxpacket;
ss_int_source_desc.bInterval = USB_MS_TO_SS_INTERVAL(int_interval);
ss_int_source_comp_desc.bmAttributes = int_mult;
ss_int_source_comp_desc.bMaxBurst = int_maxburst;
ss_int_source_comp_desc.wBytesPerInterval =
int_maxpacket * (int_mult + 1) * (int_maxburst + 1);
ss_int_source_desc.bEndpointAddress =
fs_int_source_desc.bEndpointAddress;
ss_int_sink_desc.wMaxPacketSize = int_maxpacket;
ss_int_sink_desc.bInterval = USB_MS_TO_SS_INTERVAL(int_interval);
ss_int_sink_comp_desc.bmAttributes = int_mult;
ss_int_sink_comp_desc.bMaxBurst = int_maxburst;
ss_int_sink_comp_desc.wBytesPerInterval =
int_maxpacket * (int_mult + 1) * (int_maxburst + 1);
ss_int_sink_desc.bEndpointAddress = fs_int_sink_desc.bEndpointAddress;
ret = usb_assign_descriptors(f, fs_source_sink_descs, ret = usb_assign_descriptors(f, fs_source_sink_descs,
hs_source_sink_descs, ss_source_sink_descs); hs_source_sink_descs, ss_source_sink_descs);
if (ret) if (ret)
return ret; return ret;
DBG(cdev, "%s speed %s: IN/%s, OUT/%s, ISO-IN/%s, ISO-OUT/%s\n", DBG(cdev, "%s speed %s: IN/%s, OUT/%s, ISO-IN/%s, ISO-OUT/%s, "
"INT-IN/%s, INT-OUT/%s\n",
(gadget_is_superspeed(c->cdev->gadget) ? "super" : (gadget_is_superspeed(c->cdev->gadget) ? "super" :
(gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full")), (gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full")),
f->name, ss->in_ep->name, ss->out_ep->name, f->name, ss->in_ep->name, ss->out_ep->name,
ss->iso_in_ep ? ss->iso_in_ep->name : "<none>", ss->iso_in_ep ? ss->iso_in_ep->name : "<none>",
ss->iso_out_ep ? ss->iso_out_ep->name : "<none>"); ss->iso_out_ep ? ss->iso_out_ep->name : "<none>",
ss->int_in_ep ? ss->int_in_ep->name : "<none>",
ss->int_out_ep ? ss->int_out_ep->name : "<none>");
return 0; return 0;
} }
@ -601,14 +807,15 @@ static void source_sink_complete(struct usb_ep *ep, struct usb_request *req)
} }
static int source_sink_start_ep(struct f_sourcesink *ss, bool is_in, static int source_sink_start_ep(struct f_sourcesink *ss, bool is_in,
bool is_iso, int speed) enum eptype ep_type, int speed)
{ {
struct usb_ep *ep; struct usb_ep *ep;
struct usb_request *req; struct usb_request *req;
int i, size, status; int i, size, status;
for (i = 0; i < 8; i++) { for (i = 0; i < 8; i++) {
if (is_iso) { switch (ep_type) {
case EP_ISOC:
switch (speed) { switch (speed) {
case USB_SPEED_SUPER: case USB_SPEED_SUPER:
size = isoc_maxpacket * (isoc_mult + 1) * size = isoc_maxpacket * (isoc_mult + 1) *
@ -624,9 +831,28 @@ static int source_sink_start_ep(struct f_sourcesink *ss, bool is_in,
} }
ep = is_in ? ss->iso_in_ep : ss->iso_out_ep; ep = is_in ? ss->iso_in_ep : ss->iso_out_ep;
req = ss_alloc_ep_req(ep, size); req = ss_alloc_ep_req(ep, size);
} else { break;
case EP_INTERRUPT:
switch (speed) {
case USB_SPEED_SUPER:
size = int_maxpacket * (int_mult + 1) *
(int_maxburst + 1);
break;
case USB_SPEED_HIGH:
size = int_maxpacket * (int_mult + 1);
break;
default:
size = int_maxpacket > 1023 ?
1023 : int_maxpacket;
break;
}
ep = is_in ? ss->int_in_ep : ss->int_out_ep;
req = ss_alloc_ep_req(ep, size);
break;
default:
ep = is_in ? ss->in_ep : ss->out_ep; ep = is_in ? ss->in_ep : ss->out_ep;
req = ss_alloc_ep_req(ep, 0); req = ss_alloc_ep_req(ep, 0);
break;
} }
if (!req) if (!req)
@ -644,12 +870,12 @@ static int source_sink_start_ep(struct f_sourcesink *ss, bool is_in,
cdev = ss->function.config->cdev; cdev = ss->function.config->cdev;
ERROR(cdev, "start %s%s %s --> %d\n", ERROR(cdev, "start %s%s %s --> %d\n",
is_iso ? "ISO-" : "", is_in ? "IN" : "OUT", get_ep_string(ep_type), is_in ? "IN" : "OUT",
ep->name, status); ep->name, status);
free_ep_req(ep, req); free_ep_req(ep, req);
} }
if (!is_iso) if (!(ep_type == EP_ISOC))
break; break;
} }
@ -662,7 +888,7 @@ static void disable_source_sink(struct f_sourcesink *ss)
cdev = ss->function.config->cdev; cdev = ss->function.config->cdev;
disable_endpoints(cdev, ss->in_ep, ss->out_ep, ss->iso_in_ep, disable_endpoints(cdev, ss->in_ep, ss->out_ep, ss->iso_in_ep,
ss->iso_out_ep); ss->iso_out_ep, ss->int_in_ep, ss->int_out_ep);
VDBG(cdev, "%s disabled\n", ss->function.name); VDBG(cdev, "%s disabled\n", ss->function.name);
} }
@ -674,6 +900,62 @@ enable_source_sink(struct usb_composite_dev *cdev, struct f_sourcesink *ss,
int speed = cdev->gadget->speed; int speed = cdev->gadget->speed;
struct usb_ep *ep; struct usb_ep *ep;
if (alt == 2) {
/* Configure for periodic interrupt endpoint */
ep = ss->int_in_ep;
if (ep) {
result = config_ep_by_speed(cdev->gadget,
&(ss->function), ep);
if (result)
return result;
result = usb_ep_enable(ep);
if (result < 0)
return result;
ep->driver_data = ss;
result = source_sink_start_ep(ss, true, EP_INTERRUPT,
speed);
if (result < 0) {
fail1:
ep = ss->int_in_ep;
if (ep) {
usb_ep_disable(ep);
ep->driver_data = NULL;
}
return result;
}
}
/*
* one interrupt endpoint reads (sinks) anything OUT (from the
* host)
*/
ep = ss->int_out_ep;
if (ep) {
result = config_ep_by_speed(cdev->gadget,
&(ss->function), ep);
if (result)
goto fail1;
result = usb_ep_enable(ep);
if (result < 0)
goto fail1;
ep->driver_data = ss;
result = source_sink_start_ep(ss, false, EP_INTERRUPT,
speed);
if (result < 0) {
ep = ss->int_out_ep;
usb_ep_disable(ep);
ep->driver_data = NULL;
goto fail1;
}
}
goto out;
}
/* one bulk endpoint writes (sources) zeroes IN (to the host) */ /* one bulk endpoint writes (sources) zeroes IN (to the host) */
ep = ss->in_ep; ep = ss->in_ep;
result = config_ep_by_speed(cdev->gadget, &(ss->function), ep); result = config_ep_by_speed(cdev->gadget, &(ss->function), ep);
@ -684,7 +966,7 @@ enable_source_sink(struct usb_composite_dev *cdev, struct f_sourcesink *ss,
return result; return result;
ep->driver_data = ss; ep->driver_data = ss;
result = source_sink_start_ep(ss, true, false, speed); result = source_sink_start_ep(ss, true, EP_BULK, speed);
if (result < 0) { if (result < 0) {
fail: fail:
ep = ss->in_ep; ep = ss->in_ep;
@ -703,7 +985,7 @@ fail:
goto fail; goto fail;
ep->driver_data = ss; ep->driver_data = ss;
result = source_sink_start_ep(ss, false, false, speed); result = source_sink_start_ep(ss, false, EP_BULK, speed);
if (result < 0) { if (result < 0) {
fail2: fail2:
ep = ss->out_ep; ep = ss->out_ep;
@ -726,7 +1008,7 @@ fail2:
goto fail2; goto fail2;
ep->driver_data = ss; ep->driver_data = ss;
result = source_sink_start_ep(ss, true, true, speed); result = source_sink_start_ep(ss, true, EP_ISOC, speed);
if (result < 0) { if (result < 0) {
fail3: fail3:
ep = ss->iso_in_ep; ep = ss->iso_in_ep;
@ -749,13 +1031,14 @@ fail3:
goto fail3; goto fail3;
ep->driver_data = ss; ep->driver_data = ss;
result = source_sink_start_ep(ss, false, true, speed); result = source_sink_start_ep(ss, false, EP_ISOC, speed);
if (result < 0) { if (result < 0) {
usb_ep_disable(ep); usb_ep_disable(ep);
ep->driver_data = NULL; ep->driver_data = NULL;
goto fail3; goto fail3;
} }
} }
out: out:
ss->cur_alt = alt; ss->cur_alt = alt;
@ -771,6 +1054,8 @@ static int sourcesink_set_alt(struct usb_function *f,
if (ss->in_ep->driver_data) if (ss->in_ep->driver_data)
disable_source_sink(ss); disable_source_sink(ss);
else if (alt == 2 && ss->int_in_ep->driver_data)
disable_source_sink(ss);
return enable_source_sink(cdev, ss, alt); return enable_source_sink(cdev, ss, alt);
} }
@ -883,6 +1168,10 @@ static struct usb_function *source_sink_alloc_func(
isoc_maxpacket = ss_opts->isoc_maxpacket; isoc_maxpacket = ss_opts->isoc_maxpacket;
isoc_mult = ss_opts->isoc_mult; isoc_mult = ss_opts->isoc_mult;
isoc_maxburst = ss_opts->isoc_maxburst; isoc_maxburst = ss_opts->isoc_maxburst;
int_interval = ss_opts->int_interval;
int_maxpacket = ss_opts->int_maxpacket;
int_mult = ss_opts->int_mult;
int_maxburst = ss_opts->int_maxburst;
buflen = ss_opts->bulk_buflen; buflen = ss_opts->bulk_buflen;
ss->function.name = "source/sink"; ss->function.name = "source/sink";
@ -1179,6 +1468,182 @@ static struct f_ss_opts_attribute f_ss_opts_bulk_buflen =
f_ss_opts_bulk_buflen_show, f_ss_opts_bulk_buflen_show,
f_ss_opts_bulk_buflen_store); f_ss_opts_bulk_buflen_store);
static ssize_t f_ss_opts_int_interval_show(struct f_ss_opts *opts, char *page)
{
int result;
mutex_lock(&opts->lock);
result = sprintf(page, "%d", opts->int_interval);
mutex_unlock(&opts->lock);
return result;
}
static ssize_t f_ss_opts_int_interval_store(struct f_ss_opts *opts,
const char *page, size_t len)
{
int ret;
u32 num;
mutex_lock(&opts->lock);
if (opts->refcnt) {
ret = -EBUSY;
goto end;
}
ret = kstrtou32(page, 0, &num);
if (ret)
goto end;
if (num > 4096) {
ret = -EINVAL;
goto end;
}
opts->int_interval = num;
ret = len;
end:
mutex_unlock(&opts->lock);
return ret;
}
static struct f_ss_opts_attribute f_ss_opts_int_interval =
__CONFIGFS_ATTR(int_interval, S_IRUGO | S_IWUSR,
f_ss_opts_int_interval_show,
f_ss_opts_int_interval_store);
static ssize_t f_ss_opts_int_maxpacket_show(struct f_ss_opts *opts, char *page)
{
int result;
mutex_lock(&opts->lock);
result = sprintf(page, "%d", opts->int_maxpacket);
mutex_unlock(&opts->lock);
return result;
}
static ssize_t f_ss_opts_int_maxpacket_store(struct f_ss_opts *opts,
const char *page, size_t len)
{
int ret;
u16 num;
mutex_lock(&opts->lock);
if (opts->refcnt) {
ret = -EBUSY;
goto end;
}
ret = kstrtou16(page, 0, &num);
if (ret)
goto end;
if (num > 1024) {
ret = -EINVAL;
goto end;
}
opts->int_maxpacket = num;
ret = len;
end:
mutex_unlock(&opts->lock);
return ret;
}
static struct f_ss_opts_attribute f_ss_opts_int_maxpacket =
__CONFIGFS_ATTR(int_maxpacket, S_IRUGO | S_IWUSR,
f_ss_opts_int_maxpacket_show,
f_ss_opts_int_maxpacket_store);
static ssize_t f_ss_opts_int_mult_show(struct f_ss_opts *opts, char *page)
{
int result;
mutex_lock(&opts->lock);
result = sprintf(page, "%d", opts->int_mult);
mutex_unlock(&opts->lock);
return result;
}
static ssize_t f_ss_opts_int_mult_store(struct f_ss_opts *opts,
const char *page, size_t len)
{
int ret;
u8 num;
mutex_lock(&opts->lock);
if (opts->refcnt) {
ret = -EBUSY;
goto end;
}
ret = kstrtou8(page, 0, &num);
if (ret)
goto end;
if (num > 2) {
ret = -EINVAL;
goto end;
}
opts->int_mult = num;
ret = len;
end:
mutex_unlock(&opts->lock);
return ret;
}
static struct f_ss_opts_attribute f_ss_opts_int_mult =
__CONFIGFS_ATTR(int_mult, S_IRUGO | S_IWUSR,
f_ss_opts_int_mult_show,
f_ss_opts_int_mult_store);
static ssize_t f_ss_opts_int_maxburst_show(struct f_ss_opts *opts, char *page)
{
int result;
mutex_lock(&opts->lock);
result = sprintf(page, "%d", opts->int_maxburst);
mutex_unlock(&opts->lock);
return result;
}
static ssize_t f_ss_opts_int_maxburst_store(struct f_ss_opts *opts,
const char *page, size_t len)
{
int ret;
u8 num;
mutex_lock(&opts->lock);
if (opts->refcnt) {
ret = -EBUSY;
goto end;
}
ret = kstrtou8(page, 0, &num);
if (ret)
goto end;
if (num > 15) {
ret = -EINVAL;
goto end;
}
opts->int_maxburst = num;
ret = len;
end:
mutex_unlock(&opts->lock);
return ret;
}
static struct f_ss_opts_attribute f_ss_opts_int_maxburst =
__CONFIGFS_ATTR(int_maxburst, S_IRUGO | S_IWUSR,
f_ss_opts_int_maxburst_show,
f_ss_opts_int_maxburst_store);
static struct configfs_attribute *ss_attrs[] = { static struct configfs_attribute *ss_attrs[] = {
&f_ss_opts_pattern.attr, &f_ss_opts_pattern.attr,
&f_ss_opts_isoc_interval.attr, &f_ss_opts_isoc_interval.attr,
@ -1186,6 +1651,10 @@ static struct configfs_attribute *ss_attrs[] = {
&f_ss_opts_isoc_mult.attr, &f_ss_opts_isoc_mult.attr,
&f_ss_opts_isoc_maxburst.attr, &f_ss_opts_isoc_maxburst.attr,
&f_ss_opts_bulk_buflen.attr, &f_ss_opts_bulk_buflen.attr,
&f_ss_opts_int_interval.attr,
&f_ss_opts_int_maxpacket.attr,
&f_ss_opts_int_mult.attr,
&f_ss_opts_int_maxburst.attr,
NULL, NULL,
}; };
@ -1215,6 +1684,8 @@ static struct usb_function_instance *source_sink_alloc_inst(void)
ss_opts->isoc_interval = GZERO_ISOC_INTERVAL; ss_opts->isoc_interval = GZERO_ISOC_INTERVAL;
ss_opts->isoc_maxpacket = GZERO_ISOC_MAXPACKET; ss_opts->isoc_maxpacket = GZERO_ISOC_MAXPACKET;
ss_opts->bulk_buflen = GZERO_BULK_BUFLEN; ss_opts->bulk_buflen = GZERO_BULK_BUFLEN;
ss_opts->int_interval = GZERO_INT_INTERVAL;
ss_opts->int_maxpacket = GZERO_INT_MAXPACKET;
config_group_init_type_name(&ss_opts->func_inst.group, "", config_group_init_type_name(&ss_opts->func_inst.group, "",
&ss_func_type); &ss_func_type);

View File

@ -11,24 +11,12 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/atomic.h> #include <linux/atomic.h>
#include "u_uac1.h" #include "u_uac1.h"
#define OUT_EP_MAX_PACKET_SIZE 200
static int req_buf_size = OUT_EP_MAX_PACKET_SIZE;
module_param(req_buf_size, int, S_IRUGO);
MODULE_PARM_DESC(req_buf_size, "ISO OUT endpoint request buffer size");
static int req_count = 256;
module_param(req_count, int, S_IRUGO);
MODULE_PARM_DESC(req_count, "ISO OUT endpoint request count");
static int audio_buf_size = 48000;
module_param(audio_buf_size, int, S_IRUGO);
MODULE_PARM_DESC(audio_buf_size, "Audio buffer size");
static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value); static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value);
static int generic_get_cmd(struct usb_audio_control *con, u8 cmd); static int generic_get_cmd(struct usb_audio_control *con, u8 cmd);
@ -46,7 +34,7 @@ static int generic_get_cmd(struct usb_audio_control *con, u8 cmd);
#define F_AUDIO_NUM_INTERFACES 2 #define F_AUDIO_NUM_INTERFACES 2
/* B.3.1 Standard AC Interface Descriptor */ /* B.3.1 Standard AC Interface Descriptor */
static struct usb_interface_descriptor ac_interface_desc __initdata = { static struct usb_interface_descriptor ac_interface_desc = {
.bLength = USB_DT_INTERFACE_SIZE, .bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE, .bDescriptorType = USB_DT_INTERFACE,
.bNumEndpoints = 0, .bNumEndpoints = 0,
@ -183,12 +171,12 @@ static struct usb_endpoint_descriptor as_out_ep_desc = {
.bEndpointAddress = USB_DIR_OUT, .bEndpointAddress = USB_DIR_OUT,
.bmAttributes = USB_ENDPOINT_SYNC_ADAPTIVE .bmAttributes = USB_ENDPOINT_SYNC_ADAPTIVE
| USB_ENDPOINT_XFER_ISOC, | USB_ENDPOINT_XFER_ISOC,
.wMaxPacketSize = __constant_cpu_to_le16(OUT_EP_MAX_PACKET_SIZE), .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
.bInterval = 4, .bInterval = 4,
}; };
/* Class-specific AS ISO OUT Endpoint Descriptor */ /* Class-specific AS ISO OUT Endpoint Descriptor */
static struct uac_iso_endpoint_descriptor as_iso_out_desc __initdata = { static struct uac_iso_endpoint_descriptor as_iso_out_desc = {
.bLength = UAC_ISO_ENDPOINT_DESC_SIZE, .bLength = UAC_ISO_ENDPOINT_DESC_SIZE,
.bDescriptorType = USB_DT_CS_ENDPOINT, .bDescriptorType = USB_DT_CS_ENDPOINT,
.bDescriptorSubtype = UAC_EP_GENERAL, .bDescriptorSubtype = UAC_EP_GENERAL,
@ -197,7 +185,7 @@ static struct uac_iso_endpoint_descriptor as_iso_out_desc __initdata = {
.wLockDelay = __constant_cpu_to_le16(1), .wLockDelay = __constant_cpu_to_le16(1),
}; };
static struct usb_descriptor_header *f_audio_desc[] __initdata = { static struct usb_descriptor_header *f_audio_desc[] = {
(struct usb_descriptor_header *)&ac_interface_desc, (struct usb_descriptor_header *)&ac_interface_desc,
(struct usb_descriptor_header *)&ac_header_desc, (struct usb_descriptor_header *)&ac_header_desc,
@ -216,6 +204,37 @@ static struct usb_descriptor_header *f_audio_desc[] __initdata = {
NULL, NULL,
}; };
enum {
STR_AC_IF,
STR_INPUT_TERMINAL,
STR_INPUT_TERMINAL_CH_NAMES,
STR_FEAT_DESC_0,
STR_OUTPUT_TERMINAL,
STR_AS_IF_ALT0,
STR_AS_IF_ALT1,
};
static struct usb_string strings_uac1[] = {
[STR_AC_IF].s = "AC Interface",
[STR_INPUT_TERMINAL].s = "Input terminal",
[STR_INPUT_TERMINAL_CH_NAMES].s = "Channels",
[STR_FEAT_DESC_0].s = "Volume control & mute",
[STR_OUTPUT_TERMINAL].s = "Output terminal",
[STR_AS_IF_ALT0].s = "AS Interface",
[STR_AS_IF_ALT1].s = "AS Interface",
{ },
};
static struct usb_gadget_strings str_uac1 = {
.language = 0x0409, /* en-us */
.strings = strings_uac1,
};
static struct usb_gadget_strings *uac1_strings[] = {
&str_uac1,
NULL,
};
/* /*
* This function is an ALSA sound card following USB Audio Class Spec 1.0. * This function is an ALSA sound card following USB Audio Class Spec 1.0.
*/ */
@ -300,8 +319,14 @@ static int f_audio_out_ep_complete(struct usb_ep *ep, struct usb_request *req)
struct f_audio *audio = req->context; struct f_audio *audio = req->context;
struct usb_composite_dev *cdev = audio->card.func.config->cdev; struct usb_composite_dev *cdev = audio->card.func.config->cdev;
struct f_audio_buf *copy_buf = audio->copy_buf; struct f_audio_buf *copy_buf = audio->copy_buf;
struct f_uac1_opts *opts;
int audio_buf_size;
int err; int err;
opts = container_of(audio->card.func.fi, struct f_uac1_opts,
func_inst);
audio_buf_size = opts->audio_buf_size;
if (!copy_buf) if (!copy_buf)
return -EINVAL; return -EINVAL;
@ -546,10 +571,17 @@ static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
struct usb_composite_dev *cdev = f->config->cdev; struct usb_composite_dev *cdev = f->config->cdev;
struct usb_ep *out_ep = audio->out_ep; struct usb_ep *out_ep = audio->out_ep;
struct usb_request *req; struct usb_request *req;
struct f_uac1_opts *opts;
int req_buf_size, req_count, audio_buf_size;
int i = 0, err = 0; int i = 0, err = 0;
DBG(cdev, "intf %d, alt %d\n", intf, alt); DBG(cdev, "intf %d, alt %d\n", intf, alt);
opts = container_of(f->fi, struct f_uac1_opts, func_inst);
req_buf_size = opts->req_buf_size;
req_count = opts->req_count;
audio_buf_size = opts->audio_buf_size;
if (intf == 1) { if (intf == 1) {
if (alt == 1) { if (alt == 1) {
usb_ep_enable(out_ep); usb_ep_enable(out_ep);
@ -625,13 +657,37 @@ static void f_audio_build_desc(struct f_audio *audio)
} }
/* audio function driver setup/binding */ /* audio function driver setup/binding */
static int __init static int
f_audio_bind(struct usb_configuration *c, struct usb_function *f) f_audio_bind(struct usb_configuration *c, struct usb_function *f)
{ {
struct usb_composite_dev *cdev = c->cdev; struct usb_composite_dev *cdev = c->cdev;
struct f_audio *audio = func_to_audio(f); struct f_audio *audio = func_to_audio(f);
struct usb_string *us;
int status; int status;
struct usb_ep *ep = NULL; struct usb_ep *ep = NULL;
struct f_uac1_opts *audio_opts;
audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst);
audio->card.gadget = c->cdev->gadget;
audio_opts->card = &audio->card;
/* set up ASLA audio devices */
if (!audio_opts->bound) {
status = gaudio_setup(&audio->card);
if (status < 0)
return status;
audio_opts->bound = true;
}
us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1));
if (IS_ERR(us))
return PTR_ERR(us);
ac_interface_desc.iInterface = us[STR_AC_IF].id;
input_terminal_desc.iTerminal = us[STR_INPUT_TERMINAL].id;
input_terminal_desc.iChannelNames = us[STR_INPUT_TERMINAL_CH_NAMES].id;
feature_unit_desc.iFeature = us[STR_FEAT_DESC_0].id;
output_terminal_desc.iTerminal = us[STR_OUTPUT_TERMINAL].id;
as_interface_alt_0_desc.iInterface = us[STR_AS_IF_ALT0].id;
as_interface_alt_1_desc.iInterface = us[STR_AS_IF_ALT1].id;
f_audio_build_desc(audio); f_audio_build_desc(audio);
@ -666,20 +722,12 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f)
return 0; return 0;
fail: fail:
gaudio_cleanup(&audio->card);
if (ep) if (ep)
ep->driver_data = NULL; ep->driver_data = NULL;
return status; return status;
} }
static void
f_audio_unbind(struct usb_configuration *c, struct usb_function *f)
{
struct f_audio *audio = func_to_audio(f);
usb_free_all_descriptors(f);
kfree(audio);
}
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value)
@ -695,7 +743,7 @@ static int generic_get_cmd(struct usb_audio_control *con, u8 cmd)
} }
/* Todo: add more control selecotor dynamically */ /* Todo: add more control selecotor dynamically */
static int __init control_selector_init(struct f_audio *audio) static int control_selector_init(struct f_audio *audio)
{ {
INIT_LIST_HEAD(&audio->cs); INIT_LIST_HEAD(&audio->cs);
list_add(&feature_unit.list, &audio->cs); list_add(&feature_unit.list, &audio->cs);
@ -712,57 +760,226 @@ static int __init control_selector_init(struct f_audio *audio)
return 0; return 0;
} }
/** static inline struct f_uac1_opts *to_f_uac1_opts(struct config_item *item)
* audio_bind_config - add USB audio function to a configuration {
* @c: the configuration to supcard the USB audio function return container_of(to_config_group(item), struct f_uac1_opts,
* Context: single threaded during gadget setup func_inst.group);
* }
* Returns zero on success, else negative errno.
*/ CONFIGFS_ATTR_STRUCT(f_uac1_opts);
static int __init audio_bind_config(struct usb_configuration *c) CONFIGFS_ATTR_OPS(f_uac1_opts);
static void f_uac1_attr_release(struct config_item *item)
{
struct f_uac1_opts *opts = to_f_uac1_opts(item);
usb_put_function_instance(&opts->func_inst);
}
static struct configfs_item_operations f_uac1_item_ops = {
.release = f_uac1_attr_release,
.show_attribute = f_uac1_opts_attr_show,
.store_attribute = f_uac1_opts_attr_store,
};
#define UAC1_INT_ATTRIBUTE(name) \
static ssize_t f_uac1_opts_##name##_show(struct f_uac1_opts *opts, \
char *page) \
{ \
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 f_uac1_opts *opts, \
const char *page, size_t len) \
{ \
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; \
} \
\
static struct f_uac1_opts_attribute f_uac1_opts_##name = \
__CONFIGFS_ATTR(name, S_IRUGO | S_IWUSR, \
f_uac1_opts_##name##_show, \
f_uac1_opts_##name##_store)
UAC1_INT_ATTRIBUTE(req_buf_size);
UAC1_INT_ATTRIBUTE(req_count);
UAC1_INT_ATTRIBUTE(audio_buf_size);
#define UAC1_STR_ATTRIBUTE(name) \
static ssize_t f_uac1_opts_##name##_show(struct f_uac1_opts *opts, \
char *page) \
{ \
int result; \
\
mutex_lock(&opts->lock); \
result = sprintf(page, "%s\n", opts->name); \
mutex_unlock(&opts->lock); \
\
return result; \
} \
\
static ssize_t f_uac1_opts_##name##_store(struct f_uac1_opts *opts, \
const char *page, size_t len) \
{ \
int ret = -EBUSY; \
char *tmp; \
\
mutex_lock(&opts->lock); \
if (opts->refcnt) \
goto end; \
\
tmp = kstrndup(page, len, GFP_KERNEL); \
if (tmp) { \
ret = -ENOMEM; \
goto end; \
} \
if (opts->name##_alloc) \
kfree(opts->name); \
opts->name##_alloc = true; \
opts->name = tmp; \
ret = len; \
\
end: \
mutex_unlock(&opts->lock); \
return ret; \
} \
\
static struct f_uac1_opts_attribute f_uac1_opts_##name = \
__CONFIGFS_ATTR(name, S_IRUGO | S_IWUSR, \
f_uac1_opts_##name##_show, \
f_uac1_opts_##name##_store)
UAC1_STR_ATTRIBUTE(fn_play);
UAC1_STR_ATTRIBUTE(fn_cap);
UAC1_STR_ATTRIBUTE(fn_cntl);
static struct configfs_attribute *f_uac1_attrs[] = {
&f_uac1_opts_req_buf_size.attr,
&f_uac1_opts_req_count.attr,
&f_uac1_opts_audio_buf_size.attr,
&f_uac1_opts_fn_play.attr,
&f_uac1_opts_fn_cap.attr,
&f_uac1_opts_fn_cntl.attr,
NULL,
};
static struct config_item_type f_uac1_func_type = {
.ct_item_ops = &f_uac1_item_ops,
.ct_attrs = f_uac1_attrs,
.ct_owner = THIS_MODULE,
};
static void f_audio_free_inst(struct usb_function_instance *f)
{
struct f_uac1_opts *opts;
opts = container_of(f, struct f_uac1_opts, func_inst);
gaudio_cleanup(opts->card);
if (opts->fn_play_alloc)
kfree(opts->fn_play);
if (opts->fn_cap_alloc)
kfree(opts->fn_cap);
if (opts->fn_cntl_alloc)
kfree(opts->fn_cntl);
kfree(opts);
}
static struct usb_function_instance *f_audio_alloc_inst(void)
{
struct f_uac1_opts *opts;
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
if (!opts)
return ERR_PTR(-ENOMEM);
mutex_init(&opts->lock);
opts->func_inst.free_func_inst = f_audio_free_inst;
config_group_init_type_name(&opts->func_inst.group, "",
&f_uac1_func_type);
opts->req_buf_size = UAC1_OUT_EP_MAX_PACKET_SIZE;
opts->req_count = UAC1_REQ_COUNT;
opts->audio_buf_size = UAC1_AUDIO_BUF_SIZE;
opts->fn_play = FILE_PCM_PLAYBACK;
opts->fn_cap = FILE_PCM_CAPTURE;
opts->fn_cntl = FILE_CONTROL;
return &opts->func_inst;
}
static void f_audio_free(struct usb_function *f)
{
struct f_audio *audio = func_to_audio(f);
struct f_uac1_opts *opts;
opts = container_of(f->fi, struct f_uac1_opts, func_inst);
kfree(audio);
mutex_lock(&opts->lock);
--opts->refcnt;
mutex_unlock(&opts->lock);
}
static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f)
{
usb_free_all_descriptors(f);
}
static struct usb_function *f_audio_alloc(struct usb_function_instance *fi)
{ {
struct f_audio *audio; struct f_audio *audio;
int status; struct f_uac1_opts *opts;
/* allocate and initialize one new instance */ /* allocate and initialize one new instance */
audio = kzalloc(sizeof *audio, GFP_KERNEL); audio = kzalloc(sizeof(*audio), GFP_KERNEL);
if (!audio) if (!audio)
return -ENOMEM; return ERR_PTR(-ENOMEM);
audio->card.func.name = "g_audio"; audio->card.func.name = "g_audio";
audio->card.gadget = c->cdev->gadget;
opts = container_of(fi, struct f_uac1_opts, func_inst);
mutex_lock(&opts->lock);
++opts->refcnt;
mutex_unlock(&opts->lock);
INIT_LIST_HEAD(&audio->play_queue); INIT_LIST_HEAD(&audio->play_queue);
spin_lock_init(&audio->lock); spin_lock_init(&audio->lock);
/* set up ASLA audio devices */
status = gaudio_setup(&audio->card);
if (status < 0)
goto setup_fail;
audio->card.func.strings = audio_strings;
audio->card.func.bind = f_audio_bind; audio->card.func.bind = f_audio_bind;
audio->card.func.unbind = f_audio_unbind; audio->card.func.unbind = f_audio_unbind;
audio->card.func.set_alt = f_audio_set_alt; audio->card.func.set_alt = f_audio_set_alt;
audio->card.func.setup = f_audio_setup; audio->card.func.setup = f_audio_setup;
audio->card.func.disable = f_audio_disable; audio->card.func.disable = f_audio_disable;
audio->card.func.free_func = f_audio_free;
control_selector_init(audio); control_selector_init(audio);
INIT_WORK(&audio->playback_work, f_audio_playback_work); INIT_WORK(&audio->playback_work, f_audio_playback_work);
status = usb_add_function(c, &audio->card.func); return &audio->card.func;
if (status)
goto add_fail;
INFO(c->cdev, "audio_buf_size %d, req_buf_size %d, req_count %d\n",
audio_buf_size, req_buf_size, req_count);
return status;
add_fail:
gaudio_cleanup();
setup_fail:
kfree(audio);
return status;
} }
DECLARE_USB_FUNCTION_INIT(uac1, f_audio_alloc_inst, f_audio_alloc);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Bryan Wu");

View File

@ -20,35 +20,7 @@
#include <sound/pcm.h> #include <sound/pcm.h>
#include <sound/pcm_params.h> #include <sound/pcm_params.h>
/* Playback(USB-IN) Default Stereo - Fl/Fr */ #include "u_uac2.h"
static int p_chmask = 0x3;
module_param(p_chmask, uint, S_IRUGO);
MODULE_PARM_DESC(p_chmask, "Playback Channel Mask");
/* Playback Default 48 KHz */
static int p_srate = 48000;
module_param(p_srate, uint, S_IRUGO);
MODULE_PARM_DESC(p_srate, "Playback Sampling Rate");
/* Playback Default 16bits/sample */
static int p_ssize = 2;
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 = 0x3;
module_param(c_chmask, uint, S_IRUGO);
MODULE_PARM_DESC(c_chmask, "Capture Channel Mask");
/* Capture Default 64 KHz */
static int c_srate = 64000;
module_param(c_srate, uint, S_IRUGO);
MODULE_PARM_DESC(c_srate, "Capture Sampling Rate");
/* Capture Default 16bits/sample */
static int c_ssize = 2;
module_param(c_ssize, uint, S_IRUGO);
MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)");
/* Keep everyone on toes */ /* Keep everyone on toes */
#define USB_XFERS 2 #define USB_XFERS 2
@ -120,6 +92,15 @@ struct snd_uac2_chip {
struct snd_card *card; struct snd_card *card;
struct snd_pcm *pcm; struct snd_pcm *pcm;
/* timekeeping for the playback endpoint */
unsigned int p_interval;
unsigned int p_residue;
/* pre-calculated values for playback iso completion */
unsigned int p_pktsize;
unsigned int p_pktsize_residue;
unsigned int p_framesize;
}; };
#define BUFF_SIZE_MAX (PAGE_SIZE * 16) #define BUFF_SIZE_MAX (PAGE_SIZE * 16)
@ -149,8 +130,6 @@ struct audio_dev {
struct snd_uac2_chip uac2; struct snd_uac2_chip uac2;
}; };
static struct audio_dev *agdev_g;
static inline static inline
struct audio_dev *func_to_agdev(struct usb_function *f) struct audio_dev *func_to_agdev(struct usb_function *f)
{ {
@ -169,6 +148,12 @@ struct snd_uac2_chip *pdev_to_uac2(struct platform_device *p)
return container_of(p, struct snd_uac2_chip, pdev); return container_of(p, struct snd_uac2_chip, pdev);
} }
static inline
struct f_uac2_opts *agdev_to_uac2_opts(struct audio_dev *agdev)
{
return container_of(agdev->func.fi, struct f_uac2_opts, func_inst);
}
static inline static inline
uint num_channels(uint chanmask) uint num_channels(uint chanmask)
{ {
@ -187,8 +172,8 @@ agdev_iso_complete(struct usb_ep *ep, struct usb_request *req)
{ {
unsigned pending; unsigned pending;
unsigned long flags; unsigned long flags;
unsigned int hw_ptr;
bool update_alsa = false; bool update_alsa = false;
unsigned char *src, *dst;
int status = req->status; int status = req->status;
struct uac2_req *ur = req->context; struct uac2_req *ur = req->context;
struct snd_pcm_substream *substream; struct snd_pcm_substream *substream;
@ -216,12 +201,27 @@ agdev_iso_complete(struct usb_ep *ep, struct usb_request *req)
spin_lock_irqsave(&prm->lock, flags); spin_lock_irqsave(&prm->lock, flags);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
src = prm->dma_area + prm->hw_ptr; /*
* For each IN packet, take the quotient of the current data
* rate and the endpoint's interval as the base packet size.
* If there is a residue from this division, add it to the
* residue accumulator.
*/
req->length = uac2->p_pktsize;
uac2->p_residue += uac2->p_pktsize_residue;
/*
* Whenever there are more bytes in the accumulator than we
* need to add one more sample frame, increase this packet's
* size and decrease the accumulator.
*/
if (uac2->p_residue / uac2->p_interval >= uac2->p_framesize) {
req->length += uac2->p_framesize;
uac2->p_residue -= uac2->p_framesize *
uac2->p_interval;
}
req->actual = req->length; req->actual = req->length;
dst = req->buf;
} else {
dst = prm->dma_area + prm->hw_ptr;
src = req->buf;
} }
pending = prm->hw_ptr % prm->period_size; pending = prm->hw_ptr % prm->period_size;
@ -229,12 +229,32 @@ agdev_iso_complete(struct usb_ep *ep, struct usb_request *req)
if (pending >= prm->period_size) if (pending >= prm->period_size)
update_alsa = true; update_alsa = true;
hw_ptr = prm->hw_ptr;
prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes; prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes;
spin_unlock_irqrestore(&prm->lock, flags); spin_unlock_irqrestore(&prm->lock, flags);
/* Pack USB load in ALSA ring buffer */ /* Pack USB load in ALSA ring buffer */
memcpy(dst, src, req->actual); pending = prm->dma_bytes - hw_ptr;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
if (unlikely(pending < req->actual)) {
memcpy(req->buf, prm->dma_area + hw_ptr, pending);
memcpy(req->buf + pending, prm->dma_area,
req->actual - pending);
} else {
memcpy(req->buf, prm->dma_area + hw_ptr, req->actual);
}
} else {
if (unlikely(pending < req->actual)) {
memcpy(prm->dma_area + hw_ptr, req->buf, pending);
memcpy(prm->dma_area, req->buf + pending,
req->actual - pending);
} else {
memcpy(prm->dma_area + hw_ptr, req->buf, req->actual);
}
}
exit: exit:
if (usb_ep_queue(ep, req, GFP_ATOMIC)) if (usb_ep_queue(ep, req, GFP_ATOMIC))
dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__); dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__);
@ -342,6 +362,21 @@ static int uac2_pcm_open(struct snd_pcm_substream *substream)
{ {
struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime; struct snd_pcm_runtime *runtime = substream->runtime;
struct audio_dev *audio_dev;
struct f_uac2_opts *opts;
int p_ssize, c_ssize;
int p_srate, c_srate;
int p_chmask, c_chmask;
audio_dev = uac2_to_agdev(uac2);
opts = container_of(audio_dev->func.fi, struct f_uac2_opts, func_inst);
p_ssize = opts->p_ssize;
c_ssize = opts->c_ssize;
p_srate = opts->p_srate;
c_srate = opts->c_srate;
p_chmask = opts->p_chmask;
c_chmask = opts->c_chmask;
uac2->p_residue = 0;
runtime->hw = uac2_pcm_hardware; runtime->hw = uac2_pcm_hardware;
@ -411,7 +446,15 @@ static int snd_uac2_probe(struct platform_device *pdev)
struct snd_uac2_chip *uac2 = pdev_to_uac2(pdev); struct snd_uac2_chip *uac2 = pdev_to_uac2(pdev);
struct snd_card *card; struct snd_card *card;
struct snd_pcm *pcm; struct snd_pcm *pcm;
struct audio_dev *audio_dev;
struct f_uac2_opts *opts;
int err; int err;
int p_chmask, c_chmask;
audio_dev = uac2_to_agdev(uac2);
opts = container_of(audio_dev->func.fi, struct f_uac2_opts, func_inst);
p_chmask = opts->p_chmask;
c_chmask = opts->c_chmask;
/* Choose any slot, with no id */ /* Choose any slot, with no id */
err = snd_card_new(&pdev->dev, -1, NULL, THIS_MODULE, 0, &card); err = snd_card_new(&pdev->dev, -1, NULL, THIS_MODULE, 0, &card);
@ -919,20 +962,58 @@ free_ep(struct uac2_rtd_params *prm, struct usb_ep *ep)
"%s:%d Error!\n", __func__, __LINE__); "%s:%d Error!\n", __func__, __LINE__);
} }
static int __init static int
afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
{ {
struct audio_dev *agdev = func_to_agdev(fn); struct audio_dev *agdev = func_to_agdev(fn);
struct snd_uac2_chip *uac2 = &agdev->uac2; struct snd_uac2_chip *uac2 = &agdev->uac2;
struct usb_composite_dev *cdev = cfg->cdev; struct usb_composite_dev *cdev = cfg->cdev;
struct usb_gadget *gadget = cdev->gadget; struct usb_gadget *gadget = cdev->gadget;
struct device *dev = &uac2->pdev.dev;
struct uac2_rtd_params *prm; struct uac2_rtd_params *prm;
struct f_uac2_opts *uac2_opts;
struct usb_string *us;
int ret; int ret;
uac2_opts = container_of(fn->fi, struct f_uac2_opts, func_inst);
us = usb_gstrings_attach(cdev, fn_strings, ARRAY_SIZE(strings_fn));
if (IS_ERR(us))
return PTR_ERR(us);
iad_desc.iFunction = us[STR_ASSOC].id;
std_ac_if_desc.iInterface = us[STR_IF_CTRL].id;
in_clk_src_desc.iClockSource = us[STR_CLKSRC_IN].id;
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_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;
std_as_out_if1_desc.iInterface = us[STR_AS_OUT_ALT1].id;
std_as_in_if0_desc.iInterface = us[STR_AS_IN_ALT0].id;
std_as_in_if1_desc.iInterface = us[STR_AS_IN_ALT1].id;
/* Initialize the configurable parameters */
usb_out_it_desc.bNrChannels = num_channels(uac2_opts->c_chmask);
usb_out_it_desc.bmChannelConfig = cpu_to_le32(uac2_opts->c_chmask);
io_in_it_desc.bNrChannels = num_channels(uac2_opts->p_chmask);
io_in_it_desc.bmChannelConfig = cpu_to_le32(uac2_opts->p_chmask);
as_out_hdr_desc.bNrChannels = num_channels(uac2_opts->c_chmask);
as_out_hdr_desc.bmChannelConfig = cpu_to_le32(uac2_opts->c_chmask);
as_in_hdr_desc.bNrChannels = num_channels(uac2_opts->p_chmask);
as_in_hdr_desc.bmChannelConfig = cpu_to_le32(uac2_opts->p_chmask);
as_out_fmt1_desc.bSubslotSize = uac2_opts->c_ssize;
as_out_fmt1_desc.bBitResolution = uac2_opts->c_ssize * 8;
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); ret = usb_interface_id(cfg, fn);
if (ret < 0) { if (ret < 0) {
dev_err(&uac2->pdev.dev, dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
"%s:%d Error!\n", __func__, __LINE__);
return ret; return ret;
} }
std_ac_if_desc.bInterfaceNumber = ret; std_ac_if_desc.bInterfaceNumber = ret;
@ -941,8 +1022,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
ret = usb_interface_id(cfg, fn); ret = usb_interface_id(cfg, fn);
if (ret < 0) { if (ret < 0) {
dev_err(&uac2->pdev.dev, dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
"%s:%d Error!\n", __func__, __LINE__);
return ret; return ret;
} }
std_as_out_if0_desc.bInterfaceNumber = ret; std_as_out_if0_desc.bInterfaceNumber = ret;
@ -952,8 +1032,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
ret = usb_interface_id(cfg, fn); ret = usb_interface_id(cfg, fn);
if (ret < 0) { if (ret < 0) {
dev_err(&uac2->pdev.dev, dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
"%s:%d Error!\n", __func__, __LINE__);
return ret; return ret;
} }
std_as_in_if0_desc.bInterfaceNumber = ret; std_as_in_if0_desc.bInterfaceNumber = ret;
@ -963,16 +1042,14 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc); agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc);
if (!agdev->out_ep) { if (!agdev->out_ep) {
dev_err(&uac2->pdev.dev, dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
"%s:%d Error!\n", __func__, __LINE__);
goto err; goto err;
} }
agdev->out_ep->driver_data = agdev; agdev->out_ep->driver_data = agdev;
agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc); agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc);
if (!agdev->in_ep) { if (!agdev->in_ep) {
dev_err(&uac2->pdev.dev, dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
"%s:%d Error!\n", __func__, __LINE__);
goto err; goto err;
} }
agdev->in_ep->driver_data = agdev; agdev->in_ep->driver_data = agdev;
@ -1020,27 +1097,6 @@ err:
return -EINVAL; return -EINVAL;
} }
static void
afunc_unbind(struct usb_configuration *cfg, struct usb_function *fn)
{
struct audio_dev *agdev = func_to_agdev(fn);
struct uac2_rtd_params *prm;
alsa_uac2_exit(agdev);
prm = &agdev->uac2.p_prm;
kfree(prm->rbuf);
prm = &agdev->uac2.c_prm;
kfree(prm->rbuf);
usb_free_all_descriptors(fn);
if (agdev->in_ep)
agdev->in_ep->driver_data = NULL;
if (agdev->out_ep)
agdev->out_ep->driver_data = NULL;
}
static int static int
afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
{ {
@ -1048,23 +1104,22 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
struct audio_dev *agdev = func_to_agdev(fn); struct audio_dev *agdev = func_to_agdev(fn);
struct snd_uac2_chip *uac2 = &agdev->uac2; struct snd_uac2_chip *uac2 = &agdev->uac2;
struct usb_gadget *gadget = cdev->gadget; struct usb_gadget *gadget = cdev->gadget;
struct device *dev = &uac2->pdev.dev;
struct usb_request *req; struct usb_request *req;
struct usb_ep *ep; struct usb_ep *ep;
struct uac2_rtd_params *prm; struct uac2_rtd_params *prm;
int i; int req_len, i;
/* No i/f has more than 2 alt settings */ /* No i/f has more than 2 alt settings */
if (alt > 1) { if (alt > 1) {
dev_err(&uac2->pdev.dev, dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
"%s:%d Error!\n", __func__, __LINE__);
return -EINVAL; return -EINVAL;
} }
if (intf == agdev->ac_intf) { if (intf == agdev->ac_intf) {
/* Control I/f has only 1 AltSetting - 0 */ /* Control I/f has only 1 AltSetting - 0 */
if (alt) { if (alt) {
dev_err(&uac2->pdev.dev, dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
"%s:%d Error!\n", __func__, __LINE__);
return -EINVAL; return -EINVAL;
} }
return 0; return 0;
@ -1075,14 +1130,43 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
prm = &uac2->c_prm; prm = &uac2->c_prm;
config_ep_by_speed(gadget, fn, ep); config_ep_by_speed(gadget, fn, ep);
agdev->as_out_alt = alt; agdev->as_out_alt = alt;
req_len = prm->max_psize;
} else if (intf == agdev->as_in_intf) { } else if (intf == agdev->as_in_intf) {
struct f_uac2_opts *opts = agdev_to_uac2_opts(agdev);
unsigned int factor, rate;
struct usb_endpoint_descriptor *ep_desc;
ep = agdev->in_ep; ep = agdev->in_ep;
prm = &uac2->p_prm; prm = &uac2->p_prm;
config_ep_by_speed(gadget, fn, ep); config_ep_by_speed(gadget, fn, ep);
agdev->as_in_alt = alt; agdev->as_in_alt = alt;
/* pre-calculate the playback endpoint's interval */
if (gadget->speed == USB_SPEED_FULL) {
ep_desc = &fs_epin_desc;
factor = 1000;
} else {
ep_desc = &hs_epin_desc;
factor = 125;
}
/* pre-compute some values for iso_complete() */
uac2->p_framesize = opts->p_ssize *
num_channels(opts->p_chmask);
rate = opts->p_srate * uac2->p_framesize;
uac2->p_interval = (1 << (ep_desc->bInterval - 1)) * factor;
uac2->p_pktsize = min_t(unsigned int, rate / uac2->p_interval,
prm->max_psize);
if (uac2->p_pktsize < prm->max_psize)
uac2->p_pktsize_residue = rate % uac2->p_interval;
else
uac2->p_pktsize_residue = 0;
req_len = uac2->p_pktsize;
uac2->p_residue = 0;
} else { } else {
dev_err(&uac2->pdev.dev, dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
"%s:%d Error!\n", __func__, __LINE__);
return -EINVAL; return -EINVAL;
} }
@ -1095,31 +1179,23 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
usb_ep_enable(ep); usb_ep_enable(ep);
for (i = 0; i < USB_XFERS; i++) { for (i = 0; i < USB_XFERS; i++) {
if (prm->ureq[i].req) { if (!prm->ureq[i].req) {
if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC)) req = usb_ep_alloc_request(ep, GFP_ATOMIC);
dev_err(&uac2->pdev.dev, "%d Error!\n", if (req == NULL)
__LINE__); return -ENOMEM;
continue;
prm->ureq[i].req = req;
prm->ureq[i].pp = prm;
req->zero = 0;
req->context = &prm->ureq[i];
req->length = req_len;
req->complete = agdev_iso_complete;
req->buf = prm->rbuf + i * prm->max_psize;
} }
req = usb_ep_alloc_request(ep, GFP_ATOMIC); if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
if (req == NULL) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
dev_err(&uac2->pdev.dev,
"%s:%d Error!\n", __func__, __LINE__);
return -EINVAL;
}
prm->ureq[i].req = req;
prm->ureq[i].pp = prm;
req->zero = 0;
req->context = &prm->ureq[i];
req->length = prm->max_psize;
req->complete = agdev_iso_complete;
req->buf = prm->rbuf + i * req->length;
if (usb_ep_queue(ep, req, GFP_ATOMIC))
dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__);
} }
return 0; return 0;
@ -1164,12 +1240,18 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
struct usb_request *req = fn->config->cdev->req; struct usb_request *req = fn->config->cdev->req;
struct audio_dev *agdev = func_to_agdev(fn); struct audio_dev *agdev = func_to_agdev(fn);
struct snd_uac2_chip *uac2 = &agdev->uac2; struct snd_uac2_chip *uac2 = &agdev->uac2;
struct f_uac2_opts *opts;
u16 w_length = le16_to_cpu(cr->wLength); u16 w_length = le16_to_cpu(cr->wLength);
u16 w_index = le16_to_cpu(cr->wIndex); u16 w_index = le16_to_cpu(cr->wIndex);
u16 w_value = le16_to_cpu(cr->wValue); u16 w_value = le16_to_cpu(cr->wValue);
u8 entity_id = (w_index >> 8) & 0xff; u8 entity_id = (w_index >> 8) & 0xff;
u8 control_selector = w_value >> 8; u8 control_selector = w_value >> 8;
int value = -EOPNOTSUPP; int value = -EOPNOTSUPP;
int p_srate, c_srate;
opts = agdev_to_uac2_opts(agdev);
p_srate = opts->p_srate;
c_srate = opts->c_srate;
if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
struct cntrl_cur_lay3 c; struct cntrl_cur_lay3 c;
@ -1199,6 +1281,7 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
struct usb_request *req = fn->config->cdev->req; struct usb_request *req = fn->config->cdev->req;
struct audio_dev *agdev = func_to_agdev(fn); struct audio_dev *agdev = func_to_agdev(fn);
struct snd_uac2_chip *uac2 = &agdev->uac2; struct snd_uac2_chip *uac2 = &agdev->uac2;
struct f_uac2_opts *opts;
u16 w_length = le16_to_cpu(cr->wLength); u16 w_length = le16_to_cpu(cr->wLength);
u16 w_index = le16_to_cpu(cr->wIndex); u16 w_index = le16_to_cpu(cr->wIndex);
u16 w_value = le16_to_cpu(cr->wValue); u16 w_value = le16_to_cpu(cr->wValue);
@ -1206,6 +1289,11 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
u8 control_selector = w_value >> 8; u8 control_selector = w_value >> 8;
struct cntrl_range_lay3 r; struct cntrl_range_lay3 r;
int value = -EOPNOTSUPP; int value = -EOPNOTSUPP;
int p_srate, c_srate;
opts = agdev_to_uac2_opts(agdev);
p_srate = opts->p_srate;
c_srate = opts->c_srate;
if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
if (entity_id == USB_IN_CLK_ID) if (entity_id == USB_IN_CLK_ID)
@ -1309,66 +1397,184 @@ afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr)
return value; return value;
} }
static int audio_bind_config(struct usb_configuration *cfg) static inline struct f_uac2_opts *to_f_uac2_opts(struct config_item *item)
{ {
int res; return container_of(to_config_group(item), struct f_uac2_opts,
func_inst.group);
agdev_g = kzalloc(sizeof *agdev_g, GFP_KERNEL);
if (agdev_g == NULL)
return -ENOMEM;
res = usb_string_ids_tab(cfg->cdev, strings_fn);
if (res)
return res;
iad_desc.iFunction = strings_fn[STR_ASSOC].id;
std_ac_if_desc.iInterface = strings_fn[STR_IF_CTRL].id;
in_clk_src_desc.iClockSource = strings_fn[STR_CLKSRC_IN].id;
out_clk_src_desc.iClockSource = strings_fn[STR_CLKSRC_OUT].id;
usb_out_it_desc.iTerminal = strings_fn[STR_USB_IT].id;
io_in_it_desc.iTerminal = strings_fn[STR_IO_IT].id;
usb_in_ot_desc.iTerminal = strings_fn[STR_USB_OT].id;
io_out_ot_desc.iTerminal = strings_fn[STR_IO_OT].id;
std_as_out_if0_desc.iInterface = strings_fn[STR_AS_OUT_ALT0].id;
std_as_out_if1_desc.iInterface = strings_fn[STR_AS_OUT_ALT1].id;
std_as_in_if0_desc.iInterface = strings_fn[STR_AS_IN_ALT0].id;
std_as_in_if1_desc.iInterface = strings_fn[STR_AS_IN_ALT1].id;
agdev_g->func.name = "uac2_func";
agdev_g->func.strings = fn_strings;
agdev_g->func.bind = afunc_bind;
agdev_g->func.unbind = afunc_unbind;
agdev_g->func.set_alt = afunc_set_alt;
agdev_g->func.get_alt = afunc_get_alt;
agdev_g->func.disable = afunc_disable;
agdev_g->func.setup = afunc_setup;
/* Initialize the configurable parameters */
usb_out_it_desc.bNrChannels = num_channels(c_chmask);
usb_out_it_desc.bmChannelConfig = cpu_to_le32(c_chmask);
io_in_it_desc.bNrChannels = num_channels(p_chmask);
io_in_it_desc.bmChannelConfig = cpu_to_le32(p_chmask);
as_out_hdr_desc.bNrChannels = num_channels(c_chmask);
as_out_hdr_desc.bmChannelConfig = cpu_to_le32(c_chmask);
as_in_hdr_desc.bNrChannels = num_channels(p_chmask);
as_in_hdr_desc.bmChannelConfig = cpu_to_le32(p_chmask);
as_out_fmt1_desc.bSubslotSize = c_ssize;
as_out_fmt1_desc.bBitResolution = c_ssize * 8;
as_in_fmt1_desc.bSubslotSize = p_ssize;
as_in_fmt1_desc.bBitResolution = p_ssize * 8;
snprintf(clksrc_in, sizeof(clksrc_in), "%uHz", p_srate);
snprintf(clksrc_out, sizeof(clksrc_out), "%uHz", c_srate);
res = usb_add_function(cfg, &agdev_g->func);
if (res < 0)
kfree(agdev_g);
return res;
} }
static void CONFIGFS_ATTR_STRUCT(f_uac2_opts);
uac2_unbind_config(struct usb_configuration *cfg) CONFIGFS_ATTR_OPS(f_uac2_opts);
static void f_uac2_attr_release(struct config_item *item)
{ {
kfree(agdev_g); struct f_uac2_opts *opts = to_f_uac2_opts(item);
agdev_g = NULL;
usb_put_function_instance(&opts->func_inst);
} }
static struct configfs_item_operations f_uac2_item_ops = {
.release = f_uac2_attr_release,
.show_attribute = f_uac2_opts_attr_show,
.store_attribute = f_uac2_opts_attr_store,
};
#define UAC2_ATTRIBUTE(name) \
static ssize_t f_uac2_opts_##name##_show(struct f_uac2_opts *opts, \
char *page) \
{ \
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 f_uac2_opts *opts, \
const char *page, size_t len) \
{ \
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; \
} \
\
static struct f_uac2_opts_attribute f_uac2_opts_##name = \
__CONFIGFS_ATTR(name, S_IRUGO | S_IWUSR, \
f_uac2_opts_##name##_show, \
f_uac2_opts_##name##_store)
UAC2_ATTRIBUTE(p_chmask);
UAC2_ATTRIBUTE(p_srate);
UAC2_ATTRIBUTE(p_ssize);
UAC2_ATTRIBUTE(c_chmask);
UAC2_ATTRIBUTE(c_srate);
UAC2_ATTRIBUTE(c_ssize);
static struct configfs_attribute *f_uac2_attrs[] = {
&f_uac2_opts_p_chmask.attr,
&f_uac2_opts_p_srate.attr,
&f_uac2_opts_p_ssize.attr,
&f_uac2_opts_c_chmask.attr,
&f_uac2_opts_c_srate.attr,
&f_uac2_opts_c_ssize.attr,
NULL,
};
static struct config_item_type f_uac2_func_type = {
.ct_item_ops = &f_uac2_item_ops,
.ct_attrs = f_uac2_attrs,
.ct_owner = THIS_MODULE,
};
static void afunc_free_inst(struct usb_function_instance *f)
{
struct f_uac2_opts *opts;
opts = container_of(f, struct f_uac2_opts, func_inst);
kfree(opts);
}
static struct usb_function_instance *afunc_alloc_inst(void)
{
struct f_uac2_opts *opts;
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
if (!opts)
return ERR_PTR(-ENOMEM);
mutex_init(&opts->lock);
opts->func_inst.free_func_inst = afunc_free_inst;
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;
return &opts->func_inst;
}
static void afunc_free(struct usb_function *f)
{
struct audio_dev *agdev;
struct f_uac2_opts *opts;
agdev = func_to_agdev(f);
opts = container_of(f->fi, struct f_uac2_opts, func_inst);
kfree(agdev);
mutex_lock(&opts->lock);
--opts->refcnt;
mutex_unlock(&opts->lock);
}
static void afunc_unbind(struct usb_configuration *c, struct usb_function *f)
{
struct audio_dev *agdev = func_to_agdev(f);
struct uac2_rtd_params *prm;
alsa_uac2_exit(agdev);
prm = &agdev->uac2.p_prm;
kfree(prm->rbuf);
prm = &agdev->uac2.c_prm;
kfree(prm->rbuf);
usb_free_all_descriptors(f);
if (agdev->in_ep)
agdev->in_ep->driver_data = NULL;
if (agdev->out_ep)
agdev->out_ep->driver_data = NULL;
}
struct usb_function *afunc_alloc(struct usb_function_instance *fi)
{
struct audio_dev *agdev;
struct f_uac2_opts *opts;
agdev = kzalloc(sizeof(*agdev), GFP_KERNEL);
if (agdev == NULL)
return ERR_PTR(-ENOMEM);
opts = container_of(fi, struct f_uac2_opts, func_inst);
mutex_lock(&opts->lock);
++opts->refcnt;
mutex_unlock(&opts->lock);
agdev->func.name = "uac2_func";
agdev->func.bind = afunc_bind;
agdev->func.unbind = afunc_unbind;
agdev->func.set_alt = afunc_set_alt;
agdev->func.get_alt = afunc_get_alt;
agdev->func.disable = afunc_disable;
agdev->func.setup = afunc_setup;
agdev->func.free_func = afunc_free;
return &agdev->func;
}
DECLARE_USB_FUNCTION_INIT(uac2, afunc_alloc_inst, afunc_alloc);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yadwinder Singh");
MODULE_AUTHOR("Jaswinder Singh");

View File

@ -11,6 +11,7 @@
*/ */
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/fs.h> #include <linux/fs.h>
@ -27,24 +28,12 @@
#include <media/v4l2-event.h> #include <media/v4l2-event.h>
#include "uvc.h" #include "uvc.h"
#include "uvc_v4l2.h"
#include "uvc_video.h"
#include "u_uvc.h"
unsigned int uvc_gadget_trace_param; unsigned int uvc_gadget_trace_param;
/*-------------------------------------------------------------------------*/
/* module parameters specific to the Video streaming endpoint */
static unsigned int streaming_interval = 1;
module_param(streaming_interval, uint, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(streaming_interval, "1 - 16");
static unsigned int streaming_maxpacket = 1024;
module_param(streaming_maxpacket, uint, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(streaming_maxpacket, "1 - 1023 (FS), 1 - 3072 (hs/ss)");
static unsigned int streaming_maxburst;
module_param(streaming_maxburst, uint, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(streaming_maxburst, "0 - 15 (ss only)");
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Function descriptors * Function descriptors
*/ */
@ -75,7 +64,7 @@ static struct usb_gadget_strings *uvc_function_strings[] = {
#define UVC_STATUS_MAX_PACKET_SIZE 16 /* 16 bytes status */ #define UVC_STATUS_MAX_PACKET_SIZE 16 /* 16 bytes status */
static struct usb_interface_assoc_descriptor uvc_iad __initdata = { static struct usb_interface_assoc_descriptor uvc_iad = {
.bLength = sizeof(uvc_iad), .bLength = sizeof(uvc_iad),
.bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
.bFirstInterface = 0, .bFirstInterface = 0,
@ -86,7 +75,7 @@ static struct usb_interface_assoc_descriptor uvc_iad __initdata = {
.iFunction = 0, .iFunction = 0,
}; };
static struct usb_interface_descriptor uvc_control_intf __initdata = { static struct usb_interface_descriptor uvc_control_intf = {
.bLength = USB_DT_INTERFACE_SIZE, .bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE, .bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = UVC_INTF_VIDEO_CONTROL, .bInterfaceNumber = UVC_INTF_VIDEO_CONTROL,
@ -98,7 +87,7 @@ static struct usb_interface_descriptor uvc_control_intf __initdata = {
.iInterface = 0, .iInterface = 0,
}; };
static struct usb_endpoint_descriptor uvc_control_ep __initdata = { static struct usb_endpoint_descriptor uvc_control_ep = {
.bLength = USB_DT_ENDPOINT_SIZE, .bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT, .bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN, .bEndpointAddress = USB_DIR_IN,
@ -107,7 +96,7 @@ static struct usb_endpoint_descriptor uvc_control_ep __initdata = {
.bInterval = 8, .bInterval = 8,
}; };
static struct usb_ss_ep_comp_descriptor uvc_ss_control_comp __initdata = { static struct usb_ss_ep_comp_descriptor uvc_ss_control_comp = {
.bLength = sizeof(uvc_ss_control_comp), .bLength = sizeof(uvc_ss_control_comp),
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP, .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
/* The following 3 values can be tweaked if necessary. */ /* The following 3 values can be tweaked if necessary. */
@ -116,14 +105,14 @@ static struct usb_ss_ep_comp_descriptor uvc_ss_control_comp __initdata = {
.wBytesPerInterval = cpu_to_le16(UVC_STATUS_MAX_PACKET_SIZE), .wBytesPerInterval = cpu_to_le16(UVC_STATUS_MAX_PACKET_SIZE),
}; };
static struct uvc_control_endpoint_descriptor uvc_control_cs_ep __initdata = { static struct uvc_control_endpoint_descriptor uvc_control_cs_ep = {
.bLength = UVC_DT_CONTROL_ENDPOINT_SIZE, .bLength = UVC_DT_CONTROL_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_CS_ENDPOINT, .bDescriptorType = USB_DT_CS_ENDPOINT,
.bDescriptorSubType = UVC_EP_INTERRUPT, .bDescriptorSubType = UVC_EP_INTERRUPT,
.wMaxTransferSize = cpu_to_le16(UVC_STATUS_MAX_PACKET_SIZE), .wMaxTransferSize = cpu_to_le16(UVC_STATUS_MAX_PACKET_SIZE),
}; };
static struct usb_interface_descriptor uvc_streaming_intf_alt0 __initdata = { static struct usb_interface_descriptor uvc_streaming_intf_alt0 = {
.bLength = USB_DT_INTERFACE_SIZE, .bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE, .bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = UVC_INTF_VIDEO_STREAMING, .bInterfaceNumber = UVC_INTF_VIDEO_STREAMING,
@ -135,7 +124,7 @@ static struct usb_interface_descriptor uvc_streaming_intf_alt0 __initdata = {
.iInterface = 0, .iInterface = 0,
}; };
static struct usb_interface_descriptor uvc_streaming_intf_alt1 __initdata = { static struct usb_interface_descriptor uvc_streaming_intf_alt1 = {
.bLength = USB_DT_INTERFACE_SIZE, .bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE, .bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = UVC_INTF_VIDEO_STREAMING, .bInterfaceNumber = UVC_INTF_VIDEO_STREAMING,
@ -147,7 +136,7 @@ static struct usb_interface_descriptor uvc_streaming_intf_alt1 __initdata = {
.iInterface = 0, .iInterface = 0,
}; };
static struct usb_endpoint_descriptor uvc_fs_streaming_ep __initdata = { static struct usb_endpoint_descriptor uvc_fs_streaming_ep = {
.bLength = USB_DT_ENDPOINT_SIZE, .bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT, .bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN, .bEndpointAddress = USB_DIR_IN,
@ -158,7 +147,7 @@ static struct usb_endpoint_descriptor uvc_fs_streaming_ep __initdata = {
*/ */
}; };
static struct usb_endpoint_descriptor uvc_hs_streaming_ep __initdata = { static struct usb_endpoint_descriptor uvc_hs_streaming_ep = {
.bLength = USB_DT_ENDPOINT_SIZE, .bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT, .bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN, .bEndpointAddress = USB_DIR_IN,
@ -169,7 +158,7 @@ static struct usb_endpoint_descriptor uvc_hs_streaming_ep __initdata = {
*/ */
}; };
static struct usb_endpoint_descriptor uvc_ss_streaming_ep __initdata = { static struct usb_endpoint_descriptor uvc_ss_streaming_ep = {
.bLength = USB_DT_ENDPOINT_SIZE, .bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT, .bDescriptorType = USB_DT_ENDPOINT,
@ -181,7 +170,7 @@ static struct usb_endpoint_descriptor uvc_ss_streaming_ep __initdata = {
*/ */
}; };
static struct usb_ss_ep_comp_descriptor uvc_ss_streaming_comp __initdata = { static struct usb_ss_ep_comp_descriptor uvc_ss_streaming_comp = {
.bLength = sizeof(uvc_ss_streaming_comp), .bLength = sizeof(uvc_ss_streaming_comp),
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP, .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
/* The bMaxBurst, bmAttributes and wBytesPerInterval values will be /* The bMaxBurst, bmAttributes and wBytesPerInterval values will be
@ -208,6 +197,12 @@ static const struct usb_descriptor_header * const uvc_ss_streaming[] = {
NULL, NULL,
}; };
void uvc_set_trace_param(unsigned int trace)
{
uvc_gadget_trace_param = trace;
}
EXPORT_SYMBOL(uvc_set_trace_param);
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Control requests * Control requests
*/ */
@ -251,6 +246,12 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
if (le16_to_cpu(ctrl->wLength) > UVC_MAX_REQUEST_SIZE) if (le16_to_cpu(ctrl->wLength) > UVC_MAX_REQUEST_SIZE)
return -EINVAL; return -EINVAL;
/* Tell the complete callback to generate an event for the next request
* that will be enqueued by UVCIOC_SEND_RESPONSE.
*/
uvc->event_setup_out = !(ctrl->bRequestType & USB_DIR_IN);
uvc->event_length = le16_to_cpu(ctrl->wLength);
memset(&v4l2_event, 0, sizeof(v4l2_event)); memset(&v4l2_event, 0, sizeof(v4l2_event));
v4l2_event.type = UVC_EVENT_SETUP; v4l2_event.type = UVC_EVENT_SETUP;
memcpy(&uvc_event->req, ctrl, sizeof(uvc_event->req)); memcpy(&uvc_event->req, ctrl, sizeof(uvc_event->req));
@ -408,7 +409,9 @@ uvc_register_video(struct uvc_device *uvc)
video->v4l2_dev = &uvc->v4l2_dev; video->v4l2_dev = &uvc->v4l2_dev;
video->fops = &uvc_v4l2_fops; video->fops = &uvc_v4l2_fops;
video->ioctl_ops = &uvc_v4l2_ioctl_ops;
video->release = video_device_release; video->release = video_device_release;
video->vfl_dir = VFL_DIR_TX;
strlcpy(video->name, cdev->gadget->name, sizeof(video->name)); strlcpy(video->name, cdev->gadget->name, sizeof(video->name));
uvc->vdev = video; uvc->vdev = video;
@ -434,7 +437,7 @@ uvc_register_video(struct uvc_device *uvc)
} \ } \
} while (0) } while (0)
static struct usb_descriptor_header ** __init static struct usb_descriptor_header **
uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed)
{ {
struct uvc_input_header_descriptor *uvc_streaming_header; struct uvc_input_header_descriptor *uvc_streaming_header;
@ -554,45 +557,26 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed)
return hdr; return hdr;
} }
static void static int
uvc_function_unbind(struct usb_configuration *c, struct usb_function *f)
{
struct usb_composite_dev *cdev = c->cdev;
struct uvc_device *uvc = to_uvc(f);
INFO(cdev, "uvc_function_unbind\n");
video_unregister_device(uvc->vdev);
v4l2_device_unregister(&uvc->v4l2_dev);
uvc->control_ep->driver_data = NULL;
uvc->video.ep->driver_data = NULL;
uvc_en_us_strings[UVC_STRING_CONTROL_IDX].id = 0;
usb_ep_free_request(cdev->gadget->ep0, uvc->control_req);
kfree(uvc->control_buf);
usb_free_all_descriptors(f);
kfree(uvc);
}
static int __init
uvc_function_bind(struct usb_configuration *c, struct usb_function *f) uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
{ {
struct usb_composite_dev *cdev = c->cdev; struct usb_composite_dev *cdev = c->cdev;
struct uvc_device *uvc = to_uvc(f); struct uvc_device *uvc = to_uvc(f);
struct usb_string *us;
unsigned int max_packet_mult; unsigned int max_packet_mult;
unsigned int max_packet_size; unsigned int max_packet_size;
struct usb_ep *ep; struct usb_ep *ep;
struct f_uvc_opts *opts;
int ret = -EINVAL; int ret = -EINVAL;
INFO(cdev, "uvc_function_bind\n"); INFO(cdev, "uvc_function_bind\n");
opts = to_f_uvc_opts(f->fi);
/* Sanity check the streaming endpoint module parameters. /* Sanity check the streaming endpoint module parameters.
*/ */
streaming_interval = clamp(streaming_interval, 1U, 16U); opts->streaming_interval = clamp(opts->streaming_interval, 1U, 16U);
streaming_maxpacket = clamp(streaming_maxpacket, 1U, 3072U); opts->streaming_maxpacket = clamp(opts->streaming_maxpacket, 1U, 3072U);
streaming_maxburst = min(streaming_maxburst, 15U); opts->streaming_maxburst = min(opts->streaming_maxburst, 15U);
/* Fill in the FS/HS/SS Video Streaming specific descriptors from the /* Fill in the FS/HS/SS Video Streaming specific descriptors from the
* module parameters. * module parameters.
@ -600,30 +584,32 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
* NOTE: We assume that the user knows what they are doing and won't * NOTE: We assume that the user knows what they are doing and won't
* give parameters that their UDC doesn't support. * give parameters that their UDC doesn't support.
*/ */
if (streaming_maxpacket <= 1024) { if (opts->streaming_maxpacket <= 1024) {
max_packet_mult = 1; max_packet_mult = 1;
max_packet_size = streaming_maxpacket; max_packet_size = opts->streaming_maxpacket;
} else if (streaming_maxpacket <= 2048) { } else if (opts->streaming_maxpacket <= 2048) {
max_packet_mult = 2; max_packet_mult = 2;
max_packet_size = streaming_maxpacket / 2; max_packet_size = opts->streaming_maxpacket / 2;
} else { } else {
max_packet_mult = 3; max_packet_mult = 3;
max_packet_size = streaming_maxpacket / 3; max_packet_size = opts->streaming_maxpacket / 3;
} }
uvc_fs_streaming_ep.wMaxPacketSize = min(streaming_maxpacket, 1023U); uvc_fs_streaming_ep.wMaxPacketSize =
uvc_fs_streaming_ep.bInterval = streaming_interval; cpu_to_le16(min(opts->streaming_maxpacket, 1023U));
uvc_fs_streaming_ep.bInterval = opts->streaming_interval;
uvc_hs_streaming_ep.wMaxPacketSize = max_packet_size; uvc_hs_streaming_ep.wMaxPacketSize =
uvc_hs_streaming_ep.wMaxPacketSize |= ((max_packet_mult - 1) << 11); cpu_to_le16(max_packet_size | ((max_packet_mult - 1) << 11));
uvc_hs_streaming_ep.bInterval = streaming_interval; uvc_hs_streaming_ep.bInterval = opts->streaming_interval;
uvc_ss_streaming_ep.wMaxPacketSize = max_packet_size; uvc_ss_streaming_ep.wMaxPacketSize = cpu_to_le16(max_packet_size);
uvc_ss_streaming_ep.bInterval = streaming_interval; uvc_ss_streaming_ep.bInterval = opts->streaming_interval;
uvc_ss_streaming_comp.bmAttributes = max_packet_mult - 1; uvc_ss_streaming_comp.bmAttributes = max_packet_mult - 1;
uvc_ss_streaming_comp.bMaxBurst = streaming_maxburst; uvc_ss_streaming_comp.bMaxBurst = opts->streaming_maxburst;
uvc_ss_streaming_comp.wBytesPerInterval = uvc_ss_streaming_comp.wBytesPerInterval =
max_packet_size * max_packet_mult * streaming_maxburst; cpu_to_le16(max_packet_size * max_packet_mult *
opts->streaming_maxburst);
/* Allocate endpoints. */ /* Allocate endpoints. */
ep = usb_ep_autoconfig(cdev->gadget, &uvc_control_ep); ep = usb_ep_autoconfig(cdev->gadget, &uvc_control_ep);
@ -653,6 +639,18 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
uvc_hs_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; uvc_ss_streaming_ep.bEndpointAddress = uvc->video.ep->address;
us = usb_gstrings_attach(cdev, uvc_function_strings,
ARRAY_SIZE(uvc_en_us_strings));
if (IS_ERR(us)) {
ret = PTR_ERR(us);
goto error;
}
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;
/* Allocate interface IDs. */ /* Allocate interface IDs. */
if ((ret = usb_interface_id(c, f)) < 0) if ((ret = usb_interface_id(c, f)) < 0)
goto error; goto error;
@ -697,7 +695,7 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
} }
/* Initialise video. */ /* Initialise video. */
ret = uvc_video_init(&uvc->video); ret = uvcg_video_init(&uvc->video);
if (ret < 0) if (ret < 0)
goto error; goto error;
@ -720,10 +718,9 @@ error:
if (uvc->video.ep) if (uvc->video.ep)
uvc->video.ep->driver_data = NULL; uvc->video.ep->driver_data = NULL;
if (uvc->control_req) { if (uvc->control_req)
usb_ep_free_request(cdev->gadget->ep0, uvc->control_req); usb_ep_free_request(cdev->gadget->ep0, uvc->control_req);
kfree(uvc->control_buf); kfree(uvc->control_buf);
}
usb_free_all_descriptors(f); usb_free_all_descriptors(f);
return ret; return ret;
@ -733,104 +730,81 @@ error:
* USB gadget function * USB gadget function
*/ */
/** static void uvc_free_inst(struct usb_function_instance *f)
* uvc_bind_config - add a UVC function to a configuration {
* @c: the configuration to support the UVC instance struct f_uvc_opts *opts = to_f_uvc_opts(f);
* Context: single threaded during gadget setup
* kfree(opts);
* Returns zero on success, else negative errno. }
*
* Caller must have called @uvc_setup(). Caller is also responsible for static struct usb_function_instance *uvc_alloc_inst(void)
* calling @uvc_cleanup() before module unload. {
*/ struct f_uvc_opts *opts;
int __init
uvc_bind_config(struct usb_configuration *c, opts = kzalloc(sizeof(*opts), GFP_KERNEL);
const struct uvc_descriptor_header * const *fs_control, if (!opts)
const struct uvc_descriptor_header * const *ss_control, return ERR_PTR(-ENOMEM);
const struct uvc_descriptor_header * const *fs_streaming, opts->func_inst.free_func_inst = uvc_free_inst;
const struct uvc_descriptor_header * const *hs_streaming,
const struct uvc_descriptor_header * const *ss_streaming) return &opts->func_inst;
}
static void uvc_free(struct usb_function *f)
{
struct uvc_device *uvc = to_uvc(f);
kfree(uvc);
}
static void uvc_unbind(struct usb_configuration *c, struct usb_function *f)
{
struct usb_composite_dev *cdev = c->cdev;
struct uvc_device *uvc = to_uvc(f);
INFO(cdev, "%s\n", __func__);
video_unregister_device(uvc->vdev);
v4l2_device_unregister(&uvc->v4l2_dev);
uvc->control_ep->driver_data = NULL;
uvc->video.ep->driver_data = NULL;
usb_ep_free_request(cdev->gadget->ep0, uvc->control_req);
kfree(uvc->control_buf);
usb_free_all_descriptors(f);
}
static struct usb_function *uvc_alloc(struct usb_function_instance *fi)
{ {
struct uvc_device *uvc; struct uvc_device *uvc;
int ret = 0; struct f_uvc_opts *opts;
/* TODO Check if the USB device controller supports the required
* features.
*/
if (!gadget_is_dualspeed(c->cdev->gadget))
return -EINVAL;
uvc = kzalloc(sizeof(*uvc), GFP_KERNEL); uvc = kzalloc(sizeof(*uvc), GFP_KERNEL);
if (uvc == NULL) if (uvc == NULL)
return -ENOMEM; return ERR_PTR(-ENOMEM);
uvc->state = UVC_STATE_DISCONNECTED; uvc->state = UVC_STATE_DISCONNECTED;
opts = to_f_uvc_opts(fi);
/* Validate the descriptors. */ uvc->desc.fs_control = opts->fs_control;
if (fs_control == NULL || fs_control[0] == NULL || uvc->desc.ss_control = opts->ss_control;
fs_control[0]->bDescriptorSubType != UVC_VC_HEADER) uvc->desc.fs_streaming = opts->fs_streaming;
goto error; uvc->desc.hs_streaming = opts->hs_streaming;
uvc->desc.ss_streaming = opts->ss_streaming;
if (ss_control == NULL || ss_control[0] == NULL ||
ss_control[0]->bDescriptorSubType != UVC_VC_HEADER)
goto error;
if (fs_streaming == NULL || fs_streaming[0] == NULL ||
fs_streaming[0]->bDescriptorSubType != UVC_VS_INPUT_HEADER)
goto error;
if (hs_streaming == NULL || hs_streaming[0] == NULL ||
hs_streaming[0]->bDescriptorSubType != UVC_VS_INPUT_HEADER)
goto error;
if (ss_streaming == NULL || ss_streaming[0] == NULL ||
ss_streaming[0]->bDescriptorSubType != UVC_VS_INPUT_HEADER)
goto error;
uvc->desc.fs_control = fs_control;
uvc->desc.ss_control = ss_control;
uvc->desc.fs_streaming = fs_streaming;
uvc->desc.hs_streaming = hs_streaming;
uvc->desc.ss_streaming = ss_streaming;
/* String descriptors are global, we only need to allocate string IDs
* for the first UVC function. UVC functions beyond the first (if any)
* will reuse the same IDs.
*/
if (uvc_en_us_strings[UVC_STRING_CONTROL_IDX].id == 0) {
ret = usb_string_ids_tab(c->cdev, uvc_en_us_strings);
if (ret)
goto error;
uvc_iad.iFunction =
uvc_en_us_strings[UVC_STRING_CONTROL_IDX].id;
uvc_control_intf.iInterface =
uvc_en_us_strings[UVC_STRING_CONTROL_IDX].id;
ret = uvc_en_us_strings[UVC_STRING_STREAMING_IDX].id;
uvc_streaming_intf_alt0.iInterface = ret;
uvc_streaming_intf_alt1.iInterface = ret;
}
/* Register the function. */ /* Register the function. */
uvc->func.name = "uvc"; uvc->func.name = "uvc";
uvc->func.strings = uvc_function_strings;
uvc->func.bind = uvc_function_bind; uvc->func.bind = uvc_function_bind;
uvc->func.unbind = uvc_function_unbind; uvc->func.unbind = uvc_unbind;
uvc->func.get_alt = uvc_function_get_alt; uvc->func.get_alt = uvc_function_get_alt;
uvc->func.set_alt = uvc_function_set_alt; uvc->func.set_alt = uvc_function_set_alt;
uvc->func.disable = uvc_function_disable; uvc->func.disable = uvc_function_disable;
uvc->func.setup = uvc_function_setup; uvc->func.setup = uvc_function_setup;
uvc->func.free_func = uvc_free;
ret = usb_add_function(c, &uvc->func); return &uvc->func;
if (ret)
kfree(uvc);
return ret;
error:
kfree(uvc);
return ret;
} }
module_param_named(trace, uvc_gadget_trace_param, uint, S_IRUGO|S_IWUSR); DECLARE_USB_FUNCTION_INIT(uvc, uvc_alloc_inst, uvc_alloc);
MODULE_PARM_DESC(trace, "Trace level bitmask"); MODULE_LICENSE("GPL");
MODULE_AUTHOR("Laurent Pinchart");

View File

@ -16,12 +16,13 @@
#include <linux/usb/composite.h> #include <linux/usb/composite.h>
#include <linux/usb/video.h> #include <linux/usb/video.h>
int uvc_bind_config(struct usb_configuration *c, #include "uvc.h"
const struct uvc_descriptor_header * const *fs_control,
const struct uvc_descriptor_header * const *hs_control, void uvc_function_setup_continue(struct uvc_device *uvc);
const struct uvc_descriptor_header * const *fs_streaming,
const struct uvc_descriptor_header * const *hs_streaming, void uvc_function_connect(struct uvc_device *uvc);
const struct uvc_descriptor_header * const *ss_streaming);
void uvc_function_disconnect(struct uvc_device *uvc);
#endif /* _F_UVC_H_ */ #endif /* _F_UVC_H_ */

View File

@ -10,6 +10,8 @@
#define GZERO_QLEN 32 #define GZERO_QLEN 32
#define GZERO_ISOC_INTERVAL 4 #define GZERO_ISOC_INTERVAL 4
#define GZERO_ISOC_MAXPACKET 1024 #define GZERO_ISOC_MAXPACKET 1024
#define GZERO_INT_INTERVAL 1 /* Default interrupt interval = 1 ms */
#define GZERO_INT_MAXPACKET 1024
struct usb_zero_options { struct usb_zero_options {
unsigned pattern; unsigned pattern;
@ -17,6 +19,10 @@ struct usb_zero_options {
unsigned isoc_maxpacket; unsigned isoc_maxpacket;
unsigned isoc_mult; unsigned isoc_mult;
unsigned isoc_maxburst; unsigned isoc_maxburst;
unsigned int_interval; /* In ms */
unsigned int_maxpacket;
unsigned int_mult;
unsigned int_maxburst;
unsigned bulk_buflen; unsigned bulk_buflen;
unsigned qlen; unsigned qlen;
}; };
@ -28,6 +34,10 @@ struct f_ss_opts {
unsigned isoc_maxpacket; unsigned isoc_maxpacket;
unsigned isoc_mult; unsigned isoc_mult;
unsigned isoc_maxburst; unsigned isoc_maxburst;
unsigned int_interval; /* In ms */
unsigned int_maxpacket;
unsigned int_mult;
unsigned int_maxburst;
unsigned bulk_buflen; unsigned bulk_buflen;
/* /*
@ -62,6 +72,7 @@ int lb_modinit(void);
void free_ep_req(struct usb_ep *ep, struct usb_request *req); void free_ep_req(struct usb_ep *ep, struct usb_request *req);
void disable_endpoints(struct usb_composite_dev *cdev, void disable_endpoints(struct usb_composite_dev *cdev,
struct usb_ep *in, struct usb_ep *out, struct usb_ep *in, struct usb_ep *out,
struct usb_ep *iso_in, struct usb_ep *iso_out); struct usb_ep *iso_in, struct usb_ep *iso_out,
struct usb_ep *int_in, struct usb_ep *int_out);
#endif /* __G_ZERO_H */ #endif /* __G_ZERO_H */

View File

@ -224,6 +224,8 @@ struct ffs_data {
void *ms_os_descs_ext_prop_name_avail; void *ms_os_descs_ext_prop_name_avail;
void *ms_os_descs_ext_prop_data_avail; void *ms_os_descs_ext_prop_data_avail;
unsigned user_flags;
u8 eps_addrmap[15]; u8 eps_addrmap[15];
unsigned short strings_count; unsigned short strings_count;

View File

@ -55,11 +55,8 @@
* for a telephone or fax link. And ttyGS2 might be something that just * for a telephone or fax link. And ttyGS2 might be something that just
* needs a simple byte stream interface for some messaging protocol that * needs a simple byte stream interface for some messaging protocol that
* is managed in userspace ... OBEX, PTP, and MTP have been mentioned. * is managed in userspace ... OBEX, PTP, and MTP have been mentioned.
*/ *
*
#define PREFIX "ttyGS"
/*
* gserial is the lifecycle interface, used by USB functions * gserial is the lifecycle interface, used by USB functions
* gs_port is the I/O nexus, used by the tty driver * gs_port is the I/O nexus, used by the tty driver
* tty_struct links to the tty/filesystem framework * tty_struct links to the tty/filesystem framework
@ -385,9 +382,9 @@ __acquires(&port->port_lock)
list_del(&req->list); list_del(&req->list);
req->zero = (gs_buf_data_avail(&port->port_write_buf) == 0); req->zero = (gs_buf_data_avail(&port->port_write_buf) == 0);
pr_vdebug(PREFIX "%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n", pr_vdebug("ttyGS%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n",
port->port_num, len, *((u8 *)req->buf), port->port_num, len, *((u8 *)req->buf),
*((u8 *)req->buf+1), *((u8 *)req->buf+2)); *((u8 *)req->buf+1), *((u8 *)req->buf+2));
/* Drop lock while we call out of driver; completions /* Drop lock while we call out of driver; completions
* could be issued while we do so. Disconnection may * could be issued while we do so. Disconnection may
@ -503,13 +500,13 @@ static void gs_rx_push(unsigned long _port)
switch (req->status) { switch (req->status) {
case -ESHUTDOWN: case -ESHUTDOWN:
disconnect = true; disconnect = true;
pr_vdebug(PREFIX "%d: shutdown\n", port->port_num); pr_vdebug("ttyGS%d: shutdown\n", port->port_num);
break; break;
default: default:
/* presumably a transient fault */ /* presumably a transient fault */
pr_warning(PREFIX "%d: unexpected RX status %d\n", pr_warn("ttyGS%d: unexpected RX status %d\n",
port->port_num, req->status); port->port_num, req->status);
/* FALLTHROUGH */ /* FALLTHROUGH */
case 0: case 0:
/* normal completion */ /* normal completion */
@ -537,9 +534,8 @@ static void gs_rx_push(unsigned long _port)
if (count != size) { if (count != size) {
/* stop pushing; TTY layer can't handle more */ /* stop pushing; TTY layer can't handle more */
port->n_read += count; port->n_read += count;
pr_vdebug(PREFIX "%d: rx block %d/%d\n", pr_vdebug("ttyGS%d: rx block %d/%d\n",
port->port_num, port->port_num, count, req->actual);
count, req->actual);
break; break;
} }
port->n_read = 0; port->n_read = 0;
@ -569,7 +565,7 @@ static void gs_rx_push(unsigned long _port)
if (do_push) if (do_push)
tasklet_schedule(&port->push); tasklet_schedule(&port->push);
else else
pr_warning(PREFIX "%d: RX not scheduled?\n", pr_warn("ttyGS%d: RX not scheduled?\n",
port->port_num); port->port_num);
} }
} }
@ -985,7 +981,7 @@ static void gs_unthrottle(struct tty_struct *tty)
* read queue backs up enough we'll be NAKing OUT packets. * read queue backs up enough we'll be NAKing OUT packets.
*/ */
tasklet_schedule(&port->push); tasklet_schedule(&port->push);
pr_vdebug(PREFIX "%d: unthrottle\n", port->port_num); pr_vdebug("ttyGS%d: unthrottle\n", port->port_num);
} }
spin_unlock_irqrestore(&port->port_lock, flags); spin_unlock_irqrestore(&port->port_lock, flags);
} }
@ -1295,7 +1291,7 @@ static int userial_init(void)
return -ENOMEM; return -ENOMEM;
gs_tty_driver->driver_name = "g_serial"; gs_tty_driver->driver_name = "g_serial";
gs_tty_driver->name = PREFIX; gs_tty_driver->name = "ttyGS";
/* uses dynamically assigned dev_t values */ /* uses dynamically assigned dev_t values */
gs_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; gs_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;

View File

@ -10,6 +10,7 @@
*/ */
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/delay.h> #include <linux/delay.h>
@ -23,22 +24,6 @@
* This component encapsulates the ALSA devices for USB audio gadget * This component encapsulates the ALSA devices for USB audio gadget
*/ */
#define FILE_PCM_PLAYBACK "/dev/snd/pcmC0D0p"
#define FILE_PCM_CAPTURE "/dev/snd/pcmC0D0c"
#define FILE_CONTROL "/dev/snd/controlC0"
static char *fn_play = FILE_PCM_PLAYBACK;
module_param(fn_play, charp, S_IRUGO);
MODULE_PARM_DESC(fn_play, "Playback PCM device file name");
static char *fn_cap = FILE_PCM_CAPTURE;
module_param(fn_cap, charp, S_IRUGO);
MODULE_PARM_DESC(fn_cap, "Capture PCM device file name");
static char *fn_cntl = FILE_CONTROL;
module_param(fn_cntl, charp, S_IRUGO);
MODULE_PARM_DESC(fn_cntl, "Control device file name");
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
/** /**
@ -167,7 +152,7 @@ static int playback_default_hw_params(struct gaudio_snd_dev *snd)
/** /**
* Playback audio buffer data by ALSA PCM device * Playback audio buffer data by ALSA PCM device
*/ */
static size_t u_audio_playback(struct gaudio *card, void *buf, size_t count) size_t u_audio_playback(struct gaudio *card, void *buf, size_t count)
{ {
struct gaudio_snd_dev *snd = &card->playback; struct gaudio_snd_dev *snd = &card->playback;
struct snd_pcm_substream *substream = snd->substream; struct snd_pcm_substream *substream = snd->substream;
@ -202,12 +187,12 @@ try_again:
return 0; return 0;
} }
static int u_audio_get_playback_channels(struct gaudio *card) int u_audio_get_playback_channels(struct gaudio *card)
{ {
return card->playback.channels; return card->playback.channels;
} }
static int u_audio_get_playback_rate(struct gaudio *card) int u_audio_get_playback_rate(struct gaudio *card)
{ {
return card->playback.rate; return card->playback.rate;
} }
@ -220,6 +205,13 @@ static int gaudio_open_snd_dev(struct gaudio *card)
{ {
struct snd_pcm_file *pcm_file; struct snd_pcm_file *pcm_file;
struct gaudio_snd_dev *snd; struct gaudio_snd_dev *snd;
struct f_uac1_opts *opts;
char *fn_play, *fn_cap, *fn_cntl;
opts = container_of(card->func.fi, struct f_uac1_opts, func_inst);
fn_play = opts->fn_play;
fn_cap = opts->fn_cap;
fn_cntl = opts->fn_cntl;
if (!card) if (!card)
return -ENODEV; return -ENODEV;
@ -293,7 +285,6 @@ static int gaudio_close_snd_dev(struct gaudio *gau)
return 0; return 0;
} }
static struct gaudio *the_card;
/** /**
* gaudio_setup - setup ALSA interface and preparing for USB transfer * gaudio_setup - setup ALSA interface and preparing for USB transfer
* *
@ -301,15 +292,13 @@ static struct gaudio *the_card;
* *
* Returns negative errno, or zero on success * Returns negative errno, or zero on success
*/ */
int __init gaudio_setup(struct gaudio *card) int gaudio_setup(struct gaudio *card)
{ {
int ret; int ret;
ret = gaudio_open_snd_dev(card); ret = gaudio_open_snd_dev(card);
if (ret) if (ret)
ERROR(card, "we need at least one control device\n"); ERROR(card, "we need at least one control device\n");
else if (!the_card)
the_card = card;
return ret; return ret;
@ -320,11 +309,10 @@ int __init gaudio_setup(struct gaudio *card)
* *
* This is called to free all resources allocated by @gaudio_setup(). * This is called to free all resources allocated by @gaudio_setup().
*/ */
void gaudio_cleanup(void) void gaudio_cleanup(struct gaudio *the_card)
{ {
if (the_card) { if (the_card) {
gaudio_close_snd_dev(the_card); gaudio_close_snd_dev(the_card);
the_card = NULL;
} }
} }

View File

@ -23,6 +23,14 @@
#include "gadget_chips.h" #include "gadget_chips.h"
#define FILE_PCM_PLAYBACK "/dev/snd/pcmC0D0p"
#define FILE_PCM_CAPTURE "/dev/snd/pcmC0D0c"
#define FILE_CONTROL "/dev/snd/controlC0"
#define UAC1_OUT_EP_MAX_PACKET_SIZE 200
#define UAC1_REQ_COUNT 256
#define UAC1_AUDIO_BUF_SIZE 48000
/* /*
* This represents the USB side of an audio card device, managed by a USB * This represents the USB side of an audio card device, managed by a USB
* function which provides control and stream interfaces. * function which provides control and stream interfaces.
@ -50,7 +58,28 @@ struct gaudio {
/* TODO */ /* TODO */
}; };
struct f_uac1_opts {
struct usb_function_instance func_inst;
int req_buf_size;
int req_count;
int audio_buf_size;
char *fn_play;
char *fn_cap;
char *fn_cntl;
unsigned bound:1;
unsigned fn_play_alloc:1;
unsigned fn_cap_alloc:1;
unsigned fn_cntl_alloc:1;
struct gaudio *card;
struct mutex lock;
int refcnt;
};
int gaudio_setup(struct gaudio *card); int gaudio_setup(struct gaudio *card);
void gaudio_cleanup(void); void gaudio_cleanup(struct gaudio *the_card);
size_t u_audio_playback(struct gaudio *card, void *buf, size_t count);
int u_audio_get_playback_channels(struct gaudio *card);
int u_audio_get_playback_rate(struct gaudio *card);
#endif /* __U_AUDIO_H */ #endif /* __U_AUDIO_H */

View File

@ -0,0 +1,42 @@
/*
* 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>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef U_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
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;
bool bound;
struct mutex lock;
int refcnt;
};
#endif

View File

@ -0,0 +1,39 @@
/*
* u_uvc.h
*
* Utility definitions for the uvc function
*
* Copyright (c) 2013-2014 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef U_UVC_H
#define U_UVC_H
#include <linux/usb/composite.h>
#define to_f_uvc_opts(f) container_of(f, struct f_uvc_opts, func_inst)
struct f_uvc_opts {
struct usb_function_instance func_inst;
unsigned int uvc_gadget_trace_param;
unsigned int streaming_interval;
unsigned int streaming_maxpacket;
unsigned int streaming_maxburst;
const struct uvc_descriptor_header * const *fs_control;
const struct uvc_descriptor_header * const *ss_control;
const struct uvc_descriptor_header * const *fs_streaming;
const struct uvc_descriptor_header * const *hs_streaming;
const struct uvc_descriptor_header * const *ss_streaming;
};
void uvc_set_trace_param(unsigned int trace);
#endif /* U_UVC_H */

View File

@ -53,6 +53,7 @@ struct uvc_event
#ifdef __KERNEL__ #ifdef __KERNEL__
#include <linux/usb.h> /* For usb_endpoint_* */ #include <linux/usb.h> /* For usb_endpoint_* */
#include <linux/usb/composite.h>
#include <linux/usb/gadget.h> #include <linux/usb/gadget.h>
#include <linux/videodev2.h> #include <linux/videodev2.h>
#include <linux/version.h> #include <linux/version.h>
@ -96,9 +97,6 @@ extern unsigned int uvc_gadget_trace_param;
* Driver specific constants * Driver specific constants
*/ */
#define DRIVER_VERSION "0.1.0"
#define DRIVER_VERSION_NUMBER KERNEL_VERSION(0, 1, 0)
#define UVC_NUM_REQUESTS 4 #define UVC_NUM_REQUESTS 4
#define UVC_MAX_REQUEST_SIZE 64 #define UVC_MAX_REQUEST_SIZE 64
#define UVC_MAX_EVENTS 4 #define UVC_MAX_EVENTS 4

View File

@ -28,7 +28,7 @@
/* ------------------------------------------------------------------------ /* ------------------------------------------------------------------------
* Video buffers queue management. * Video buffers queue management.
* *
* Video queues is initialized by uvc_queue_init(). The function performs * Video queues is initialized by uvcg_queue_init(). The function performs
* basic initialization of the uvc_video_queue struct and never fails. * basic initialization of the uvc_video_queue struct and never fails.
* *
* Video buffers are managed by videobuf2. The driver uses a mutex to protect * Video buffers are managed by videobuf2. The driver uses a mutex to protect
@ -126,13 +126,12 @@ static struct vb2_ops uvc_queue_qops = {
.wait_finish = uvc_wait_finish, .wait_finish = uvc_wait_finish,
}; };
static int uvc_queue_init(struct uvc_video_queue *queue, int uvcg_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type)
enum v4l2_buf_type type)
{ {
int ret; int ret;
queue->queue.type = type; queue->queue.type = type;
queue->queue.io_modes = VB2_MMAP | VB2_USERPTR; queue->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
queue->queue.drv_priv = queue; queue->queue.drv_priv = queue;
queue->queue.buf_struct_size = sizeof(struct uvc_buffer); queue->queue.buf_struct_size = sizeof(struct uvc_buffer);
queue->queue.ops = &uvc_queue_qops; queue->queue.ops = &uvc_queue_qops;
@ -154,7 +153,7 @@ static int uvc_queue_init(struct uvc_video_queue *queue,
/* /*
* Free the video buffers. * Free the video buffers.
*/ */
static void uvc_free_buffers(struct uvc_video_queue *queue) void uvcg_free_buffers(struct uvc_video_queue *queue)
{ {
mutex_lock(&queue->mutex); mutex_lock(&queue->mutex);
vb2_queue_release(&queue->queue); vb2_queue_release(&queue->queue);
@ -164,8 +163,8 @@ static void uvc_free_buffers(struct uvc_video_queue *queue)
/* /*
* Allocate the video buffers. * Allocate the video buffers.
*/ */
static int uvc_alloc_buffers(struct uvc_video_queue *queue, int uvcg_alloc_buffers(struct uvc_video_queue *queue,
struct v4l2_requestbuffers *rb) struct v4l2_requestbuffers *rb)
{ {
int ret; int ret;
@ -176,8 +175,7 @@ static int uvc_alloc_buffers(struct uvc_video_queue *queue,
return ret ? ret : rb->count; return ret ? ret : rb->count;
} }
static int uvc_query_buffer(struct uvc_video_queue *queue, int uvcg_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
struct v4l2_buffer *buf)
{ {
int ret; int ret;
@ -188,8 +186,7 @@ static int uvc_query_buffer(struct uvc_video_queue *queue,
return ret; return ret;
} }
static int uvc_queue_buffer(struct uvc_video_queue *queue, int uvcg_queue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
struct v4l2_buffer *buf)
{ {
unsigned long flags; unsigned long flags;
int ret; int ret;
@ -213,8 +210,8 @@ done:
* Dequeue a video buffer. If nonblocking is false, block until a buffer is * Dequeue a video buffer. If nonblocking is false, block until a buffer is
* available. * available.
*/ */
static int uvc_dequeue_buffer(struct uvc_video_queue *queue, int uvcg_dequeue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf,
struct v4l2_buffer *buf, int nonblocking) int nonblocking)
{ {
int ret; int ret;
@ -231,8 +228,8 @@ static int uvc_dequeue_buffer(struct uvc_video_queue *queue,
* This function implements video queue polling and is intended to be used by * This function implements video queue polling and is intended to be used by
* the device poll handler. * the device poll handler.
*/ */
static unsigned int uvc_queue_poll(struct uvc_video_queue *queue, unsigned int uvcg_queue_poll(struct uvc_video_queue *queue, struct file *file,
struct file *file, poll_table *wait) poll_table *wait)
{ {
unsigned int ret; unsigned int ret;
@ -243,8 +240,7 @@ static unsigned int uvc_queue_poll(struct uvc_video_queue *queue,
return ret; return ret;
} }
static int uvc_queue_mmap(struct uvc_video_queue *queue, int uvcg_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma)
struct vm_area_struct *vma)
{ {
int ret; int ret;
@ -261,8 +257,8 @@ static int uvc_queue_mmap(struct uvc_video_queue *queue,
* *
* NO-MMU arch need this function to make mmap() work correctly. * NO-MMU arch need this function to make mmap() work correctly.
*/ */
static unsigned long uvc_queue_get_unmapped_area(struct uvc_video_queue *queue, unsigned long uvcg_queue_get_unmapped_area(struct uvc_video_queue *queue,
unsigned long pgoff) unsigned long pgoff)
{ {
unsigned long ret; unsigned long ret;
@ -285,7 +281,7 @@ static unsigned long uvc_queue_get_unmapped_area(struct uvc_video_queue *queue,
* This function acquires the irq spinlock and can be called from interrupt * This function acquires the irq spinlock and can be called from interrupt
* context. * context.
*/ */
static void uvc_queue_cancel(struct uvc_video_queue *queue, int disconnect) void uvcg_queue_cancel(struct uvc_video_queue *queue, int disconnect)
{ {
struct uvc_buffer *buf; struct uvc_buffer *buf;
unsigned long flags; unsigned long flags;
@ -324,9 +320,9 @@ static void uvc_queue_cancel(struct uvc_video_queue *queue, int disconnect)
* the main queue. * the main queue.
* *
* This function can't be called from interrupt context. Use * This function can't be called from interrupt context. Use
* uvc_queue_cancel() instead. * uvcg_queue_cancel() instead.
*/ */
static int uvc_queue_enable(struct uvc_video_queue *queue, int enable) int uvcg_queue_enable(struct uvc_video_queue *queue, int enable)
{ {
unsigned long flags; unsigned long flags;
int ret = 0; int ret = 0;
@ -363,8 +359,8 @@ done:
} }
/* called with &queue_irqlock held.. */ /* called with &queue_irqlock held.. */
static struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue, struct uvc_buffer *uvcg_queue_next_buffer(struct uvc_video_queue *queue,
struct uvc_buffer *buf) struct uvc_buffer *buf)
{ {
struct uvc_buffer *nextbuf; struct uvc_buffer *nextbuf;
@ -392,7 +388,7 @@ static struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue,
return nextbuf; return nextbuf;
} }
static struct uvc_buffer *uvc_queue_head(struct uvc_video_queue *queue) struct uvc_buffer *uvcg_queue_head(struct uvc_video_queue *queue)
{ {
struct uvc_buffer *buf = NULL; struct uvc_buffer *buf = NULL;

View File

@ -57,6 +57,39 @@ static inline int uvc_queue_streaming(struct uvc_video_queue *queue)
return vb2_is_streaming(&queue->queue); return vb2_is_streaming(&queue->queue);
} }
int uvcg_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type);
void uvcg_free_buffers(struct uvc_video_queue *queue);
int uvcg_alloc_buffers(struct uvc_video_queue *queue,
struct v4l2_requestbuffers *rb);
int uvcg_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf);
int uvcg_queue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf);
int uvcg_dequeue_buffer(struct uvc_video_queue *queue,
struct v4l2_buffer *buf, int nonblocking);
unsigned int uvcg_queue_poll(struct uvc_video_queue *queue,
struct file *file, poll_table *wait);
int uvcg_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma);
#ifndef CONFIG_MMU
unsigned long uvcg_queue_get_unmapped_area(struct uvc_video_queue *queue,
unsigned long pgoff);
#endif /* CONFIG_MMU */
void uvcg_queue_cancel(struct uvc_video_queue *queue, int disconnect);
int uvcg_queue_enable(struct uvc_video_queue *queue, int enable);
struct uvc_buffer *uvcg_queue_next_buffer(struct uvc_video_queue *queue,
struct uvc_buffer *buf);
struct uvc_buffer *uvcg_queue_head(struct uvc_video_queue *queue);
#endif /* __KERNEL__ */ #endif /* __KERNEL__ */
#endif /* _UVC_QUEUE_H_ */ #endif /* _UVC_QUEUE_H_ */

View File

@ -23,8 +23,10 @@
#include <media/v4l2-event.h> #include <media/v4l2-event.h>
#include <media/v4l2-ioctl.h> #include <media/v4l2-ioctl.h>
#include "f_uvc.h"
#include "uvc.h" #include "uvc.h"
#include "uvc_queue.h" #include "uvc_queue.h"
#include "uvc_video.h"
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Requests handling * Requests handling
@ -48,7 +50,7 @@ uvc_send_response(struct uvc_device *uvc, struct uvc_request_data *data)
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* V4L2 * V4L2 ioctls
*/ */
struct uvc_format struct uvc_format
@ -63,8 +65,29 @@ static struct uvc_format uvc_formats[] = {
}; };
static int static int
uvc_v4l2_get_format(struct uvc_video *video, struct v4l2_format *fmt) uvc_v4l2_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
{ {
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct usb_composite_dev *cdev = uvc->func.config->cdev;
strlcpy(cap->driver, "g_uvc", sizeof(cap->driver));
strlcpy(cap->card, cdev->gadget->name, sizeof(cap->card));
strlcpy(cap->bus_info, dev_name(&cdev->gadget->dev),
sizeof(cap->bus_info));
cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
return 0;
}
static int
uvc_v4l2_get_format(struct file *file, void *fh, struct v4l2_format *fmt)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
fmt->fmt.pix.pixelformat = video->fcc; fmt->fmt.pix.pixelformat = video->fcc;
fmt->fmt.pix.width = video->width; fmt->fmt.pix.width = video->width;
fmt->fmt.pix.height = video->height; fmt->fmt.pix.height = video->height;
@ -78,8 +101,11 @@ uvc_v4l2_get_format(struct uvc_video *video, struct v4l2_format *fmt)
} }
static int static int
uvc_v4l2_set_format(struct uvc_video *video, struct v4l2_format *fmt) uvc_v4l2_set_format(struct file *file, void *fh, struct v4l2_format *fmt)
{ {
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
struct uvc_format *format; struct uvc_format *format;
unsigned int imagesize; unsigned int imagesize;
unsigned int bpl; unsigned int bpl;
@ -115,6 +141,145 @@ uvc_v4l2_set_format(struct uvc_video *video, struct v4l2_format *fmt)
return 0; return 0;
} }
static int
uvc_v4l2_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *b)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
if (b->type != video->queue.queue.type)
return -EINVAL;
return uvcg_alloc_buffers(&video->queue, b);
}
static int
uvc_v4l2_querybuf(struct file *file, void *fh, struct v4l2_buffer *b)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
return uvcg_query_buffer(&video->queue, b);
}
static int
uvc_v4l2_qbuf(struct file *file, void *fh, struct v4l2_buffer *b)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
int ret;
ret = uvcg_queue_buffer(&video->queue, b);
if (ret < 0)
return ret;
return uvcg_video_pump(video);
}
static int
uvc_v4l2_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
return uvcg_dequeue_buffer(&video->queue, b, file->f_flags & O_NONBLOCK);
}
static int
uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
int ret;
if (type != video->queue.queue.type)
return -EINVAL;
/* 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.
*/
uvc_function_setup_continue(uvc);
uvc->state = UVC_STATE_STREAMING;
return 0;
}
static int
uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_video *video = &uvc->video;
if (type != video->queue.queue.type)
return -EINVAL;
return uvcg_video_enable(video, 0);
}
static int
uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
const struct v4l2_event_subscription *sub)
{
if (sub->type < UVC_EVENT_FIRST || sub->type > UVC_EVENT_LAST)
return -EINVAL;
return v4l2_event_subscribe(fh, sub, 2, NULL);
}
static int
uvc_v4l2_unsubscribe_event(struct v4l2_fh *fh,
const struct v4l2_event_subscription *sub)
{
return v4l2_event_unsubscribe(fh, sub);
}
static long
uvc_v4l2_ioctl_default(struct file *file, void *fh, bool valid_prio,
unsigned int cmd, void *arg)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
switch (cmd) {
case UVCIOC_SEND_RESPONSE:
return uvc_send_response(uvc, arg);
default:
return -ENOIOCTLCMD;
}
}
const struct v4l2_ioctl_ops uvc_v4l2_ioctl_ops = {
.vidioc_querycap = uvc_v4l2_querycap,
.vidioc_g_fmt_vid_out = uvc_v4l2_get_format,
.vidioc_s_fmt_vid_out = uvc_v4l2_set_format,
.vidioc_reqbufs = uvc_v4l2_reqbufs,
.vidioc_querybuf = uvc_v4l2_querybuf,
.vidioc_qbuf = uvc_v4l2_qbuf,
.vidioc_dqbuf = uvc_v4l2_dqbuf,
.vidioc_streamon = uvc_v4l2_streamon,
.vidioc_streamoff = uvc_v4l2_streamoff,
.vidioc_subscribe_event = uvc_v4l2_subscribe_event,
.vidioc_unsubscribe_event = uvc_v4l2_unsubscribe_event,
.vidioc_default = uvc_v4l2_ioctl_default,
};
/* --------------------------------------------------------------------------
* V4L2
*/
static int static int
uvc_v4l2_open(struct file *file) uvc_v4l2_open(struct file *file)
{ {
@ -146,8 +311,8 @@ uvc_v4l2_release(struct file *file)
uvc_function_disconnect(uvc); uvc_function_disconnect(uvc);
uvc_video_enable(video, 0); uvcg_video_enable(video, 0);
uvc_free_buffers(&video->queue); uvcg_free_buffers(&video->queue);
file->private_data = NULL; file->private_data = NULL;
v4l2_fh_del(&handle->vfh); v4l2_fh_del(&handle->vfh);
@ -157,177 +322,13 @@ uvc_v4l2_release(struct file *file)
return 0; return 0;
} }
static long
uvc_v4l2_do_ioctl(struct file *file, unsigned int cmd, void *arg)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_file_handle *handle = to_uvc_file_handle(file->private_data);
struct usb_composite_dev *cdev = uvc->func.config->cdev;
struct uvc_video *video = &uvc->video;
int ret = 0;
switch (cmd) {
/* Query capabilities */
case VIDIOC_QUERYCAP:
{
struct v4l2_capability *cap = arg;
memset(cap, 0, sizeof *cap);
strlcpy(cap->driver, "g_uvc", sizeof(cap->driver));
strlcpy(cap->card, cdev->gadget->name, sizeof(cap->card));
strlcpy(cap->bus_info, dev_name(&cdev->gadget->dev),
sizeof cap->bus_info);
cap->version = DRIVER_VERSION_NUMBER;
cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
break;
}
/* Get & Set format */
case VIDIOC_G_FMT:
{
struct v4l2_format *fmt = arg;
if (fmt->type != video->queue.queue.type)
return -EINVAL;
return uvc_v4l2_get_format(video, fmt);
}
case VIDIOC_S_FMT:
{
struct v4l2_format *fmt = arg;
if (fmt->type != video->queue.queue.type)
return -EINVAL;
return uvc_v4l2_set_format(video, fmt);
}
/* Buffers & streaming */
case VIDIOC_REQBUFS:
{
struct v4l2_requestbuffers *rb = arg;
if (rb->type != video->queue.queue.type)
return -EINVAL;
ret = uvc_alloc_buffers(&video->queue, rb);
if (ret < 0)
return ret;
ret = 0;
break;
}
case VIDIOC_QUERYBUF:
{
struct v4l2_buffer *buf = arg;
return uvc_query_buffer(&video->queue, buf);
}
case VIDIOC_QBUF:
if ((ret = uvc_queue_buffer(&video->queue, arg)) < 0)
return ret;
return uvc_video_pump(video);
case VIDIOC_DQBUF:
return uvc_dequeue_buffer(&video->queue, arg,
file->f_flags & O_NONBLOCK);
case VIDIOC_STREAMON:
{
int *type = arg;
if (*type != video->queue.queue.type)
return -EINVAL;
/* Enable UVC video. */
ret = uvc_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.
*/
uvc_function_setup_continue(uvc);
uvc->state = UVC_STATE_STREAMING;
return 0;
}
case VIDIOC_STREAMOFF:
{
int *type = arg;
if (*type != video->queue.queue.type)
return -EINVAL;
return uvc_video_enable(video, 0);
}
/* Events */
case VIDIOC_DQEVENT:
{
struct v4l2_event *event = arg;
ret = v4l2_event_dequeue(&handle->vfh, event,
file->f_flags & O_NONBLOCK);
if (ret == 0 && event->type == UVC_EVENT_SETUP) {
struct uvc_event *uvc_event = (void *)&event->u.data;
/* Tell the complete callback to generate an event for
* the next request that will be enqueued by
* uvc_event_write.
*/
uvc->event_setup_out =
!(uvc_event->req.bRequestType & USB_DIR_IN);
uvc->event_length = uvc_event->req.wLength;
}
return ret;
}
case VIDIOC_SUBSCRIBE_EVENT:
{
struct v4l2_event_subscription *sub = arg;
if (sub->type < UVC_EVENT_FIRST || sub->type > UVC_EVENT_LAST)
return -EINVAL;
return v4l2_event_subscribe(&handle->vfh, arg, 2, NULL);
}
case VIDIOC_UNSUBSCRIBE_EVENT:
return v4l2_event_unsubscribe(&handle->vfh, arg);
case UVCIOC_SEND_RESPONSE:
ret = uvc_send_response(uvc, arg);
break;
default:
return -ENOIOCTLCMD;
}
return ret;
}
static long
uvc_v4l2_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
return video_usercopy(file, cmd, arg, uvc_v4l2_do_ioctl);
}
static int static int
uvc_v4l2_mmap(struct file *file, struct vm_area_struct *vma) uvc_v4l2_mmap(struct file *file, struct vm_area_struct *vma)
{ {
struct video_device *vdev = video_devdata(file); struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev); struct uvc_device *uvc = video_get_drvdata(vdev);
return uvc_queue_mmap(&uvc->video.queue, vma); return uvcg_queue_mmap(&uvc->video.queue, vma);
} }
static unsigned int static unsigned int
@ -336,7 +337,7 @@ uvc_v4l2_poll(struct file *file, poll_table *wait)
struct video_device *vdev = video_devdata(file); struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev); struct uvc_device *uvc = video_get_drvdata(vdev);
return uvc_queue_poll(&uvc->video.queue, file, wait); return uvcg_queue_poll(&uvc->video.queue, file, wait);
} }
#ifndef CONFIG_MMU #ifndef CONFIG_MMU
@ -347,19 +348,19 @@ static unsigned long uvc_v4l2_get_unmapped_area(struct file *file,
struct video_device *vdev = video_devdata(file); struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev); struct uvc_device *uvc = video_get_drvdata(vdev);
return uvc_queue_get_unmapped_area(&uvc->video.queue, pgoff); return uvcg_queue_get_unmapped_area(&uvc->video.queue, pgoff);
} }
#endif #endif
static struct v4l2_file_operations uvc_v4l2_fops = { struct v4l2_file_operations uvc_v4l2_fops = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.open = uvc_v4l2_open, .open = uvc_v4l2_open,
.release = uvc_v4l2_release, .release = uvc_v4l2_release,
.ioctl = uvc_v4l2_ioctl, .ioctl = video_ioctl2,
.mmap = uvc_v4l2_mmap, .mmap = uvc_v4l2_mmap,
.poll = uvc_v4l2_poll, .poll = uvc_v4l2_poll,
#ifndef CONFIG_MMU #ifndef CONFIG_MMU
.get_unmapped_area = uvc_v4l2_get_unmapped_area, .get_unmapped_area = uvcg_v4l2_get_unmapped_area,
#endif #endif
}; };

View File

@ -0,0 +1,22 @@
/*
* uvc_v4l2.h -- USB Video Class Gadget driver
*
* Copyright (C) 2009-2010
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
* http://www.samsung.com
* Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __UVC_V4L2_H__
#define __UVC_V4L2_H__
extern const struct v4l2_ioctl_ops uvc_v4l2_ioctl_ops;
extern struct v4l2_file_operations uvc_v4l2_fops;
#endif /* __UVC_V4L2_H__ */

View File

@ -15,6 +15,7 @@
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/usb/ch9.h> #include <linux/usb/ch9.h>
#include <linux/usb/gadget.h> #include <linux/usb/gadget.h>
#include <linux/usb/video.h>
#include <media/v4l2-dev.h> #include <media/v4l2-dev.h>
@ -85,7 +86,7 @@ uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video,
if (buf->bytesused == video->queue.buf_used) { if (buf->bytesused == video->queue.buf_used) {
video->queue.buf_used = 0; video->queue.buf_used = 0;
buf->state = UVC_BUF_STATE_DONE; buf->state = UVC_BUF_STATE_DONE;
uvc_queue_next_buffer(&video->queue, buf); uvcg_queue_next_buffer(&video->queue, buf);
video->fid ^= UVC_STREAM_FID; video->fid ^= UVC_STREAM_FID;
video->payload_size = 0; video->payload_size = 0;
@ -118,7 +119,7 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
if (buf->bytesused == video->queue.buf_used) { if (buf->bytesused == video->queue.buf_used) {
video->queue.buf_used = 0; video->queue.buf_used = 0;
buf->state = UVC_BUF_STATE_DONE; buf->state = UVC_BUF_STATE_DONE;
uvc_queue_next_buffer(&video->queue, buf); uvcg_queue_next_buffer(&video->queue, buf);
video->fid ^= UVC_STREAM_FID; video->fid ^= UVC_STREAM_FID;
} }
} }
@ -171,19 +172,19 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
break; break;
case -ESHUTDOWN: /* disconnect from host. */ case -ESHUTDOWN: /* disconnect from host. */
printk(KERN_INFO "VS request cancelled.\n"); printk(KERN_DEBUG "VS request cancelled.\n");
uvc_queue_cancel(queue, 1); uvcg_queue_cancel(queue, 1);
goto requeue; goto requeue;
default: default:
printk(KERN_INFO "VS request completed with status %d.\n", printk(KERN_INFO "VS request completed with status %d.\n",
req->status); req->status);
uvc_queue_cancel(queue, 0); uvcg_queue_cancel(queue, 0);
goto requeue; goto requeue;
} }
spin_lock_irqsave(&video->queue.irqlock, flags); spin_lock_irqsave(&video->queue.irqlock, flags);
buf = uvc_queue_head(&video->queue); buf = uvcg_queue_head(&video->queue);
if (buf == NULL) { if (buf == NULL) {
spin_unlock_irqrestore(&video->queue.irqlock, flags); spin_unlock_irqrestore(&video->queue.irqlock, flags);
goto requeue; goto requeue;
@ -195,7 +196,7 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
printk(KERN_INFO "Failed to queue request (%d).\n", ret); printk(KERN_INFO "Failed to queue request (%d).\n", ret);
usb_ep_set_halt(ep); usb_ep_set_halt(ep);
spin_unlock_irqrestore(&video->queue.irqlock, flags); spin_unlock_irqrestore(&video->queue.irqlock, flags);
uvc_queue_cancel(queue, 0); uvcg_queue_cancel(queue, 0);
goto requeue; goto requeue;
} }
spin_unlock_irqrestore(&video->queue.irqlock, flags); spin_unlock_irqrestore(&video->queue.irqlock, flags);
@ -274,13 +275,12 @@ error:
*/ */
/* /*
* uvc_video_pump - Pump video data into the USB requests * uvcg_video_pump - Pump video data into the USB requests
* *
* This function fills the available USB requests (listed in req_free) with * This function fills the available USB requests (listed in req_free) with
* video data from the queued buffers. * video data from the queued buffers.
*/ */
static int int uvcg_video_pump(struct uvc_video *video)
uvc_video_pump(struct uvc_video *video)
{ {
struct uvc_video_queue *queue = &video->queue; struct uvc_video_queue *queue = &video->queue;
struct usb_request *req; struct usb_request *req;
@ -288,7 +288,7 @@ uvc_video_pump(struct uvc_video *video)
unsigned long flags; unsigned long flags;
int ret; int ret;
/* FIXME TODO Race between uvc_video_pump and requests completion /* FIXME TODO Race between uvcg_video_pump and requests completion
* handler ??? * handler ???
*/ */
@ -309,10 +309,10 @@ uvc_video_pump(struct uvc_video *video)
/* Retrieve the first available video buffer and fill the /* Retrieve the first available video buffer and fill the
* request, protected by the video queue irqlock. * request, protected by the video queue irqlock.
*/ */
spin_lock_irqsave(&video->queue.irqlock, flags); spin_lock_irqsave(&queue->irqlock, flags);
buf = uvc_queue_head(&video->queue); buf = uvcg_queue_head(queue);
if (buf == NULL) { if (buf == NULL) {
spin_unlock_irqrestore(&video->queue.irqlock, flags); spin_unlock_irqrestore(&queue->irqlock, flags);
break; break;
} }
@ -323,11 +323,11 @@ uvc_video_pump(struct uvc_video *video)
if (ret < 0) { if (ret < 0) {
printk(KERN_INFO "Failed to queue request (%d)\n", ret); printk(KERN_INFO "Failed to queue request (%d)\n", ret);
usb_ep_set_halt(video->ep); usb_ep_set_halt(video->ep);
spin_unlock_irqrestore(&video->queue.irqlock, flags); spin_unlock_irqrestore(&queue->irqlock, flags);
uvc_queue_cancel(queue, 0); uvcg_queue_cancel(queue, 0);
break; break;
} }
spin_unlock_irqrestore(&video->queue.irqlock, flags); spin_unlock_irqrestore(&queue->irqlock, flags);
} }
spin_lock_irqsave(&video->req_lock, flags); spin_lock_irqsave(&video->req_lock, flags);
@ -339,8 +339,7 @@ uvc_video_pump(struct uvc_video *video)
/* /*
* Enable or disable the video stream. * Enable or disable the video stream.
*/ */
static int int uvcg_video_enable(struct uvc_video *video, int enable)
uvc_video_enable(struct uvc_video *video, int enable)
{ {
unsigned int i; unsigned int i;
int ret; int ret;
@ -356,11 +355,11 @@ uvc_video_enable(struct uvc_video *video, int enable)
usb_ep_dequeue(video->ep, video->req[i]); usb_ep_dequeue(video->ep, video->req[i]);
uvc_video_free_requests(video); uvc_video_free_requests(video);
uvc_queue_enable(&video->queue, 0); uvcg_queue_enable(&video->queue, 0);
return 0; return 0;
} }
if ((ret = uvc_queue_enable(&video->queue, 1)) < 0) if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
return ret; return ret;
if ((ret = uvc_video_alloc_requests(video)) < 0) if ((ret = uvc_video_alloc_requests(video)) < 0)
@ -372,14 +371,13 @@ uvc_video_enable(struct uvc_video *video, int enable)
} else } else
video->encode = uvc_video_encode_isoc; video->encode = uvc_video_encode_isoc;
return uvc_video_pump(video); return uvcg_video_pump(video);
} }
/* /*
* Initialize the UVC video stream. * Initialize the UVC video stream.
*/ */
static int int uvcg_video_init(struct uvc_video *video)
uvc_video_init(struct uvc_video *video)
{ {
INIT_LIST_HEAD(&video->req_free); INIT_LIST_HEAD(&video->req_free);
spin_lock_init(&video->req_lock); spin_lock_init(&video->req_lock);
@ -391,7 +389,7 @@ uvc_video_init(struct uvc_video *video)
video->imagesize = 320 * 240 * 2; video->imagesize = 320 * 240 * 2;
/* Initialize the video buffers queue. */ /* Initialize the video buffers queue. */
uvc_queue_init(&video->queue, V4L2_BUF_TYPE_VIDEO_OUTPUT); uvcg_queue_init(&video->queue, V4L2_BUF_TYPE_VIDEO_OUTPUT);
return 0; return 0;
} }

View File

@ -0,0 +1,24 @@
/*
* uvc_video.h -- USB Video Class Gadget driver
*
* Copyright (C) 2009-2010
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
* http://www.samsung.com
* Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __UVC_VIDEO_H__
#define __UVC_VIDEO_H__
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);
#endif /* __UVC_VIDEO_H__ */

View File

@ -54,6 +54,8 @@ config USB_AUDIO
depends on SND depends on SND
select USB_LIBCOMPOSITE select USB_LIBCOMPOSITE
select SND_PCM select SND_PCM
select USB_F_UAC1 if GADGET_UAC1
select USB_F_UAC2 if !GADGET_UAC1
help help
This Gadget Audio driver is compatible with USB Audio Class This Gadget Audio driver is compatible with USB Audio Class
specification 2.0. It implements 1 AudioControl interface, specification 2.0. It implements 1 AudioControl interface,
@ -466,6 +468,7 @@ config USB_G_WEBCAM
depends on VIDEO_DEV depends on VIDEO_DEV
select USB_LIBCOMPOSITE select USB_LIBCOMPOSITE
select VIDEOBUF2_VMALLOC select VIDEOBUF2_VMALLOC
select USB_F_UVC
help help
The Webcam Gadget acts as a composite USB Audio and Video Class The Webcam Gadget acts as a composite USB Audio and Video Class
device. It provides a userspace API to process UVC control requests device. It provides a userspace API to process UVC control requests

View File

@ -2,9 +2,9 @@
# USB gadget drivers # USB gadget drivers
# #
ccflags-y := -Idrivers/usb/gadget/ ccflags-y := -I$(srctree)/drivers/usb/gadget/
ccflags-y += -Idrivers/usb/gadget/udc/ ccflags-y += -I$(srctree)/drivers/usb/gadget/udc/
ccflags-y += -Idrivers/usb/gadget/function/ ccflags-y += -I$(srctree)/drivers/usb/gadget/function/
g_zero-y := zero.o g_zero-y := zero.o
g_audio-y := audio.o g_audio-y := audio.o

View File

@ -21,6 +21,66 @@
USB_GADGET_COMPOSITE_OPTIONS(); USB_GADGET_COMPOSITE_OPTIONS();
#ifndef CONFIG_GADGET_UAC1
#include "u_uac2.h"
/* Playback(USB-IN) Default Stereo - Fl/Fr */
static int p_chmask = UAC2_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;
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;
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;
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;
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
#include "u_uac1.h"
static char *fn_play = FILE_PCM_PLAYBACK;
module_param(fn_play, charp, S_IRUGO);
MODULE_PARM_DESC(fn_play, "Playback PCM device file name");
static char *fn_cap = FILE_PCM_CAPTURE;
module_param(fn_cap, charp, S_IRUGO);
MODULE_PARM_DESC(fn_cap, "Capture PCM device file name");
static char *fn_cntl = FILE_CONTROL;
module_param(fn_cntl, charp, S_IRUGO);
MODULE_PARM_DESC(fn_cntl, "Control device file name");
static int req_buf_size = UAC1_OUT_EP_MAX_PACKET_SIZE;
module_param(req_buf_size, int, S_IRUGO);
MODULE_PARM_DESC(req_buf_size, "ISO OUT endpoint request buffer size");
static int req_count = UAC1_REQ_COUNT;
module_param(req_count, int, S_IRUGO);
MODULE_PARM_DESC(req_count, "ISO OUT endpoint request count");
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
/* string IDs are assigned dynamically */ /* string IDs are assigned dynamically */
static struct usb_string strings_dev[] = { static struct usb_string strings_dev[] = {
@ -40,12 +100,12 @@ static struct usb_gadget_strings *audio_strings[] = {
NULL, NULL,
}; };
#ifdef CONFIG_GADGET_UAC1 #ifndef CONFIG_GADGET_UAC1
#include "u_uac1.h" static struct usb_function_instance *fi_uac2;
#include "u_uac1.c" static struct usb_function *f_uac2;
#include "f_uac1.c"
#else #else
#include "f_uac2.c" static struct usb_function_instance *fi_uac1;
static struct usb_function *f_uac1;
#endif #endif
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
@ -109,6 +169,8 @@ static const struct usb_descriptor_header *otg_desc[] = {
static int __init audio_do_config(struct usb_configuration *c) static int __init audio_do_config(struct usb_configuration *c)
{ {
int status;
/* FIXME alloc iConfiguration string, set it in c->strings */ /* FIXME alloc iConfiguration string, set it in c->strings */
if (gadget_is_otg(c->cdev->gadget)) { if (gadget_is_otg(c->cdev->gadget)) {
@ -116,7 +178,31 @@ static int __init audio_do_config(struct usb_configuration *c)
c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; c->bmAttributes |= USB_CONFIG_ATT_WAKEUP;
} }
audio_bind_config(c); #ifdef CONFIG_GADGET_UAC1
f_uac1 = usb_get_function(fi_uac1);
if (IS_ERR(f_uac1)) {
status = PTR_ERR(f_uac1);
return status;
}
status = usb_add_function(c, f_uac1);
if (status < 0) {
usb_put_function(f_uac1);
return status;
}
#else
f_uac2 = usb_get_function(fi_uac2);
if (IS_ERR(f_uac2)) {
status = PTR_ERR(f_uac2);
return status;
}
status = usb_add_function(c, f_uac2);
if (status < 0) {
usb_put_function(f_uac2);
return status;
}
#endif
return 0; return 0;
} }
@ -126,17 +212,47 @@ static struct usb_configuration audio_config_driver = {
.bConfigurationValue = 1, .bConfigurationValue = 1,
/* .iConfiguration = DYNAMIC */ /* .iConfiguration = DYNAMIC */
.bmAttributes = USB_CONFIG_ATT_SELFPOWER, .bmAttributes = USB_CONFIG_ATT_SELFPOWER,
#ifndef CONFIG_GADGET_UAC1
.unbind = uac2_unbind_config,
#endif
}; };
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
static int __init audio_bind(struct usb_composite_dev *cdev) static int __init audio_bind(struct usb_composite_dev *cdev)
{ {
#ifndef CONFIG_GADGET_UAC1
struct f_uac2_opts *uac2_opts;
#else
struct f_uac1_opts *uac1_opts;
#endif
int status; int status;
#ifndef CONFIG_GADGET_UAC1
fi_uac2 = usb_get_function_instance("uac2");
if (IS_ERR(fi_uac2))
return PTR_ERR(fi_uac2);
#else
fi_uac1 = usb_get_function_instance("uac1");
if (IS_ERR(fi_uac1))
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;
#else
uac1_opts = container_of(fi_uac1, struct f_uac1_opts, func_inst);
uac1_opts->fn_play = fn_play;
uac1_opts->fn_cap = fn_cap;
uac1_opts->fn_cntl = fn_cntl;
uac1_opts->req_buf_size = req_buf_size;
uac1_opts->req_count = req_count;
uac1_opts->audio_buf_size = audio_buf_size;
#endif
status = usb_string_ids_tab(cdev, strings_dev); status = usb_string_ids_tab(cdev, strings_dev);
if (status < 0) if (status < 0)
goto fail; goto fail;
@ -152,13 +268,26 @@ static int __init audio_bind(struct usb_composite_dev *cdev)
return 0; return 0;
fail: fail:
#ifndef CONFIG_GADGET_UAC1
usb_put_function_instance(fi_uac2);
#else
usb_put_function_instance(fi_uac1);
#endif
return status; return status;
} }
static int __exit audio_unbind(struct usb_composite_dev *cdev) static int __exit audio_unbind(struct usb_composite_dev *cdev)
{ {
#ifdef CONFIG_GADGET_UAC1 #ifdef CONFIG_GADGET_UAC1
gaudio_cleanup(); if (!IS_ERR_OR_NULL(f_uac1))
usb_put_function(f_uac1);
if (!IS_ERR_OR_NULL(fi_uac1))
usb_put_function_instance(fi_uac1);
#else
if (!IS_ERR_OR_NULL(f_uac2))
usb_put_function(f_uac2);
if (!IS_ERR_OR_NULL(fi_uac2))
usb_put_function_instance(fi_uac2);
#endif #endif
return 0; return 0;
} }

View File

@ -410,6 +410,7 @@ static __refdata struct usb_gadget_driver dbgp_driver = {
.bind = dbgp_bind, .bind = dbgp_bind,
.unbind = dbgp_unbind, .unbind = dbgp_unbind,
.setup = dbgp_setup, .setup = dbgp_setup,
.reset = dbgp_disconnect,
.disconnect = dbgp_disconnect, .disconnect = dbgp_disconnect,
.driver = { .driver = {
.owner = THIS_MODULE, .owner = THIS_MODULE,

View File

@ -1775,6 +1775,7 @@ static struct usb_gadget_driver gadgetfs_driver = {
.bind = gadgetfs_bind, .bind = gadgetfs_bind,
.unbind = gadgetfs_unbind, .unbind = gadgetfs_unbind,
.setup = gadgetfs_setup, .setup = gadgetfs_setup,
.reset = gadgetfs_disconnect,
.disconnect = gadgetfs_disconnect, .disconnect = gadgetfs_disconnect,
.suspend = gadgetfs_suspend, .suspend = gadgetfs_suspend,

View File

@ -12,23 +12,31 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/module.h>
#include <linux/usb/video.h> #include <linux/usb/video.h>
#include "f_uvc.h" #include "u_uvc.h"
/*
* Kbuild is not very cooperative with respect to linking separately
* compiled library objects into one module. So for now we won't use
* separate compilation ... ensuring init/exit sections work to shrink
* the runtime footprint, and giving us at least some parts of what
* a "gcc --combine ... part1.c part2.c part3.c ... " build would.
*/
#include "uvc_queue.c"
#include "uvc_video.c"
#include "uvc_v4l2.c"
#include "f_uvc.c"
USB_GADGET_COMPOSITE_OPTIONS(); USB_GADGET_COMPOSITE_OPTIONS();
/*-------------------------------------------------------------------------*/
/* module parameters specific to the Video streaming endpoint */
static unsigned int streaming_interval = 1;
module_param(streaming_interval, uint, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(streaming_interval, "1 - 16");
static unsigned int streaming_maxpacket = 1024;
module_param(streaming_maxpacket, uint, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(streaming_maxpacket, "1 - 1023 (FS), 1 - 3072 (hs/ss)");
static unsigned int streaming_maxburst;
module_param(streaming_maxburst, uint, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(streaming_maxburst, "0 - 15 (ss only)");
static unsigned int trace;
module_param(trace, uint, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(trace, "Trace level bitmask");
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Device descriptor * Device descriptor
*/ */
@ -63,6 +71,9 @@ static struct usb_gadget_strings *webcam_device_strings[] = {
NULL, NULL,
}; };
static struct usb_function_instance *fi_uvc;
static struct usb_function *f_uvc;
static struct usb_device_descriptor webcam_device_descriptor = { static struct usb_device_descriptor webcam_device_descriptor = {
.bLength = USB_DT_DEVICE_SIZE, .bLength = USB_DT_DEVICE_SIZE,
.bDescriptorType = USB_DT_DEVICE, .bDescriptorType = USB_DT_DEVICE,
@ -326,9 +337,17 @@ static const struct uvc_descriptor_header * const uvc_ss_streaming_cls[] = {
static int __init static int __init
webcam_config_bind(struct usb_configuration *c) webcam_config_bind(struct usb_configuration *c)
{ {
return uvc_bind_config(c, uvc_fs_control_cls, uvc_ss_control_cls, int status = 0;
uvc_fs_streaming_cls, uvc_hs_streaming_cls,
uvc_ss_streaming_cls); f_uvc = usb_get_function(fi_uvc);
if (IS_ERR(f_uvc))
return PTR_ERR(f_uvc);
status = usb_add_function(c, f_uvc);
if (status < 0)
usb_put_function(f_uvc);
return status;
} }
static struct usb_configuration webcam_config_driver = { static struct usb_configuration webcam_config_driver = {
@ -342,14 +361,36 @@ static struct usb_configuration webcam_config_driver = {
static int /* __init_or_exit */ static int /* __init_or_exit */
webcam_unbind(struct usb_composite_dev *cdev) webcam_unbind(struct usb_composite_dev *cdev)
{ {
if (!IS_ERR_OR_NULL(f_uvc))
usb_put_function(f_uvc);
if (!IS_ERR_OR_NULL(fi_uvc))
usb_put_function_instance(fi_uvc);
return 0; return 0;
} }
static int __init static int __init
webcam_bind(struct usb_composite_dev *cdev) webcam_bind(struct usb_composite_dev *cdev)
{ {
struct f_uvc_opts *uvc_opts;
int ret; int ret;
fi_uvc = usb_get_function_instance("uvc");
if (IS_ERR(fi_uvc))
return PTR_ERR(fi_uvc);
uvc_opts = container_of(fi_uvc, struct f_uvc_opts, func_inst);
uvc_opts->streaming_interval = streaming_interval;
uvc_opts->streaming_maxpacket = streaming_maxpacket;
uvc_opts->streaming_maxburst = streaming_maxburst;
uvc_set_trace_param(trace);
uvc_opts->fs_control = uvc_fs_control_cls;
uvc_opts->ss_control = uvc_ss_control_cls;
uvc_opts->fs_streaming = uvc_fs_streaming_cls;
uvc_opts->hs_streaming = uvc_hs_streaming_cls;
uvc_opts->ss_streaming = uvc_ss_streaming_cls;
/* Allocate string descriptor numbers ... note that string contents /* Allocate string descriptor numbers ... note that string contents
* can be overridden by the composite_dev glue. * can be overridden by the composite_dev glue.
*/ */
@ -373,7 +414,7 @@ webcam_bind(struct usb_composite_dev *cdev)
return 0; return 0;
error: error:
webcam_unbind(cdev); usb_put_function_instance(fi_uvc);
return ret; return ret;
} }

View File

@ -68,6 +68,8 @@ static struct usb_zero_options gzero_options = {
.isoc_maxpacket = GZERO_ISOC_MAXPACKET, .isoc_maxpacket = GZERO_ISOC_MAXPACKET,
.bulk_buflen = GZERO_BULK_BUFLEN, .bulk_buflen = GZERO_BULK_BUFLEN,
.qlen = GZERO_QLEN, .qlen = GZERO_QLEN,
.int_interval = GZERO_INT_INTERVAL,
.int_maxpacket = GZERO_INT_MAXPACKET,
}; };
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
@ -266,6 +268,21 @@ module_param_named(isoc_maxburst, gzero_options.isoc_maxburst, uint,
S_IRUGO|S_IWUSR); S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(isoc_maxburst, "0 - 15 (ss only)"); MODULE_PARM_DESC(isoc_maxburst, "0 - 15 (ss only)");
module_param_named(int_interval, gzero_options.int_interval, uint,
S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(int_interval, "1 - 16");
module_param_named(int_maxpacket, gzero_options.int_maxpacket, uint,
S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(int_maxpacket, "0 - 1023 (fs), 0 - 1024 (hs/ss)");
module_param_named(int_mult, gzero_options.int_mult, uint, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(int_mult, "0 - 2 (hs/ss only)");
module_param_named(int_maxburst, gzero_options.int_maxburst, uint,
S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(int_maxburst, "0 - 15 (ss only)");
static struct usb_function *func_lb; static struct usb_function *func_lb;
static struct usb_function_instance *func_inst_lb; static struct usb_function_instance *func_inst_lb;
@ -301,6 +318,10 @@ static int __init zero_bind(struct usb_composite_dev *cdev)
ss_opts->isoc_maxpacket = gzero_options.isoc_maxpacket; ss_opts->isoc_maxpacket = gzero_options.isoc_maxpacket;
ss_opts->isoc_mult = gzero_options.isoc_mult; ss_opts->isoc_mult = gzero_options.isoc_mult;
ss_opts->isoc_maxburst = gzero_options.isoc_maxburst; ss_opts->isoc_maxburst = gzero_options.isoc_maxburst;
ss_opts->int_interval = gzero_options.int_interval;
ss_opts->int_maxpacket = gzero_options.int_maxpacket;
ss_opts->int_mult = gzero_options.int_mult;
ss_opts->int_maxburst = gzero_options.int_maxburst;
ss_opts->bulk_buflen = gzero_options.bulk_buflen; ss_opts->bulk_buflen = gzero_options.bulk_buflen;
func_ss = usb_get_function(func_inst_ss); func_ss = usb_get_function(func_inst_ss);

View File

@ -163,7 +163,7 @@ config USB_R8A66597
config USB_RENESAS_USBHS_UDC config USB_RENESAS_USBHS_UDC
tristate 'Renesas USBHS controller' tristate 'Renesas USBHS controller'
depends on USB_RENESAS_USBHS depends on USB_RENESAS_USBHS && HAS_DMA
help help
Renesas USBHS is a discrete USB host and peripheral controller chip Renesas USBHS is a discrete USB host and peripheral controller chip
that supports both full and high speed USB 2.0 data transfers. that supports both full and high speed USB 2.0 data transfers.
@ -354,6 +354,21 @@ config USB_EG20T
ML7213/ML7831 is completely compatible for Intel EG20T PCH. ML7213/ML7831 is completely compatible for Intel EG20T PCH.
This driver can be used with Intel's Quark X1000 SOC platform This driver can be used with Intel's Quark X1000 SOC platform
config USB_GADGET_XILINX
tristate "Xilinx USB Driver"
depends on OF || COMPILE_TEST
help
USB peripheral controller driver for Xilinx USB2 device.
Xilinx USB2 device is a soft IP which supports both full
and high speed USB 2.0 data transfers. It has seven configurable
endpoints(bulk or interrupt or isochronous), as well as
endpoint zero(for control transfers).
Say "y" to link the driver statically, or "m" to build a
dynamically linked module called "udc-xilinx" and force all
gadget drivers to also be dynamically linked.
# #
# LAST -- dummy/emulated controller # LAST -- dummy/emulated controller
# #

View File

@ -29,3 +29,4 @@ obj-$(CONFIG_USB_FUSB300) += fusb300_udc.o
obj-$(CONFIG_USB_FOTG210_UDC) += fotg210-udc.o obj-$(CONFIG_USB_FOTG210_UDC) += fotg210-udc.o
obj-$(CONFIG_USB_MV_U3D) += mv_u3d_core.o obj-$(CONFIG_USB_MV_U3D) += mv_u3d_core.o
obj-$(CONFIG_USB_GR_UDC) += gr_udc.o obj-$(CONFIG_USB_GR_UDC) += gr_udc.o
obj-$(CONFIG_USB_GADGET_XILINX) += udc-xilinx.o

View File

@ -318,8 +318,26 @@ static void gr_finish_request(struct gr_ep *ep, struct gr_request *req,
usb_gadget_unmap_request(&dev->gadget, &req->req, ep->is_in); usb_gadget_unmap_request(&dev->gadget, &req->req, ep->is_in);
gr_free_dma_desc_chain(dev, req); gr_free_dma_desc_chain(dev, req);
if (ep->is_in) /* For OUT, actual gets updated bit by bit */ if (ep->is_in) { /* For OUT, req->req.actual gets updated bit by bit */
req->req.actual = req->req.length; req->req.actual = req->req.length;
} else if (req->oddlen && req->req.actual > req->evenlen) {
/*
* Copy to user buffer in this case where length was not evenly
* divisible by ep->ep.maxpacket and the last descriptor was
* actually used.
*/
char *buftail = ((char *)req->req.buf + req->evenlen);
memcpy(buftail, ep->tailbuf, req->oddlen);
if (req->req.actual > req->req.length) {
/* We got more data than was requested */
dev_dbg(ep->dev->dev, "Overflow for ep %s\n",
ep->ep.name);
gr_dbgprint_request("OVFL", ep, req);
req->req.status = -EOVERFLOW;
}
}
if (!status) { if (!status) {
if (ep->is_in) if (ep->is_in)
@ -379,6 +397,15 @@ static void gr_start_dma(struct gr_ep *ep)
/* A descriptor should already have been allocated */ /* A descriptor should already have been allocated */
BUG_ON(!req->curr_desc); BUG_ON(!req->curr_desc);
/*
* The DMA controller can not handle smaller OUT buffers than
* ep->ep.maxpacket. It could lead to buffer overruns if an unexpectedly
* long packet are received. Therefore an internal bounce buffer gets
* used when such a request gets enabled.
*/
if (!ep->is_in && req->oddlen)
req->last_desc->data = ep->tailbuf_paddr;
wmb(); /* Make sure all is settled before handing it over to DMA */ wmb(); /* Make sure all is settled before handing it over to DMA */
/* Set the descriptor pointer in the hardware */ /* Set the descriptor pointer in the hardware */
@ -480,11 +507,11 @@ static int gr_setup_out_desc_list(struct gr_ep *ep, struct gr_request *req,
dma_addr_t start = req->req.dma + bytes_used; dma_addr_t start = req->req.dma + bytes_used;
u16 size = min(bytes_left, ep->bytes_per_buffer); u16 size = min(bytes_left, ep->bytes_per_buffer);
/* Should not happen however - gr_queue stops such lengths */ if (size < ep->bytes_per_buffer) {
if (size < ep->bytes_per_buffer) /* Prepare using bounce buffer */
dev_warn(ep->dev->dev, req->evenlen = req->req.length - bytes_left;
"Buffer overrun risk: %u < %u bytes/buffer\n", req->oddlen = size;
size, ep->bytes_per_buffer); }
ret = gr_add_dma_desc(ep, req, start, size, gfp_flags); ret = gr_add_dma_desc(ep, req, start, size, gfp_flags);
if (ret) if (ret)
@ -584,18 +611,6 @@ static int gr_queue(struct gr_ep *ep, struct gr_request *req, gfp_t gfp_flags)
return -EINVAL; return -EINVAL;
} }
/*
* The DMA controller can not handle smaller OUT buffers than
* maxpacket. It could lead to buffer overruns if unexpectedly long
* packet are received.
*/
if (!ep->is_in && (req->req.length % ep->ep.maxpacket) != 0) {
dev_err(dev->dev,
"OUT request length %d is not multiple of maxpacket\n",
req->req.length);
return -EMSGSIZE;
}
if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) { if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) {
dev_err(dev->dev, "-ESHUTDOWN"); dev_err(dev->dev, "-ESHUTDOWN");
return -ESHUTDOWN; return -ESHUTDOWN;
@ -1286,8 +1301,8 @@ static int gr_handle_out_ep(struct gr_ep *ep)
if (ctrl & GR_DESC_OUT_CTRL_SE) if (ctrl & GR_DESC_OUT_CTRL_SE)
req->setup = 1; req->setup = 1;
if (len < ep->ep.maxpacket || req->req.actual == req->req.length) { if (len < ep->ep.maxpacket || req->req.actual >= req->req.length) {
/* Short packet or the expected size - we are done */ /* Short packet or >= expected size - we are done */
if ((ep == &dev->epo[0]) && (dev->ep0state == GR_EP0_OSTATUS)) { if ((ep == &dev->epo[0]) && (dev->ep0state == GR_EP0_OSTATUS)) {
/* /*
@ -2015,6 +2030,11 @@ static int gr_ep_init(struct gr_udc *dev, int num, int is_in, u32 maxplimit)
} }
list_add_tail(&ep->ep_list, &dev->ep_list); list_add_tail(&ep->ep_list, &dev->ep_list);
ep->tailbuf = dma_alloc_coherent(dev->dev, ep->ep.maxpacket_limit,
&ep->tailbuf_paddr, GFP_ATOMIC);
if (!ep->tailbuf)
return -ENOMEM;
return 0; return 0;
} }
@ -2067,9 +2087,24 @@ static int gr_udc_init(struct gr_udc *dev)
return 0; return 0;
} }
static void gr_ep_remove(struct gr_udc *dev, int num, int is_in)
{
struct gr_ep *ep;
if (is_in)
ep = &dev->epi[num];
else
ep = &dev->epo[num];
if (ep->tailbuf)
dma_free_coherent(dev->dev, ep->ep.maxpacket_limit,
ep->tailbuf, ep->tailbuf_paddr);
}
static int gr_remove(struct platform_device *pdev) static int gr_remove(struct platform_device *pdev)
{ {
struct gr_udc *dev = platform_get_drvdata(pdev); struct gr_udc *dev = platform_get_drvdata(pdev);
int i;
if (dev->added) if (dev->added)
usb_del_gadget_udc(&dev->gadget); /* Shuts everything down */ usb_del_gadget_udc(&dev->gadget); /* Shuts everything down */
@ -2084,6 +2119,11 @@ static int gr_remove(struct platform_device *pdev)
gr_free_request(&dev->epi[0].ep, &dev->ep0reqi->req); gr_free_request(&dev->epi[0].ep, &dev->ep0reqi->req);
gr_free_request(&dev->epo[0].ep, &dev->ep0reqo->req); gr_free_request(&dev->epo[0].ep, &dev->ep0reqo->req);
for (i = 0; i < dev->nepo; i++)
gr_ep_remove(dev, i, 0);
for (i = 0; i < dev->nepi; i++)
gr_ep_remove(dev, i, 1);
return 0; return 0;
} }
static int gr_request_irq(struct gr_udc *dev, int irq) static int gr_request_irq(struct gr_udc *dev, int irq)
@ -2131,7 +2171,6 @@ static int gr_probe(struct platform_device *pdev)
dev->gadget.name = driver_name; dev->gadget.name = driver_name;
dev->gadget.max_speed = USB_SPEED_HIGH; dev->gadget.max_speed = USB_SPEED_HIGH;
dev->gadget.ops = &gr_ops; dev->gadget.ops = &gr_ops;
dev->gadget.quirk_ep_out_aligned_size = true;
spin_lock_init(&dev->lock); spin_lock_init(&dev->lock);
dev->regs = regs; dev->regs = regs;

View File

@ -156,6 +156,10 @@ struct gr_ep {
struct list_head queue; struct list_head queue;
struct list_head ep_list; struct list_head ep_list;
/* Bounce buffer for end of "odd" sized OUT requests */
void *tailbuf;
dma_addr_t tailbuf_paddr;
}; };
struct gr_request { struct gr_request {
@ -167,6 +171,9 @@ struct gr_request {
struct gr_dma_desc *curr_desc; /* Current descriptor */ struct gr_dma_desc *curr_desc; /* Current descriptor */
struct gr_dma_desc *last_desc; /* Last in the chain */ struct gr_dma_desc *last_desc; /* Last in the chain */
u16 evenlen; /* Size of even length head (if oddlen != 0) */
u16 oddlen; /* Size of odd length tail if buffer length is "odd" */
u8 setup; /* Setup packet */ u8 setup; /* Setup packet */
}; };

View File

@ -430,7 +430,7 @@ static void r8a66597_ep_setting(struct r8a66597 *r8a66597,
ep->pipenum = pipenum; ep->pipenum = pipenum;
ep->ep.maxpacket = usb_endpoint_maxp(desc); ep->ep.maxpacket = usb_endpoint_maxp(desc);
r8a66597->pipenum2ep[pipenum] = ep; r8a66597->pipenum2ep[pipenum] = ep;
r8a66597->epaddr2ep[desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK] r8a66597->epaddr2ep[usb_endpoint_num(desc)]
= ep; = ep;
INIT_LIST_HEAD(&ep->queue); INIT_LIST_HEAD(&ep->queue);
} }
@ -464,7 +464,7 @@ static int alloc_pipe_config(struct r8a66597_ep *ep,
if (ep->pipenum) /* already allocated pipe */ if (ep->pipenum) /* already allocated pipe */
return 0; return 0;
switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { switch (usb_endpoint_type(desc)) {
case USB_ENDPOINT_XFER_BULK: case USB_ENDPOINT_XFER_BULK:
if (r8a66597->bulk >= R8A66597_MAX_NUM_BULK) { if (r8a66597->bulk >= R8A66597_MAX_NUM_BULK) {
if (r8a66597->isochronous >= R8A66597_MAX_NUM_ISOC) { if (r8a66597->isochronous >= R8A66597_MAX_NUM_ISOC) {
@ -509,7 +509,7 @@ static int alloc_pipe_config(struct r8a66597_ep *ep,
} }
ep->type = info.type; ep->type = info.type;
info.epnum = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; info.epnum = usb_endpoint_num(desc);
info.maxpacket = usb_endpoint_maxp(desc); info.maxpacket = usb_endpoint_maxp(desc);
info.interval = desc->bInterval; info.interval = desc->bInterval;
if (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) if (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
@ -1846,10 +1846,8 @@ static int r8a66597_sudmac_ioremap(struct r8a66597 *r8a66597,
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sudmac"); res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sudmac");
r8a66597->sudmac_reg = devm_ioremap_resource(&pdev->dev, res); r8a66597->sudmac_reg = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(r8a66597->sudmac_reg)) { if (IS_ERR(r8a66597->sudmac_reg))
dev_err(&pdev->dev, "ioremap error(sudmac).\n");
return PTR_ERR(r8a66597->sudmac_reg); return PTR_ERR(r8a66597->sudmac_reg);
}
return 0; return 0;
} }

View File

@ -109,8 +109,20 @@ EXPORT_SYMBOL_GPL(usb_gadget_unmap_request);
static void usb_gadget_state_work(struct work_struct *work) static void usb_gadget_state_work(struct work_struct *work)
{ {
struct usb_gadget *gadget = work_to_gadget(work); struct usb_gadget *gadget = work_to_gadget(work);
struct usb_udc *udc = NULL;
sysfs_notify(&gadget->dev.kobj, NULL, "state"); mutex_lock(&udc_lock);
list_for_each_entry(udc, &udc_list, list)
if (udc->gadget == gadget)
goto found;
mutex_unlock(&udc_lock);
return;
found:
mutex_unlock(&udc_lock);
sysfs_notify(&udc->dev.kobj, NULL, "state");
} }
void usb_gadget_set_state(struct usb_gadget *gadget, void usb_gadget_set_state(struct usb_gadget *gadget,
@ -123,6 +135,23 @@ EXPORT_SYMBOL_GPL(usb_gadget_set_state);
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
/**
* usb_gadget_udc_reset - notifies the udc core that bus reset occurs
* @gadget: The gadget which bus reset occurs
* @driver: The gadget driver we want to notify
*
* If the udc driver has bus reset handler, it needs to call this when the bus
* reset occurs, it notifies the gadget driver that the bus reset occurs as
* well as updates gadget state.
*/
void usb_gadget_udc_reset(struct usb_gadget *gadget,
struct usb_gadget_driver *driver)
{
driver->reset(gadget);
usb_gadget_set_state(gadget, USB_STATE_DEFAULT);
}
EXPORT_SYMBOL_GPL(usb_gadget_udc_reset);
/** /**
* usb_gadget_udc_start - tells usb device controller to start up * usb_gadget_udc_start - tells usb device controller to start up
* @gadget: The gadget we want to get started * @gadget: The gadget we want to get started

File diff suppressed because it is too large Load Diff

View File

@ -54,6 +54,7 @@ struct usbtest_info {
unsigned autoconf:1; unsigned autoconf:1;
unsigned ctrl_out:1; unsigned ctrl_out:1;
unsigned iso:1; /* try iso in/out */ unsigned iso:1; /* try iso in/out */
unsigned intr:1; /* try interrupt in/out */
int alt; int alt;
}; };
@ -70,7 +71,10 @@ struct usbtest_dev {
int out_pipe; int out_pipe;
int in_iso_pipe; int in_iso_pipe;
int out_iso_pipe; int out_iso_pipe;
int in_int_pipe;
int out_int_pipe;
struct usb_endpoint_descriptor *iso_in, *iso_out; struct usb_endpoint_descriptor *iso_in, *iso_out;
struct usb_endpoint_descriptor *int_in, *int_out;
struct mutex lock; struct mutex lock;
#define TBUF_SIZE 256 #define TBUF_SIZE 256
@ -101,6 +105,7 @@ get_endpoints(struct usbtest_dev *dev, struct usb_interface *intf)
struct usb_host_interface *alt; struct usb_host_interface *alt;
struct usb_host_endpoint *in, *out; struct usb_host_endpoint *in, *out;
struct usb_host_endpoint *iso_in, *iso_out; struct usb_host_endpoint *iso_in, *iso_out;
struct usb_host_endpoint *int_in, *int_out;
struct usb_device *udev; struct usb_device *udev;
for (tmp = 0; tmp < intf->num_altsetting; tmp++) { for (tmp = 0; tmp < intf->num_altsetting; tmp++) {
@ -108,6 +113,7 @@ get_endpoints(struct usbtest_dev *dev, struct usb_interface *intf)
in = out = NULL; in = out = NULL;
iso_in = iso_out = NULL; iso_in = iso_out = NULL;
int_in = int_out = NULL;
alt = intf->altsetting + tmp; alt = intf->altsetting + tmp;
if (override_alt >= 0 && if (override_alt >= 0 &&
@ -124,6 +130,9 @@ get_endpoints(struct usbtest_dev *dev, struct usb_interface *intf)
switch (usb_endpoint_type(&e->desc)) { switch (usb_endpoint_type(&e->desc)) {
case USB_ENDPOINT_XFER_BULK: case USB_ENDPOINT_XFER_BULK:
break; break;
case USB_ENDPOINT_XFER_INT:
if (dev->info->intr)
goto try_intr;
case USB_ENDPOINT_XFER_ISOC: case USB_ENDPOINT_XFER_ISOC:
if (dev->info->iso) if (dev->info->iso)
goto try_iso; goto try_iso;
@ -139,6 +148,15 @@ get_endpoints(struct usbtest_dev *dev, struct usb_interface *intf)
out = e; out = e;
} }
continue; continue;
try_intr:
if (usb_endpoint_dir_in(&e->desc)) {
if (!int_in)
int_in = e;
} else {
if (!int_out)
int_out = e;
}
continue;
try_iso: try_iso:
if (usb_endpoint_dir_in(&e->desc)) { if (usb_endpoint_dir_in(&e->desc)) {
if (!iso_in) if (!iso_in)
@ -148,7 +166,7 @@ try_iso:
iso_out = e; iso_out = e;
} }
} }
if ((in && out) || iso_in || iso_out) if ((in && out) || iso_in || iso_out || int_in || int_out)
goto found; goto found;
} }
return -EINVAL; return -EINVAL;
@ -183,6 +201,20 @@ found:
iso_out->desc.bEndpointAddress iso_out->desc.bEndpointAddress
& USB_ENDPOINT_NUMBER_MASK); & USB_ENDPOINT_NUMBER_MASK);
} }
if (int_in) {
dev->int_in = &int_in->desc;
dev->in_int_pipe = usb_rcvintpipe(udev,
int_in->desc.bEndpointAddress
& USB_ENDPOINT_NUMBER_MASK);
}
if (int_out) {
dev->int_out = &int_out->desc;
dev->out_int_pipe = usb_sndintpipe(udev,
int_out->desc.bEndpointAddress
& USB_ENDPOINT_NUMBER_MASK);
}
return 0; return 0;
} }
@ -205,14 +237,22 @@ static struct urb *usbtest_alloc_urb(
int pipe, int pipe,
unsigned long bytes, unsigned long bytes,
unsigned transfer_flags, unsigned transfer_flags,
unsigned offset) unsigned offset,
u8 bInterval)
{ {
struct urb *urb; struct urb *urb;
urb = usb_alloc_urb(0, GFP_KERNEL); urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) if (!urb)
return urb; return urb;
usb_fill_bulk_urb(urb, udev, pipe, NULL, bytes, simple_callback, NULL);
if (bInterval)
usb_fill_int_urb(urb, udev, pipe, NULL, bytes, simple_callback,
NULL, bInterval);
else
usb_fill_bulk_urb(urb, udev, pipe, NULL, bytes, simple_callback,
NULL);
urb->interval = (udev->speed == USB_SPEED_HIGH) urb->interval = (udev->speed == USB_SPEED_HIGH)
? (INTERRUPT_RATE << 3) ? (INTERRUPT_RATE << 3)
: INTERRUPT_RATE; : INTERRUPT_RATE;
@ -251,9 +291,11 @@ static struct urb *usbtest_alloc_urb(
static struct urb *simple_alloc_urb( static struct urb *simple_alloc_urb(
struct usb_device *udev, struct usb_device *udev,
int pipe, int pipe,
unsigned long bytes) unsigned long bytes,
u8 bInterval)
{ {
return usbtest_alloc_urb(udev, pipe, bytes, URB_NO_TRANSFER_DMA_MAP, 0); return usbtest_alloc_urb(udev, pipe, bytes, URB_NO_TRANSFER_DMA_MAP, 0,
bInterval);
} }
static unsigned pattern; static unsigned pattern;
@ -1255,7 +1297,7 @@ test_ctrl_queue(struct usbtest_dev *dev, struct usbtest_param *param)
goto cleanup; goto cleanup;
} }
req.wLength = cpu_to_le16(len); req.wLength = cpu_to_le16(len);
urb[i] = u = simple_alloc_urb(udev, pipe, len); urb[i] = u = simple_alloc_urb(udev, pipe, len, 0);
if (!u) if (!u)
goto cleanup; goto cleanup;
@ -1328,7 +1370,7 @@ static int unlink1(struct usbtest_dev *dev, int pipe, int size, int async)
int retval = 0; int retval = 0;
init_completion(&completion); init_completion(&completion);
urb = simple_alloc_urb(testdev_to_usbdev(dev), pipe, size); urb = simple_alloc_urb(testdev_to_usbdev(dev), pipe, size, 0);
if (!urb) if (!urb)
return -ENOMEM; return -ENOMEM;
urb->context = &completion; urb->context = &completion;
@ -1616,9 +1658,9 @@ static int halt_simple(struct usbtest_dev *dev)
struct usb_device *udev = testdev_to_usbdev(dev); struct usb_device *udev = testdev_to_usbdev(dev);
if (udev->speed == USB_SPEED_SUPER) if (udev->speed == USB_SPEED_SUPER)
urb = simple_alloc_urb(udev, 0, 1024); urb = simple_alloc_urb(udev, 0, 1024, 0);
else else
urb = simple_alloc_urb(udev, 0, 512); urb = simple_alloc_urb(udev, 0, 512, 0);
if (urb == NULL) if (urb == NULL)
return -ENOMEM; return -ENOMEM;
@ -1962,7 +2004,7 @@ static int test_unaligned_bulk(
{ {
int retval; int retval;
struct urb *urb = usbtest_alloc_urb( struct urb *urb = usbtest_alloc_urb(
testdev_to_usbdev(tdev), pipe, length, transfer_flags, 1); testdev_to_usbdev(tdev), pipe, length, transfer_flags, 1, 0);
if (!urb) if (!urb)
return -ENOMEM; return -ENOMEM;
@ -2068,7 +2110,7 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf)
dev_info(&intf->dev, dev_info(&intf->dev,
"TEST 1: write %d bytes %u times\n", "TEST 1: write %d bytes %u times\n",
param->length, param->iterations); param->length, param->iterations);
urb = simple_alloc_urb(udev, dev->out_pipe, param->length); urb = simple_alloc_urb(udev, dev->out_pipe, param->length, 0);
if (!urb) { if (!urb) {
retval = -ENOMEM; retval = -ENOMEM;
break; break;
@ -2083,7 +2125,7 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf)
dev_info(&intf->dev, dev_info(&intf->dev,
"TEST 2: read %d bytes %u times\n", "TEST 2: read %d bytes %u times\n",
param->length, param->iterations); param->length, param->iterations);
urb = simple_alloc_urb(udev, dev->in_pipe, param->length); urb = simple_alloc_urb(udev, dev->in_pipe, param->length, 0);
if (!urb) { if (!urb) {
retval = -ENOMEM; retval = -ENOMEM;
break; break;
@ -2098,7 +2140,7 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf)
dev_info(&intf->dev, dev_info(&intf->dev,
"TEST 3: write/%d 0..%d bytes %u times\n", "TEST 3: write/%d 0..%d bytes %u times\n",
param->vary, param->length, param->iterations); param->vary, param->length, param->iterations);
urb = simple_alloc_urb(udev, dev->out_pipe, param->length); urb = simple_alloc_urb(udev, dev->out_pipe, param->length, 0);
if (!urb) { if (!urb) {
retval = -ENOMEM; retval = -ENOMEM;
break; break;
@ -2114,7 +2156,7 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf)
dev_info(&intf->dev, dev_info(&intf->dev,
"TEST 4: read/%d 0..%d bytes %u times\n", "TEST 4: read/%d 0..%d bytes %u times\n",
param->vary, param->length, param->iterations); param->vary, param->length, param->iterations);
urb = simple_alloc_urb(udev, dev->in_pipe, param->length); urb = simple_alloc_urb(udev, dev->in_pipe, param->length, 0);
if (!urb) { if (!urb) {
retval = -ENOMEM; retval = -ENOMEM;
break; break;
@ -2411,6 +2453,39 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf)
} }
break; break;
/* Simple non-queued interrupt I/O tests */
case 25:
if (dev->out_int_pipe == 0)
break;
dev_info(&intf->dev,
"TEST 25: write %d bytes %u times\n",
param->length, param->iterations);
urb = simple_alloc_urb(udev, dev->out_int_pipe, param->length,
dev->int_out->bInterval);
if (!urb) {
retval = -ENOMEM;
break;
}
/* FIRMWARE: interrupt sink (maybe accepts short writes) */
retval = simple_io(dev, urb, param->iterations, 0, 0, "test25");
simple_free_urb(urb);
break;
case 26:
if (dev->in_int_pipe == 0)
break;
dev_info(&intf->dev,
"TEST 26: read %d bytes %u times\n",
param->length, param->iterations);
urb = simple_alloc_urb(udev, dev->in_int_pipe, param->length,
dev->int_in->bInterval);
if (!urb) {
retval = -ENOMEM;
break;
}
/* FIRMWARE: interrupt source (maybe generates short writes) */
retval = simple_io(dev, urb, param->iterations, 0, 0, "test26");
simple_free_urb(urb);
break;
} }
do_gettimeofday(&param->duration); do_gettimeofday(&param->duration);
param->duration.tv_sec -= start.tv_sec; param->duration.tv_sec -= start.tv_sec;
@ -2447,6 +2522,7 @@ usbtest_probe(struct usb_interface *intf, const struct usb_device_id *id)
struct usbtest_info *info; struct usbtest_info *info;
char *rtest, *wtest; char *rtest, *wtest;
char *irtest, *iwtest; char *irtest, *iwtest;
char *intrtest, *intwtest;
udev = interface_to_usbdev(intf); udev = interface_to_usbdev(intf);
@ -2487,6 +2563,7 @@ usbtest_probe(struct usb_interface *intf, const struct usb_device_id *id)
*/ */
rtest = wtest = ""; rtest = wtest = "";
irtest = iwtest = ""; irtest = iwtest = "";
intrtest = intwtest = "";
if (force_interrupt || udev->speed == USB_SPEED_LOW) { if (force_interrupt || udev->speed == USB_SPEED_LOW) {
if (info->ep_in) { if (info->ep_in) {
dev->in_pipe = usb_rcvintpipe(udev, info->ep_in); dev->in_pipe = usb_rcvintpipe(udev, info->ep_in);
@ -2525,15 +2602,20 @@ usbtest_probe(struct usb_interface *intf, const struct usb_device_id *id)
irtest = " iso-in"; irtest = " iso-in";
if (dev->out_iso_pipe) if (dev->out_iso_pipe)
iwtest = " iso-out"; iwtest = " iso-out";
if (dev->in_int_pipe)
intrtest = " int-in";
if (dev->out_int_pipe)
intwtest = " int-out";
} }
usb_set_intfdata(intf, dev); usb_set_intfdata(intf, dev);
dev_info(&intf->dev, "%s\n", info->name); dev_info(&intf->dev, "%s\n", info->name);
dev_info(&intf->dev, "%s {control%s%s%s%s%s} tests%s\n", dev_info(&intf->dev, "%s {control%s%s%s%s%s%s%s} tests%s\n",
usb_speed_string(udev->speed), usb_speed_string(udev->speed),
info->ctrl_out ? " in/out" : "", info->ctrl_out ? " in/out" : "",
rtest, wtest, rtest, wtest,
irtest, iwtest, irtest, iwtest,
intrtest, intwtest,
info->alt >= 0 ? " (+alt)" : ""); info->alt >= 0 ? " (+alt)" : "");
return 0; return 0;
} }
@ -2607,6 +2689,7 @@ static struct usbtest_info gz_info = {
.autoconf = 1, .autoconf = 1,
.ctrl_out = 1, .ctrl_out = 1,
.iso = 1, .iso = 1,
.intr = 1,
.alt = 0, .alt = 0,
}; };

View File

@ -212,7 +212,7 @@ static enum hrtimer_restart cppi41_recheck_tx_req(struct hrtimer *timer)
if (!list_empty(&controller->early_tx_list)) { if (!list_empty(&controller->early_tx_list)) {
ret = HRTIMER_RESTART; ret = HRTIMER_RESTART;
hrtimer_forward_now(&controller->early_tx, hrtimer_forward_now(&controller->early_tx,
ktime_set(0, 50 * NSEC_PER_USEC)); ktime_set(0, 20 * NSEC_PER_USEC));
} }
spin_unlock_irqrestore(&musb->lock, flags); spin_unlock_irqrestore(&musb->lock, flags);
@ -290,7 +290,7 @@ static void cppi41_dma_callback(void *private_data)
hrtimer_start_range_ns(&controller->early_tx, hrtimer_start_range_ns(&controller->early_tx,
ktime_set(0, usecs * NSEC_PER_USEC), ktime_set(0, usecs * NSEC_PER_USEC),
40 * NSEC_PER_USEC, 20 * NSEC_PER_USEC,
HRTIMER_MODE_REL); HRTIMER_MODE_REL);
} }
} }

View File

@ -870,6 +870,7 @@ static int dsps_suspend(struct device *dev)
struct musb *musb = platform_get_drvdata(glue->musb); struct musb *musb = platform_get_drvdata(glue->musb);
void __iomem *mbase = musb->ctrl_base; void __iomem *mbase = musb->ctrl_base;
del_timer_sync(&glue->timer);
glue->context.control = dsps_readl(mbase, wrp->control); glue->context.control = dsps_readl(mbase, wrp->control);
glue->context.epintr = dsps_readl(mbase, wrp->epintr_set); glue->context.epintr = dsps_readl(mbase, wrp->epintr_set);
glue->context.coreintr = dsps_readl(mbase, wrp->coreintr_set); glue->context.coreintr = dsps_readl(mbase, wrp->coreintr_set);
@ -895,6 +896,7 @@ static int dsps_resume(struct device *dev)
dsps_writel(mbase, wrp->mode, glue->context.mode); dsps_writel(mbase, wrp->mode, glue->context.mode);
dsps_writel(mbase, wrp->tx_mode, glue->context.tx_mode); dsps_writel(mbase, wrp->tx_mode, glue->context.tx_mode);
dsps_writel(mbase, wrp->rx_mode, glue->context.rx_mode); dsps_writel(mbase, wrp->rx_mode, glue->context.rx_mode);
setup_timer(&glue->timer, otg_timer, (unsigned long) musb);
return 0; return 0;
} }

View File

@ -78,22 +78,6 @@ config SAMSUNG_USBPHY
This driver provides common interface to interact, for Samsung USB 2.0 PHY This driver provides common interface to interact, for Samsung USB 2.0 PHY
driver and later for Samsung USB 3.0 PHY driver. driver and later for Samsung USB 3.0 PHY driver.
config SAMSUNG_USB2PHY
tristate "Samsung USB 2.0 PHY controller Driver"
select SAMSUNG_USBPHY
select USB_PHY
help
Enable this to support Samsung USB 2.0 (High Speed) PHY controller
driver for Samsung SoCs.
config SAMSUNG_USB3PHY
tristate "Samsung USB 3.0 PHY controller Driver"
select SAMSUNG_USBPHY
select USB_PHY
help
Enable this to support Samsung USB 3.0 (Super Speed) phy controller
for samsung SoCs.
config TWL6030_USB config TWL6030_USB
tristate "TWL6030 USB Transceiver Driver" tristate "TWL6030 USB Transceiver Driver"
depends on TWL4030_CORE && OMAP_USB2 && USB_MUSB_OMAP2PLUS depends on TWL4030_CORE && OMAP_USB2 && USB_MUSB_OMAP2PLUS

View File

@ -15,8 +15,6 @@ obj-$(CONFIG_AM335X_CONTROL_USB) += phy-am335x-control.o
obj-$(CONFIG_AM335X_PHY_USB) += phy-am335x.o obj-$(CONFIG_AM335X_PHY_USB) += phy-am335x.o
obj-$(CONFIG_OMAP_OTG) += phy-omap-otg.o obj-$(CONFIG_OMAP_OTG) += phy-omap-otg.o
obj-$(CONFIG_SAMSUNG_USBPHY) += phy-samsung-usb.o obj-$(CONFIG_SAMSUNG_USBPHY) += phy-samsung-usb.o
obj-$(CONFIG_SAMSUNG_USB2PHY) += phy-samsung-usb2.o
obj-$(CONFIG_SAMSUNG_USB3PHY) += phy-samsung-usb3.o
obj-$(CONFIG_TWL6030_USB) += phy-twl6030-usb.o obj-$(CONFIG_TWL6030_USB) += phy-twl6030-usb.o
obj-$(CONFIG_USB_EHCI_TEGRA) += phy-tegra-usb.o obj-$(CONFIG_USB_EHCI_TEGRA) += phy-tegra-usb.o
obj-$(CONFIG_USB_GPIO_VBUS) += phy-gpio-vbus-usb.o obj-$(CONFIG_USB_GPIO_VBUS) += phy-gpio-vbus-usb.o

View File

@ -281,7 +281,7 @@ static int msm_otg_phy_clk_reset(struct msm_otg *motg)
{ {
int ret = 0; int ret = 0;
if (motg->pdata->phy_clk_reset && motg->phy_reset_clk) if (motg->pdata->phy_clk_reset)
ret = motg->pdata->phy_clk_reset(motg->phy_reset_clk); ret = motg->pdata->phy_clk_reset(motg->phy_reset_clk);
else if (motg->phy_rst) else if (motg->phy_rst)
ret = reset_control_reset(motg->phy_rst); ret = reset_control_reset(motg->phy_rst);
@ -1394,7 +1394,7 @@ out:
return status; return status;
} }
const struct file_operations msm_otg_mode_fops = { static const struct file_operations msm_otg_mode_fops = {
.open = msm_otg_mode_open, .open = msm_otg_mode_open,
.read = seq_read, .read = seq_read,
.write = msm_otg_mode_write, .write = msm_otg_mode_write,
@ -1554,11 +1554,14 @@ static int msm_otg_probe(struct platform_device *pdev)
phy = &motg->phy; phy = &motg->phy;
phy->dev = &pdev->dev; phy->dev = &pdev->dev;
motg->phy_reset_clk = devm_clk_get(&pdev->dev, if (motg->pdata->phy_clk_reset) {
motg->phy_reset_clk = devm_clk_get(&pdev->dev,
np ? "phy" : "usb_phy_clk"); np ? "phy" : "usb_phy_clk");
if (IS_ERR(motg->phy_reset_clk)) {
dev_err(&pdev->dev, "failed to get usb_phy_clk\n"); if (IS_ERR(motg->phy_reset_clk)) {
motg->phy_reset_clk = NULL; dev_err(&pdev->dev, "failed to get usb_phy_clk\n");
return PTR_ERR(motg->phy_reset_clk);
}
} }
motg->clk = devm_clk_get(&pdev->dev, np ? "core" : "usb_hs_clk"); motg->clk = devm_clk_get(&pdev->dev, np ? "core" : "usb_hs_clk");

View File

@ -125,6 +125,11 @@ static const struct mxs_phy_data imx6sl_phy_data = {
MXS_PHY_NEED_IP_FIX, MXS_PHY_NEED_IP_FIX,
}; };
static const struct mxs_phy_data vf610_phy_data = {
.flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
MXS_PHY_NEED_IP_FIX,
};
static const struct mxs_phy_data imx6sx_phy_data = { static const struct mxs_phy_data imx6sx_phy_data = {
.flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
MXS_PHY_NEED_IP_FIX, MXS_PHY_NEED_IP_FIX,
@ -135,6 +140,7 @@ static const struct of_device_id mxs_phy_dt_ids[] = {
{ .compatible = "fsl,imx6sl-usbphy", .data = &imx6sl_phy_data, }, { .compatible = "fsl,imx6sl-usbphy", .data = &imx6sl_phy_data, },
{ .compatible = "fsl,imx6q-usbphy", .data = &imx6q_phy_data, }, { .compatible = "fsl,imx6q-usbphy", .data = &imx6q_phy_data, },
{ .compatible = "fsl,imx23-usbphy", .data = &imx23_phy_data, }, { .compatible = "fsl,imx23-usbphy", .data = &imx23_phy_data, },
{ .compatible = "fsl,vf610-usbphy", .data = &vf610_phy_data, },
{ /* sentinel */ } { /* sentinel */ }
}; };
MODULE_DEVICE_TABLE(of, mxs_phy_dt_ids); MODULE_DEVICE_TABLE(of, mxs_phy_dt_ids);

View File

@ -1,241 +0,0 @@
/* linux/drivers/usb/phy/phy-samsung-usb.c
*
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Praveen Paneri <p.paneri@samsung.com>
*
* Samsung USB-PHY helper driver with common function calls;
* interacts with Samsung USB 2.0 PHY controller driver and later
* with Samsung USB 3.0 PHY driver.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/usb/samsung_usb_phy.h>
#include "phy-samsung-usb.h"
int samsung_usbphy_parse_dt(struct samsung_usbphy *sphy)
{
struct device_node *usbphy_sys;
/* Getting node for system controller interface for usb-phy */
usbphy_sys = of_get_child_by_name(sphy->dev->of_node, "usbphy-sys");
if (!usbphy_sys) {
dev_err(sphy->dev, "No sys-controller interface for usb-phy\n");
return -ENODEV;
}
sphy->pmuregs = of_iomap(usbphy_sys, 0);
if (sphy->pmuregs == NULL) {
dev_err(sphy->dev, "Can't get usb-phy pmu control register\n");
goto err0;
}
sphy->sysreg = of_iomap(usbphy_sys, 1);
/*
* Not returning error code here, since this situation is not fatal.
* Few SoCs may not have this switch available
*/
if (sphy->sysreg == NULL)
dev_warn(sphy->dev, "Can't get usb-phy sysreg cfg register\n");
of_node_put(usbphy_sys);
return 0;
err0:
of_node_put(usbphy_sys);
return -ENXIO;
}
EXPORT_SYMBOL_GPL(samsung_usbphy_parse_dt);
/*
* Set isolation here for phy.
* Here 'on = true' would mean USB PHY block is isolated, hence
* de-activated and vice-versa.
*/
void samsung_usbphy_set_isolation_4210(struct samsung_usbphy *sphy, bool on)
{
void __iomem *reg = NULL;
u32 reg_val;
u32 en_mask = 0;
if (!sphy->pmuregs) {
dev_warn(sphy->dev, "Can't set pmu isolation\n");
return;
}
if (sphy->phy_type == USB_PHY_TYPE_DEVICE) {
reg = sphy->pmuregs + sphy->drv_data->devphy_reg_offset;
en_mask = sphy->drv_data->devphy_en_mask;
} else if (sphy->phy_type == USB_PHY_TYPE_HOST) {
reg = sphy->pmuregs + sphy->drv_data->hostphy_reg_offset;
en_mask = sphy->drv_data->hostphy_en_mask;
}
reg_val = readl(reg);
if (on)
reg_val &= ~en_mask;
else
reg_val |= en_mask;
writel(reg_val, reg);
if (sphy->drv_data->cpu_type == TYPE_EXYNOS4X12) {
writel(reg_val, sphy->pmuregs + EXYNOS4X12_PHY_HSIC_CTRL0);
writel(reg_val, sphy->pmuregs + EXYNOS4X12_PHY_HSIC_CTRL1);
}
}
EXPORT_SYMBOL_GPL(samsung_usbphy_set_isolation_4210);
/*
* Configure the mode of working of usb-phy here: HOST/DEVICE.
*/
void samsung_usbphy_cfg_sel(struct samsung_usbphy *sphy)
{
u32 reg;
if (!sphy->sysreg) {
dev_warn(sphy->dev, "Can't configure specified phy mode\n");
return;
}
reg = readl(sphy->sysreg);
if (sphy->phy_type == USB_PHY_TYPE_DEVICE)
reg &= ~EXYNOS_USB20PHY_CFG_HOST_LINK;
else if (sphy->phy_type == USB_PHY_TYPE_HOST)
reg |= EXYNOS_USB20PHY_CFG_HOST_LINK;
writel(reg, sphy->sysreg);
}
EXPORT_SYMBOL_GPL(samsung_usbphy_cfg_sel);
/*
* PHYs are different for USB Device and USB Host.
* This make sure that correct PHY type is selected before
* any operation on PHY.
*/
int samsung_usbphy_set_type(struct usb_phy *phy,
enum samsung_usb_phy_type phy_type)
{
struct samsung_usbphy *sphy = phy_to_sphy(phy);
sphy->phy_type = phy_type;
return 0;
}
EXPORT_SYMBOL_GPL(samsung_usbphy_set_type);
int samsung_usbphy_rate_to_clksel_64xx(struct samsung_usbphy *sphy,
unsigned long rate)
{
unsigned int clksel;
switch (rate) {
case 12 * MHZ:
clksel = PHYCLK_CLKSEL_12M;
break;
case 24 * MHZ:
clksel = PHYCLK_CLKSEL_24M;
break;
case 48 * MHZ:
clksel = PHYCLK_CLKSEL_48M;
break;
default:
dev_err(sphy->dev,
"Invalid reference clock frequency: %lu\n", rate);
return -EINVAL;
}
return clksel;
}
EXPORT_SYMBOL_GPL(samsung_usbphy_rate_to_clksel_64xx);
int samsung_usbphy_rate_to_clksel_4x12(struct samsung_usbphy *sphy,
unsigned long rate)
{
unsigned int clksel;
switch (rate) {
case 9600 * KHZ:
clksel = FSEL_CLKSEL_9600K;
break;
case 10 * MHZ:
clksel = FSEL_CLKSEL_10M;
break;
case 12 * MHZ:
clksel = FSEL_CLKSEL_12M;
break;
case 19200 * KHZ:
clksel = FSEL_CLKSEL_19200K;
break;
case 20 * MHZ:
clksel = FSEL_CLKSEL_20M;
break;
case 24 * MHZ:
clksel = FSEL_CLKSEL_24M;
break;
case 50 * MHZ:
clksel = FSEL_CLKSEL_50M;
break;
default:
dev_err(sphy->dev,
"Invalid reference clock frequency: %lu\n", rate);
return -EINVAL;
}
return clksel;
}
EXPORT_SYMBOL_GPL(samsung_usbphy_rate_to_clksel_4x12);
/*
* Returns reference clock frequency selection value
*/
int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy)
{
struct clk *ref_clk;
unsigned long rate;
int refclk_freq;
/*
* In exynos5250 USB host and device PHY use
* external crystal clock XXTI
*/
if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250)
ref_clk = clk_get(sphy->dev, "ext_xtal");
else
ref_clk = clk_get(sphy->dev, "xusbxti");
if (IS_ERR(ref_clk)) {
dev_err(sphy->dev, "Failed to get reference clock\n");
return PTR_ERR(ref_clk);
}
rate = clk_get_rate(ref_clk);
refclk_freq = sphy->drv_data->rate_to_clksel(sphy, rate);
clk_put(ref_clk);
return refclk_freq;
}
EXPORT_SYMBOL_GPL(samsung_usbphy_get_refclk_freq);

View File

@ -1,349 +0,0 @@
/* linux/drivers/usb/phy/phy-samsung-usb.h
*
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Samsung USB-PHY transceiver; talks to S3C HS OTG controller, EHCI-S5P and
* OHCI-EXYNOS controllers.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/usb/phy.h>
/* Register definitions */
#define SAMSUNG_PHYPWR (0x00)
#define PHYPWR_NORMAL_MASK (0x19 << 0)
#define PHYPWR_OTG_DISABLE (0x1 << 4)
#define PHYPWR_ANALOG_POWERDOWN (0x1 << 3)
#define PHYPWR_FORCE_SUSPEND (0x1 << 1)
/* For Exynos4 */
#define PHYPWR_NORMAL_MASK_PHY0 (0x39 << 0)
#define PHYPWR_SLEEP_PHY0 (0x1 << 5)
#define SAMSUNG_PHYCLK (0x04)
#define PHYCLK_MODE_USB11 (0x1 << 6)
#define PHYCLK_EXT_OSC (0x1 << 5)
#define PHYCLK_COMMON_ON_N (0x1 << 4)
#define PHYCLK_ID_PULL (0x1 << 2)
#define PHYCLK_CLKSEL_MASK (0x3 << 0)
#define PHYCLK_CLKSEL_48M (0x0 << 0)
#define PHYCLK_CLKSEL_12M (0x2 << 0)
#define PHYCLK_CLKSEL_24M (0x3 << 0)
#define SAMSUNG_RSTCON (0x08)
#define RSTCON_PHYLINK_SWRST (0x1 << 2)
#define RSTCON_HLINK_SWRST (0x1 << 1)
#define RSTCON_SWRST (0x1 << 0)
/* EXYNOS4X12 */
#define EXYNOS4X12_PHY_HSIC_CTRL0 (0x04)
#define EXYNOS4X12_PHY_HSIC_CTRL1 (0x08)
#define PHYPWR_NORMAL_MASK_HSIC1 (0x7 << 12)
#define PHYPWR_NORMAL_MASK_HSIC0 (0x7 << 9)
#define PHYPWR_NORMAL_MASK_PHY1 (0x7 << 6)
#define RSTCON_HOSTPHY_SWRST (0xf << 3)
/* EXYNOS5 */
#define EXYNOS5_PHY_HOST_CTRL0 (0x00)
#define HOST_CTRL0_PHYSWRSTALL (0x1 << 31)
#define HOST_CTRL0_REFCLKSEL_MASK (0x3 << 19)
#define HOST_CTRL0_REFCLKSEL_XTAL (0x0 << 19)
#define HOST_CTRL0_REFCLKSEL_EXTL (0x1 << 19)
#define HOST_CTRL0_REFCLKSEL_CLKCORE (0x2 << 19)
#define HOST_CTRL0_FSEL_MASK (0x7 << 16)
#define HOST_CTRL0_FSEL(_x) ((_x) << 16)
#define FSEL_CLKSEL_50M (0x7)
#define FSEL_CLKSEL_24M (0x5)
#define FSEL_CLKSEL_20M (0x4)
#define FSEL_CLKSEL_19200K (0x3)
#define FSEL_CLKSEL_12M (0x2)
#define FSEL_CLKSEL_10M (0x1)
#define FSEL_CLKSEL_9600K (0x0)
#define HOST_CTRL0_TESTBURNIN (0x1 << 11)
#define HOST_CTRL0_RETENABLE (0x1 << 10)
#define HOST_CTRL0_COMMONON_N (0x1 << 9)
#define HOST_CTRL0_SIDDQ (0x1 << 6)
#define HOST_CTRL0_FORCESLEEP (0x1 << 5)
#define HOST_CTRL0_FORCESUSPEND (0x1 << 4)
#define HOST_CTRL0_WORDINTERFACE (0x1 << 3)
#define HOST_CTRL0_UTMISWRST (0x1 << 2)
#define HOST_CTRL0_LINKSWRST (0x1 << 1)
#define HOST_CTRL0_PHYSWRST (0x1 << 0)
#define EXYNOS5_PHY_HOST_TUNE0 (0x04)
#define EXYNOS5_PHY_HSIC_CTRL1 (0x10)
#define EXYNOS5_PHY_HSIC_TUNE1 (0x14)
#define EXYNOS5_PHY_HSIC_CTRL2 (0x20)
#define EXYNOS5_PHY_HSIC_TUNE2 (0x24)
#define HSIC_CTRL_REFCLKSEL_MASK (0x3 << 23)
#define HSIC_CTRL_REFCLKSEL (0x2 << 23)
#define HSIC_CTRL_REFCLKDIV_MASK (0x7f << 16)
#define HSIC_CTRL_REFCLKDIV(_x) ((_x) << 16)
#define HSIC_CTRL_REFCLKDIV_12 (0x24 << 16)
#define HSIC_CTRL_REFCLKDIV_15 (0x1c << 16)
#define HSIC_CTRL_REFCLKDIV_16 (0x1a << 16)
#define HSIC_CTRL_REFCLKDIV_19_2 (0x15 << 16)
#define HSIC_CTRL_REFCLKDIV_20 (0x14 << 16)
#define HSIC_CTRL_SIDDQ (0x1 << 6)
#define HSIC_CTRL_FORCESLEEP (0x1 << 5)
#define HSIC_CTRL_FORCESUSPEND (0x1 << 4)
#define HSIC_CTRL_WORDINTERFACE (0x1 << 3)
#define HSIC_CTRL_UTMISWRST (0x1 << 2)
#define HSIC_CTRL_PHYSWRST (0x1 << 0)
#define EXYNOS5_PHY_HOST_EHCICTRL (0x30)
#define HOST_EHCICTRL_ENAINCRXALIGN (0x1 << 29)
#define HOST_EHCICTRL_ENAINCR4 (0x1 << 28)
#define HOST_EHCICTRL_ENAINCR8 (0x1 << 27)
#define HOST_EHCICTRL_ENAINCR16 (0x1 << 26)
#define EXYNOS5_PHY_HOST_OHCICTRL (0x34)
#define HOST_OHCICTRL_SUSPLGCY (0x1 << 3)
#define HOST_OHCICTRL_APPSTARTCLK (0x1 << 2)
#define HOST_OHCICTRL_CNTSEL (0x1 << 1)
#define HOST_OHCICTRL_CLKCKTRST (0x1 << 0)
#define EXYNOS5_PHY_OTG_SYS (0x38)
#define OTG_SYS_PHYLINK_SWRESET (0x1 << 14)
#define OTG_SYS_LINKSWRST_UOTG (0x1 << 13)
#define OTG_SYS_PHY0_SWRST (0x1 << 12)
#define OTG_SYS_REFCLKSEL_MASK (0x3 << 9)
#define OTG_SYS_REFCLKSEL_XTAL (0x0 << 9)
#define OTG_SYS_REFCLKSEL_EXTL (0x1 << 9)
#define OTG_SYS_REFCLKSEL_CLKCORE (0x2 << 9)
#define OTG_SYS_IDPULLUP_UOTG (0x1 << 8)
#define OTG_SYS_COMMON_ON (0x1 << 7)
#define OTG_SYS_FSEL_MASK (0x7 << 4)
#define OTG_SYS_FSEL(_x) ((_x) << 4)
#define OTG_SYS_FORCESLEEP (0x1 << 3)
#define OTG_SYS_OTGDISABLE (0x1 << 2)
#define OTG_SYS_SIDDQ_UOTG (0x1 << 1)
#define OTG_SYS_FORCESUSPEND (0x1 << 0)
#define EXYNOS5_PHY_OTG_TUNE (0x40)
/* EXYNOS5: USB 3.0 DRD */
#define EXYNOS5_DRD_LINKSYSTEM (0x04)
#define LINKSYSTEM_FLADJ_MASK (0x3f << 1)
#define LINKSYSTEM_FLADJ(_x) ((_x) << 1)
#define LINKSYSTEM_XHCI_VERSION_CONTROL (0x1 << 27)
#define EXYNOS5_DRD_PHYUTMI (0x08)
#define PHYUTMI_OTGDISABLE (0x1 << 6)
#define PHYUTMI_FORCESUSPEND (0x1 << 1)
#define PHYUTMI_FORCESLEEP (0x1 << 0)
#define EXYNOS5_DRD_PHYPIPE (0x0c)
#define EXYNOS5_DRD_PHYCLKRST (0x10)
#define PHYCLKRST_SSC_REFCLKSEL_MASK (0xff << 23)
#define PHYCLKRST_SSC_REFCLKSEL(_x) ((_x) << 23)
#define PHYCLKRST_SSC_RANGE_MASK (0x03 << 21)
#define PHYCLKRST_SSC_RANGE(_x) ((_x) << 21)
#define PHYCLKRST_SSC_EN (0x1 << 20)
#define PHYCLKRST_REF_SSP_EN (0x1 << 19)
#define PHYCLKRST_REF_CLKDIV2 (0x1 << 18)
#define PHYCLKRST_MPLL_MULTIPLIER_MASK (0x7f << 11)
#define PHYCLKRST_MPLL_MULTIPLIER_100MHZ_REF (0x19 << 11)
#define PHYCLKRST_MPLL_MULTIPLIER_50M_REF (0x02 << 11)
#define PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF (0x68 << 11)
#define PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF (0x7d << 11)
#define PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF (0x02 << 11)
#define PHYCLKRST_FSEL_MASK (0x3f << 5)
#define PHYCLKRST_FSEL(_x) ((_x) << 5)
#define PHYCLKRST_FSEL_PAD_100MHZ (0x27 << 5)
#define PHYCLKRST_FSEL_PAD_24MHZ (0x2a << 5)
#define PHYCLKRST_FSEL_PAD_20MHZ (0x31 << 5)
#define PHYCLKRST_FSEL_PAD_19_2MHZ (0x38 << 5)
#define PHYCLKRST_RETENABLEN (0x1 << 4)
#define PHYCLKRST_REFCLKSEL_MASK (0x03 << 2)
#define PHYCLKRST_REFCLKSEL_PAD_REFCLK (0x2 << 2)
#define PHYCLKRST_REFCLKSEL_EXT_REFCLK (0x3 << 2)
#define PHYCLKRST_PORTRESET (0x1 << 1)
#define PHYCLKRST_COMMONONN (0x1 << 0)
#define EXYNOS5_DRD_PHYREG0 (0x14)
#define EXYNOS5_DRD_PHYREG1 (0x18)
#define EXYNOS5_DRD_PHYPARAM0 (0x1c)
#define PHYPARAM0_REF_USE_PAD (0x1 << 31)
#define PHYPARAM0_REF_LOSLEVEL_MASK (0x1f << 26)
#define PHYPARAM0_REF_LOSLEVEL (0x9 << 26)
#define EXYNOS5_DRD_PHYPARAM1 (0x20)
#define PHYPARAM1_PCS_TXDEEMPH_MASK (0x3f << 0)
#define PHYPARAM1_PCS_TXDEEMPH (0x1c)
#define EXYNOS5_DRD_PHYTERM (0x24)
#define EXYNOS5_DRD_PHYTEST (0x28)
#define PHYTEST_POWERDOWN_SSP (0x1 << 3)
#define PHYTEST_POWERDOWN_HSP (0x1 << 2)
#define EXYNOS5_DRD_PHYADP (0x2c)
#define EXYNOS5_DRD_PHYBATCHG (0x30)
#define PHYBATCHG_UTMI_CLKSEL (0x1 << 2)
#define EXYNOS5_DRD_PHYRESUME (0x34)
#define EXYNOS5_DRD_LINKPORT (0x44)
#ifndef MHZ
#define MHZ (1000*1000)
#endif
#ifndef KHZ
#define KHZ (1000)
#endif
#define EXYNOS_USBHOST_PHY_CTRL_OFFSET (0x4)
#define S3C64XX_USBPHY_ENABLE (0x1 << 16)
#define EXYNOS_USBPHY_ENABLE (0x1 << 0)
#define EXYNOS_USB20PHY_CFG_HOST_LINK (0x1 << 0)
enum samsung_cpu_type {
TYPE_S3C64XX,
TYPE_EXYNOS4210,
TYPE_EXYNOS4X12,
TYPE_EXYNOS5250,
};
struct samsung_usbphy;
/*
* struct samsung_usbphy_drvdata - driver data for various SoC variants
* @cpu_type: machine identifier
* @devphy_en_mask: device phy enable mask for PHY CONTROL register
* @hostphy_en_mask: host phy enable mask for PHY CONTROL register
* @devphy_reg_offset: offset to DEVICE PHY CONTROL register from
* mapped address of system controller.
* @hostphy_reg_offset: offset to HOST PHY CONTROL register from
* mapped address of system controller.
*
* Here we have a separate mask for device type phy.
* Having different masks for host and device type phy helps
* in setting independent masks in case of SoCs like S5PV210,
* in which PHY0 and PHY1 enable bits belong to same register
* placed at position 0 and 1 respectively.
* Although for newer SoCs like exynos these bits belong to
* different registers altogether placed at position 0.
*/
struct samsung_usbphy_drvdata {
int cpu_type;
int devphy_en_mask;
int hostphy_en_mask;
u32 devphy_reg_offset;
u32 hostphy_reg_offset;
int (*rate_to_clksel)(struct samsung_usbphy *, unsigned long);
void (*set_isolation)(struct samsung_usbphy *, bool);
void (*phy_enable)(struct samsung_usbphy *);
void (*phy_disable)(struct samsung_usbphy *);
};
/*
* struct samsung_usbphy - transceiver driver state
* @phy: transceiver structure
* @plat: platform data
* @dev: The parent device supplied to the probe function
* @clk: usb phy clock
* @regs: usb phy controller registers memory base
* @pmuregs: USB device PHY_CONTROL register memory base
* @sysreg: USB2.0 PHY_CFG register memory base
* @ref_clk_freq: reference clock frequency selection
* @drv_data: driver data available for different SoCs
* @phy_type: Samsung SoCs specific phy types: #HOST
* #DEVICE
* @phy_usage: usage count for phy
* @lock: lock for phy operations
*/
struct samsung_usbphy {
struct usb_phy phy;
struct samsung_usbphy_data *plat;
struct device *dev;
struct clk *clk;
void __iomem *regs;
void __iomem *pmuregs;
void __iomem *sysreg;
int ref_clk_freq;
const struct samsung_usbphy_drvdata *drv_data;
enum samsung_usb_phy_type phy_type;
atomic_t phy_usage;
spinlock_t lock;
};
#define phy_to_sphy(x) container_of((x), struct samsung_usbphy, phy)
static const struct of_device_id samsung_usbphy_dt_match[];
static inline const struct samsung_usbphy_drvdata
*samsung_usbphy_get_driver_data(struct platform_device *pdev)
{
if (pdev->dev.of_node) {
const struct of_device_id *match;
match = of_match_node(samsung_usbphy_dt_match,
pdev->dev.of_node);
return match->data;
}
return (struct samsung_usbphy_drvdata *)
platform_get_device_id(pdev)->driver_data;
}
extern int samsung_usbphy_parse_dt(struct samsung_usbphy *sphy);
extern void samsung_usbphy_set_isolation_4210(struct samsung_usbphy *sphy,
bool on);
extern void samsung_usbphy_cfg_sel(struct samsung_usbphy *sphy);
extern int samsung_usbphy_set_type(struct usb_phy *phy,
enum samsung_usb_phy_type phy_type);
extern int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy);
extern int samsung_usbphy_rate_to_clksel_64xx(struct samsung_usbphy *sphy,
unsigned long rate);
extern int samsung_usbphy_rate_to_clksel_4x12(struct samsung_usbphy *sphy,
unsigned long rate);

View File

@ -1,541 +0,0 @@
/* linux/drivers/usb/phy/phy-samsung-usb2.c
*
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Praveen Paneri <p.paneri@samsung.com>
*
* Samsung USB2.0 PHY transceiver; talks to S3C HS OTG controller, EHCI-S5P and
* OHCI-EXYNOS controllers.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/usb/otg.h>
#include <linux/usb/samsung_usb_phy.h>
#include <linux/platform_data/samsung-usbphy.h>
#include "phy-samsung-usb.h"
static int samsung_usbphy_set_host(struct usb_otg *otg, struct usb_bus *host)
{
if (!otg)
return -ENODEV;
if (!otg->host)
otg->host = host;
return 0;
}
static bool exynos5_phyhost_is_on(void __iomem *regs)
{
u32 reg;
reg = readl(regs + EXYNOS5_PHY_HOST_CTRL0);
return !(reg & HOST_CTRL0_SIDDQ);
}
static void samsung_exynos5_usb2phy_enable(struct samsung_usbphy *sphy)
{
void __iomem *regs = sphy->regs;
u32 phyclk = sphy->ref_clk_freq;
u32 phyhost;
u32 phyotg;
u32 phyhsic;
u32 ehcictrl;
u32 ohcictrl;
/*
* phy_usage helps in keeping usage count for phy
* so that the first consumer enabling the phy is also
* the last consumer to disable it.
*/
atomic_inc(&sphy->phy_usage);
if (exynos5_phyhost_is_on(regs)) {
dev_info(sphy->dev, "Already power on PHY\n");
return;
}
/* Host configuration */
phyhost = readl(regs + EXYNOS5_PHY_HOST_CTRL0);
/* phy reference clock configuration */
phyhost &= ~HOST_CTRL0_FSEL_MASK;
phyhost |= HOST_CTRL0_FSEL(phyclk);
/* host phy reset */
phyhost &= ~(HOST_CTRL0_PHYSWRST |
HOST_CTRL0_PHYSWRSTALL |
HOST_CTRL0_SIDDQ |
/* Enable normal mode of operation */
HOST_CTRL0_FORCESUSPEND |
HOST_CTRL0_FORCESLEEP);
/* Link reset */
phyhost |= (HOST_CTRL0_LINKSWRST |
HOST_CTRL0_UTMISWRST |
/* COMMON Block configuration during suspend */
HOST_CTRL0_COMMONON_N);
writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0);
udelay(10);
phyhost &= ~(HOST_CTRL0_LINKSWRST |
HOST_CTRL0_UTMISWRST);
writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0);
/* OTG configuration */
phyotg = readl(regs + EXYNOS5_PHY_OTG_SYS);
/* phy reference clock configuration */
phyotg &= ~OTG_SYS_FSEL_MASK;
phyotg |= OTG_SYS_FSEL(phyclk);
/* Enable normal mode of operation */
phyotg &= ~(OTG_SYS_FORCESUSPEND |
OTG_SYS_SIDDQ_UOTG |
OTG_SYS_FORCESLEEP |
OTG_SYS_REFCLKSEL_MASK |
/* COMMON Block configuration during suspend */
OTG_SYS_COMMON_ON);
/* OTG phy & link reset */
phyotg |= (OTG_SYS_PHY0_SWRST |
OTG_SYS_LINKSWRST_UOTG |
OTG_SYS_PHYLINK_SWRESET |
OTG_SYS_OTGDISABLE |
/* Set phy refclk */
OTG_SYS_REFCLKSEL_CLKCORE);
writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS);
udelay(10);
phyotg &= ~(OTG_SYS_PHY0_SWRST |
OTG_SYS_LINKSWRST_UOTG |
OTG_SYS_PHYLINK_SWRESET);
writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS);
/* HSIC phy configuration */
phyhsic = (HSIC_CTRL_REFCLKDIV_12 |
HSIC_CTRL_REFCLKSEL |
HSIC_CTRL_PHYSWRST);
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1);
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2);
udelay(10);
phyhsic &= ~HSIC_CTRL_PHYSWRST;
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1);
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2);
udelay(80);
/* enable EHCI DMA burst */
ehcictrl = readl(regs + EXYNOS5_PHY_HOST_EHCICTRL);
ehcictrl |= (HOST_EHCICTRL_ENAINCRXALIGN |
HOST_EHCICTRL_ENAINCR4 |
HOST_EHCICTRL_ENAINCR8 |
HOST_EHCICTRL_ENAINCR16);
writel(ehcictrl, regs + EXYNOS5_PHY_HOST_EHCICTRL);
/* set ohci_suspend_on_n */
ohcictrl = readl(regs + EXYNOS5_PHY_HOST_OHCICTRL);
ohcictrl |= HOST_OHCICTRL_SUSPLGCY;
writel(ohcictrl, regs + EXYNOS5_PHY_HOST_OHCICTRL);
}
static void samsung_usb2phy_enable(struct samsung_usbphy *sphy)
{
void __iomem *regs = sphy->regs;
u32 phypwr;
u32 phyclk;
u32 rstcon;
/* set clock frequency for PLL */
phyclk = sphy->ref_clk_freq;
phypwr = readl(regs + SAMSUNG_PHYPWR);
rstcon = readl(regs + SAMSUNG_RSTCON);
switch (sphy->drv_data->cpu_type) {
case TYPE_S3C64XX:
phyclk &= ~PHYCLK_COMMON_ON_N;
phypwr &= ~PHYPWR_NORMAL_MASK;
rstcon |= RSTCON_SWRST;
break;
case TYPE_EXYNOS4X12:
phypwr &= ~(PHYPWR_NORMAL_MASK_HSIC0 |
PHYPWR_NORMAL_MASK_HSIC1 |
PHYPWR_NORMAL_MASK_PHY1);
rstcon |= RSTCON_HOSTPHY_SWRST;
case TYPE_EXYNOS4210:
phypwr &= ~PHYPWR_NORMAL_MASK_PHY0;
rstcon |= RSTCON_SWRST;
default:
break;
}
writel(phyclk, regs + SAMSUNG_PHYCLK);
/* Configure PHY0 for normal operation*/
writel(phypwr, regs + SAMSUNG_PHYPWR);
/* reset all ports of PHY and Link */
writel(rstcon, regs + SAMSUNG_RSTCON);
udelay(10);
if (sphy->drv_data->cpu_type == TYPE_EXYNOS4X12)
rstcon &= ~RSTCON_HOSTPHY_SWRST;
rstcon &= ~RSTCON_SWRST;
writel(rstcon, regs + SAMSUNG_RSTCON);
}
static void samsung_exynos5_usb2phy_disable(struct samsung_usbphy *sphy)
{
void __iomem *regs = sphy->regs;
u32 phyhost;
u32 phyotg;
u32 phyhsic;
if (atomic_dec_return(&sphy->phy_usage) > 0) {
dev_info(sphy->dev, "still being used\n");
return;
}
phyhsic = (HSIC_CTRL_REFCLKDIV_12 |
HSIC_CTRL_REFCLKSEL |
HSIC_CTRL_SIDDQ |
HSIC_CTRL_FORCESLEEP |
HSIC_CTRL_FORCESUSPEND);
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1);
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2);
phyhost = readl(regs + EXYNOS5_PHY_HOST_CTRL0);
phyhost |= (HOST_CTRL0_SIDDQ |
HOST_CTRL0_FORCESUSPEND |
HOST_CTRL0_FORCESLEEP |
HOST_CTRL0_PHYSWRST |
HOST_CTRL0_PHYSWRSTALL);
writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0);
phyotg = readl(regs + EXYNOS5_PHY_OTG_SYS);
phyotg |= (OTG_SYS_FORCESUSPEND |
OTG_SYS_SIDDQ_UOTG |
OTG_SYS_FORCESLEEP);
writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS);
}
static void samsung_usb2phy_disable(struct samsung_usbphy *sphy)
{
void __iomem *regs = sphy->regs;
u32 phypwr;
phypwr = readl(regs + SAMSUNG_PHYPWR);
switch (sphy->drv_data->cpu_type) {
case TYPE_S3C64XX:
phypwr |= PHYPWR_NORMAL_MASK;
break;
case TYPE_EXYNOS4X12:
phypwr |= (PHYPWR_NORMAL_MASK_HSIC0 |
PHYPWR_NORMAL_MASK_HSIC1 |
PHYPWR_NORMAL_MASK_PHY1);
case TYPE_EXYNOS4210:
phypwr |= PHYPWR_NORMAL_MASK_PHY0;
default:
break;
}
/* Disable analog and otg block power */
writel(phypwr, regs + SAMSUNG_PHYPWR);
}
/*
* The function passed to the usb driver for phy initialization
*/
static int samsung_usb2phy_init(struct usb_phy *phy)
{
struct samsung_usbphy *sphy;
struct usb_bus *host = NULL;
unsigned long flags;
int ret = 0;
sphy = phy_to_sphy(phy);
host = phy->otg->host;
/* Enable the phy clock */
ret = clk_prepare_enable(sphy->clk);
if (ret) {
dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__);
return ret;
}
spin_lock_irqsave(&sphy->lock, flags);
if (host) {
/* setting default phy-type for USB 2.0 */
if (!strstr(dev_name(host->controller), "ehci") ||
!strstr(dev_name(host->controller), "ohci"))
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_HOST);
} else {
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE);
}
/* Disable phy isolation */
if (sphy->plat && sphy->plat->pmu_isolation)
sphy->plat->pmu_isolation(false);
else if (sphy->drv_data->set_isolation)
sphy->drv_data->set_isolation(sphy, false);
/* Selecting Host/OTG mode; After reset USB2.0PHY_CFG: HOST */
samsung_usbphy_cfg_sel(sphy);
/* Initialize usb phy registers */
sphy->drv_data->phy_enable(sphy);
spin_unlock_irqrestore(&sphy->lock, flags);
/* Disable the phy clock */
clk_disable_unprepare(sphy->clk);
return ret;
}
/*
* The function passed to the usb driver for phy shutdown
*/
static void samsung_usb2phy_shutdown(struct usb_phy *phy)
{
struct samsung_usbphy *sphy;
struct usb_bus *host = NULL;
unsigned long flags;
sphy = phy_to_sphy(phy);
host = phy->otg->host;
if (clk_prepare_enable(sphy->clk)) {
dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__);
return;
}
spin_lock_irqsave(&sphy->lock, flags);
if (host) {
/* setting default phy-type for USB 2.0 */
if (!strstr(dev_name(host->controller), "ehci") ||
!strstr(dev_name(host->controller), "ohci"))
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_HOST);
} else {
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE);
}
/* De-initialize usb phy registers */
sphy->drv_data->phy_disable(sphy);
/* Enable phy isolation */
if (sphy->plat && sphy->plat->pmu_isolation)
sphy->plat->pmu_isolation(true);
else if (sphy->drv_data->set_isolation)
sphy->drv_data->set_isolation(sphy, true);
spin_unlock_irqrestore(&sphy->lock, flags);
clk_disable_unprepare(sphy->clk);
}
static int samsung_usb2phy_probe(struct platform_device *pdev)
{
struct samsung_usbphy *sphy;
struct usb_otg *otg;
struct samsung_usbphy_data *pdata = dev_get_platdata(&pdev->dev);
const struct samsung_usbphy_drvdata *drv_data;
struct device *dev = &pdev->dev;
struct resource *phy_mem;
void __iomem *phy_base;
struct clk *clk;
int ret;
phy_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
phy_base = devm_ioremap_resource(dev, phy_mem);
if (IS_ERR(phy_base))
return PTR_ERR(phy_base);
sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL);
if (!sphy)
return -ENOMEM;
otg = devm_kzalloc(dev, sizeof(*otg), GFP_KERNEL);
if (!otg)
return -ENOMEM;
drv_data = samsung_usbphy_get_driver_data(pdev);
if (drv_data->cpu_type == TYPE_EXYNOS5250)
clk = devm_clk_get(dev, "usbhost");
else
clk = devm_clk_get(dev, "otg");
if (IS_ERR(clk)) {
dev_err(dev, "Failed to get usbhost/otg clock\n");
return PTR_ERR(clk);
}
sphy->dev = dev;
if (dev->of_node) {
ret = samsung_usbphy_parse_dt(sphy);
if (ret < 0)
return ret;
} else {
if (!pdata) {
dev_err(dev, "no platform data specified\n");
return -EINVAL;
}
}
sphy->plat = pdata;
sphy->regs = phy_base;
sphy->clk = clk;
sphy->drv_data = drv_data;
sphy->phy.dev = sphy->dev;
sphy->phy.label = "samsung-usb2phy";
sphy->phy.type = USB_PHY_TYPE_USB2;
sphy->phy.init = samsung_usb2phy_init;
sphy->phy.shutdown = samsung_usb2phy_shutdown;
sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy);
if (sphy->ref_clk_freq < 0)
return -EINVAL;
sphy->phy.otg = otg;
sphy->phy.otg->phy = &sphy->phy;
sphy->phy.otg->set_host = samsung_usbphy_set_host;
spin_lock_init(&sphy->lock);
platform_set_drvdata(pdev, sphy);
return usb_add_phy_dev(&sphy->phy);
}
static int samsung_usb2phy_remove(struct platform_device *pdev)
{
struct samsung_usbphy *sphy = platform_get_drvdata(pdev);
usb_remove_phy(&sphy->phy);
if (sphy->pmuregs)
iounmap(sphy->pmuregs);
if (sphy->sysreg)
iounmap(sphy->sysreg);
return 0;
}
static const struct samsung_usbphy_drvdata usb2phy_s3c64xx = {
.cpu_type = TYPE_S3C64XX,
.devphy_en_mask = S3C64XX_USBPHY_ENABLE,
.rate_to_clksel = samsung_usbphy_rate_to_clksel_64xx,
.set_isolation = NULL, /* TODO */
.phy_enable = samsung_usb2phy_enable,
.phy_disable = samsung_usb2phy_disable,
};
static const struct samsung_usbphy_drvdata usb2phy_exynos4 = {
.cpu_type = TYPE_EXYNOS4210,
.devphy_en_mask = EXYNOS_USBPHY_ENABLE,
.hostphy_en_mask = EXYNOS_USBPHY_ENABLE,
.rate_to_clksel = samsung_usbphy_rate_to_clksel_64xx,
.set_isolation = samsung_usbphy_set_isolation_4210,
.phy_enable = samsung_usb2phy_enable,
.phy_disable = samsung_usb2phy_disable,
};
static const struct samsung_usbphy_drvdata usb2phy_exynos4x12 = {
.cpu_type = TYPE_EXYNOS4X12,
.devphy_en_mask = EXYNOS_USBPHY_ENABLE,
.hostphy_en_mask = EXYNOS_USBPHY_ENABLE,
.rate_to_clksel = samsung_usbphy_rate_to_clksel_4x12,
.set_isolation = samsung_usbphy_set_isolation_4210,
.phy_enable = samsung_usb2phy_enable,
.phy_disable = samsung_usb2phy_disable,
};
static struct samsung_usbphy_drvdata usb2phy_exynos5 = {
.cpu_type = TYPE_EXYNOS5250,
.hostphy_en_mask = EXYNOS_USBPHY_ENABLE,
.hostphy_reg_offset = EXYNOS_USBHOST_PHY_CTRL_OFFSET,
.rate_to_clksel = samsung_usbphy_rate_to_clksel_4x12,
.set_isolation = samsung_usbphy_set_isolation_4210,
.phy_enable = samsung_exynos5_usb2phy_enable,
.phy_disable = samsung_exynos5_usb2phy_disable,
};
#ifdef CONFIG_OF
static const struct of_device_id samsung_usbphy_dt_match[] = {
{
.compatible = "samsung,s3c64xx-usb2phy",
.data = &usb2phy_s3c64xx,
}, {
.compatible = "samsung,exynos4210-usb2phy",
.data = &usb2phy_exynos4,
}, {
.compatible = "samsung,exynos4x12-usb2phy",
.data = &usb2phy_exynos4x12,
}, {
.compatible = "samsung,exynos5250-usb2phy",
.data = &usb2phy_exynos5
},
{},
};
MODULE_DEVICE_TABLE(of, samsung_usbphy_dt_match);
#endif
static struct platform_device_id samsung_usbphy_driver_ids[] = {
{
.name = "s3c64xx-usb2phy",
.driver_data = (unsigned long)&usb2phy_s3c64xx,
}, {
.name = "exynos4210-usb2phy",
.driver_data = (unsigned long)&usb2phy_exynos4,
}, {
.name = "exynos4x12-usb2phy",
.driver_data = (unsigned long)&usb2phy_exynos4x12,
}, {
.name = "exynos5250-usb2phy",
.driver_data = (unsigned long)&usb2phy_exynos5,
},
{},
};
MODULE_DEVICE_TABLE(platform, samsung_usbphy_driver_ids);
static struct platform_driver samsung_usb2phy_driver = {
.probe = samsung_usb2phy_probe,
.remove = samsung_usb2phy_remove,
.id_table = samsung_usbphy_driver_ids,
.driver = {
.name = "samsung-usb2phy",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_usbphy_dt_match),
},
};
module_platform_driver(samsung_usb2phy_driver);
MODULE_DESCRIPTION("Samsung USB 2.0 phy controller");
MODULE_AUTHOR("Praveen Paneri <p.paneri@samsung.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:samsung-usb2phy");

View File

@ -1,350 +0,0 @@
/* linux/drivers/usb/phy/phy-samsung-usb3.c
*
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Vivek Gautam <gautam.vivek@samsung.com>
*
* Samsung USB 3.0 PHY transceiver; talks to DWC3 controller.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/usb/samsung_usb_phy.h>
#include <linux/platform_data/samsung-usbphy.h>
#include "phy-samsung-usb.h"
/*
* Sets the phy clk as EXTREFCLK (XXTI) which is internal clock from clock core.
*/
static u32 samsung_usb3phy_set_refclk(struct samsung_usbphy *sphy)
{
u32 reg;
u32 refclk;
refclk = sphy->ref_clk_freq;
reg = PHYCLKRST_REFCLKSEL_EXT_REFCLK |
PHYCLKRST_FSEL(refclk);
switch (refclk) {
case FSEL_CLKSEL_50M:
reg |= (PHYCLKRST_MPLL_MULTIPLIER_50M_REF |
PHYCLKRST_SSC_REFCLKSEL(0x00));
break;
case FSEL_CLKSEL_20M:
reg |= (PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF |
PHYCLKRST_SSC_REFCLKSEL(0x00));
break;
case FSEL_CLKSEL_19200K:
reg |= (PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF |
PHYCLKRST_SSC_REFCLKSEL(0x88));
break;
case FSEL_CLKSEL_24M:
default:
reg |= (PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF |
PHYCLKRST_SSC_REFCLKSEL(0x88));
break;
}
return reg;
}
static void samsung_exynos5_usb3phy_enable(struct samsung_usbphy *sphy)
{
void __iomem *regs = sphy->regs;
u32 phyparam0;
u32 phyparam1;
u32 linksystem;
u32 phybatchg;
u32 phytest;
u32 phyclkrst;
/* Reset USB 3.0 PHY */
writel(0x0, regs + EXYNOS5_DRD_PHYREG0);
phyparam0 = readl(regs + EXYNOS5_DRD_PHYPARAM0);
/* Select PHY CLK source */
phyparam0 &= ~PHYPARAM0_REF_USE_PAD;
/* Set Loss-of-Signal Detector sensitivity */
phyparam0 &= ~PHYPARAM0_REF_LOSLEVEL_MASK;
phyparam0 |= PHYPARAM0_REF_LOSLEVEL;
writel(phyparam0, regs + EXYNOS5_DRD_PHYPARAM0);
writel(0x0, regs + EXYNOS5_DRD_PHYRESUME);
/*
* Setting the Frame length Adj value[6:1] to default 0x20
* See xHCI 1.0 spec, 5.2.4
*/
linksystem = LINKSYSTEM_XHCI_VERSION_CONTROL |
LINKSYSTEM_FLADJ(0x20);
writel(linksystem, regs + EXYNOS5_DRD_LINKSYSTEM);
phyparam1 = readl(regs + EXYNOS5_DRD_PHYPARAM1);
/* Set Tx De-Emphasis level */
phyparam1 &= ~PHYPARAM1_PCS_TXDEEMPH_MASK;
phyparam1 |= PHYPARAM1_PCS_TXDEEMPH;
writel(phyparam1, regs + EXYNOS5_DRD_PHYPARAM1);
phybatchg = readl(regs + EXYNOS5_DRD_PHYBATCHG);
phybatchg |= PHYBATCHG_UTMI_CLKSEL;
writel(phybatchg, regs + EXYNOS5_DRD_PHYBATCHG);
/* PHYTEST POWERDOWN Control */
phytest = readl(regs + EXYNOS5_DRD_PHYTEST);
phytest &= ~(PHYTEST_POWERDOWN_SSP |
PHYTEST_POWERDOWN_HSP);
writel(phytest, regs + EXYNOS5_DRD_PHYTEST);
/* UTMI Power Control */
writel(PHYUTMI_OTGDISABLE, regs + EXYNOS5_DRD_PHYUTMI);
phyclkrst = samsung_usb3phy_set_refclk(sphy);
phyclkrst |= PHYCLKRST_PORTRESET |
/* Digital power supply in normal operating mode */
PHYCLKRST_RETENABLEN |
/* Enable ref clock for SS function */
PHYCLKRST_REF_SSP_EN |
/* Enable spread spectrum */
PHYCLKRST_SSC_EN |
/* Power down HS Bias and PLL blocks in suspend mode */
PHYCLKRST_COMMONONN;
writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST);
udelay(10);
phyclkrst &= ~(PHYCLKRST_PORTRESET);
writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST);
}
static void samsung_exynos5_usb3phy_disable(struct samsung_usbphy *sphy)
{
u32 phyutmi;
u32 phyclkrst;
u32 phytest;
void __iomem *regs = sphy->regs;
phyutmi = PHYUTMI_OTGDISABLE |
PHYUTMI_FORCESUSPEND |
PHYUTMI_FORCESLEEP;
writel(phyutmi, regs + EXYNOS5_DRD_PHYUTMI);
/* Resetting the PHYCLKRST enable bits to reduce leakage current */
phyclkrst = readl(regs + EXYNOS5_DRD_PHYCLKRST);
phyclkrst &= ~(PHYCLKRST_REF_SSP_EN |
PHYCLKRST_SSC_EN |
PHYCLKRST_COMMONONN);
writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST);
/* Control PHYTEST to remove leakage current */
phytest = readl(regs + EXYNOS5_DRD_PHYTEST);
phytest |= (PHYTEST_POWERDOWN_SSP |
PHYTEST_POWERDOWN_HSP);
writel(phytest, regs + EXYNOS5_DRD_PHYTEST);
}
static int samsung_usb3phy_init(struct usb_phy *phy)
{
struct samsung_usbphy *sphy;
unsigned long flags;
int ret = 0;
sphy = phy_to_sphy(phy);
/* Enable the phy clock */
ret = clk_prepare_enable(sphy->clk);
if (ret) {
dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__);
return ret;
}
spin_lock_irqsave(&sphy->lock, flags);
/* setting default phy-type for USB 3.0 */
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE);
/* Disable phy isolation */
if (sphy->drv_data->set_isolation)
sphy->drv_data->set_isolation(sphy, false);
/* Initialize usb phy registers */
sphy->drv_data->phy_enable(sphy);
spin_unlock_irqrestore(&sphy->lock, flags);
/* Disable the phy clock */
clk_disable_unprepare(sphy->clk);
return ret;
}
/*
* The function passed to the usb driver for phy shutdown
*/
static void samsung_usb3phy_shutdown(struct usb_phy *phy)
{
struct samsung_usbphy *sphy;
unsigned long flags;
sphy = phy_to_sphy(phy);
if (clk_prepare_enable(sphy->clk)) {
dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__);
return;
}
spin_lock_irqsave(&sphy->lock, flags);
/* setting default phy-type for USB 3.0 */
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE);
/* De-initialize usb phy registers */
sphy->drv_data->phy_disable(sphy);
/* Enable phy isolation */
if (sphy->drv_data->set_isolation)
sphy->drv_data->set_isolation(sphy, true);
spin_unlock_irqrestore(&sphy->lock, flags);
clk_disable_unprepare(sphy->clk);
}
static int samsung_usb3phy_probe(struct platform_device *pdev)
{
struct samsung_usbphy *sphy;
struct samsung_usbphy_data *pdata = dev_get_platdata(&pdev->dev);
struct device *dev = &pdev->dev;
struct resource *phy_mem;
void __iomem *phy_base;
struct clk *clk;
int ret;
phy_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
phy_base = devm_ioremap_resource(dev, phy_mem);
if (IS_ERR(phy_base))
return PTR_ERR(phy_base);
sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL);
if (!sphy)
return -ENOMEM;
clk = devm_clk_get(dev, "usbdrd30");
if (IS_ERR(clk)) {
dev_err(dev, "Failed to get device clock\n");
return PTR_ERR(clk);
}
sphy->dev = dev;
if (dev->of_node) {
ret = samsung_usbphy_parse_dt(sphy);
if (ret < 0)
return ret;
} else {
if (!pdata) {
dev_err(dev, "no platform data specified\n");
return -EINVAL;
}
}
sphy->plat = pdata;
sphy->regs = phy_base;
sphy->clk = clk;
sphy->phy.dev = sphy->dev;
sphy->phy.label = "samsung-usb3phy";
sphy->phy.type = USB_PHY_TYPE_USB3;
sphy->phy.init = samsung_usb3phy_init;
sphy->phy.shutdown = samsung_usb3phy_shutdown;
sphy->drv_data = samsung_usbphy_get_driver_data(pdev);
sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy);
if (sphy->ref_clk_freq < 0)
return -EINVAL;
spin_lock_init(&sphy->lock);
platform_set_drvdata(pdev, sphy);
return usb_add_phy_dev(&sphy->phy);
}
static int samsung_usb3phy_remove(struct platform_device *pdev)
{
struct samsung_usbphy *sphy = platform_get_drvdata(pdev);
usb_remove_phy(&sphy->phy);
if (sphy->pmuregs)
iounmap(sphy->pmuregs);
if (sphy->sysreg)
iounmap(sphy->sysreg);
return 0;
}
static struct samsung_usbphy_drvdata usb3phy_exynos5 = {
.cpu_type = TYPE_EXYNOS5250,
.devphy_en_mask = EXYNOS_USBPHY_ENABLE,
.rate_to_clksel = samsung_usbphy_rate_to_clksel_4x12,
.set_isolation = samsung_usbphy_set_isolation_4210,
.phy_enable = samsung_exynos5_usb3phy_enable,
.phy_disable = samsung_exynos5_usb3phy_disable,
};
#ifdef CONFIG_OF
static const struct of_device_id samsung_usbphy_dt_match[] = {
{
.compatible = "samsung,exynos5250-usb3phy",
.data = &usb3phy_exynos5
},
{},
};
MODULE_DEVICE_TABLE(of, samsung_usbphy_dt_match);
#endif
static struct platform_device_id samsung_usbphy_driver_ids[] = {
{
.name = "exynos5250-usb3phy",
.driver_data = (unsigned long)&usb3phy_exynos5,
},
{},
};
MODULE_DEVICE_TABLE(platform, samsung_usbphy_driver_ids);
static struct platform_driver samsung_usb3phy_driver = {
.probe = samsung_usb3phy_probe,
.remove = samsung_usb3phy_remove,
.id_table = samsung_usbphy_driver_ids,
.driver = {
.name = "samsung-usb3phy",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_usbphy_dt_match),
},
};
module_platform_driver(samsung_usb3phy_driver);
MODULE_DESCRIPTION("Samsung USB 3.0 phy controller");
MODULE_AUTHOR("Vivek Gautam <gautam.vivek@samsung.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:samsung-usb3phy");

View File

@ -104,7 +104,6 @@ struct twl6030_usb {
int irq2; int irq2;
enum omap_musb_vbus_id_status linkstat; enum omap_musb_vbus_id_status linkstat;
u8 asleep; u8 asleep;
bool irq_enabled;
bool vbus_enable; bool vbus_enable;
const char *regulator; const char *regulator;
}; };
@ -373,7 +372,6 @@ static int twl6030_usb_probe(struct platform_device *pdev)
INIT_WORK(&twl->set_vbus_work, otg_set_vbus_work); INIT_WORK(&twl->set_vbus_work, otg_set_vbus_work);
twl->irq_enabled = true;
status = request_threaded_irq(twl->irq1, NULL, twl6030_usbotg_irq, status = request_threaded_irq(twl->irq1, NULL, twl6030_usbotg_irq,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"twl6030_usb", twl); "twl6030_usb", twl);

View File

@ -18,6 +18,8 @@
#include <linux/gpio.h> #include <linux/gpio.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/sysfs.h> #include <linux/sysfs.h>
@ -438,6 +440,43 @@ static int usbhsc_drvcllbck_notify_hotplug(struct platform_device *pdev)
/* /*
* platform functions * platform functions
*/ */
static const struct of_device_id usbhs_of_match[] = {
{
.compatible = "renesas,usbhs-r8a7790",
.data = (void *)USBHS_TYPE_R8A7790,
},
{
.compatible = "renesas,usbhs-r8a7791",
.data = (void *)USBHS_TYPE_R8A7791,
},
{ },
};
MODULE_DEVICE_TABLE(of, usbhs_of_match);
static struct renesas_usbhs_platform_info *usbhs_parse_dt(struct device *dev)
{
struct renesas_usbhs_platform_info *info;
struct renesas_usbhs_driver_param *dparam;
const struct of_device_id *of_id = of_match_device(usbhs_of_match, dev);
u32 tmp;
int gpio;
info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
if (!info)
return NULL;
dparam = &info->driver_param;
dparam->type = of_id ? (u32)of_id->data : 0;
if (!of_property_read_u32(dev->of_node, "renesas,buswait", &tmp))
dparam->buswait_bwait = tmp;
gpio = of_get_named_gpio_flags(dev->of_node, "renesas,enable-gpio", 0,
NULL);
if (gpio > 0)
dparam->enable_gpio = gpio;
return info;
}
static int usbhs_probe(struct platform_device *pdev) static int usbhs_probe(struct platform_device *pdev)
{ {
struct renesas_usbhs_platform_info *info = dev_get_platdata(&pdev->dev); struct renesas_usbhs_platform_info *info = dev_get_platdata(&pdev->dev);
@ -446,6 +485,10 @@ static int usbhs_probe(struct platform_device *pdev)
struct resource *res, *irq_res; struct resource *res, *irq_res;
int ret; int ret;
/* check device node */
if (pdev->dev.of_node)
info = pdev->dev.platform_data = usbhs_parse_dt(&pdev->dev);
/* check platform information */ /* check platform information */
if (!info) { if (!info) {
dev_err(&pdev->dev, "no platform information\n"); dev_err(&pdev->dev, "no platform information\n");
@ -689,6 +732,7 @@ static struct platform_driver renesas_usbhs_driver = {
.driver = { .driver = {
.name = "renesas_usbhs", .name = "renesas_usbhs",
.pm = &usbhsc_pm_ops, .pm = &usbhsc_pm_ops,
.of_match_table = of_match_ptr(usbhs_of_match),
}, },
.probe = usbhs_probe, .probe = usbhs_probe,
.remove = usbhs_remove, .remove = usbhs_remove,

View File

@ -1,27 +0,0 @@
/*
* Copyright (C) 2012 Samsung Electronics Co.Ltd
* http://www.samsung.com/
* Author: Praveen Paneri <p.paneri@samsung.com>
*
* Defines platform data for samsung usb phy driver.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#ifndef __SAMSUNG_USBPHY_PLATFORM_H
#define __SAMSUNG_USBPHY_PLATFORM_H
/**
* samsung_usbphy_data - Platform data for USB PHY driver.
* @pmu_isolation: Function to control usb phy isolation in PMU.
*/
struct samsung_usbphy_data {
void (*pmu_isolation)(int on);
};
extern void samsung_usbphy_set_pdata(struct samsung_usbphy_data *pd);
#endif /* __SAMSUNG_USBPHY_PLATFORM_H */

View File

@ -345,12 +345,13 @@ static inline int usb_ep_queue(struct usb_ep *ep,
* @ep:the endpoint associated with the request * @ep:the endpoint associated with the request
* @req:the request being canceled * @req:the request being canceled
* *
* if the request is still active on the endpoint, it is dequeued and its * If the request is still active on the endpoint, it is dequeued and its
* completion routine is called (with status -ECONNRESET); else a negative * completion routine is called (with status -ECONNRESET); else a negative
* error code is returned. * error code is returned. This is guaranteed to happen before the call to
* usb_ep_dequeue() returns.
* *
* note that some hardware can't clear out write fifos (to unlink the request * Note that some hardware can't clear out write fifos (to unlink the request
* at the head of the queue) except as part of disconnecting from usb. such * at the head of the queue) except as part of disconnecting from usb. Such
* restrictions prevent drivers from supporting configuration changes, * restrictions prevent drivers from supporting configuration changes,
* even to configuration zero (a "chapter 9" requirement). * even to configuration zero (a "chapter 9" requirement).
*/ */
@ -816,6 +817,8 @@ static inline int usb_gadget_disconnect(struct usb_gadget *gadget)
* Called in a context that permits sleeping. * Called in a context that permits sleeping.
* @suspend: Invoked on USB suspend. May be called in_interrupt. * @suspend: Invoked on USB suspend. May be called in_interrupt.
* @resume: Invoked on USB resume. May be called in_interrupt. * @resume: Invoked on USB resume. May be called in_interrupt.
* @reset: Invoked on USB bus reset. It is mandatory for all gadget drivers
* and should be called in_interrupt.
* @driver: Driver model state for this driver. * @driver: Driver model state for this driver.
* *
* Devices are disabled till a gadget driver successfully bind()s, which * Devices are disabled till a gadget driver successfully bind()s, which
@ -873,6 +876,7 @@ struct usb_gadget_driver {
void (*disconnect)(struct usb_gadget *); void (*disconnect)(struct usb_gadget *);
void (*suspend)(struct usb_gadget *); void (*suspend)(struct usb_gadget *);
void (*resume)(struct usb_gadget *); void (*resume)(struct usb_gadget *);
void (*reset)(struct usb_gadget *);
/* FIXME support safe rmmod */ /* FIXME support safe rmmod */
struct device_driver driver; struct device_driver driver;
@ -1013,6 +1017,12 @@ extern void usb_gadget_set_state(struct usb_gadget *gadget,
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
/* utility to tell udc core that the bus reset occurs */
extern void usb_gadget_udc_reset(struct usb_gadget *gadget,
struct usb_gadget_driver *driver);
/*-------------------------------------------------------------------------*/
/* utility wrapping a simple endpoint selection policy */ /* utility wrapping a simple endpoint selection policy */
extern struct usb_ep *usb_ep_autoconfig(struct usb_gadget *, extern struct usb_ep *usb_ep_autoconfig(struct usb_gadget *,

View File

@ -19,6 +19,7 @@ enum functionfs_flags {
FUNCTIONFS_HAS_HS_DESC = 2, FUNCTIONFS_HAS_HS_DESC = 2,
FUNCTIONFS_HAS_SS_DESC = 4, FUNCTIONFS_HAS_SS_DESC = 4,
FUNCTIONFS_HAS_MS_OS_DESC = 8, FUNCTIONFS_HAS_MS_OS_DESC = 8,
FUNCTIONFS_VIRTUAL_ADDR = 16,
}; };
/* Descriptor of an non-audio endpoint */ /* Descriptor of an non-audio endpoint */
@ -32,6 +33,16 @@ struct usb_endpoint_descriptor_no_audio {
__u8 bInterval; __u8 bInterval;
} __attribute__((packed)); } __attribute__((packed));
struct usb_functionfs_descs_head_v2 {
__le32 magic;
__le32 length;
__le32 flags;
/*
* __le32 fs_count, hs_count, fs_count; must be included manually in
* the structure taking flags into consideration.
*/
} __attribute__((packed));
/* Legacy format, deprecated as of 3.14. */ /* Legacy format, deprecated as of 3.14. */
struct usb_functionfs_descs_head { struct usb_functionfs_descs_head {
__le32 magic; __le32 magic;
@ -92,7 +103,7 @@ struct usb_ext_prop_desc {
* structure. Any flags that are not recognised cause the whole block to be * structure. Any flags that are not recognised cause the whole block to be
* rejected with -ENOSYS. * rejected with -ENOSYS.
* *
* Legacy descriptors format: * Legacy descriptors format (deprecated as of 3.14):
* *
* | off | name | type | description | * | off | name | type | description |
* |-----+-----------+--------------+--------------------------------------| * |-----+-----------+--------------+--------------------------------------|
@ -265,6 +276,12 @@ struct usb_functionfs_event {
*/ */
#define FUNCTIONFS_ENDPOINT_REVMAP _IO('g', 129) #define FUNCTIONFS_ENDPOINT_REVMAP _IO('g', 129)
/*
* Returns endpoint descriptor. If function is not active returns -ENODEV.
*/
#define FUNCTIONFS_ENDPOINT_DESC _IOR('g', 130, \
struct usb_endpoint_descriptor)
#endif /* _UAPI__LINUX_FUNCTIONFS_H__ */ #endif /* _UAPI__LINUX_FUNCTIONFS_H__ */

View File

@ -1,5 +1,5 @@
/* /*
* ffs-test.c.c -- user mode filesystem api for usb composite function * ffs-test.c -- user mode filesystem api for usb composite function
* *
* Copyright (C) 2010 Samsung Electronics * Copyright (C) 2010 Samsung Electronics
* Author: Michal Nazarewicz <mina86@mina86.com> * Author: Michal Nazarewicz <mina86@mina86.com>
@ -29,6 +29,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <pthread.h> #include <pthread.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -106,7 +107,9 @@ static void _msg(unsigned level, const char *fmt, ...)
/******************** Descriptors and Strings *******************************/ /******************** Descriptors and Strings *******************************/
static const struct { static const struct {
struct usb_functionfs_descs_head header; struct usb_functionfs_descs_head_v2 header;
__le32 fs_count;
__le32 hs_count;
struct { struct {
struct usb_interface_descriptor intf; struct usb_interface_descriptor intf;
struct usb_endpoint_descriptor_no_audio sink; struct usb_endpoint_descriptor_no_audio sink;
@ -114,11 +117,12 @@ static const struct {
} __attribute__((packed)) fs_descs, hs_descs; } __attribute__((packed)) fs_descs, hs_descs;
} __attribute__((packed)) descriptors = { } __attribute__((packed)) descriptors = {
.header = { .header = {
.magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC), .magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2),
.flags = cpu_to_le32(FUNCTIONFS_HAS_FS_DESC |
FUNCTIONFS_HAS_HS_DESC),
.length = cpu_to_le32(sizeof descriptors), .length = cpu_to_le32(sizeof descriptors),
.fs_count = cpu_to_le32(3),
.hs_count = cpu_to_le32(3),
}, },
.fs_count = cpu_to_le32(3),
.fs_descs = { .fs_descs = {
.intf = { .intf = {
.bLength = sizeof descriptors.fs_descs.intf, .bLength = sizeof descriptors.fs_descs.intf,
@ -142,6 +146,7 @@ static const struct {
/* .wMaxPacketSize = autoconfiguration (kernel) */ /* .wMaxPacketSize = autoconfiguration (kernel) */
}, },
}, },
.hs_count = cpu_to_le32(3),
.hs_descs = { .hs_descs = {
.intf = { .intf = {
.bLength = sizeof descriptors.fs_descs.intf, .bLength = sizeof descriptors.fs_descs.intf,
@ -168,6 +173,89 @@ static const struct {
}, },
}; };
static size_t descs_to_legacy(void **legacy, const void *descriptors_v2)
{
const unsigned char *descs_end, *descs_start;
__u32 length, fs_count = 0, hs_count = 0, count;
/* Read v2 header */
{
const struct {
const struct usb_functionfs_descs_head_v2 header;
const __le32 counts[];
} __attribute__((packed)) *const in = descriptors_v2;
const __le32 *counts = in->counts;
__u32 flags;
if (le32_to_cpu(in->header.magic) !=
FUNCTIONFS_DESCRIPTORS_MAGIC_V2)
return 0;
length = le32_to_cpu(in->header.length);
if (length <= sizeof in->header)
return 0;
length -= sizeof in->header;
flags = le32_to_cpu(in->header.flags);
if (flags & ~(FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC |
FUNCTIONFS_HAS_SS_DESC))
return 0;
#define GET_NEXT_COUNT_IF_FLAG(ret, flg) do { \
if (!(flags & (flg))) \
break; \
if (length < 4) \
return 0; \
ret = le32_to_cpu(*counts); \
length -= 4; \
++counts; \
} while (0)
GET_NEXT_COUNT_IF_FLAG(fs_count, FUNCTIONFS_HAS_FS_DESC);
GET_NEXT_COUNT_IF_FLAG(hs_count, FUNCTIONFS_HAS_HS_DESC);
GET_NEXT_COUNT_IF_FLAG(count, FUNCTIONFS_HAS_SS_DESC);
count = fs_count + hs_count;
if (!count)
return 0;
descs_start = (const void *)counts;
#undef GET_NEXT_COUNT_IF_FLAG
}
/*
* Find the end of FS and HS USB descriptors. SS descriptors
* are ignored since legacy format does not support them.
*/
descs_end = descs_start;
do {
if (length < *descs_end)
return 0;
length -= *descs_end;
descs_end += *descs_end;
} while (--count);
/* Allocate legacy descriptors and copy the data. */
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
struct {
struct usb_functionfs_descs_head header;
__u8 descriptors[];
} __attribute__((packed)) *out;
#pragma GCC diagnostic pop
length = sizeof out->header + (descs_end - descs_start);
out = malloc(length);
out->header.magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC);
out->header.length = cpu_to_le32(length);
out->header.fs_count = cpu_to_le32(fs_count);
out->header.hs_count = cpu_to_le32(hs_count);
memcpy(out->descriptors, descs_start, descs_end - descs_start);
*legacy = out;
}
return length;
}
#define STR_INTERFACE_ "Source/Sink" #define STR_INTERFACE_ "Source/Sink"
@ -487,12 +575,29 @@ ep0_consume(struct thread *ignore, const void *buf, size_t nbytes)
return nbytes; return nbytes;
} }
static void ep0_init(struct thread *t) static void ep0_init(struct thread *t, bool legacy_descriptors)
{ {
void *legacy;
ssize_t ret; ssize_t ret;
size_t len;
info("%s: writing descriptors\n", t->filename); if (legacy_descriptors) {
info("%s: writing descriptors\n", t->filename);
goto legacy;
}
info("%s: writing descriptors (in v2 format)\n", t->filename);
ret = write(t->fd, &descriptors, sizeof descriptors); ret = write(t->fd, &descriptors, sizeof descriptors);
if (ret < 0 && errno == EINVAL) {
warn("%s: new format rejected, trying legacy\n", t->filename);
legacy:
len = descs_to_legacy(&legacy, &descriptors);
if (len) {
ret = write(t->fd, legacy, len);
free(legacy);
}
}
die_on(ret < 0, "%s: write: descriptors", t->filename); die_on(ret < 0, "%s: write: descriptors", t->filename);
info("%s: writing strings\n", t->filename); info("%s: writing strings\n", t->filename);
@ -503,14 +608,15 @@ static void ep0_init(struct thread *t)
/******************** Main **************************************************/ /******************** Main **************************************************/
int main(void) int main(int argc, char **argv)
{ {
bool legacy_descriptors;
unsigned i; unsigned i;
/* XXX TODO: Argument parsing missing */ legacy_descriptors = argc > 2 && !strcmp(argv[1], "-l");
init_thread(threads); init_thread(threads);
ep0_init(threads); ep0_init(threads, legacy_descriptors);
for (i = 1; i < sizeof threads / sizeof *threads; ++i) for (i = 1; i < sizeof threads / sizeof *threads; ++i)
init_thread(threads + i); init_thread(threads + i);