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:
commit
213db49399
|
@ -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
|
|
@ -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)
|
|
@ -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";
|
||||
};
|
|
@ -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>;
|
||||
};
|
||||
};
|
|
@ -5,6 +5,7 @@ Required properties:
|
|||
* "fsl,imx23-usbphy" for imx23 and imx28
|
||||
* "fsl,imx6q-usbphy" for imx6dq and imx6dl
|
||||
* "fsl,imx6sl-usbphy" for imx6sl
|
||||
* "fsl,vf610-usbphy" for Vybrid vf610
|
||||
* "fsl,imx6sx-usbphy" for imx6sx
|
||||
"fsl,imx23-usbphy" is still a fallback for other strings
|
||||
- reg: Should contain registers location and length
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
};
|
||||
|
|
@ -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>;
|
||||
};
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -1398,6 +1398,7 @@ F: drivers/media/rc/st_rc.c
|
|||
F: drivers/i2c/busses/i2c-st.c
|
||||
F: drivers/tty/serial/st-asc.c
|
||||
F: drivers/mmc/host/sdhci-st.c
|
||||
F: drivers/usb/dwc3/dwc3-st.c
|
||||
|
||||
ARM/TECHNOLOGIC SYSTEMS TS7250 MACHINE SUPPORT
|
||||
M: Lennert Buytenhek <kernel@wantstofly.org>
|
||||
|
@ -9683,7 +9684,7 @@ USB WEBCAM GADGET
|
|||
M: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
L: linux-usb@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/usb/gadget/function/*uvc*.c
|
||||
F: drivers/usb/gadget/function/*uvc*
|
||||
F: drivers/usb/gadget/legacy/webcam.c
|
||||
|
||||
USB WIRELESS RNDIS DRIVER (rndis_wlan)
|
||||
|
|
|
@ -80,6 +80,23 @@ config USB_DWC3_KEYSTONE
|
|||
Support of USB2/3 functionality in TI Keystone2 platforms.
|
||||
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"
|
||||
|
||||
config USB_DWC3_DEBUG
|
||||
|
|
|
@ -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_VERBOSE) += -DVERBOSE_DEBUG
|
||||
|
||||
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)),)
|
||||
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_PCI) += dwc3-pci.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
|
||||
|
|
|
@ -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,
|
||||
GFP_KERNEL);
|
||||
if (!dwc->ev_buffs) {
|
||||
dev_err(dwc->dev, "can't allocate event buffers array\n");
|
||||
if (!dwc->ev_buffs)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
struct dwc3_event_buffer *evt;
|
||||
|
@ -639,10 +637,9 @@ static int dwc3_probe(struct platform_device *pdev)
|
|||
void *mem;
|
||||
|
||||
mem = devm_kzalloc(dev, sizeof(*dwc) + DWC3_ALIGN_MASK, GFP_KERNEL);
|
||||
if (!mem) {
|
||||
dev_err(dev, "not enough memory\n");
|
||||
if (!mem)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
dwc = PTR_ALIGN(mem, DWC3_ALIGN_MASK + 1);
|
||||
dwc->mem = mem;
|
||||
dwc->dev = dev;
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
|
||||
#include <linux/phy/phy.h>
|
||||
|
||||
#define DWC3_MSG_MAX 500
|
||||
|
||||
/* Global constants */
|
||||
#define DWC3_EP0_BOUNCE_SIZE 512
|
||||
#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_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep,
|
||||
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
|
||||
static inline int dwc3_gadget_init(struct dwc3 *dwc)
|
||||
{ return 0; }
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -16,8 +16,206 @@
|
|||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef __DWC3_DEBUG_H
|
||||
#define __DWC3_DEBUG_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
|
||||
extern int dwc3_debugfs_init(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)
|
||||
{ }
|
||||
#endif
|
||||
|
||||
#endif /* __DWC3_DEBUG_H */
|
||||
|
|
|
@ -113,10 +113,8 @@ static int dwc3_exynos_probe(struct platform_device *pdev)
|
|||
int ret;
|
||||
|
||||
exynos = devm_kzalloc(dev, sizeof(*exynos), GFP_KERNEL);
|
||||
if (!exynos) {
|
||||
dev_err(dev, "not enough memory\n");
|
||||
if (!exynos)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/*
|
||||
* Right now device-tree probed devices don't get dma_mask set.
|
||||
|
|
|
@ -481,10 +481,8 @@ static int dwc3_omap_probe(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
omap = devm_kzalloc(dev, sizeof(*omap), GFP_KERNEL);
|
||||
if (!omap) {
|
||||
dev_err(dev, "not enough memory\n");
|
||||
if (!omap)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, omap);
|
||||
|
||||
|
|
|
@ -103,10 +103,8 @@ static int dwc3_pci_probe(struct pci_dev *pci,
|
|||
struct device *dev = &pci->dev;
|
||||
|
||||
glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL);
|
||||
if (!glue) {
|
||||
dev_err(dev, "not enough memory\n");
|
||||
if (!glue)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
glue->dev = dev;
|
||||
|
||||
|
|
|
@ -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>");
|
|
@ -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
|
||||
* 2’b00 : Override value from Reg 0x30 is selected
|
||||
* 2’b01 : utmiotg_<signal_name> from usb3_top is selected
|
||||
* 2’b10 : pipew_<signal_name> from PIPEW instance is selected
|
||||
* 2’b11 : 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");
|
|
@ -31,6 +31,7 @@
|
|||
#include <linux/usb/composite.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "debug.h"
|
||||
#include "gadget.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];
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
DWC3_DEPCMD_STARTTRANSFER, ¶ms);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -153,7 +155,8 @@ static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep,
|
|||
if (dwc->ep0state == EP0_STATUS_PHASE)
|
||||
__dwc3_ep0_do_control_status(dwc, dwc->eps[direction]);
|
||||
else
|
||||
dev_dbg(dwc->dev, "too early for delayed status\n");
|
||||
dwc3_trace(trace_dwc3_ep0,
|
||||
"too early for delayed status");
|
||||
|
||||
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);
|
||||
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);
|
||||
ret = -ESHUTDOWN;
|
||||
goto out;
|
||||
|
@ -229,7 +233,8 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request,
|
|||
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,
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -556,7 +562,7 @@ static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl)
|
|||
dwc3_writel(dwc->regs, DWC3_DCTL, reg);
|
||||
|
||||
dwc->resize_fifos = true;
|
||||
dev_dbg(dwc->dev, "resize fifos flag SET\n");
|
||||
dwc3_trace(trace_dwc3_ep0, "resize FIFOs flag SET");
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -680,35 +686,35 @@ static int dwc3_ep0_std_request(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl)
|
|||
|
||||
switch (ctrl->bRequest) {
|
||||
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);
|
||||
break;
|
||||
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);
|
||||
break;
|
||||
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);
|
||||
break;
|
||||
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);
|
||||
break;
|
||||
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);
|
||||
break;
|
||||
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);
|
||||
break;
|
||||
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);
|
||||
break;
|
||||
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);
|
||||
break;
|
||||
}
|
||||
|
@ -726,6 +732,8 @@ static void dwc3_ep0_inspect_setup(struct dwc3 *dwc,
|
|||
if (!dwc->gadget_driver)
|
||||
goto out;
|
||||
|
||||
trace_dwc3_ctrl_req(ctrl);
|
||||
|
||||
len = le16_to_cpu(ctrl->wLength);
|
||||
if (!len) {
|
||||
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);
|
||||
if (status == DWC3_TRBSTS_SETUP_PENDING) {
|
||||
dev_dbg(dwc->dev, "Setup Pending received\n");
|
||||
dwc3_trace(trace_dwc3_ep0, "Setup Pending received");
|
||||
|
||||
if (r)
|
||||
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);
|
||||
if (ret < 0) {
|
||||
dev_dbg(dwc->dev, "Invalid Test #%d\n",
|
||||
dwc3_trace(trace_dwc3_ep0, "Invalid Test #%d",
|
||||
dwc->test_mode_nr);
|
||||
dwc3_ep0_stall_and_restart(dwc);
|
||||
return;
|
||||
|
@ -843,7 +851,7 @@ static void dwc3_ep0_complete_status(struct dwc3 *dwc,
|
|||
|
||||
status = DWC3_TRB_SIZE_TRBSTS(trb->size);
|
||||
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;
|
||||
dwc3_ep0_out_start(dwc);
|
||||
|
@ -860,17 +868,17 @@ static void dwc3_ep0_xfer_complete(struct dwc3 *dwc,
|
|||
|
||||
switch (dwc->ep0state) {
|
||||
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);
|
||||
break;
|
||||
|
||||
case EP0_DATA_PHASE:
|
||||
dev_vdbg(dwc->dev, "Data Phase\n");
|
||||
dwc3_trace(trace_dwc3_ep0, "Data Phase");
|
||||
dwc3_ep0_complete_data(dwc, event);
|
||||
break;
|
||||
|
||||
case EP0_STATUS_PHASE:
|
||||
dev_vdbg(dwc->dev, "Status Phase\n");
|
||||
dwc3_trace(trace_dwc3_ep0, "Status Phase");
|
||||
dwc3_ep0_complete_status(dwc, event);
|
||||
break;
|
||||
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)
|
||||
{
|
||||
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);
|
||||
dwc->resize_fifos = 0;
|
||||
}
|
||||
|
@ -987,7 +995,7 @@ static void dwc3_ep0_xfernotready(struct dwc3 *dwc,
|
|||
|
||||
switch (event->status) {
|
||||
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,
|
||||
|
@ -1001,7 +1009,8 @@ static void dwc3_ep0_xfernotready(struct dwc3 *dwc,
|
|||
if (dwc->ep0_expect_in != event->endpoint_number) {
|
||||
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_stall_and_restart(dwc);
|
||||
return;
|
||||
|
@ -1013,13 +1022,13 @@ static void dwc3_ep0_xfernotready(struct dwc3 *dwc,
|
|||
if (dwc->ep0_next_event != DWC3_EP0_NRDY_STATUS)
|
||||
return;
|
||||
|
||||
dev_vdbg(dwc->dev, "Control Status\n");
|
||||
dwc3_trace(trace_dwc3_ep0, "Control Status");
|
||||
|
||||
dwc->ep0state = EP0_STATUS_PHASE;
|
||||
|
||||
if (dwc->delayed_status) {
|
||||
WARN_ON_ONCE(event->endpoint_number != 1);
|
||||
dev_vdbg(dwc->dev, "Mass Storage delayed status\n");
|
||||
dwc3_trace(trace_dwc3_ep0, "Delayed Status");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1032,7 +1041,7 @@ void dwc3_ep0_interrupt(struct dwc3 *dwc,
|
|||
{
|
||||
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),
|
||||
epnum >> 1, (epnum & 1) ? "in" : "out",
|
||||
dwc3_ep0_state_string(dwc->ep0state));
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <linux/usb/ch9.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "core.h"
|
||||
#include "gadget.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",
|
||||
req, dep->name, req->request.actual,
|
||||
req->request.length, status);
|
||||
trace_dwc3_gadget_giveback(req);
|
||||
|
||||
spin_unlock(&dwc->lock);
|
||||
req->request.complete(&dep->endpoint, &req->request);
|
||||
spin_lock(&dwc->lock);
|
||||
}
|
||||
|
||||
static 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";
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param)
|
||||
{
|
||||
u32 timeout = 500;
|
||||
u32 reg;
|
||||
|
||||
dev_vdbg(dwc->dev, "generic cmd '%s' [%d] param %08x\n",
|
||||
dwc3_gadget_generic_cmd_string(cmd), cmd, param);
|
||||
trace_dwc3_gadget_generic_cmd(cmd, param);
|
||||
|
||||
dwc3_writel(dwc->regs, DWC3_DGCMDPAR, param);
|
||||
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 reg;
|
||||
|
||||
dev_vdbg(dwc->dev, "%s: cmd '%s' [%d] params %08x %08x %08x\n",
|
||||
dep->name,
|
||||
dwc3_gadget_ep_cmd_string(cmd), cmd, params->param0,
|
||||
params->param1, params->param2);
|
||||
trace_dwc3_gadget_ep_cmd(dep, cmd, params);
|
||||
|
||||
dwc3_writel(dwc->regs, DWC3_DEPCMDPAR0(ep), params->param0);
|
||||
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_ep *dep = to_dwc3_ep(ep);
|
||||
struct dwc3 *dwc = dep->dwc;
|
||||
|
||||
req = kzalloc(sizeof(*req), gfp_flags);
|
||||
if (!req) {
|
||||
dev_err(dwc->dev, "not enough memory\n");
|
||||
if (!req)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
req->epnum = dep->number;
|
||||
req->dep = dep;
|
||||
|
||||
trace_dwc3_alloc_request(req);
|
||||
|
||||
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);
|
||||
|
||||
trace_dwc3_free_request(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_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",
|
||||
request, ep->name, request->length);
|
||||
trace_dwc3_ep_queue(req);
|
||||
|
||||
ret = __dwc3_gadget_ep_queue(dep, req);
|
||||
spin_unlock_irqrestore(&dwc->lock, flags);
|
||||
|
@ -1254,6 +1167,8 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep,
|
|||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
trace_dwc3_ep_dequeue(req);
|
||||
|
||||
spin_lock_irqsave(&dwc->lock, flags);
|
||||
|
||||
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);
|
||||
|
||||
dep = kzalloc(sizeof(*dep), GFP_KERNEL);
|
||||
if (!dep) {
|
||||
dev_err(dwc->dev, "can't allocate endpoint %d\n",
|
||||
epnum);
|
||||
if (!dep)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
dep->dwc = dwc;
|
||||
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 trb_status;
|
||||
|
||||
trace_dwc3_complete_trb(dep, trb);
|
||||
|
||||
if ((trb->ctrl & DWC3_TRB_CTRL_HWO) && status != -ESHUTDOWN)
|
||||
/*
|
||||
* 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))
|
||||
return;
|
||||
|
||||
dev_vdbg(dwc->dev, "%s: %s\n", dep->name,
|
||||
dwc3_ep_event_string(event->endpoint_event));
|
||||
|
||||
if (epnum == 0 || epnum == 1) {
|
||||
dwc3_ep0_interrupt(dwc, event);
|
||||
return;
|
||||
|
@ -2210,8 +2121,6 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc)
|
|||
{
|
||||
int reg;
|
||||
|
||||
dev_vdbg(dwc->dev, "%s\n", __func__);
|
||||
|
||||
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
|
||||
reg &= ~DWC3_DCTL_INITU1ENA;
|
||||
dwc3_writel(dwc->regs, DWC3_DCTL, reg);
|
||||
|
@ -2230,8 +2139,6 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc)
|
|||
{
|
||||
u32 reg;
|
||||
|
||||
dev_vdbg(dwc->dev, "%s\n", __func__);
|
||||
|
||||
/*
|
||||
* WORKAROUND: DWC3 revisions <1.88a have an issue which
|
||||
* 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;
|
||||
u8 speed;
|
||||
|
||||
dev_vdbg(dwc->dev, "%s\n", __func__);
|
||||
|
||||
reg = dwc3_readl(dwc->regs, DWC3_DSTS);
|
||||
speed = reg & DWC3_DSTS_CONNECTSPD;
|
||||
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)
|
||||
{
|
||||
dev_vdbg(dwc->dev, "%s\n", __func__);
|
||||
|
||||
/*
|
||||
* TODO take core out of low power mode when that's
|
||||
* implemented.
|
||||
|
@ -2521,10 +2424,6 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -2601,6 +2500,8 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc,
|
|||
static void dwc3_process_event_entry(struct dwc3 *dwc,
|
||||
const union dwc3_event *event)
|
||||
{
|
||||
trace_dwc3_event(event->raw);
|
||||
|
||||
/* Endpoint IRQ, handle it and return early */
|
||||
if (event->type.is_devspec == 0) {
|
||||
/* depevt */
|
||||
|
@ -2754,7 +2655,6 @@ int dwc3_gadget_init(struct dwc3 *dwc)
|
|||
|
||||
dwc->setup_buf = kzalloc(DWC3_EP0_BOUNCE_SIZE, GFP_KERNEL);
|
||||
if (!dwc->setup_buf) {
|
||||
dev_err(dwc->dev, "failed to allocate setup buffer\n");
|
||||
ret = -ENOMEM;
|
||||
goto err2;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 */
|
||||
|
|
|
@ -20,27 +20,51 @@
|
|||
#define __DRIVERS_USB_DWC3_IO_H
|
||||
|
||||
#include <linux/io.h>
|
||||
|
||||
#include "trace.h"
|
||||
#include "debug.h"
|
||||
#include "core.h"
|
||||
|
||||
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
|
||||
* space, see dwc3_probe in core.c.
|
||||
* 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)
|
||||
{
|
||||
u32 offs = offset - DWC3_GLOBALS_REGS_START;
|
||||
|
||||
/*
|
||||
* We requested the mem region starting from the Globals address
|
||||
* space, see dwc3_probe in core.c.
|
||||
* 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 */
|
||||
|
|
|
@ -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"
|
|
@ -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>
|
|
@ -181,6 +181,15 @@ config USB_F_MASS_STORAGE
|
|||
config USB_F_FS
|
||||
tristate
|
||||
|
||||
config USB_F_UAC1
|
||||
tristate
|
||||
|
||||
config USB_F_UAC2
|
||||
tristate
|
||||
|
||||
config USB_F_UVC
|
||||
tristate
|
||||
|
||||
choice
|
||||
tristate "USB Gadget Drivers"
|
||||
default USB_ETH
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#
|
||||
subdir-ccflags-$(CONFIG_USB_GADGET_DEBUG) := -DDEBUG
|
||||
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
|
||||
libcomposite-y := usbstring.o config.o epautoconf.o
|
||||
|
|
|
@ -2073,6 +2073,7 @@ static const struct usb_gadget_driver composite_driver_template = {
|
|||
.unbind = composite_unbind,
|
||||
|
||||
.setup = composite_setup,
|
||||
.reset = composite_disconnect,
|
||||
.disconnect = composite_disconnect,
|
||||
|
||||
.suspend = composite_suspend,
|
||||
|
|
|
@ -1450,6 +1450,7 @@ static const struct usb_gadget_driver configfs_driver_template = {
|
|||
.unbind = configfs_composite_unbind,
|
||||
|
||||
.setup = composite_setup,
|
||||
.reset = composite_disconnect,
|
||||
.disconnect = composite_disconnect,
|
||||
|
||||
.max_speed = USB_SPEED_SUPER,
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
# USB peripheral controller drivers
|
||||
#
|
||||
|
||||
ccflags-y := -Idrivers/usb/gadget/
|
||||
ccflags-y += -Idrivers/usb/gadget/udc/
|
||||
ccflags-y := -I$(srctree)/drivers/usb/gadget/
|
||||
ccflags-y += -I$(srctree)/drivers/usb/gadget/udc/
|
||||
|
||||
# USB Functions
|
||||
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
|
||||
usb_f_fs-y := 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
|
||||
|
|
|
@ -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;
|
||||
|
||||
if (req->status != 0) {
|
||||
DBG(cdev, "acm ttyGS%d completion, err %d\n",
|
||||
acm->port_num, req->status);
|
||||
dev_dbg(&cdev->gadget->dev, "acm ttyGS%d completion, err %d\n",
|
||||
acm->port_num, req->status);
|
||||
return;
|
||||
}
|
||||
|
||||
/* normal completion */
|
||||
if (req->actual != sizeof(acm->port_line_coding)) {
|
||||
DBG(cdev, "acm ttyGS%d short resp, len %d\n",
|
||||
acm->port_num, req->actual);
|
||||
dev_dbg(&cdev->gadget->dev, "acm ttyGS%d short resp, len %d\n",
|
||||
acm->port_num, req->actual);
|
||||
usb_ep_set_halt(ep);
|
||||
} else {
|
||||
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:
|
||||
invalid:
|
||||
VDBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
dev_vdbg(&cdev->gadget->dev,
|
||||
"invalid control req%02x.%02x v%04x i%04x l%d\n",
|
||||
ctrl->bRequestType, ctrl->bRequest,
|
||||
w_value, w_index, w_length);
|
||||
}
|
||||
|
||||
/* respond with data transfer or status phase? */
|
||||
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,
|
||||
w_value, w_index, w_length);
|
||||
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 (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);
|
||||
} 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))
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
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,
|
||||
acm->port.in) ||
|
||||
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 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);
|
||||
usb_ep_disable(acm->notify);
|
||||
acm->notify->driver_data = NULL;
|
||||
|
@ -537,8 +543,8 @@ static int acm_notify_serial_state(struct f_acm *acm)
|
|||
|
||||
spin_lock(&acm->lock);
|
||||
if (acm->notify_req) {
|
||||
DBG(cdev, "acm ttyGS%d serial state %04x\n",
|
||||
acm->port_num, acm->serial_state);
|
||||
dev_dbg(&cdev->gadget->dev, "acm ttyGS%d serial state %04x\n",
|
||||
acm->port_num, acm->serial_state);
|
||||
status = acm_cdc_notify(acm, USB_CDC_NOTIFY_SERIAL_STATE,
|
||||
0, &acm->serial_state, sizeof(acm->serial_state));
|
||||
} else {
|
||||
|
@ -691,12 +697,13 @@ acm_bind(struct usb_configuration *c, struct usb_function *f)
|
|||
if (status)
|
||||
goto fail;
|
||||
|
||||
DBG(cdev, "acm ttyGS%d: %s speed IN/%s OUT/%s NOTIFY/%s\n",
|
||||
acm->port_num,
|
||||
gadget_is_superspeed(c->cdev->gadget) ? "super" :
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
acm->port.in->name, acm->port.out->name,
|
||||
acm->notify->name);
|
||||
dev_dbg(&cdev->gadget->dev,
|
||||
"acm ttyGS%d: %s speed IN/%s OUT/%s NOTIFY/%s\n",
|
||||
acm->port_num,
|
||||
gadget_is_superspeed(c->cdev->gadget) ? "super" :
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
acm->port.in->name, acm->port.out->name,
|
||||
acm->notify->name);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
|
|
|
@ -1032,6 +1032,29 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
|
|||
case FUNCTIONFS_ENDPOINT_REVMAP:
|
||||
ret = epfile->ep->num;
|
||||
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:
|
||||
ret = -ENOTTY;
|
||||
}
|
||||
|
@ -1534,7 +1557,10 @@ static int ffs_epfiles_create(struct ffs_data *ffs)
|
|||
epfile->ffs = ffs;
|
||||
mutex_init(&epfile->mutex);
|
||||
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,
|
||||
&ffs_epfile_operations,
|
||||
&epfile->dentry))) {
|
||||
|
@ -2083,10 +2109,12 @@ static int __ffs_data_got_descs(struct ffs_data *ffs,
|
|||
break;
|
||||
case FUNCTIONFS_DESCRIPTORS_MAGIC_V2:
|
||||
flags = get_unaligned_le32(data + 8);
|
||||
ffs->user_flags = flags;
|
||||
if (flags & ~(FUNCTIONFS_HAS_FS_DESC |
|
||||
FUNCTIONFS_HAS_HS_DESC |
|
||||
FUNCTIONFS_HAS_SS_DESC |
|
||||
FUNCTIONFS_HAS_MS_OS_DESC)) {
|
||||
FUNCTIONFS_HAS_MS_OS_DESC |
|
||||
FUNCTIONFS_VIRTUAL_ADDR)) {
|
||||
ret = -ENOSYS;
|
||||
goto error;
|
||||
}
|
||||
|
@ -2346,7 +2374,8 @@ static void __ffs_event_add(struct ffs_data *ffs,
|
|||
break;
|
||||
|
||||
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 ffs_function *func = priv;
|
||||
struct ffs_ep *ffs_ep;
|
||||
unsigned ep_desc_id, idx;
|
||||
unsigned ep_desc_id;
|
||||
int idx;
|
||||
static const char *speed_names[] = { "full", "high", "super" };
|
||||
|
||||
if (type != FFS_DESCRIPTOR)
|
||||
|
@ -2441,7 +2471,13 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep,
|
|||
} else {
|
||||
struct usb_request *req;
|
||||
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");
|
||||
ep = usb_ep_autoconfig(func->gadget, ds);
|
||||
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;
|
||||
func->eps_revmap[ds->bEndpointAddress &
|
||||
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);
|
||||
|
||||
|
@ -2900,6 +2942,8 @@ static int ffs_func_setup(struct usb_function *f,
|
|||
ret = ffs_func_revmap_ep(func, le16_to_cpu(creq->wIndex));
|
||||
if (unlikely(ret < 0))
|
||||
return ret;
|
||||
if (func->ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)
|
||||
ret = func->ffs->eps_addrmap[ret];
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
|
@ -298,7 +298,8 @@ static void disable_loopback(struct f_loopback *loop)
|
|||
struct usb_composite_dev *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);
|
||||
}
|
||||
|
||||
|
|
|
@ -566,22 +566,22 @@ static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
|
|||
*pbusy = 1;
|
||||
*state = BUF_STATE_BUSY;
|
||||
spin_unlock_irq(&fsg->common->lock);
|
||||
|
||||
rc = usb_ep_queue(ep, req, GFP_KERNEL);
|
||||
if (rc != 0) {
|
||||
*pbusy = 0;
|
||||
*state = BUF_STATE_EMPTY;
|
||||
if (rc == 0)
|
||||
return; /* All good, we're done */
|
||||
|
||||
/* We can't do much more than wait for a reset */
|
||||
*pbusy = 0;
|
||||
*state = BUF_STATE_EMPTY;
|
||||
|
||||
/*
|
||||
* Note: currently the net2280 driver fails zero-length
|
||||
* submissions if DMA is enabled.
|
||||
*/
|
||||
if (rc != -ESHUTDOWN &&
|
||||
!(rc == -EOPNOTSUPP && req->length == 0))
|
||||
WARNING(fsg, "error in submission: %s --> %d\n",
|
||||
ep->name, rc);
|
||||
}
|
||||
/* We can't do much more than wait for a reset */
|
||||
|
||||
/*
|
||||
* Note: currently the net2280 driver fails zero-length
|
||||
* submissions if DMA is enabled.
|
||||
*/
|
||||
if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP && req->length == 0))
|
||||
WARNING(fsg, "error in submission: %s --> %d\n", ep->name, rc);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fsg_config_from_params);
|
||||
|
||||
|
|
|
@ -200,19 +200,22 @@ static int obex_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
|||
if (alt != 0)
|
||||
goto fail;
|
||||
/* 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) {
|
||||
if (alt > 1)
|
||||
goto fail;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
obex->port.in) ||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -252,7 +256,7 @@ static void obex_disable(struct usb_function *f)
|
|||
struct f_obex *obex = func_to_obex(f);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -269,7 +273,8 @@ static void obex_connect(struct gserial *g)
|
|||
|
||||
status = usb_function_activate(&g->func);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -284,7 +289,8 @@ static void obex_disconnect(struct gserial *g)
|
|||
|
||||
status = usb_function_deactivate(&g->func);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -383,10 +389,10 @@ static int obex_bind(struct usb_configuration *c, struct usb_function *f)
|
|||
obex->can_activate = true;
|
||||
|
||||
|
||||
DBG(cdev, "obex ttyGS%d: %s speed IN/%s OUT/%s\n",
|
||||
obex->port_num,
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
obex->port.in->name, obex->port.out->name);
|
||||
dev_dbg(&cdev->gadget->dev, "obex ttyGS%d: %s speed IN/%s OUT/%s\n",
|
||||
obex->port_num,
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
obex->port.in->name, obex->port.out->name);
|
||||
|
||||
return 0;
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
||||
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);
|
||||
}
|
||||
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) ||
|
||||
config_ep_by_speed(cdev->gadget, f, gser->port.out)) {
|
||||
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 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);
|
||||
}
|
||||
|
||||
|
@ -239,11 +242,11 @@ static int gser_bind(struct usb_configuration *c, struct usb_function *f)
|
|||
gser_ss_function);
|
||||
if (status)
|
||||
goto fail;
|
||||
DBG(cdev, "generic ttyGS%d: %s speed IN/%s OUT/%s\n",
|
||||
gser->port_num,
|
||||
gadget_is_superspeed(c->cdev->gadget) ? "super" :
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
gser->port.in->name, gser->port.out->name);
|
||||
dev_dbg(&cdev->gadget->dev, "generic ttyGS%d: %s speed IN/%s OUT/%s\n",
|
||||
gser->port_num,
|
||||
gadget_is_superspeed(c->cdev->gadget) ? "super" :
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
gser->port.in->name, gser->port.out->name);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
|
|
|
@ -23,6 +23,15 @@
|
|||
#include "gadget_chips.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
|
||||
* controller drivers.
|
||||
|
@ -55,6 +64,8 @@ struct f_sourcesink {
|
|||
struct usb_ep *out_ep;
|
||||
struct usb_ep *iso_in_ep;
|
||||
struct usb_ep *iso_out_ep;
|
||||
struct usb_ep *int_in_ep;
|
||||
struct usb_ep *int_out_ep;
|
||||
int cur_alt;
|
||||
};
|
||||
|
||||
|
@ -68,6 +79,10 @@ static unsigned isoc_interval;
|
|||
static unsigned isoc_maxpacket;
|
||||
static unsigned isoc_mult;
|
||||
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;
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
@ -92,6 +107,16 @@ static struct usb_interface_descriptor source_sink_intf_alt1 = {
|
|||
/* .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: */
|
||||
|
||||
static struct usb_endpoint_descriptor fs_source_desc = {
|
||||
|
@ -130,6 +155,26 @@ static struct usb_endpoint_descriptor fs_iso_sink_desc = {
|
|||
.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[] = {
|
||||
(struct usb_descriptor_header *) &source_sink_intf_alt0,
|
||||
(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_iso_sink_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,
|
||||
};
|
||||
|
||||
|
@ -179,6 +228,24 @@ static struct usb_endpoint_descriptor hs_iso_sink_desc = {
|
|||
.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[] = {
|
||||
(struct usb_descriptor_header *) &source_sink_intf_alt0,
|
||||
(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_iso_source_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,
|
||||
};
|
||||
|
||||
|
@ -264,6 +335,42 @@ static struct usb_ss_ep_comp_descriptor ss_iso_sink_comp_desc = {
|
|||
.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[] = {
|
||||
(struct usb_descriptor_header *) &source_sink_intf_alt0,
|
||||
(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_sink_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,
|
||||
};
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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,
|
||||
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, out);
|
||||
|
@ -336,6 +465,10 @@ void disable_endpoints(struct usb_composite_dev *cdev,
|
|||
disable_ep(cdev, iso_in);
|
||||
if (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
|
||||
|
@ -352,6 +485,7 @@ sourcesink_bind(struct usb_configuration *c, struct usb_function *f)
|
|||
return id;
|
||||
source_sink_intf_alt0.bInterfaceNumber = id;
|
||||
source_sink_intf_alt1.bInterfaceNumber = id;
|
||||
source_sink_intf_alt2.bInterfaceNumber = id;
|
||||
|
||||
/* allocate bulk endpoints */
|
||||
ss->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_source_desc);
|
||||
|
@ -412,14 +546,55 @@ no_iso:
|
|||
if (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 */
|
||||
hs_source_desc.bEndpointAddress = fs_source_desc.bEndpointAddress;
|
||||
hs_sink_desc.bEndpointAddress = fs_sink_desc.bEndpointAddress;
|
||||
|
||||
/*
|
||||
* Fill in the HS isoc descriptors from the module parameters.
|
||||
* We assume that the user knows what they are doing and won't
|
||||
* give parameters that their UDC doesn't support.
|
||||
* Fill in the HS isoc and interrupt descriptors from the module
|
||||
* parameters. We assume that the user knows what they are doing and
|
||||
* won't give parameters that their UDC doesn't support.
|
||||
*/
|
||||
hs_iso_source_desc.wMaxPacketSize = isoc_maxpacket;
|
||||
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.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 */
|
||||
ss_source_desc.bEndpointAddress =
|
||||
fs_source_desc.bEndpointAddress;
|
||||
|
@ -439,9 +625,9 @@ no_iso:
|
|||
fs_sink_desc.bEndpointAddress;
|
||||
|
||||
/*
|
||||
* Fill in the SS isoc descriptors from the module parameters.
|
||||
* We assume that the user knows what they are doing and won't
|
||||
* give parameters that their UDC doesn't support.
|
||||
* Fill in the SS isoc and interrupt descriptors from the module
|
||||
* parameters. We assume that the user knows what they are doing and
|
||||
* won't give parameters that their UDC doesn't support.
|
||||
*/
|
||||
ss_iso_source_desc.wMaxPacketSize = isoc_maxpacket;
|
||||
ss_iso_source_desc.bInterval = isoc_interval;
|
||||
|
@ -460,17 +646,37 @@ no_iso:
|
|||
isoc_maxpacket * (isoc_mult + 1) * (isoc_maxburst + 1);
|
||||
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,
|
||||
hs_source_sink_descs, ss_source_sink_descs);
|
||||
if (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_dualspeed(c->cdev->gadget) ? "dual" : "full")),
|
||||
f->name, ss->in_ep->name, ss->out_ep->name,
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
bool is_iso, int speed)
|
||||
enum eptype ep_type, int speed)
|
||||
{
|
||||
struct usb_ep *ep;
|
||||
struct usb_request *req;
|
||||
int i, size, status;
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (is_iso) {
|
||||
switch (ep_type) {
|
||||
case EP_ISOC:
|
||||
switch (speed) {
|
||||
case USB_SPEED_SUPER:
|
||||
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;
|
||||
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;
|
||||
req = ss_alloc_ep_req(ep, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!req)
|
||||
|
@ -644,12 +870,12 @@ static int source_sink_start_ep(struct f_sourcesink *ss, bool is_in,
|
|||
|
||||
cdev = ss->function.config->cdev;
|
||||
ERROR(cdev, "start %s%s %s --> %d\n",
|
||||
is_iso ? "ISO-" : "", is_in ? "IN" : "OUT",
|
||||
ep->name, status);
|
||||
get_ep_string(ep_type), is_in ? "IN" : "OUT",
|
||||
ep->name, status);
|
||||
free_ep_req(ep, req);
|
||||
}
|
||||
|
||||
if (!is_iso)
|
||||
if (!(ep_type == EP_ISOC))
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -662,7 +888,7 @@ static void disable_source_sink(struct f_sourcesink *ss)
|
|||
|
||||
cdev = ss->function.config->cdev;
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -674,6 +900,62 @@ enable_source_sink(struct usb_composite_dev *cdev, struct f_sourcesink *ss,
|
|||
int speed = cdev->gadget->speed;
|
||||
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) */
|
||||
ep = ss->in_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;
|
||||
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) {
|
||||
fail:
|
||||
ep = ss->in_ep;
|
||||
|
@ -703,7 +985,7 @@ fail:
|
|||
goto fail;
|
||||
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) {
|
||||
fail2:
|
||||
ep = ss->out_ep;
|
||||
|
@ -726,7 +1008,7 @@ fail2:
|
|||
goto fail2;
|
||||
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) {
|
||||
fail3:
|
||||
ep = ss->iso_in_ep;
|
||||
|
@ -749,13 +1031,14 @@ fail3:
|
|||
goto fail3;
|
||||
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) {
|
||||
usb_ep_disable(ep);
|
||||
ep->driver_data = NULL;
|
||||
goto fail3;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
ss->cur_alt = alt;
|
||||
|
||||
|
@ -771,6 +1054,8 @@ static int sourcesink_set_alt(struct usb_function *f,
|
|||
|
||||
if (ss->in_ep->driver_data)
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -883,6 +1168,10 @@ static struct usb_function *source_sink_alloc_func(
|
|||
isoc_maxpacket = ss_opts->isoc_maxpacket;
|
||||
isoc_mult = ss_opts->isoc_mult;
|
||||
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;
|
||||
|
||||
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_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[] = {
|
||||
&f_ss_opts_pattern.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_maxburst.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,
|
||||
};
|
||||
|
||||
|
@ -1215,6 +1684,8 @@ static struct usb_function_instance *source_sink_alloc_inst(void)
|
|||
ss_opts->isoc_interval = GZERO_ISOC_INTERVAL;
|
||||
ss_opts->isoc_maxpacket = GZERO_ISOC_MAXPACKET;
|
||||
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, "",
|
||||
&ss_func_type);
|
||||
|
|
|
@ -11,24 +11,12 @@
|
|||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/atomic.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_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
|
||||
|
||||
/* 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,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bNumEndpoints = 0,
|
||||
|
@ -183,12 +171,12 @@ static struct usb_endpoint_descriptor as_out_ep_desc = {
|
|||
.bEndpointAddress = USB_DIR_OUT,
|
||||
.bmAttributes = USB_ENDPOINT_SYNC_ADAPTIVE
|
||||
| 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,
|
||||
};
|
||||
|
||||
/* 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,
|
||||
.bDescriptorType = USB_DT_CS_ENDPOINT,
|
||||
.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),
|
||||
};
|
||||
|
||||
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_header_desc,
|
||||
|
||||
|
@ -216,6 +204,37 @@ static struct usb_descriptor_header *f_audio_desc[] __initdata = {
|
|||
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.
|
||||
*/
|
||||
|
@ -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 usb_composite_dev *cdev = audio->card.func.config->cdev;
|
||||
struct f_audio_buf *copy_buf = audio->copy_buf;
|
||||
struct f_uac1_opts *opts;
|
||||
int audio_buf_size;
|
||||
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)
|
||||
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_ep *out_ep = audio->out_ep;
|
||||
struct usb_request *req;
|
||||
struct f_uac1_opts *opts;
|
||||
int req_buf_size, req_count, audio_buf_size;
|
||||
int i = 0, err = 0;
|
||||
|
||||
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 (alt == 1) {
|
||||
usb_ep_enable(out_ep);
|
||||
|
@ -625,13 +657,37 @@ static void f_audio_build_desc(struct f_audio *audio)
|
|||
}
|
||||
|
||||
/* audio function driver setup/binding */
|
||||
static int __init
|
||||
static int
|
||||
f_audio_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct f_audio *audio = func_to_audio(f);
|
||||
struct usb_string *us;
|
||||
int status;
|
||||
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);
|
||||
|
||||
|
@ -666,20 +722,12 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f)
|
|||
return 0;
|
||||
|
||||
fail:
|
||||
gaudio_cleanup(&audio->card);
|
||||
if (ep)
|
||||
ep->driver_data = NULL;
|
||||
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)
|
||||
|
@ -695,7 +743,7 @@ static int generic_get_cmd(struct usb_audio_control *con, u8 cmd)
|
|||
}
|
||||
|
||||
/* 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);
|
||||
list_add(&feature_unit.list, &audio->cs);
|
||||
|
@ -712,57 +760,226 @@ static int __init control_selector_init(struct f_audio *audio)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* audio_bind_config - add USB audio function to a configuration
|
||||
* @c: the configuration to supcard the USB audio function
|
||||
* Context: single threaded during gadget setup
|
||||
*
|
||||
* Returns zero on success, else negative errno.
|
||||
*/
|
||||
static int __init audio_bind_config(struct usb_configuration *c)
|
||||
static inline struct f_uac1_opts *to_f_uac1_opts(struct config_item *item)
|
||||
{
|
||||
return container_of(to_config_group(item), struct f_uac1_opts,
|
||||
func_inst.group);
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR_STRUCT(f_uac1_opts);
|
||||
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;
|
||||
int status;
|
||||
struct f_uac1_opts *opts;
|
||||
|
||||
/* allocate and initialize one new instance */
|
||||
audio = kzalloc(sizeof *audio, GFP_KERNEL);
|
||||
audio = kzalloc(sizeof(*audio), GFP_KERNEL);
|
||||
if (!audio)
|
||||
return -ENOMEM;
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
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);
|
||||
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.unbind = f_audio_unbind;
|
||||
audio->card.func.set_alt = f_audio_set_alt;
|
||||
audio->card.func.setup = f_audio_setup;
|
||||
audio->card.func.disable = f_audio_disable;
|
||||
audio->card.func.free_func = f_audio_free;
|
||||
|
||||
control_selector_init(audio);
|
||||
|
||||
INIT_WORK(&audio->playback_work, f_audio_playback_work);
|
||||
|
||||
status = usb_add_function(c, &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;
|
||||
return &audio->card.func;
|
||||
}
|
||||
|
||||
DECLARE_USB_FUNCTION_INIT(uac1, f_audio_alloc_inst, f_audio_alloc);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Bryan Wu");
|
||||
|
|
|
@ -20,35 +20,7 @@
|
|||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
/* Playback(USB-IN) Default Stereo - Fl/Fr */
|
||||
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)");
|
||||
#include "u_uac2.h"
|
||||
|
||||
/* Keep everyone on toes */
|
||||
#define USB_XFERS 2
|
||||
|
@ -120,6 +92,15 @@ struct snd_uac2_chip {
|
|||
|
||||
struct snd_card *card;
|
||||
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)
|
||||
|
@ -149,8 +130,6 @@ struct audio_dev {
|
|||
struct snd_uac2_chip uac2;
|
||||
};
|
||||
|
||||
static struct audio_dev *agdev_g;
|
||||
|
||||
static inline
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
uint num_channels(uint chanmask)
|
||||
{
|
||||
|
@ -187,8 +172,8 @@ agdev_iso_complete(struct usb_ep *ep, struct usb_request *req)
|
|||
{
|
||||
unsigned pending;
|
||||
unsigned long flags;
|
||||
unsigned int hw_ptr;
|
||||
bool update_alsa = false;
|
||||
unsigned char *src, *dst;
|
||||
int status = req->status;
|
||||
struct uac2_req *ur = req->context;
|
||||
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);
|
||||
|
||||
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;
|
||||
dst = req->buf;
|
||||
} else {
|
||||
dst = prm->dma_area + prm->hw_ptr;
|
||||
src = req->buf;
|
||||
}
|
||||
|
||||
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)
|
||||
update_alsa = true;
|
||||
|
||||
hw_ptr = prm->hw_ptr;
|
||||
prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes;
|
||||
|
||||
spin_unlock_irqrestore(&prm->lock, flags);
|
||||
|
||||
/* 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:
|
||||
if (usb_ep_queue(ep, req, GFP_ATOMIC))
|
||||
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_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;
|
||||
|
||||
|
@ -411,7 +446,15 @@ static int snd_uac2_probe(struct platform_device *pdev)
|
|||
struct snd_uac2_chip *uac2 = pdev_to_uac2(pdev);
|
||||
struct snd_card *card;
|
||||
struct snd_pcm *pcm;
|
||||
struct audio_dev *audio_dev;
|
||||
struct f_uac2_opts *opts;
|
||||
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 */
|
||||
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__);
|
||||
}
|
||||
|
||||
static int __init
|
||||
static int
|
||||
afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
|
||||
{
|
||||
struct audio_dev *agdev = func_to_agdev(fn);
|
||||
struct snd_uac2_chip *uac2 = &agdev->uac2;
|
||||
struct usb_composite_dev *cdev = cfg->cdev;
|
||||
struct usb_gadget *gadget = cdev->gadget;
|
||||
struct device *dev = &uac2->pdev.dev;
|
||||
struct uac2_rtd_params *prm;
|
||||
struct f_uac2_opts *uac2_opts;
|
||||
struct usb_string *us;
|
||||
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);
|
||||
if (ret < 0) {
|
||||
dev_err(&uac2->pdev.dev,
|
||||
"%s:%d Error!\n", __func__, __LINE__);
|
||||
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
|
||||
return 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);
|
||||
if (ret < 0) {
|
||||
dev_err(&uac2->pdev.dev,
|
||||
"%s:%d Error!\n", __func__, __LINE__);
|
||||
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
|
||||
return 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);
|
||||
if (ret < 0) {
|
||||
dev_err(&uac2->pdev.dev,
|
||||
"%s:%d Error!\n", __func__, __LINE__);
|
||||
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
|
||||
return 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);
|
||||
if (!agdev->out_ep) {
|
||||
dev_err(&uac2->pdev.dev,
|
||||
"%s:%d Error!\n", __func__, __LINE__);
|
||||
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
|
||||
goto err;
|
||||
}
|
||||
agdev->out_ep->driver_data = agdev;
|
||||
|
||||
agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc);
|
||||
if (!agdev->in_ep) {
|
||||
dev_err(&uac2->pdev.dev,
|
||||
"%s:%d Error!\n", __func__, __LINE__);
|
||||
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
|
||||
goto err;
|
||||
}
|
||||
agdev->in_ep->driver_data = agdev;
|
||||
|
@ -1020,27 +1097,6 @@ err:
|
|||
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
|
||||
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 snd_uac2_chip *uac2 = &agdev->uac2;
|
||||
struct usb_gadget *gadget = cdev->gadget;
|
||||
struct device *dev = &uac2->pdev.dev;
|
||||
struct usb_request *req;
|
||||
struct usb_ep *ep;
|
||||
struct uac2_rtd_params *prm;
|
||||
int i;
|
||||
int req_len, i;
|
||||
|
||||
/* No i/f has more than 2 alt settings */
|
||||
if (alt > 1) {
|
||||
dev_err(&uac2->pdev.dev,
|
||||
"%s:%d Error!\n", __func__, __LINE__);
|
||||
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (intf == agdev->ac_intf) {
|
||||
/* Control I/f has only 1 AltSetting - 0 */
|
||||
if (alt) {
|
||||
dev_err(&uac2->pdev.dev,
|
||||
"%s:%d Error!\n", __func__, __LINE__);
|
||||
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
|
@ -1075,14 +1130,43 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
|
|||
prm = &uac2->c_prm;
|
||||
config_ep_by_speed(gadget, fn, ep);
|
||||
agdev->as_out_alt = alt;
|
||||
req_len = prm->max_psize;
|
||||
} 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;
|
||||
prm = &uac2->p_prm;
|
||||
config_ep_by_speed(gadget, fn, ep);
|
||||
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 {
|
||||
dev_err(&uac2->pdev.dev,
|
||||
"%s:%d Error!\n", __func__, __LINE__);
|
||||
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
@ -1095,31 +1179,23 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
|
|||
usb_ep_enable(ep);
|
||||
|
||||
for (i = 0; i < USB_XFERS; i++) {
|
||||
if (prm->ureq[i].req) {
|
||||
if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
|
||||
dev_err(&uac2->pdev.dev, "%d Error!\n",
|
||||
__LINE__);
|
||||
continue;
|
||||
if (!prm->ureq[i].req) {
|
||||
req = usb_ep_alloc_request(ep, GFP_ATOMIC);
|
||||
if (req == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
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 (req == NULL) {
|
||||
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__);
|
||||
if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
|
||||
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
|
||||
}
|
||||
|
||||
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 audio_dev *agdev = func_to_agdev(fn);
|
||||
struct snd_uac2_chip *uac2 = &agdev->uac2;
|
||||
struct f_uac2_opts *opts;
|
||||
u16 w_length = le16_to_cpu(cr->wLength);
|
||||
u16 w_index = le16_to_cpu(cr->wIndex);
|
||||
u16 w_value = le16_to_cpu(cr->wValue);
|
||||
u8 entity_id = (w_index >> 8) & 0xff;
|
||||
u8 control_selector = w_value >> 8;
|
||||
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) {
|
||||
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 audio_dev *agdev = func_to_agdev(fn);
|
||||
struct snd_uac2_chip *uac2 = &agdev->uac2;
|
||||
struct f_uac2_opts *opts;
|
||||
u16 w_length = le16_to_cpu(cr->wLength);
|
||||
u16 w_index = le16_to_cpu(cr->wIndex);
|
||||
u16 w_value = le16_to_cpu(cr->wValue);
|
||||
|
@ -1206,6 +1289,11 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
|
|||
u8 control_selector = w_value >> 8;
|
||||
struct cntrl_range_lay3 r;
|
||||
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 (entity_id == USB_IN_CLK_ID)
|
||||
|
@ -1309,66 +1397,184 @@ afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr)
|
|||
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;
|
||||
|
||||
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;
|
||||
return container_of(to_config_group(item), struct f_uac2_opts,
|
||||
func_inst.group);
|
||||
}
|
||||
|
||||
static void
|
||||
uac2_unbind_config(struct usb_configuration *cfg)
|
||||
CONFIGFS_ATTR_STRUCT(f_uac2_opts);
|
||||
CONFIGFS_ATTR_OPS(f_uac2_opts);
|
||||
|
||||
static void f_uac2_attr_release(struct config_item *item)
|
||||
{
|
||||
kfree(agdev_g);
|
||||
agdev_g = NULL;
|
||||
struct f_uac2_opts *opts = to_f_uac2_opts(item);
|
||||
|
||||
usb_put_function_instance(&opts->func_inst);
|
||||
}
|
||||
|
||||
static struct configfs_item_operations f_uac2_item_ops = {
|
||||
.release = f_uac2_attr_release,
|
||||
.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");
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/fs.h>
|
||||
|
@ -27,24 +28,12 @@
|
|||
#include <media/v4l2-event.h>
|
||||
|
||||
#include "uvc.h"
|
||||
#include "uvc_v4l2.h"
|
||||
#include "uvc_video.h"
|
||||
#include "u_uvc.h"
|
||||
|
||||
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
|
||||
*/
|
||||
|
@ -75,7 +64,7 @@ static struct usb_gadget_strings *uvc_function_strings[] = {
|
|||
|
||||
#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),
|
||||
.bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
|
||||
.bFirstInterface = 0,
|
||||
|
@ -86,7 +75,7 @@ static struct usb_interface_assoc_descriptor uvc_iad __initdata = {
|
|||
.iFunction = 0,
|
||||
};
|
||||
|
||||
static struct usb_interface_descriptor uvc_control_intf __initdata = {
|
||||
static struct usb_interface_descriptor uvc_control_intf = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = UVC_INTF_VIDEO_CONTROL,
|
||||
|
@ -98,7 +87,7 @@ static struct usb_interface_descriptor uvc_control_intf __initdata = {
|
|||
.iInterface = 0,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor uvc_control_ep __initdata = {
|
||||
static struct usb_endpoint_descriptor uvc_control_ep = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
|
@ -107,7 +96,7 @@ static struct usb_endpoint_descriptor uvc_control_ep __initdata = {
|
|||
.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),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
||||
/* 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),
|
||||
};
|
||||
|
||||
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,
|
||||
.bDescriptorType = USB_DT_CS_ENDPOINT,
|
||||
.bDescriptorSubType = UVC_EP_INTERRUPT,
|
||||
.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,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = UVC_INTF_VIDEO_STREAMING,
|
||||
|
@ -135,7 +124,7 @@ static struct usb_interface_descriptor uvc_streaming_intf_alt0 __initdata = {
|
|||
.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,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = UVC_INTF_VIDEO_STREAMING,
|
||||
|
@ -147,7 +136,7 @@ static struct usb_interface_descriptor uvc_streaming_intf_alt1 __initdata = {
|
|||
.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,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.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,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.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,
|
||||
.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),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
||||
/* The bMaxBurst, bmAttributes and wBytesPerInterval values will be
|
||||
|
@ -208,6 +197,12 @@ static const struct usb_descriptor_header * const uvc_ss_streaming[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
void uvc_set_trace_param(unsigned int trace)
|
||||
{
|
||||
uvc_gadget_trace_param = trace;
|
||||
}
|
||||
EXPORT_SYMBOL(uvc_set_trace_param);
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* 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)
|
||||
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));
|
||||
v4l2_event.type = UVC_EVENT_SETUP;
|
||||
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->fops = &uvc_v4l2_fops;
|
||||
video->ioctl_ops = &uvc_v4l2_ioctl_ops;
|
||||
video->release = video_device_release;
|
||||
video->vfl_dir = VFL_DIR_TX;
|
||||
strlcpy(video->name, cdev->gadget->name, sizeof(video->name));
|
||||
|
||||
uvc->vdev = video;
|
||||
|
@ -434,7 +437,7 @@ uvc_register_video(struct uvc_device *uvc)
|
|||
} \
|
||||
} 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
static void
|
||||
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
|
||||
static int
|
||||
uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
{
|
||||
struct usb_composite_dev *cdev = c->cdev;
|
||||
struct uvc_device *uvc = to_uvc(f);
|
||||
struct usb_string *us;
|
||||
unsigned int max_packet_mult;
|
||||
unsigned int max_packet_size;
|
||||
struct usb_ep *ep;
|
||||
struct f_uvc_opts *opts;
|
||||
int ret = -EINVAL;
|
||||
|
||||
INFO(cdev, "uvc_function_bind\n");
|
||||
|
||||
opts = to_f_uvc_opts(f->fi);
|
||||
/* Sanity check the streaming endpoint module parameters.
|
||||
*/
|
||||
streaming_interval = clamp(streaming_interval, 1U, 16U);
|
||||
streaming_maxpacket = clamp(streaming_maxpacket, 1U, 3072U);
|
||||
streaming_maxburst = min(streaming_maxburst, 15U);
|
||||
opts->streaming_interval = clamp(opts->streaming_interval, 1U, 16U);
|
||||
opts->streaming_maxpacket = clamp(opts->streaming_maxpacket, 1U, 3072U);
|
||||
opts->streaming_maxburst = min(opts->streaming_maxburst, 15U);
|
||||
|
||||
/* Fill in the FS/HS/SS Video Streaming specific descriptors from the
|
||||
* 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
|
||||
* give parameters that their UDC doesn't support.
|
||||
*/
|
||||
if (streaming_maxpacket <= 1024) {
|
||||
if (opts->streaming_maxpacket <= 1024) {
|
||||
max_packet_mult = 1;
|
||||
max_packet_size = streaming_maxpacket;
|
||||
} else if (streaming_maxpacket <= 2048) {
|
||||
max_packet_size = opts->streaming_maxpacket;
|
||||
} else if (opts->streaming_maxpacket <= 2048) {
|
||||
max_packet_mult = 2;
|
||||
max_packet_size = streaming_maxpacket / 2;
|
||||
max_packet_size = opts->streaming_maxpacket / 2;
|
||||
} else {
|
||||
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.bInterval = streaming_interval;
|
||||
uvc_fs_streaming_ep.wMaxPacketSize =
|
||||
cpu_to_le16(min(opts->streaming_maxpacket, 1023U));
|
||||
uvc_fs_streaming_ep.bInterval = opts->streaming_interval;
|
||||
|
||||
uvc_hs_streaming_ep.wMaxPacketSize = max_packet_size;
|
||||
uvc_hs_streaming_ep.wMaxPacketSize |= ((max_packet_mult - 1) << 11);
|
||||
uvc_hs_streaming_ep.bInterval = streaming_interval;
|
||||
uvc_hs_streaming_ep.wMaxPacketSize =
|
||||
cpu_to_le16(max_packet_size | ((max_packet_mult - 1) << 11));
|
||||
uvc_hs_streaming_ep.bInterval = opts->streaming_interval;
|
||||
|
||||
uvc_ss_streaming_ep.wMaxPacketSize = max_packet_size;
|
||||
uvc_ss_streaming_ep.bInterval = streaming_interval;
|
||||
uvc_ss_streaming_ep.wMaxPacketSize = cpu_to_le16(max_packet_size);
|
||||
uvc_ss_streaming_ep.bInterval = opts->streaming_interval;
|
||||
uvc_ss_streaming_comp.bmAttributes = max_packet_mult - 1;
|
||||
uvc_ss_streaming_comp.bMaxBurst = streaming_maxburst;
|
||||
uvc_ss_streaming_comp.bMaxBurst = opts->streaming_maxburst;
|
||||
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. */
|
||||
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_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. */
|
||||
if ((ret = usb_interface_id(c, f)) < 0)
|
||||
goto error;
|
||||
|
@ -697,7 +695,7 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
|
|||
}
|
||||
|
||||
/* Initialise video. */
|
||||
ret = uvc_video_init(&uvc->video);
|
||||
ret = uvcg_video_init(&uvc->video);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
|
@ -720,10 +718,9 @@ error:
|
|||
if (uvc->video.ep)
|
||||
uvc->video.ep->driver_data = NULL;
|
||||
|
||||
if (uvc->control_req) {
|
||||
if (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);
|
||||
return ret;
|
||||
|
@ -733,104 +730,81 @@ error:
|
|||
* USB gadget function
|
||||
*/
|
||||
|
||||
/**
|
||||
* uvc_bind_config - add a UVC function to a configuration
|
||||
* @c: the configuration to support the UVC instance
|
||||
* Context: single threaded during gadget setup
|
||||
*
|
||||
* Returns zero on success, else negative errno.
|
||||
*
|
||||
* Caller must have called @uvc_setup(). Caller is also responsible for
|
||||
* calling @uvc_cleanup() before module unload.
|
||||
*/
|
||||
int __init
|
||||
uvc_bind_config(struct usb_configuration *c,
|
||||
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)
|
||||
static void uvc_free_inst(struct usb_function_instance *f)
|
||||
{
|
||||
struct f_uvc_opts *opts = to_f_uvc_opts(f);
|
||||
|
||||
kfree(opts);
|
||||
}
|
||||
|
||||
static struct usb_function_instance *uvc_alloc_inst(void)
|
||||
{
|
||||
struct f_uvc_opts *opts;
|
||||
|
||||
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
|
||||
if (!opts)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
opts->func_inst.free_func_inst = uvc_free_inst;
|
||||
|
||||
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;
|
||||
int ret = 0;
|
||||
|
||||
/* TODO Check if the USB device controller supports the required
|
||||
* features.
|
||||
*/
|
||||
if (!gadget_is_dualspeed(c->cdev->gadget))
|
||||
return -EINVAL;
|
||||
struct f_uvc_opts *opts;
|
||||
|
||||
uvc = kzalloc(sizeof(*uvc), GFP_KERNEL);
|
||||
if (uvc == NULL)
|
||||
return -ENOMEM;
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
uvc->state = UVC_STATE_DISCONNECTED;
|
||||
opts = to_f_uvc_opts(fi);
|
||||
|
||||
/* Validate the descriptors. */
|
||||
if (fs_control == NULL || fs_control[0] == NULL ||
|
||||
fs_control[0]->bDescriptorSubType != UVC_VC_HEADER)
|
||||
goto error;
|
||||
|
||||
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;
|
||||
}
|
||||
uvc->desc.fs_control = opts->fs_control;
|
||||
uvc->desc.ss_control = opts->ss_control;
|
||||
uvc->desc.fs_streaming = opts->fs_streaming;
|
||||
uvc->desc.hs_streaming = opts->hs_streaming;
|
||||
uvc->desc.ss_streaming = opts->ss_streaming;
|
||||
|
||||
/* Register the function. */
|
||||
uvc->func.name = "uvc";
|
||||
uvc->func.strings = uvc_function_strings;
|
||||
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.set_alt = uvc_function_set_alt;
|
||||
uvc->func.disable = uvc_function_disable;
|
||||
uvc->func.setup = uvc_function_setup;
|
||||
uvc->func.free_func = uvc_free;
|
||||
|
||||
ret = usb_add_function(c, &uvc->func);
|
||||
if (ret)
|
||||
kfree(uvc);
|
||||
|
||||
return ret;
|
||||
|
||||
error:
|
||||
kfree(uvc);
|
||||
return ret;
|
||||
return &uvc->func;
|
||||
}
|
||||
|
||||
module_param_named(trace, uvc_gadget_trace_param, uint, S_IRUGO|S_IWUSR);
|
||||
MODULE_PARM_DESC(trace, "Trace level bitmask");
|
||||
|
||||
DECLARE_USB_FUNCTION_INIT(uvc, uvc_alloc_inst, uvc_alloc);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Laurent Pinchart");
|
||||
|
|
|
@ -16,12 +16,13 @@
|
|||
#include <linux/usb/composite.h>
|
||||
#include <linux/usb/video.h>
|
||||
|
||||
int uvc_bind_config(struct usb_configuration *c,
|
||||
const struct uvc_descriptor_header * const *fs_control,
|
||||
const struct uvc_descriptor_header * const *hs_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);
|
||||
#include "uvc.h"
|
||||
|
||||
void uvc_function_setup_continue(struct uvc_device *uvc);
|
||||
|
||||
void uvc_function_connect(struct uvc_device *uvc);
|
||||
|
||||
void uvc_function_disconnect(struct uvc_device *uvc);
|
||||
|
||||
#endif /* _F_UVC_H_ */
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#define GZERO_QLEN 32
|
||||
#define GZERO_ISOC_INTERVAL 4
|
||||
#define GZERO_ISOC_MAXPACKET 1024
|
||||
#define GZERO_INT_INTERVAL 1 /* Default interrupt interval = 1 ms */
|
||||
#define GZERO_INT_MAXPACKET 1024
|
||||
|
||||
struct usb_zero_options {
|
||||
unsigned pattern;
|
||||
|
@ -17,6 +19,10 @@ struct usb_zero_options {
|
|||
unsigned isoc_maxpacket;
|
||||
unsigned isoc_mult;
|
||||
unsigned isoc_maxburst;
|
||||
unsigned int_interval; /* In ms */
|
||||
unsigned int_maxpacket;
|
||||
unsigned int_mult;
|
||||
unsigned int_maxburst;
|
||||
unsigned bulk_buflen;
|
||||
unsigned qlen;
|
||||
};
|
||||
|
@ -28,6 +34,10 @@ struct f_ss_opts {
|
|||
unsigned isoc_maxpacket;
|
||||
unsigned isoc_mult;
|
||||
unsigned isoc_maxburst;
|
||||
unsigned int_interval; /* In ms */
|
||||
unsigned int_maxpacket;
|
||||
unsigned int_mult;
|
||||
unsigned int_maxburst;
|
||||
unsigned bulk_buflen;
|
||||
|
||||
/*
|
||||
|
@ -62,6 +72,7 @@ int lb_modinit(void);
|
|||
void free_ep_req(struct usb_ep *ep, struct usb_request *req);
|
||||
void disable_endpoints(struct usb_composite_dev *cdev,
|
||||
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 */
|
||||
|
|
|
@ -224,6 +224,8 @@ struct ffs_data {
|
|||
void *ms_os_descs_ext_prop_name_avail;
|
||||
void *ms_os_descs_ext_prop_data_avail;
|
||||
|
||||
unsigned user_flags;
|
||||
|
||||
u8 eps_addrmap[15];
|
||||
|
||||
unsigned short strings_count;
|
||||
|
|
|
@ -55,11 +55,8 @@
|
|||
* for a telephone or fax link. And ttyGS2 might be something that just
|
||||
* needs a simple byte stream interface for some messaging protocol that
|
||||
* is managed in userspace ... OBEX, PTP, and MTP have been mentioned.
|
||||
*/
|
||||
|
||||
#define PREFIX "ttyGS"
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
* gserial is the lifecycle interface, used by USB functions
|
||||
* gs_port is the I/O nexus, used by the tty driver
|
||||
* tty_struct links to the tty/filesystem framework
|
||||
|
@ -385,9 +382,9 @@ __acquires(&port->port_lock)
|
|||
list_del(&req->list);
|
||||
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",
|
||||
port->port_num, len, *((u8 *)req->buf),
|
||||
*((u8 *)req->buf+1), *((u8 *)req->buf+2));
|
||||
pr_vdebug("ttyGS%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n",
|
||||
port->port_num, len, *((u8 *)req->buf),
|
||||
*((u8 *)req->buf+1), *((u8 *)req->buf+2));
|
||||
|
||||
/* Drop lock while we call out of driver; completions
|
||||
* could be issued while we do so. Disconnection may
|
||||
|
@ -503,13 +500,13 @@ static void gs_rx_push(unsigned long _port)
|
|||
switch (req->status) {
|
||||
case -ESHUTDOWN:
|
||||
disconnect = true;
|
||||
pr_vdebug(PREFIX "%d: shutdown\n", port->port_num);
|
||||
pr_vdebug("ttyGS%d: shutdown\n", port->port_num);
|
||||
break;
|
||||
|
||||
default:
|
||||
/* presumably a transient fault */
|
||||
pr_warning(PREFIX "%d: unexpected RX status %d\n",
|
||||
port->port_num, req->status);
|
||||
pr_warn("ttyGS%d: unexpected RX status %d\n",
|
||||
port->port_num, req->status);
|
||||
/* FALLTHROUGH */
|
||||
case 0:
|
||||
/* normal completion */
|
||||
|
@ -537,9 +534,8 @@ static void gs_rx_push(unsigned long _port)
|
|||
if (count != size) {
|
||||
/* stop pushing; TTY layer can't handle more */
|
||||
port->n_read += count;
|
||||
pr_vdebug(PREFIX "%d: rx block %d/%d\n",
|
||||
port->port_num,
|
||||
count, req->actual);
|
||||
pr_vdebug("ttyGS%d: rx block %d/%d\n",
|
||||
port->port_num, count, req->actual);
|
||||
break;
|
||||
}
|
||||
port->n_read = 0;
|
||||
|
@ -569,7 +565,7 @@ static void gs_rx_push(unsigned long _port)
|
|||
if (do_push)
|
||||
tasklet_schedule(&port->push);
|
||||
else
|
||||
pr_warning(PREFIX "%d: RX not scheduled?\n",
|
||||
pr_warn("ttyGS%d: RX not scheduled?\n",
|
||||
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.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
@ -1295,7 +1291,7 @@ static int userial_init(void)
|
|||
return -ENOMEM;
|
||||
|
||||
gs_tty_driver->driver_name = "g_serial";
|
||||
gs_tty_driver->name = PREFIX;
|
||||
gs_tty_driver->name = "ttyGS";
|
||||
/* uses dynamically assigned dev_t values */
|
||||
|
||||
gs_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
|
@ -23,22 +24,6 @@
|
|||
* 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
|
||||
*/
|
||||
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 snd_pcm_substream *substream = snd->substream;
|
||||
|
@ -202,12 +187,12 @@ try_again:
|
|||
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;
|
||||
}
|
||||
|
||||
static int u_audio_get_playback_rate(struct gaudio *card)
|
||||
int u_audio_get_playback_rate(struct gaudio *card)
|
||||
{
|
||||
return card->playback.rate;
|
||||
}
|
||||
|
@ -220,6 +205,13 @@ static int gaudio_open_snd_dev(struct gaudio *card)
|
|||
{
|
||||
struct snd_pcm_file *pcm_file;
|
||||
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)
|
||||
return -ENODEV;
|
||||
|
@ -293,7 +285,6 @@ static int gaudio_close_snd_dev(struct gaudio *gau)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct gaudio *the_card;
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
int __init gaudio_setup(struct gaudio *card)
|
||||
int gaudio_setup(struct gaudio *card)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = gaudio_open_snd_dev(card);
|
||||
if (ret)
|
||||
ERROR(card, "we need at least one control device\n");
|
||||
else if (!the_card)
|
||||
the_card = card;
|
||||
|
||||
return ret;
|
||||
|
||||
|
@ -320,11 +309,10 @@ int __init gaudio_setup(struct gaudio *card)
|
|||
*
|
||||
* 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) {
|
||||
gaudio_close_snd_dev(the_card);
|
||||
the_card = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,14 @@
|
|||
|
||||
#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
|
||||
* function which provides control and stream interfaces.
|
||||
|
@ -50,7 +58,28 @@ struct gaudio {
|
|||
/* 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);
|
||||
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 */
|
||||
|
|
|
@ -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
|
|
@ -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 */
|
||||
|
|
@ -53,6 +53,7 @@ struct uvc_event
|
|||
#ifdef __KERNEL__
|
||||
|
||||
#include <linux/usb.h> /* For usb_endpoint_* */
|
||||
#include <linux/usb/composite.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/version.h>
|
||||
|
@ -96,9 +97,6 @@ extern unsigned int uvc_gadget_trace_param;
|
|||
* 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_MAX_REQUEST_SIZE 64
|
||||
#define UVC_MAX_EVENTS 4
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
/* ------------------------------------------------------------------------
|
||||
* 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.
|
||||
*
|
||||
* 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,
|
||||
};
|
||||
|
||||
static int uvc_queue_init(struct uvc_video_queue *queue,
|
||||
enum v4l2_buf_type type)
|
||||
int uvcg_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type)
|
||||
{
|
||||
int ret;
|
||||
|
||||
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.buf_struct_size = sizeof(struct uvc_buffer);
|
||||
queue->queue.ops = &uvc_queue_qops;
|
||||
|
@ -154,7 +153,7 @@ static int uvc_queue_init(struct uvc_video_queue *queue,
|
|||
/*
|
||||
* 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);
|
||||
vb2_queue_release(&queue->queue);
|
||||
|
@ -164,8 +163,8 @@ static void uvc_free_buffers(struct uvc_video_queue *queue)
|
|||
/*
|
||||
* Allocate the video buffers.
|
||||
*/
|
||||
static int uvc_alloc_buffers(struct uvc_video_queue *queue,
|
||||
struct v4l2_requestbuffers *rb)
|
||||
int uvcg_alloc_buffers(struct uvc_video_queue *queue,
|
||||
struct v4l2_requestbuffers *rb)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -176,8 +175,7 @@ static int uvc_alloc_buffers(struct uvc_video_queue *queue,
|
|||
return ret ? ret : rb->count;
|
||||
}
|
||||
|
||||
static int uvc_query_buffer(struct uvc_video_queue *queue,
|
||||
struct v4l2_buffer *buf)
|
||||
int uvcg_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -188,8 +186,7 @@ static int uvc_query_buffer(struct uvc_video_queue *queue,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int uvc_queue_buffer(struct uvc_video_queue *queue,
|
||||
struct v4l2_buffer *buf)
|
||||
int uvcg_queue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
@ -213,8 +210,8 @@ done:
|
|||
* Dequeue a video buffer. If nonblocking is false, block until a buffer is
|
||||
* available.
|
||||
*/
|
||||
static int uvc_dequeue_buffer(struct uvc_video_queue *queue,
|
||||
struct v4l2_buffer *buf, int nonblocking)
|
||||
int uvcg_dequeue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf,
|
||||
int nonblocking)
|
||||
{
|
||||
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
|
||||
* the device poll handler.
|
||||
*/
|
||||
static unsigned int uvc_queue_poll(struct uvc_video_queue *queue,
|
||||
struct file *file, poll_table *wait)
|
||||
unsigned int uvcg_queue_poll(struct uvc_video_queue *queue, struct file *file,
|
||||
poll_table *wait)
|
||||
{
|
||||
unsigned int ret;
|
||||
|
||||
|
@ -243,8 +240,7 @@ static unsigned int uvc_queue_poll(struct uvc_video_queue *queue,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int uvc_queue_mmap(struct uvc_video_queue *queue,
|
||||
struct vm_area_struct *vma)
|
||||
int uvcg_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma)
|
||||
{
|
||||
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.
|
||||
*/
|
||||
static unsigned long uvc_queue_get_unmapped_area(struct uvc_video_queue *queue,
|
||||
unsigned long pgoff)
|
||||
unsigned long uvcg_queue_get_unmapped_area(struct uvc_video_queue *queue,
|
||||
unsigned long pgoff)
|
||||
{
|
||||
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
|
||||
* 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;
|
||||
unsigned long flags;
|
||||
|
@ -324,9 +320,9 @@ static void uvc_queue_cancel(struct uvc_video_queue *queue, int disconnect)
|
|||
* the main queue.
|
||||
*
|
||||
* 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;
|
||||
int ret = 0;
|
||||
|
@ -363,8 +359,8 @@ done:
|
|||
}
|
||||
|
||||
/* called with &queue_irqlock held.. */
|
||||
static struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue,
|
||||
struct uvc_buffer *buf)
|
||||
struct uvc_buffer *uvcg_queue_next_buffer(struct uvc_video_queue *queue,
|
||||
struct uvc_buffer *buf)
|
||||
{
|
||||
struct uvc_buffer *nextbuf;
|
||||
|
||||
|
@ -392,7 +388,7 @@ static struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue,
|
|||
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;
|
||||
|
||||
|
|
|
@ -57,6 +57,39 @@ static inline int uvc_queue_streaming(struct uvc_video_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 /* _UVC_QUEUE_H_ */
|
||||
|
|
|
@ -23,8 +23,10 @@
|
|||
#include <media/v4l2-event.h>
|
||||
#include <media/v4l2-ioctl.h>
|
||||
|
||||
#include "f_uvc.h"
|
||||
#include "uvc.h"
|
||||
#include "uvc_queue.h"
|
||||
#include "uvc_video.h"
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Requests handling
|
||||
|
@ -48,7 +50,7 @@ uvc_send_response(struct uvc_device *uvc, struct uvc_request_data *data)
|
|||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* V4L2
|
||||
* V4L2 ioctls
|
||||
*/
|
||||
|
||||
struct uvc_format
|
||||
|
@ -63,8 +65,29 @@ static struct uvc_format uvc_formats[] = {
|
|||
};
|
||||
|
||||
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.width = video->width;
|
||||
fmt->fmt.pix.height = video->height;
|
||||
|
@ -78,8 +101,11 @@ uvc_v4l2_get_format(struct uvc_video *video, struct v4l2_format *fmt)
|
|||
}
|
||||
|
||||
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;
|
||||
unsigned int imagesize;
|
||||
unsigned int bpl;
|
||||
|
@ -115,6 +141,145 @@ uvc_v4l2_set_format(struct uvc_video *video, struct v4l2_format *fmt)
|
|||
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
|
||||
uvc_v4l2_open(struct file *file)
|
||||
{
|
||||
|
@ -146,8 +311,8 @@ uvc_v4l2_release(struct file *file)
|
|||
|
||||
uvc_function_disconnect(uvc);
|
||||
|
||||
uvc_video_enable(video, 0);
|
||||
uvc_free_buffers(&video->queue);
|
||||
uvcg_video_enable(video, 0);
|
||||
uvcg_free_buffers(&video->queue);
|
||||
|
||||
file->private_data = NULL;
|
||||
v4l2_fh_del(&handle->vfh);
|
||||
|
@ -157,177 +322,13 @@ uvc_v4l2_release(struct file *file)
|
|||
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
|
||||
uvc_v4l2_mmap(struct file *file, struct vm_area_struct *vma)
|
||||
{
|
||||
struct video_device *vdev = video_devdata(file);
|
||||
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
|
||||
|
@ -336,7 +337,7 @@ uvc_v4l2_poll(struct file *file, poll_table *wait)
|
|||
struct video_device *vdev = video_devdata(file);
|
||||
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
|
||||
|
@ -347,19 +348,19 @@ static unsigned long uvc_v4l2_get_unmapped_area(struct file *file,
|
|||
struct video_device *vdev = video_devdata(file);
|
||||
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
|
||||
|
||||
static struct v4l2_file_operations uvc_v4l2_fops = {
|
||||
struct v4l2_file_operations uvc_v4l2_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = uvc_v4l2_open,
|
||||
.release = uvc_v4l2_release,
|
||||
.ioctl = uvc_v4l2_ioctl,
|
||||
.ioctl = video_ioctl2,
|
||||
.mmap = uvc_v4l2_mmap,
|
||||
.poll = uvc_v4l2_poll,
|
||||
#ifndef CONFIG_MMU
|
||||
.get_unmapped_area = uvc_v4l2_get_unmapped_area,
|
||||
.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -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__ */
|
|
@ -15,6 +15,7 @@
|
|||
#include <linux/errno.h>
|
||||
#include <linux/usb/ch9.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
#include <linux/usb/video.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) {
|
||||
video->queue.buf_used = 0;
|
||||
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->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) {
|
||||
video->queue.buf_used = 0;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -171,19 +172,19 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
|
|||
break;
|
||||
|
||||
case -ESHUTDOWN: /* disconnect from host. */
|
||||
printk(KERN_INFO "VS request cancelled.\n");
|
||||
uvc_queue_cancel(queue, 1);
|
||||
printk(KERN_DEBUG "VS request cancelled.\n");
|
||||
uvcg_queue_cancel(queue, 1);
|
||||
goto requeue;
|
||||
|
||||
default:
|
||||
printk(KERN_INFO "VS request completed with status %d.\n",
|
||||
req->status);
|
||||
uvc_queue_cancel(queue, 0);
|
||||
uvcg_queue_cancel(queue, 0);
|
||||
goto requeue;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&video->queue.irqlock, flags);
|
||||
buf = uvc_queue_head(&video->queue);
|
||||
buf = uvcg_queue_head(&video->queue);
|
||||
if (buf == NULL) {
|
||||
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
||||
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);
|
||||
usb_ep_set_halt(ep);
|
||||
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
||||
uvc_queue_cancel(queue, 0);
|
||||
uvcg_queue_cancel(queue, 0);
|
||||
goto requeue;
|
||||
}
|
||||
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
|
||||
* video data from the queued buffers.
|
||||
*/
|
||||
static int
|
||||
uvc_video_pump(struct uvc_video *video)
|
||||
int uvcg_video_pump(struct uvc_video *video)
|
||||
{
|
||||
struct uvc_video_queue *queue = &video->queue;
|
||||
struct usb_request *req;
|
||||
|
@ -288,7 +288,7 @@ uvc_video_pump(struct uvc_video *video)
|
|||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
/* FIXME TODO Race between uvc_video_pump and requests completion
|
||||
/* FIXME TODO Race between uvcg_video_pump and requests completion
|
||||
* handler ???
|
||||
*/
|
||||
|
||||
|
@ -309,10 +309,10 @@ uvc_video_pump(struct uvc_video *video)
|
|||
/* Retrieve the first available video buffer and fill the
|
||||
* request, protected by the video queue irqlock.
|
||||
*/
|
||||
spin_lock_irqsave(&video->queue.irqlock, flags);
|
||||
buf = uvc_queue_head(&video->queue);
|
||||
spin_lock_irqsave(&queue->irqlock, flags);
|
||||
buf = uvcg_queue_head(queue);
|
||||
if (buf == NULL) {
|
||||
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
||||
spin_unlock_irqrestore(&queue->irqlock, flags);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -323,11 +323,11 @@ uvc_video_pump(struct uvc_video *video)
|
|||
if (ret < 0) {
|
||||
printk(KERN_INFO "Failed to queue request (%d)\n", ret);
|
||||
usb_ep_set_halt(video->ep);
|
||||
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
||||
uvc_queue_cancel(queue, 0);
|
||||
spin_unlock_irqrestore(&queue->irqlock, flags);
|
||||
uvcg_queue_cancel(queue, 0);
|
||||
break;
|
||||
}
|
||||
spin_unlock_irqrestore(&video->queue.irqlock, flags);
|
||||
spin_unlock_irqrestore(&queue->irqlock, 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.
|
||||
*/
|
||||
static int
|
||||
uvc_video_enable(struct uvc_video *video, int enable)
|
||||
int uvcg_video_enable(struct uvc_video *video, int enable)
|
||||
{
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
@ -356,11 +355,11 @@ uvc_video_enable(struct uvc_video *video, int enable)
|
|||
usb_ep_dequeue(video->ep, video->req[i]);
|
||||
|
||||
uvc_video_free_requests(video);
|
||||
uvc_queue_enable(&video->queue, 0);
|
||||
uvcg_queue_enable(&video->queue, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((ret = uvc_queue_enable(&video->queue, 1)) < 0)
|
||||
if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
|
||||
return ret;
|
||||
|
||||
if ((ret = uvc_video_alloc_requests(video)) < 0)
|
||||
|
@ -372,14 +371,13 @@ uvc_video_enable(struct uvc_video *video, int enable)
|
|||
} else
|
||||
video->encode = uvc_video_encode_isoc;
|
||||
|
||||
return uvc_video_pump(video);
|
||||
return uvcg_video_pump(video);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the UVC video stream.
|
||||
*/
|
||||
static int
|
||||
uvc_video_init(struct uvc_video *video)
|
||||
int uvcg_video_init(struct uvc_video *video)
|
||||
{
|
||||
INIT_LIST_HEAD(&video->req_free);
|
||||
spin_lock_init(&video->req_lock);
|
||||
|
@ -391,7 +389,7 @@ uvc_video_init(struct uvc_video *video)
|
|||
video->imagesize = 320 * 240 * 2;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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__ */
|
|
@ -54,6 +54,8 @@ config USB_AUDIO
|
|||
depends on SND
|
||||
select USB_LIBCOMPOSITE
|
||||
select SND_PCM
|
||||
select USB_F_UAC1 if GADGET_UAC1
|
||||
select USB_F_UAC2 if !GADGET_UAC1
|
||||
help
|
||||
This Gadget Audio driver is compatible with USB Audio Class
|
||||
specification 2.0. It implements 1 AudioControl interface,
|
||||
|
@ -466,6 +468,7 @@ config USB_G_WEBCAM
|
|||
depends on VIDEO_DEV
|
||||
select USB_LIBCOMPOSITE
|
||||
select VIDEOBUF2_VMALLOC
|
||||
select USB_F_UVC
|
||||
help
|
||||
The Webcam Gadget acts as a composite USB Audio and Video Class
|
||||
device. It provides a userspace API to process UVC control requests
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
# USB gadget drivers
|
||||
#
|
||||
|
||||
ccflags-y := -Idrivers/usb/gadget/
|
||||
ccflags-y += -Idrivers/usb/gadget/udc/
|
||||
ccflags-y += -Idrivers/usb/gadget/function/
|
||||
ccflags-y := -I$(srctree)/drivers/usb/gadget/
|
||||
ccflags-y += -I$(srctree)/drivers/usb/gadget/udc/
|
||||
ccflags-y += -I$(srctree)/drivers/usb/gadget/function/
|
||||
|
||||
g_zero-y := zero.o
|
||||
g_audio-y := audio.o
|
||||
|
|
|
@ -21,6 +21,66 @@
|
|||
|
||||
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 */
|
||||
|
||||
static struct usb_string strings_dev[] = {
|
||||
|
@ -40,12 +100,12 @@ static struct usb_gadget_strings *audio_strings[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_GADGET_UAC1
|
||||
#include "u_uac1.h"
|
||||
#include "u_uac1.c"
|
||||
#include "f_uac1.c"
|
||||
#ifndef CONFIG_GADGET_UAC1
|
||||
static struct usb_function_instance *fi_uac2;
|
||||
static struct usb_function *f_uac2;
|
||||
#else
|
||||
#include "f_uac2.c"
|
||||
static struct usb_function_instance *fi_uac1;
|
||||
static struct usb_function *f_uac1;
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
@ -109,6 +169,8 @@ static const struct usb_descriptor_header *otg_desc[] = {
|
|||
|
||||
static int __init audio_do_config(struct usb_configuration *c)
|
||||
{
|
||||
int status;
|
||||
|
||||
/* FIXME alloc iConfiguration string, set it in c->strings */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -126,17 +212,47 @@ static struct usb_configuration audio_config_driver = {
|
|||
.bConfigurationValue = 1,
|
||||
/* .iConfiguration = DYNAMIC */
|
||||
.bmAttributes = USB_CONFIG_ATT_SELFPOWER,
|
||||
#ifndef CONFIG_GADGET_UAC1
|
||||
.unbind = uac2_unbind_config,
|
||||
#endif
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
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;
|
||||
|
||||
#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);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
|
@ -152,13 +268,26 @@ static int __init audio_bind(struct usb_composite_dev *cdev)
|
|||
return 0;
|
||||
|
||||
fail:
|
||||
#ifndef CONFIG_GADGET_UAC1
|
||||
usb_put_function_instance(fi_uac2);
|
||||
#else
|
||||
usb_put_function_instance(fi_uac1);
|
||||
#endif
|
||||
return status;
|
||||
}
|
||||
|
||||
static int __exit audio_unbind(struct usb_composite_dev *cdev)
|
||||
{
|
||||
#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
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -410,6 +410,7 @@ static __refdata struct usb_gadget_driver dbgp_driver = {
|
|||
.bind = dbgp_bind,
|
||||
.unbind = dbgp_unbind,
|
||||
.setup = dbgp_setup,
|
||||
.reset = dbgp_disconnect,
|
||||
.disconnect = dbgp_disconnect,
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
|
|
|
@ -1775,6 +1775,7 @@ static struct usb_gadget_driver gadgetfs_driver = {
|
|||
.bind = gadgetfs_bind,
|
||||
.unbind = gadgetfs_unbind,
|
||||
.setup = gadgetfs_setup,
|
||||
.reset = gadgetfs_disconnect,
|
||||
.disconnect = gadgetfs_disconnect,
|
||||
.suspend = gadgetfs_suspend,
|
||||
|
||||
|
|
|
@ -12,23 +12,31 @@
|
|||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/usb/video.h>
|
||||
|
||||
#include "f_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"
|
||||
#include "u_uvc.h"
|
||||
|
||||
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
|
||||
*/
|
||||
|
@ -63,6 +71,9 @@ static struct usb_gadget_strings *webcam_device_strings[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
static struct usb_function_instance *fi_uvc;
|
||||
static struct usb_function *f_uvc;
|
||||
|
||||
static struct usb_device_descriptor webcam_device_descriptor = {
|
||||
.bLength = USB_DT_DEVICE_SIZE,
|
||||
.bDescriptorType = USB_DT_DEVICE,
|
||||
|
@ -326,9 +337,17 @@ static const struct uvc_descriptor_header * const uvc_ss_streaming_cls[] = {
|
|||
static int __init
|
||||
webcam_config_bind(struct usb_configuration *c)
|
||||
{
|
||||
return uvc_bind_config(c, uvc_fs_control_cls, uvc_ss_control_cls,
|
||||
uvc_fs_streaming_cls, uvc_hs_streaming_cls,
|
||||
uvc_ss_streaming_cls);
|
||||
int status = 0;
|
||||
|
||||
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 = {
|
||||
|
@ -342,14 +361,36 @@ static struct usb_configuration webcam_config_driver = {
|
|||
static int /* __init_or_exit */
|
||||
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;
|
||||
}
|
||||
|
||||
static int __init
|
||||
webcam_bind(struct usb_composite_dev *cdev)
|
||||
{
|
||||
struct f_uvc_opts *uvc_opts;
|
||||
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
|
||||
* can be overridden by the composite_dev glue.
|
||||
*/
|
||||
|
@ -373,7 +414,7 @@ webcam_bind(struct usb_composite_dev *cdev)
|
|||
return 0;
|
||||
|
||||
error:
|
||||
webcam_unbind(cdev);
|
||||
usb_put_function_instance(fi_uvc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -68,6 +68,8 @@ static struct usb_zero_options gzero_options = {
|
|||
.isoc_maxpacket = GZERO_ISOC_MAXPACKET,
|
||||
.bulk_buflen = GZERO_BULK_BUFLEN,
|
||||
.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);
|
||||
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_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_mult = gzero_options.isoc_mult;
|
||||
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;
|
||||
|
||||
func_ss = usb_get_function(func_inst_ss);
|
||||
|
|
|
@ -163,7 +163,7 @@ config USB_R8A66597
|
|||
|
||||
config USB_RENESAS_USBHS_UDC
|
||||
tristate 'Renesas USBHS controller'
|
||||
depends on USB_RENESAS_USBHS
|
||||
depends on USB_RENESAS_USBHS && HAS_DMA
|
||||
help
|
||||
Renesas USBHS is a discrete USB host and peripheral controller chip
|
||||
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.
|
||||
|
||||
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
|
||||
#
|
||||
|
|
|
@ -29,3 +29,4 @@ obj-$(CONFIG_USB_FUSB300) += fusb300_udc.o
|
|||
obj-$(CONFIG_USB_FOTG210_UDC) += fotg210-udc.o
|
||||
obj-$(CONFIG_USB_MV_U3D) += mv_u3d_core.o
|
||||
obj-$(CONFIG_USB_GR_UDC) += gr_udc.o
|
||||
obj-$(CONFIG_USB_GADGET_XILINX) += udc-xilinx.o
|
||||
|
|
|
@ -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);
|
||||
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;
|
||||
} 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 (ep->is_in)
|
||||
|
@ -379,6 +397,15 @@ static void gr_start_dma(struct gr_ep *ep)
|
|||
/* A descriptor should already have been allocated */
|
||||
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 */
|
||||
|
||||
/* 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;
|
||||
u16 size = min(bytes_left, ep->bytes_per_buffer);
|
||||
|
||||
/* Should not happen however - gr_queue stops such lengths */
|
||||
if (size < ep->bytes_per_buffer)
|
||||
dev_warn(ep->dev->dev,
|
||||
"Buffer overrun risk: %u < %u bytes/buffer\n",
|
||||
size, ep->bytes_per_buffer);
|
||||
if (size < ep->bytes_per_buffer) {
|
||||
/* Prepare using bounce buffer */
|
||||
req->evenlen = req->req.length - bytes_left;
|
||||
req->oddlen = size;
|
||||
}
|
||||
|
||||
ret = gr_add_dma_desc(ep, req, start, size, gfp_flags);
|
||||
if (ret)
|
||||
|
@ -584,18 +611,6 @@ static int gr_queue(struct gr_ep *ep, struct gr_request *req, gfp_t gfp_flags)
|
|||
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)) {
|
||||
dev_err(dev->dev, "-ESHUTDOWN");
|
||||
return -ESHUTDOWN;
|
||||
|
@ -1286,8 +1301,8 @@ static int gr_handle_out_ep(struct gr_ep *ep)
|
|||
if (ctrl & GR_DESC_OUT_CTRL_SE)
|
||||
req->setup = 1;
|
||||
|
||||
if (len < ep->ep.maxpacket || req->req.actual == req->req.length) {
|
||||
/* Short packet or the expected size - we are done */
|
||||
if (len < ep->ep.maxpacket || req->req.actual >= req->req.length) {
|
||||
/* Short packet or >= expected size - we are done */
|
||||
|
||||
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);
|
||||
|
||||
ep->tailbuf = dma_alloc_coherent(dev->dev, ep->ep.maxpacket_limit,
|
||||
&ep->tailbuf_paddr, GFP_ATOMIC);
|
||||
if (!ep->tailbuf)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -2067,9 +2087,24 @@ static int gr_udc_init(struct gr_udc *dev)
|
|||
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)
|
||||
{
|
||||
struct gr_udc *dev = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
if (dev->added)
|
||||
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->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;
|
||||
}
|
||||
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.max_speed = USB_SPEED_HIGH;
|
||||
dev->gadget.ops = &gr_ops;
|
||||
dev->gadget.quirk_ep_out_aligned_size = true;
|
||||
|
||||
spin_lock_init(&dev->lock);
|
||||
dev->regs = regs;
|
||||
|
|
|
@ -156,6 +156,10 @@ struct gr_ep {
|
|||
struct list_head queue;
|
||||
|
||||
struct list_head ep_list;
|
||||
|
||||
/* Bounce buffer for end of "odd" sized OUT requests */
|
||||
void *tailbuf;
|
||||
dma_addr_t tailbuf_paddr;
|
||||
};
|
||||
|
||||
struct gr_request {
|
||||
|
@ -167,6 +171,9 @@ struct gr_request {
|
|||
struct gr_dma_desc *curr_desc; /* Current descriptor */
|
||||
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 */
|
||||
};
|
||||
|
||||
|
|
|
@ -430,7 +430,7 @@ static void r8a66597_ep_setting(struct r8a66597 *r8a66597,
|
|||
ep->pipenum = pipenum;
|
||||
ep->ep.maxpacket = usb_endpoint_maxp(desc);
|
||||
r8a66597->pipenum2ep[pipenum] = ep;
|
||||
r8a66597->epaddr2ep[desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK]
|
||||
r8a66597->epaddr2ep[usb_endpoint_num(desc)]
|
||||
= ep;
|
||||
INIT_LIST_HEAD(&ep->queue);
|
||||
}
|
||||
|
@ -464,7 +464,7 @@ static int alloc_pipe_config(struct r8a66597_ep *ep,
|
|||
if (ep->pipenum) /* already allocated pipe */
|
||||
return 0;
|
||||
|
||||
switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) {
|
||||
switch (usb_endpoint_type(desc)) {
|
||||
case USB_ENDPOINT_XFER_BULK:
|
||||
if (r8a66597->bulk >= R8A66597_MAX_NUM_BULK) {
|
||||
if (r8a66597->isochronous >= R8A66597_MAX_NUM_ISOC) {
|
||||
|
@ -509,7 +509,7 @@ static int alloc_pipe_config(struct r8a66597_ep *ep,
|
|||
}
|
||||
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.interval = desc->bInterval;
|
||||
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");
|
||||
r8a66597->sudmac_reg = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(r8a66597->sudmac_reg)) {
|
||||
dev_err(&pdev->dev, "ioremap error(sudmac).\n");
|
||||
if (IS_ERR(r8a66597->sudmac_reg))
|
||||
return PTR_ERR(r8a66597->sudmac_reg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -109,8 +109,20 @@ EXPORT_SYMBOL_GPL(usb_gadget_unmap_request);
|
|||
static void usb_gadget_state_work(struct work_struct *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,
|
||||
|
@ -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
|
||||
* @gadget: The gadget we want to get started
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -54,6 +54,7 @@ struct usbtest_info {
|
|||
unsigned autoconf:1;
|
||||
unsigned ctrl_out:1;
|
||||
unsigned iso:1; /* try iso in/out */
|
||||
unsigned intr:1; /* try interrupt in/out */
|
||||
int alt;
|
||||
};
|
||||
|
||||
|
@ -70,7 +71,10 @@ struct usbtest_dev {
|
|||
int out_pipe;
|
||||
int in_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 *int_in, *int_out;
|
||||
struct mutex lock;
|
||||
|
||||
#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_endpoint *in, *out;
|
||||
struct usb_host_endpoint *iso_in, *iso_out;
|
||||
struct usb_host_endpoint *int_in, *int_out;
|
||||
struct usb_device *udev;
|
||||
|
||||
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;
|
||||
iso_in = iso_out = NULL;
|
||||
int_in = int_out = NULL;
|
||||
alt = intf->altsetting + tmp;
|
||||
|
||||
if (override_alt >= 0 &&
|
||||
|
@ -124,6 +130,9 @@ get_endpoints(struct usbtest_dev *dev, struct usb_interface *intf)
|
|||
switch (usb_endpoint_type(&e->desc)) {
|
||||
case USB_ENDPOINT_XFER_BULK:
|
||||
break;
|
||||
case USB_ENDPOINT_XFER_INT:
|
||||
if (dev->info->intr)
|
||||
goto try_intr;
|
||||
case USB_ENDPOINT_XFER_ISOC:
|
||||
if (dev->info->iso)
|
||||
goto try_iso;
|
||||
|
@ -139,6 +148,15 @@ get_endpoints(struct usbtest_dev *dev, struct usb_interface *intf)
|
|||
out = e;
|
||||
}
|
||||
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:
|
||||
if (usb_endpoint_dir_in(&e->desc)) {
|
||||
if (!iso_in)
|
||||
|
@ -148,7 +166,7 @@ try_iso:
|
|||
iso_out = e;
|
||||
}
|
||||
}
|
||||
if ((in && out) || iso_in || iso_out)
|
||||
if ((in && out) || iso_in || iso_out || int_in || int_out)
|
||||
goto found;
|
||||
}
|
||||
return -EINVAL;
|
||||
|
@ -183,6 +201,20 @@ found:
|
|||
iso_out->desc.bEndpointAddress
|
||||
& 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;
|
||||
}
|
||||
|
||||
|
@ -205,14 +237,22 @@ static struct urb *usbtest_alloc_urb(
|
|||
int pipe,
|
||||
unsigned long bytes,
|
||||
unsigned transfer_flags,
|
||||
unsigned offset)
|
||||
unsigned offset,
|
||||
u8 bInterval)
|
||||
{
|
||||
struct urb *urb;
|
||||
|
||||
urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (!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)
|
||||
? (INTERRUPT_RATE << 3)
|
||||
: INTERRUPT_RATE;
|
||||
|
@ -251,9 +291,11 @@ static struct urb *usbtest_alloc_urb(
|
|||
static struct urb *simple_alloc_urb(
|
||||
struct usb_device *udev,
|
||||
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;
|
||||
|
@ -1255,7 +1297,7 @@ test_ctrl_queue(struct usbtest_dev *dev, struct usbtest_param *param)
|
|||
goto cleanup;
|
||||
}
|
||||
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)
|
||||
goto cleanup;
|
||||
|
||||
|
@ -1328,7 +1370,7 @@ static int unlink1(struct usbtest_dev *dev, int pipe, int size, int async)
|
|||
int retval = 0;
|
||||
|
||||
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)
|
||||
return -ENOMEM;
|
||||
urb->context = &completion;
|
||||
|
@ -1616,9 +1658,9 @@ static int halt_simple(struct usbtest_dev *dev)
|
|||
struct usb_device *udev = testdev_to_usbdev(dev);
|
||||
|
||||
if (udev->speed == USB_SPEED_SUPER)
|
||||
urb = simple_alloc_urb(udev, 0, 1024);
|
||||
urb = simple_alloc_urb(udev, 0, 1024, 0);
|
||||
else
|
||||
urb = simple_alloc_urb(udev, 0, 512);
|
||||
urb = simple_alloc_urb(udev, 0, 512, 0);
|
||||
if (urb == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
|
@ -1962,7 +2004,7 @@ static int test_unaligned_bulk(
|
|||
{
|
||||
int retval;
|
||||
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)
|
||||
return -ENOMEM;
|
||||
|
@ -2068,7 +2110,7 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf)
|
|||
dev_info(&intf->dev,
|
||||
"TEST 1: write %d bytes %u times\n",
|
||||
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) {
|
||||
retval = -ENOMEM;
|
||||
break;
|
||||
|
@ -2083,7 +2125,7 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf)
|
|||
dev_info(&intf->dev,
|
||||
"TEST 2: read %d bytes %u times\n",
|
||||
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) {
|
||||
retval = -ENOMEM;
|
||||
break;
|
||||
|
@ -2098,7 +2140,7 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf)
|
|||
dev_info(&intf->dev,
|
||||
"TEST 3: write/%d 0..%d bytes %u times\n",
|
||||
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) {
|
||||
retval = -ENOMEM;
|
||||
break;
|
||||
|
@ -2114,7 +2156,7 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf)
|
|||
dev_info(&intf->dev,
|
||||
"TEST 4: read/%d 0..%d bytes %u times\n",
|
||||
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) {
|
||||
retval = -ENOMEM;
|
||||
break;
|
||||
|
@ -2411,6 +2453,39 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf)
|
|||
}
|
||||
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(¶m->duration);
|
||||
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;
|
||||
char *rtest, *wtest;
|
||||
char *irtest, *iwtest;
|
||||
char *intrtest, *intwtest;
|
||||
|
||||
udev = interface_to_usbdev(intf);
|
||||
|
||||
|
@ -2487,6 +2563,7 @@ usbtest_probe(struct usb_interface *intf, const struct usb_device_id *id)
|
|||
*/
|
||||
rtest = wtest = "";
|
||||
irtest = iwtest = "";
|
||||
intrtest = intwtest = "";
|
||||
if (force_interrupt || udev->speed == USB_SPEED_LOW) {
|
||||
if (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";
|
||||
if (dev->out_iso_pipe)
|
||||
iwtest = " iso-out";
|
||||
if (dev->in_int_pipe)
|
||||
intrtest = " int-in";
|
||||
if (dev->out_int_pipe)
|
||||
intwtest = " int-out";
|
||||
}
|
||||
|
||||
usb_set_intfdata(intf, dev);
|
||||
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),
|
||||
info->ctrl_out ? " in/out" : "",
|
||||
rtest, wtest,
|
||||
irtest, iwtest,
|
||||
intrtest, intwtest,
|
||||
info->alt >= 0 ? " (+alt)" : "");
|
||||
return 0;
|
||||
}
|
||||
|
@ -2607,6 +2689,7 @@ static struct usbtest_info gz_info = {
|
|||
.autoconf = 1,
|
||||
.ctrl_out = 1,
|
||||
.iso = 1,
|
||||
.intr = 1,
|
||||
.alt = 0,
|
||||
};
|
||||
|
||||
|
|
|
@ -212,7 +212,7 @@ static enum hrtimer_restart cppi41_recheck_tx_req(struct hrtimer *timer)
|
|||
if (!list_empty(&controller->early_tx_list)) {
|
||||
ret = HRTIMER_RESTART;
|
||||
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);
|
||||
|
@ -290,7 +290,7 @@ static void cppi41_dma_callback(void *private_data)
|
|||
|
||||
hrtimer_start_range_ns(&controller->early_tx,
|
||||
ktime_set(0, usecs * NSEC_PER_USEC),
|
||||
40 * NSEC_PER_USEC,
|
||||
20 * NSEC_PER_USEC,
|
||||
HRTIMER_MODE_REL);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -870,6 +870,7 @@ static int dsps_suspend(struct device *dev)
|
|||
struct musb *musb = platform_get_drvdata(glue->musb);
|
||||
void __iomem *mbase = musb->ctrl_base;
|
||||
|
||||
del_timer_sync(&glue->timer);
|
||||
glue->context.control = dsps_readl(mbase, wrp->control);
|
||||
glue->context.epintr = dsps_readl(mbase, wrp->epintr_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->tx_mode, glue->context.tx_mode);
|
||||
dsps_writel(mbase, wrp->rx_mode, glue->context.rx_mode);
|
||||
setup_timer(&glue->timer, otg_timer, (unsigned long) musb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -78,22 +78,6 @@ config SAMSUNG_USBPHY
|
|||
This driver provides common interface to interact, for Samsung USB 2.0 PHY
|
||||
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
|
||||
tristate "TWL6030 USB Transceiver Driver"
|
||||
depends on TWL4030_CORE && OMAP_USB2 && USB_MUSB_OMAP2PLUS
|
||||
|
|
|
@ -15,8 +15,6 @@ obj-$(CONFIG_AM335X_CONTROL_USB) += phy-am335x-control.o
|
|||
obj-$(CONFIG_AM335X_PHY_USB) += phy-am335x.o
|
||||
obj-$(CONFIG_OMAP_OTG) += phy-omap-otg.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_USB_EHCI_TEGRA) += phy-tegra-usb.o
|
||||
obj-$(CONFIG_USB_GPIO_VBUS) += phy-gpio-vbus-usb.o
|
||||
|
|
|
@ -281,7 +281,7 @@ static int msm_otg_phy_clk_reset(struct msm_otg *motg)
|
|||
{
|
||||
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);
|
||||
else if (motg->phy_rst)
|
||||
ret = reset_control_reset(motg->phy_rst);
|
||||
|
@ -1394,7 +1394,7 @@ out:
|
|||
return status;
|
||||
}
|
||||
|
||||
const struct file_operations msm_otg_mode_fops = {
|
||||
static const struct file_operations msm_otg_mode_fops = {
|
||||
.open = msm_otg_mode_open,
|
||||
.read = seq_read,
|
||||
.write = msm_otg_mode_write,
|
||||
|
@ -1554,11 +1554,14 @@ static int msm_otg_probe(struct platform_device *pdev)
|
|||
phy = &motg->phy;
|
||||
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");
|
||||
if (IS_ERR(motg->phy_reset_clk)) {
|
||||
dev_err(&pdev->dev, "failed to get usb_phy_clk\n");
|
||||
motg->phy_reset_clk = NULL;
|
||||
|
||||
if (IS_ERR(motg->phy_reset_clk)) {
|
||||
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");
|
||||
|
|
|
@ -125,6 +125,11 @@ static const struct mxs_phy_data imx6sl_phy_data = {
|
|||
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 = {
|
||||
.flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
|
||||
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,imx6q-usbphy", .data = &imx6q_phy_data, },
|
||||
{ .compatible = "fsl,imx23-usbphy", .data = &imx23_phy_data, },
|
||||
{ .compatible = "fsl,vf610-usbphy", .data = &vf610_phy_data, },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mxs_phy_dt_ids);
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
|
@ -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");
|
|
@ -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");
|
|
@ -104,7 +104,6 @@ struct twl6030_usb {
|
|||
int irq2;
|
||||
enum omap_musb_vbus_id_status linkstat;
|
||||
u8 asleep;
|
||||
bool irq_enabled;
|
||||
bool vbus_enable;
|
||||
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);
|
||||
|
||||
twl->irq_enabled = true;
|
||||
status = request_threaded_irq(twl->irq1, NULL, twl6030_usbotg_irq,
|
||||
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
"twl6030_usb", twl);
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#include <linux/gpio.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
@ -438,6 +440,43 @@ static int usbhsc_drvcllbck_notify_hotplug(struct platform_device *pdev)
|
|||
/*
|
||||
* 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)
|
||||
{
|
||||
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;
|
||||
int ret;
|
||||
|
||||
/* check device node */
|
||||
if (pdev->dev.of_node)
|
||||
info = pdev->dev.platform_data = usbhs_parse_dt(&pdev->dev);
|
||||
|
||||
/* check platform information */
|
||||
if (!info) {
|
||||
dev_err(&pdev->dev, "no platform information\n");
|
||||
|
@ -689,6 +732,7 @@ static struct platform_driver renesas_usbhs_driver = {
|
|||
.driver = {
|
||||
.name = "renesas_usbhs",
|
||||
.pm = &usbhsc_pm_ops,
|
||||
.of_match_table = of_match_ptr(usbhs_of_match),
|
||||
},
|
||||
.probe = usbhs_probe,
|
||||
.remove = usbhs_remove,
|
||||
|
|
|
@ -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 */
|
|
@ -345,12 +345,13 @@ static inline int usb_ep_queue(struct usb_ep *ep,
|
|||
* @ep:the endpoint associated with the request
|
||||
* @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
|
||||
* 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
|
||||
* at the head of the queue) except as part of disconnecting from usb. such
|
||||
* 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
|
||||
* restrictions prevent drivers from supporting configuration changes,
|
||||
* 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.
|
||||
* @suspend: Invoked on USB suspend. 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.
|
||||
*
|
||||
* 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 (*suspend)(struct usb_gadget *);
|
||||
void (*resume)(struct usb_gadget *);
|
||||
void (*reset)(struct usb_gadget *);
|
||||
|
||||
/* FIXME support safe rmmod */
|
||||
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 */
|
||||
|
||||
extern struct usb_ep *usb_ep_autoconfig(struct usb_gadget *,
|
||||
|
|
|
@ -19,6 +19,7 @@ enum functionfs_flags {
|
|||
FUNCTIONFS_HAS_HS_DESC = 2,
|
||||
FUNCTIONFS_HAS_SS_DESC = 4,
|
||||
FUNCTIONFS_HAS_MS_OS_DESC = 8,
|
||||
FUNCTIONFS_VIRTUAL_ADDR = 16,
|
||||
};
|
||||
|
||||
/* Descriptor of an non-audio endpoint */
|
||||
|
@ -32,6 +33,16 @@ struct usb_endpoint_descriptor_no_audio {
|
|||
__u8 bInterval;
|
||||
} __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. */
|
||||
struct usb_functionfs_descs_head {
|
||||
__le32 magic;
|
||||
|
@ -92,7 +103,7 @@ struct usb_ext_prop_desc {
|
|||
* structure. Any flags that are not recognised cause the whole block to be
|
||||
* rejected with -ENOSYS.
|
||||
*
|
||||
* Legacy descriptors format:
|
||||
* Legacy descriptors format (deprecated as of 3.14):
|
||||
*
|
||||
* | off | name | type | description |
|
||||
* |-----+-----------+--------------+--------------------------------------|
|
||||
|
@ -265,6 +276,12 @@ struct usb_functionfs_event {
|
|||
*/
|
||||
#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__ */
|
||||
|
|
|
@ -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
|
||||
* Author: Michal Nazarewicz <mina86@mina86.com>
|
||||
|
@ -29,6 +29,7 @@
|
|||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
@ -106,7 +107,9 @@ static void _msg(unsigned level, const char *fmt, ...)
|
|||
/******************** Descriptors and Strings *******************************/
|
||||
|
||||
static const struct {
|
||||
struct usb_functionfs_descs_head header;
|
||||
struct usb_functionfs_descs_head_v2 header;
|
||||
__le32 fs_count;
|
||||
__le32 hs_count;
|
||||
struct {
|
||||
struct usb_interface_descriptor intf;
|
||||
struct usb_endpoint_descriptor_no_audio sink;
|
||||
|
@ -114,11 +117,12 @@ static const struct {
|
|||
} __attribute__((packed)) fs_descs, hs_descs;
|
||||
} __attribute__((packed)) descriptors = {
|
||||
.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),
|
||||
.fs_count = cpu_to_le32(3),
|
||||
.hs_count = cpu_to_le32(3),
|
||||
},
|
||||
.fs_count = cpu_to_le32(3),
|
||||
.fs_descs = {
|
||||
.intf = {
|
||||
.bLength = sizeof descriptors.fs_descs.intf,
|
||||
|
@ -142,6 +146,7 @@ static const struct {
|
|||
/* .wMaxPacketSize = autoconfiguration (kernel) */
|
||||
},
|
||||
},
|
||||
.hs_count = cpu_to_le32(3),
|
||||
.hs_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"
|
||||
|
||||
|
@ -487,12 +575,29 @@ ep0_consume(struct thread *ignore, const void *buf, size_t 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;
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
info("%s: writing strings\n", t->filename);
|
||||
|
@ -503,14 +608,15 @@ static void ep0_init(struct thread *t)
|
|||
|
||||
/******************** Main **************************************************/
|
||||
|
||||
int main(void)
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
bool legacy_descriptors;
|
||||
unsigned i;
|
||||
|
||||
/* XXX TODO: Argument parsing missing */
|
||||
legacy_descriptors = argc > 2 && !strcmp(argv[1], "-l");
|
||||
|
||||
init_thread(threads);
|
||||
ep0_init(threads);
|
||||
ep0_init(threads, legacy_descriptors);
|
||||
|
||||
for (i = 1; i < sizeof threads / sizeof *threads; ++i)
|
||||
init_thread(threads + i);
|
||||
|
|
Loading…
Reference in New Issue