usb: changes for v4.13 merge window
This time around we have a total of 57 non-merge commits. A list of most important changes follows: - Improvements to dwc3 tracing interface - Initial dual-role support for dwc3 - Improvements to how we handle DMA resources in dwc3 - A new f_uac1 implementation which much more flexible - Removal of AVR32 bits - Improvements to f_mass_storage driver -----BEGIN PGP SIGNATURE----- iQJRBAABCAA7FiEElLzh7wn96CXwjh2IzL64meEamQYFAllHcK0dHGZlbGlwZS5i YWxiaUBsaW51eC5pbnRlbC5jb20ACgkQzL64meEamQbRQQ//as9W89twbmwOqaSU pXlbR/gmmEjD6POLWM2GuG3jH8oD3pQq7ZxH22YEFx8Z4wN7vPJ67JJkyoSfhAui ppnp6AbSPiNolZRb5nTnASnq0cJiTE/rbSM5s1wpe+Qa3ZoQgTHhipnL1/qf8SgR PN1wgUTGeXxiIA00iOYTG2pjM+OvFO5UpqFJCfh4vuEjcdBWvHDXTUwga5G+qwIa pgNECcmUsXmHimp6jE+qLUhRYOqvTEC+lc9nzZj4MCru3PDEhZYuOah0XrepiNKU NB49DVMtDwaGXrKPwa6rNWD8JZF0CAsShvO6V/2p1peInJZUuIrEjXYRUlkPQt9G yLzxBS+asLCBauzxBFNPrR2BFfJ2uyUCLHYiKP2UbRfWCBFo84MDJCUWACL1aKCH YLeM1Q7Urxp5suirr2UmwBJdUxXNTncEXsKrtGcrNndKt/Uq8/DxcrbZ2/6ANTDT wdzm8gSSjtQaFLRc5KgAqrX/ClfEDgQwfgq0DXn9cKxZ9E9xID45s71feKEoBI42 6S/oAopSuBsEmTsPTy0WLR8MTJG3MoJAdr/mHau11Tl0k9qr9KKnHbdUjji9CF6E aGcV40nKqp9Bd4bcbCNB6NsJUB+zcN7t0bjTsPZQH3XOCSS+fF7oJ6+SIxhsKAFT rHRl6RhLOIP0vZ0UIsDzeDttCVM= =v6a5 -----END PGP SIGNATURE----- Merge tag 'usb-for-v4.13' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb into usb-testing Felipe writes: usb: changes for v4.13 merge window This time around we have a total of 57 non-merge commits. A list of most important changes follows: - Improvements to dwc3 tracing interface - Initial dual-role support for dwc3 - Improvements to how we handle DMA resources in dwc3 - A new f_uac1 implementation which much more flexible - Removal of AVR32 bits - Improvements to f_mass_storage driver
This commit is contained in:
commit
24040a5837
|
@ -55,14 +55,6 @@ Description:
|
|||
Indicates the maximum USB speed supported by this port.
|
||||
Users:
|
||||
|
||||
What: /sys/class/udc/<udc>/maximum_speed
|
||||
Date: June 2011
|
||||
KernelVersion: 3.1
|
||||
Contact: Felipe Balbi <balbi@kernel.org>
|
||||
Description:
|
||||
Indicates the maximum USB speed supported by this port.
|
||||
Users:
|
||||
|
||||
What: /sys/class/udc/<udc>/soft_connect
|
||||
Date: June 2011
|
||||
KernelVersion: 3.1
|
||||
|
@ -91,3 +83,11 @@ Description:
|
|||
'configured', and 'suspended'; however not all USB Device
|
||||
Controllers support reporting all states.
|
||||
Users:
|
||||
|
||||
What: /sys/class/udc/<udc>/function
|
||||
Date: June 2017
|
||||
KernelVersion: 4.13
|
||||
Contact: Felipe Balbi <balbi@kernel.org>
|
||||
Description:
|
||||
Prints out name of currently running USB Gadget Driver.
|
||||
Users:
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
What: /config/usb-gadget/gadget/functions/uac1.name
|
||||
Date: Sep 2014
|
||||
KernelVersion: 3.18
|
||||
Date: June 2017
|
||||
KernelVersion: 4.14
|
||||
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
|
||||
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)
|
||||
req_number - the number of pre-allocated request
|
||||
for both capture and playback
|
||||
|
|
12
Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy
Normal file
12
Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy
Normal file
|
@ -0,0 +1,12 @@
|
|||
What: /config/usb-gadget/gadget/functions/uac1_legacy.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
|
|
@ -45,6 +45,8 @@ Optional properties:
|
|||
a free-running PHY clock.
|
||||
- snps,dis-del-phy-power-chg-quirk: when set core will change PHY power
|
||||
from P0 to P1/P2/P3 without delay.
|
||||
- snps,dis-tx-ipgap-linecheck-quirk: when set, disable u2mac linestate check
|
||||
during HS transmit.
|
||||
- snps,is-utmi-l1-suspend: true when DWC3 asserts output signal
|
||||
utmi_l1_suspend_n, false when asserts utmi_sleep_n
|
||||
- snps,hird-threshold: HIRD threshold
|
||||
|
|
21
Documentation/devicetree/bindings/usb/iproc-udc.txt
Normal file
21
Documentation/devicetree/bindings/usb/iproc-udc.txt
Normal file
|
@ -0,0 +1,21 @@
|
|||
Broadcom IPROC USB Device controller.
|
||||
|
||||
The device node is used for UDCs integrated into Broadcom's
|
||||
iProc family (Northstar2, Cygnus) of SoCs'. The UDC is based
|
||||
on Synopsys Designware Cores AHB Subsystem Device Controller
|
||||
IP.
|
||||
|
||||
Required properties:
|
||||
- compatible: Add the compatibility strings for supported platforms.
|
||||
For Broadcom NS2 platform, add "brcm,ns2-udc","brcm,iproc-udc".
|
||||
For Broadcom Cygnus platform, add "brcm,cygnus-udc", "brcm,iproc-udc".
|
||||
- reg: Offset and length of UDC register set
|
||||
- interrupts: description of interrupt line
|
||||
- phys: phandle to phy node.
|
||||
|
||||
Example:
|
||||
udc_dwc: usb@664e0000 {
|
||||
compatible = "brcm,ns2-udc", "brcm,iproc-udc";
|
||||
reg = <0x664e0000 0x2000>;
|
||||
interrupts = <GIC_SPI 424 IRQ_TYPE_LEVEL_HIGH>;
|
||||
phys = <&usbdrd_phy>;
|
712
Documentation/driver-api/usb/dwc3.rst
Normal file
712
Documentation/driver-api/usb/dwc3.rst
Normal file
|
@ -0,0 +1,712 @@
|
|||
===============================================================
|
||||
Synopsys DesignWare Core SuperSpeed USB 3.0 Controller
|
||||
===============================================================
|
||||
|
||||
:Author: Felipe Balbi <felipe.balbi@linux.intel.com>
|
||||
:Date: April 2017
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
The *Synopsys DesignWare Core SuperSpeed USB 3.0 Controller*
|
||||
(hereinafter referred to as *DWC3*) is a USB SuperSpeed compliant
|
||||
controller which can be configured in one of 4 ways:
|
||||
|
||||
1. Peripheral-only configuration
|
||||
2. Host-only configuration
|
||||
3. Dual-Role configuration
|
||||
4. Hub configuration
|
||||
|
||||
Linux currently supports several versions of this controller. In all
|
||||
likelyhood, the version in your SoC is already supported. At the time
|
||||
of this writing, known tested versions range from 2.02a to 3.10a. As a
|
||||
rule of thumb, anything above 2.02a should work reliably well.
|
||||
|
||||
Currently, we have many known users for this driver. In alphabetical
|
||||
order:
|
||||
|
||||
1. Cavium
|
||||
2. Intel Corporation
|
||||
3. Qualcomm
|
||||
4. Rockchip
|
||||
5. ST
|
||||
6. Samsung
|
||||
7. Texas Instruments
|
||||
8. Xilinx
|
||||
|
||||
Summary of Features
|
||||
======================
|
||||
|
||||
For details about features supported by your version of DWC3, consult
|
||||
your IP team and/or *Synopsys DesignWare Core SuperSpeed USB 3.0
|
||||
Controller Databook*. Following is a list of features supported by the
|
||||
driver at the time of this writing:
|
||||
|
||||
1. Up to 16 bidirectional endpoints (including the control
|
||||
pipe - ep0)
|
||||
2. Flexible endpoint configuration
|
||||
3. Simultaneous IN and OUT transfer support
|
||||
4. Scatter-list support
|
||||
5. Up to 256 TRBs [#trb]_ per endpoint
|
||||
6. Support for all transfer types (*Control*, *Bulk*,
|
||||
*Interrupt*, and *Isochronous*)
|
||||
7. SuperSpeed Bulk Streams
|
||||
8. Link Power Management
|
||||
9. Trace Events for debugging
|
||||
10. DebugFS [#debugfs]_ interface
|
||||
|
||||
These features have all been exercised with many of the **in-tree**
|
||||
gadget drivers. We have verified both *ConfigFS* [#configfs]_ and
|
||||
legacy gadget drivers.
|
||||
|
||||
Driver Design
|
||||
==============
|
||||
|
||||
The DWC3 driver sits on the *drivers/usb/dwc3/* directory. All files
|
||||
related to this driver are in this one directory. This makes it easy
|
||||
for new-comers to read the code and understand how it behaves.
|
||||
|
||||
Because of DWC3's configuration flexibility, the driver is a little
|
||||
complex in some places but it should be rather straightforward to
|
||||
understand.
|
||||
|
||||
The biggest part of the driver refers to the Gadget API.
|
||||
|
||||
Known Limitations
|
||||
===================
|
||||
|
||||
Like any other HW, DWC3 has its own set of limitations. To avoid
|
||||
constant questions about such problems, we decided to document them
|
||||
here and have a single location to where we could point users.
|
||||
|
||||
OUT Transfer Size Requirements
|
||||
---------------------------------
|
||||
|
||||
According to Synopsys Databook, all OUT transfer TRBs [#trb]_ must
|
||||
have their *size* field set to a value which is integer divisible by
|
||||
the endpoint's *wMaxPacketSize*. This means that *e.g.* in order to
|
||||
receive a Mass Storage *CBW* [#cbw]_, req->length must either be set
|
||||
to a value that's divisible by *wMaxPacketSize* (1024 on SuperSpeed,
|
||||
512 on HighSpeed, etc), or DWC3 driver must add a Chained TRB pointing
|
||||
to a throw-away buffer for the remaining length. Without this, OUT
|
||||
transfers will **NOT** start.
|
||||
|
||||
Note that as of this writing, this won't be a problem because DWC3 is
|
||||
fully capable of appending a chained TRB for the remaining length and
|
||||
completely hide this detail from the gadget driver. It's still worth
|
||||
mentioning because this seems to be the largest source of queries
|
||||
about DWC3 and *non-working transfers*.
|
||||
|
||||
TRB Ring Size Limitation
|
||||
-------------------------
|
||||
|
||||
We, currently, have a hard limit of 256 TRBs [#trb]_ per endpoint,
|
||||
with the last TRB being a Link TRB [#link_trb]_ pointing back to the
|
||||
first. This limit is arbitrary but it has the benefit of adding up to
|
||||
exactly 4096 bytes, or 1 Page.
|
||||
|
||||
DWC3 driver will try its best to cope with more than 255 requests and,
|
||||
for the most part, it should work normally. However this is not
|
||||
something that has been exercised very frequently. If you experience
|
||||
any problems, see section **Reporting Bugs** below.
|
||||
|
||||
Reporting Bugs
|
||||
================
|
||||
|
||||
Whenever you encounter a problem with DWC3, first and foremost you
|
||||
should make sure that:
|
||||
|
||||
1. You're running latest tag from `Linus' tree`_
|
||||
2. You can reproduce the error without any out-of-tree changes
|
||||
to DWC3
|
||||
3. You have checked that it's not a fault on the host machine
|
||||
|
||||
After all these are verified, then here's how to capture enough
|
||||
information so we can be of any help to you.
|
||||
|
||||
Required Information
|
||||
---------------------
|
||||
|
||||
DWC3 relies exclusively on Trace Events for debugging. Everything is
|
||||
exposed there, with some extra bits being exposed to DebugFS
|
||||
[#debugfs]_.
|
||||
|
||||
In order to capture DWC3's Trace Events you should run the following
|
||||
commands **before** plugging the USB cable to a host machine:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
# mkdir -p /d
|
||||
# mkdir -p /t
|
||||
# mount -t debugfs none /d
|
||||
# mount -t tracefs none /t
|
||||
# echo 81920 > /t/buffer_size_kb
|
||||
# echo 1 > /t/events/dwc3/enable
|
||||
|
||||
After this is done, you can connect your USB cable and reproduce the
|
||||
problem. As soon as the fault is reproduced, make a copy of files
|
||||
``trace`` and ``regdump``, like so:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
# cp /t/trace /root/trace.txt
|
||||
# cat /d/*dwc3*/regdump > /root/regdump.txt
|
||||
|
||||
Make sure to compress ``trace.txt`` and ``regdump.txt`` in a tarball
|
||||
and email it to `me`_ with `linux-usb`_ in Cc. If you want to be extra
|
||||
sure that I'll help you, write your subject line in the following
|
||||
format:
|
||||
|
||||
**[BUG REPORT] usb: dwc3: Bug while doing XYZ**
|
||||
|
||||
On the email body, make sure to detail what you doing, which gadget
|
||||
driver you were using, how to reproduce the problem, what SoC you're
|
||||
using, which OS (and its version) was running on the Host machine.
|
||||
|
||||
With all this information, we should be able to understand what's
|
||||
going on and be helpful to you.
|
||||
|
||||
Debugging
|
||||
===========
|
||||
|
||||
First and foremost a disclaimer::
|
||||
|
||||
DISCLAIMER: The information available on DebugFS and/or TraceFS can
|
||||
change at any time at any Major Linux Kernel Release. If writing
|
||||
scripts, do **NOT** assume information to be available in the
|
||||
current format.
|
||||
|
||||
With that out of the way, let's carry on.
|
||||
|
||||
If you're willing to debug your own problem, you deserve a round of
|
||||
applause :-)
|
||||
|
||||
Anyway, there isn't much to say here other than Trace Events will be
|
||||
really helpful in figuring out issues with DWC3. Also, access to
|
||||
Synopsys Databook will be **really** valuable in this case.
|
||||
|
||||
A USB Sniffer can be helpful at times but it's not entirely required,
|
||||
there's a lot that can be understood without looking at the wire.
|
||||
|
||||
Feel free to email `me`_ and Cc `linux-usb`_ if you need any help.
|
||||
|
||||
``DebugFS``
|
||||
-------------
|
||||
|
||||
``DebugFS`` is very good for gathering snapshots of what's going on
|
||||
with DWC3 and/or any endpoint.
|
||||
|
||||
On DWC3's ``DebugFS`` directory, you will find the following files and
|
||||
directories:
|
||||
|
||||
``ep[0..15]{in,out}/``
|
||||
``link_state``
|
||||
``regdump``
|
||||
``testmode``
|
||||
|
||||
``link_state``
|
||||
``````````````
|
||||
|
||||
When read, ``link_state`` will print out one of ``U0``, ``U1``,
|
||||
``U2``, ``U3``, ``SS.Disabled``, ``RX.Detect``, ``SS.Inactive``,
|
||||
``Polling``, ``Recovery``, ``Hot Reset``, ``Compliance``,
|
||||
``Loopback``, ``Reset``, ``Resume`` or ``UNKNOWN link state``.
|
||||
|
||||
This file can also be written to in order to force link to one of the
|
||||
states above.
|
||||
|
||||
``regdump``
|
||||
`````````````
|
||||
|
||||
File name is self-explanatory. When read, ``regdump`` will print out a
|
||||
register dump of DWC3. Note that this file can be grepped to find the
|
||||
information you want.
|
||||
|
||||
``testmode``
|
||||
``````````````
|
||||
|
||||
When read, ``testmode`` will print out a name of one of the specified
|
||||
USB 2.0 Testmodes (``test_j``, ``test_k``, ``test_se0_nak``,
|
||||
``test_packet``, ``test_force_enable``) or the string ``no test`` in
|
||||
case no tests are currently being executed.
|
||||
|
||||
In order to start any of these test modes, the same strings can be
|
||||
written to the file and DWC3 will enter the requested test mode.
|
||||
|
||||
|
||||
``ep[0..15]{in,out}``
|
||||
``````````````````````
|
||||
|
||||
For each endpoint we expose one directory following the naming
|
||||
convention ``ep$num$dir`` *(ep0in, ep0out, ep1in, ...)*. Inside each
|
||||
of these directories you will find the following files:
|
||||
|
||||
``descriptor_fetch_queue``
|
||||
``event_queue``
|
||||
``rx_fifo_queue``
|
||||
``rx_info_queue``
|
||||
``rx_request_queue``
|
||||
``transfer_type``
|
||||
``trb_ring``
|
||||
``tx_fifo_queue``
|
||||
``tx_request_queue``
|
||||
|
||||
With access to Synopsys Databook, you can decode the information on
|
||||
them.
|
||||
|
||||
``transfer_type``
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When read, ``transfer_type`` will print out one of ``control``,
|
||||
``bulk``, ``interrupt`` or ``isochronous`` depending on what the
|
||||
endpoint descriptor says. If the endpoint hasn't been enabled yet, it
|
||||
will print ``--``.
|
||||
|
||||
``trb_ring``
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
When read, ``trb_ring`` will print out details about all TRBs on the
|
||||
ring. It will also tell you where our enqueue and dequeue pointers are
|
||||
located in the ring:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
buffer_addr,size,type,ioc,isp_imi,csp,chn,lst,hwo
|
||||
000000002c754000,481,normal,1,0,1,0,0,0
|
||||
000000002c75c000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c78c000,481,normal,1,0,1,0,0,0
|
||||
000000002c754000,481,normal,1,0,1,0,0,0
|
||||
000000002c75c000,481,normal,1,0,1,0,0,0
|
||||
000000002c784000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c78c000,481,normal,1,0,1,0,0,0
|
||||
000000002c790000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c790000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c784000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c78c000,481,normal,1,0,1,0,0,0
|
||||
000000002c754000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c784000,481,normal,1,0,1,0,0,0
|
||||
000000002c78c000,481,normal,1,0,1,0,0,0
|
||||
000000002c790000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c790000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c790000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c790000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c790000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c790000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c790000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c790000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c78c000,481,normal,1,0,1,0,0,0
|
||||
000000002c784000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c78c000,481,normal,1,0,1,0,0,0
|
||||
000000002c754000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c790000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c78c000,481,normal,1,0,1,0,0,0
|
||||
000000002c75c000,481,normal,1,0,1,0,0,0
|
||||
000000002c78c000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c754000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c754000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c78c000,481,normal,1,0,1,0,0,0
|
||||
000000002c790000,481,normal,1,0,1,0,0,0
|
||||
000000002c754000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c75c000,481,normal,1,0,1,0,0,0
|
||||
000000002c780000,481,normal,1,0,1,0,0,0
|
||||
000000002c784000,481,normal,1,0,1,0,0,0
|
||||
000000002c788000,481,normal,1,0,1,0,0,0
|
||||
000000002c78c000,481,normal,1,0,1,0,0,0
|
||||
000000002c790000,481,normal,1,0,1,0,0,0
|
||||
000000002c754000,481,normal,1,0,1,0,0,0
|
||||
000000002c758000,481,normal,1,0,1,0,0,0
|
||||
000000002c75c000,512,normal,1,0,1,0,0,1 D
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0 E
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
0000000000000000,0,UNKNOWN,0,0,0,0,0,0
|
||||
00000000381ab000,0,link,0,0,0,0,0,1
|
||||
|
||||
|
||||
Trace Events
|
||||
-------------
|
||||
|
||||
DWC3 also provides several trace events which help us gathering
|
||||
information about the behavior of the driver during runtime.
|
||||
|
||||
In order to use these events, you must enable ``CONFIG_FTRACE`` in
|
||||
your kernel config.
|
||||
|
||||
For details about how enable DWC3 events, see section **Reporting
|
||||
Bugs**.
|
||||
|
||||
The following subsections will give details about each Event Class and
|
||||
each Event defined by DWC3.
|
||||
|
||||
MMIO
|
||||
```````
|
||||
|
||||
It is sometimes useful to look at every MMIO access when looking for
|
||||
bugs. Because of that, DWC3 offers two Trace Events (one for
|
||||
dwc3_readl() and one for dwc3_writel()). ``TP_printk`` follows::
|
||||
|
||||
TP_printk("addr %p value %08x", __entry->base + __entry->offset,
|
||||
__entry->value)
|
||||
|
||||
Interrupt Events
|
||||
````````````````
|
||||
|
||||
Every IRQ event can be logged and decoded into a human readable
|
||||
string. Because every event will be different, we don't give an
|
||||
example other than the ``TP_printk`` format used::
|
||||
|
||||
TP_printk("event (%08x): %s", __entry->event,
|
||||
dwc3_decode_event(__entry->event, __entry->ep0state))
|
||||
|
||||
Control Request
|
||||
`````````````````
|
||||
|
||||
Every USB Control Request can be logged to the trace buffer. The
|
||||
output format is::
|
||||
|
||||
TP_printk("%s", dwc3_decode_ctrl(__entry->bRequestType,
|
||||
__entry->bRequest, __entry->wValue,
|
||||
__entry->wIndex, __entry->wLength)
|
||||
)
|
||||
|
||||
Note that Standard Control Requests will be decoded into
|
||||
human-readable strings with their respective arguments. Class and
|
||||
Vendor requests will be printed out a sequence of 8 bytes in hex
|
||||
format.
|
||||
|
||||
Lifetime of a ``struct usb_request``
|
||||
```````````````````````````````````````
|
||||
|
||||
The entire lifetime of a ``struct usb_request`` can be tracked on the
|
||||
trace buffer. We have one event for each of allocation, free,
|
||||
queueing, dequeueing, and giveback. Output format is::
|
||||
|
||||
TP_printk("%s: req %p length %u/%u %s%s%s ==> %d",
|
||||
__get_str(name), __entry->req, __entry->actual, __entry->length,
|
||||
__entry->zero ? "Z" : "z",
|
||||
__entry->short_not_ok ? "S" : "s",
|
||||
__entry->no_interrupt ? "i" : "I",
|
||||
__entry->status
|
||||
)
|
||||
|
||||
Generic Commands
|
||||
````````````````````
|
||||
|
||||
We can log and decode every Generic Command with its completion
|
||||
code. Format is::
|
||||
|
||||
TP_printk("cmd '%s' [%x] param %08x --> status: %s",
|
||||
dwc3_gadget_generic_cmd_string(__entry->cmd),
|
||||
__entry->cmd, __entry->param,
|
||||
dwc3_gadget_generic_cmd_status_string(__entry->status)
|
||||
)
|
||||
|
||||
Endpoint Commands
|
||||
````````````````````
|
||||
|
||||
Endpoints commands can also be logged together with completion
|
||||
code. Format is::
|
||||
|
||||
TP_printk("%s: cmd '%s' [%d] params %08x %08x %08x --> status: %s",
|
||||
__get_str(name), dwc3_gadget_ep_cmd_string(__entry->cmd),
|
||||
__entry->cmd, __entry->param0,
|
||||
__entry->param1, __entry->param2,
|
||||
dwc3_ep_cmd_status_string(__entry->cmd_status)
|
||||
)
|
||||
|
||||
Lifetime of a ``TRB``
|
||||
``````````````````````
|
||||
|
||||
A ``TRB`` Lifetime is simple. We are either preparing a ``TRB`` or
|
||||
completing it. With these two events, we can see how a ``TRB`` changes
|
||||
over time. Format is::
|
||||
|
||||
TP_printk("%s: %d/%d trb %p buf %08x%08x size %s%d ctrl %08x (%c%c%c%c:%c%c:%s)",
|
||||
__get_str(name), __entry->queued, __entry->allocated,
|
||||
__entry->trb, __entry->bph, __entry->bpl,
|
||||
({char *s;
|
||||
int pcm = ((__entry->size >> 24) & 3) + 1;
|
||||
switch (__entry->type) {
|
||||
case USB_ENDPOINT_XFER_INT:
|
||||
case USB_ENDPOINT_XFER_ISOC:
|
||||
switch (pcm) {
|
||||
case 1:
|
||||
s = "1x ";
|
||||
break;
|
||||
case 2:
|
||||
s = "2x ";
|
||||
break;
|
||||
case 3:
|
||||
s = "3x ";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
s = "";
|
||||
} s; }),
|
||||
DWC3_TRB_SIZE_LENGTH(__entry->size), __entry->ctrl,
|
||||
__entry->ctrl & DWC3_TRB_CTRL_HWO ? 'H' : 'h',
|
||||
__entry->ctrl & DWC3_TRB_CTRL_LST ? 'L' : 'l',
|
||||
__entry->ctrl & DWC3_TRB_CTRL_CHN ? 'C' : 'c',
|
||||
__entry->ctrl & DWC3_TRB_CTRL_CSP ? 'S' : 's',
|
||||
__entry->ctrl & DWC3_TRB_CTRL_ISP_IMI ? 'S' : 's',
|
||||
__entry->ctrl & DWC3_TRB_CTRL_IOC ? 'C' : 'c',
|
||||
dwc3_trb_type_string(DWC3_TRBCTL_TYPE(__entry->ctrl))
|
||||
)
|
||||
|
||||
Lifetime of an Endpoint
|
||||
```````````````````````
|
||||
|
||||
And endpoint's lifetime is summarized with enable and disable
|
||||
operations, both of which can be traced. Format is::
|
||||
|
||||
TP_printk("%s: mps %d/%d streams %d burst %d ring %d/%d flags %c:%c%c%c%c%c:%c:%c",
|
||||
__get_str(name), __entry->maxpacket,
|
||||
__entry->maxpacket_limit, __entry->max_streams,
|
||||
__entry->maxburst, __entry->trb_enqueue,
|
||||
__entry->trb_dequeue,
|
||||
__entry->flags & DWC3_EP_ENABLED ? 'E' : 'e',
|
||||
__entry->flags & DWC3_EP_STALL ? 'S' : 's',
|
||||
__entry->flags & DWC3_EP_WEDGE ? 'W' : 'w',
|
||||
__entry->flags & DWC3_EP_BUSY ? 'B' : 'b',
|
||||
__entry->flags & DWC3_EP_PENDING_REQUEST ? 'P' : 'p',
|
||||
__entry->flags & DWC3_EP_MISSED_ISOC ? 'M' : 'm',
|
||||
__entry->flags & DWC3_EP_END_TRANSFER_PENDING ? 'E' : 'e',
|
||||
__entry->direction ? '<' : '>'
|
||||
)
|
||||
|
||||
|
||||
Structures, Methods and Definitions
|
||||
====================================
|
||||
|
||||
.. kernel-doc:: drivers/usb/dwc3/core.h
|
||||
:doc: main data structures
|
||||
:internal:
|
||||
|
||||
.. kernel-doc:: drivers/usb/dwc3/gadget.h
|
||||
:doc: gadget-only helpers
|
||||
:internal:
|
||||
|
||||
.. kernel-doc:: drivers/usb/dwc3/gadget.c
|
||||
:doc: gadget-side implementation
|
||||
:internal:
|
||||
|
||||
.. kernel-doc:: drivers/usb/dwc3/core.c
|
||||
:doc: core driver (probe, PM, etc)
|
||||
:internal:
|
||||
|
||||
.. [#trb] Transfer Request Block
|
||||
.. [#link_trb] Transfer Request Block pointing to another Transfer
|
||||
Request Block.
|
||||
.. [#debugfs] The Debug File System
|
||||
.. [#configfs] The Config File System
|
||||
.. [#cbw] Command Block Wrapper
|
||||
.. _Linus' tree: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/
|
||||
.. _me: felipe.balbi@linux.intel.com
|
||||
.. _linux-usb: linux-usb@vger.kernel.org
|
|
@ -16,7 +16,10 @@ Linux USB API
|
|||
persist
|
||||
error-codes
|
||||
writing_usb_driver
|
||||
dwc3
|
||||
writing_musb_glue_layer
|
||||
typec
|
||||
usb3-debug-port
|
||||
|
||||
.. only:: subproject and html
|
||||
|
||||
|
|
|
@ -16,10 +16,11 @@ provided by gadgets.
|
|||
13. RNDIS function
|
||||
14. SERIAL function
|
||||
15. SOURCESINK function
|
||||
16. UAC1 function
|
||||
16. UAC1 function (legacy implementation)
|
||||
17. UAC2 function
|
||||
18. UVC function
|
||||
19. PRINTER function
|
||||
20. UAC1 function (new API)
|
||||
|
||||
|
||||
1. ACM function
|
||||
|
@ -589,15 +590,16 @@ device: run the gadget
|
|||
host: test-usb (tools/usb/testusb.c)
|
||||
|
||||
|
||||
16. UAC1 function
|
||||
16. UAC1 function (legacy implementation)
|
||||
=================
|
||||
|
||||
The function is provided by usb_f_uac1.ko module.
|
||||
The function is provided by usb_f_uac1_legacy.ko module.
|
||||
|
||||
Function-specific configfs interface
|
||||
------------------------------------
|
||||
|
||||
The function name to use when creating the function directory is "uac1".
|
||||
The function name to use when creating the function directory
|
||||
is "uac1_legacy".
|
||||
The uac1 function provides these attributes in its function directory:
|
||||
|
||||
audio_buf_size - audio buffer size
|
||||
|
@ -772,3 +774,46 @@ host:
|
|||
|
||||
More advanced testing can be done with the prn_example
|
||||
described in Documentation/usb/gadget-printer.txt.
|
||||
|
||||
|
||||
20. UAC1 function (virtual ALSA card, using u_audio API)
|
||||
=================
|
||||
|
||||
The function is provided by usb_f_uac1.ko module.
|
||||
It will create a virtual ALSA card and the audio streams are simply
|
||||
sinked to and sourced from it.
|
||||
|
||||
Function-specific configfs interface
|
||||
------------------------------------
|
||||
|
||||
The function name to use when creating the function directory is "uac1".
|
||||
The uac1 function provides these attributes in its function directory:
|
||||
|
||||
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)
|
||||
req_number - the number of pre-allocated request for both capture
|
||||
and playback
|
||||
|
||||
The attributes have sane default values.
|
||||
|
||||
Testing the UAC1 function
|
||||
-------------------------
|
||||
|
||||
device: run the gadget
|
||||
host: aplay -l # should list our USB Audio Gadget
|
||||
|
||||
This function does not require real hardware support, it just
|
||||
sends a stream of audio data to/from the host. In order to
|
||||
actually hear something at the device side, a command similar
|
||||
to this must be used at the device side:
|
||||
|
||||
$ arecord -f dat -t wav -D hw:2,0 | aplay -D hw:0,0 &
|
||||
|
||||
e.g.:
|
||||
|
||||
$ arecord -f dat -t wav -D hw:CARD=UAC1Gadget,DEV=0 | \
|
||||
aplay -D default:CARD=OdroidU3
|
||||
|
|
|
@ -151,11 +151,24 @@ static void __dwc3_set_mode(struct work_struct *work)
|
|||
switch (dwc->desired_dr_role) {
|
||||
case DWC3_GCTL_PRTCAP_HOST:
|
||||
ret = dwc3_host_init(dwc);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
dev_err(dwc->dev, "failed to initialize host\n");
|
||||
} else {
|
||||
if (dwc->usb2_phy)
|
||||
otg_set_vbus(dwc->usb2_phy->otg, true);
|
||||
if (dwc->usb2_generic_phy)
|
||||
phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_HOST);
|
||||
|
||||
}
|
||||
break;
|
||||
case DWC3_GCTL_PRTCAP_DEVICE:
|
||||
dwc3_event_buffers_setup(dwc);
|
||||
|
||||
if (dwc->usb2_phy)
|
||||
otg_set_vbus(dwc->usb2_phy->otg, false);
|
||||
if (dwc->usb2_generic_phy)
|
||||
phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_DEVICE);
|
||||
|
||||
ret = dwc3_gadget_init(dwc);
|
||||
if (ret)
|
||||
dev_err(dwc->dev, "failed to initialize peripheral\n");
|
||||
|
@ -721,6 +734,8 @@ static void dwc3_core_setup_global_control(struct dwc3 *dwc)
|
|||
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
|
||||
}
|
||||
|
||||
static int dwc3_core_get_phy(struct dwc3 *dwc);
|
||||
|
||||
/**
|
||||
* dwc3_core_init - Low-level initialization of DWC3 Core
|
||||
* @dwc: Pointer to our controller context structure
|
||||
|
@ -759,6 +774,10 @@ static int dwc3_core_init(struct dwc3 *dwc)
|
|||
if (ret)
|
||||
goto err0;
|
||||
|
||||
ret = dwc3_core_get_phy(dwc);
|
||||
if (ret)
|
||||
goto err0;
|
||||
|
||||
dwc3_core_setup_global_control(dwc);
|
||||
dwc3_core_num_eps(dwc);
|
||||
|
||||
|
@ -796,13 +815,19 @@ static int dwc3_core_init(struct dwc3 *dwc)
|
|||
dwc3_writel(dwc->regs, DWC3_GUCTL2, reg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable hardware control of sending remote wakeup in HS when
|
||||
* the device is in the L1 state.
|
||||
*/
|
||||
if (dwc->revision >= DWC3_REVISION_290A) {
|
||||
if (dwc->revision >= DWC3_REVISION_250A) {
|
||||
reg = dwc3_readl(dwc->regs, DWC3_GUCTL1);
|
||||
reg |= DWC3_GUCTL1_DEV_L1_EXIT_BY_HW;
|
||||
|
||||
/*
|
||||
* Enable hardware control of sending remote wakeup
|
||||
* in HS when the device is in the L1 state.
|
||||
*/
|
||||
if (dwc->revision >= DWC3_REVISION_290A)
|
||||
reg |= DWC3_GUCTL1_DEV_L1_EXIT_BY_HW;
|
||||
|
||||
if (dwc->dis_tx_ipgap_linecheck_quirk)
|
||||
reg |= DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS;
|
||||
|
||||
dwc3_writel(dwc->regs, DWC3_GUCTL1, reg);
|
||||
}
|
||||
|
||||
|
@ -903,6 +928,12 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
|
|||
switch (dwc->dr_mode) {
|
||||
case USB_DR_MODE_PERIPHERAL:
|
||||
dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE);
|
||||
|
||||
if (dwc->usb2_phy)
|
||||
otg_set_vbus(dwc->usb2_phy->otg, false);
|
||||
if (dwc->usb2_generic_phy)
|
||||
phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_DEVICE);
|
||||
|
||||
ret = dwc3_gadget_init(dwc);
|
||||
if (ret) {
|
||||
if (ret != -EPROBE_DEFER)
|
||||
|
@ -912,6 +943,12 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
|
|||
break;
|
||||
case USB_DR_MODE_HOST:
|
||||
dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST);
|
||||
|
||||
if (dwc->usb2_phy)
|
||||
otg_set_vbus(dwc->usb2_phy->otg, true);
|
||||
if (dwc->usb2_generic_phy)
|
||||
phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_HOST);
|
||||
|
||||
ret = dwc3_host_init(dwc);
|
||||
if (ret) {
|
||||
if (ret != -EPROBE_DEFER)
|
||||
|
@ -1023,6 +1060,8 @@ static void dwc3_get_properties(struct dwc3 *dwc)
|
|||
"snps,dis-u2-freeclk-exists-quirk");
|
||||
dwc->dis_del_phy_power_chg_quirk = device_property_read_bool(dev,
|
||||
"snps,dis-del-phy-power-chg-quirk");
|
||||
dwc->dis_tx_ipgap_linecheck_quirk = device_property_read_bool(dev,
|
||||
"snps,dis-tx-ipgap-linecheck-quirk");
|
||||
|
||||
dwc->tx_de_emphasis_quirk = device_property_read_bool(dev,
|
||||
"snps,tx_de_emphasis_quirk");
|
||||
|
@ -1148,10 +1187,6 @@ static int dwc3_probe(struct platform_device *pdev)
|
|||
platform_set_drvdata(pdev, dwc);
|
||||
dwc3_cache_hwparams(dwc);
|
||||
|
||||
ret = dwc3_core_get_phy(dwc);
|
||||
if (ret)
|
||||
goto err0;
|
||||
|
||||
spin_lock_init(&dwc->lock);
|
||||
|
||||
pm_runtime_set_active(dev);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/*
|
||||
* core.h - DesignWare USB3 DRD Core Header
|
||||
*
|
||||
* Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com
|
||||
|
@ -204,6 +204,7 @@
|
|||
#define DWC3_GCTL_DSBLCLKGTNG BIT(0)
|
||||
|
||||
/* Global User Control 1 Register */
|
||||
#define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS BIT(28)
|
||||
#define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24)
|
||||
|
||||
/* Global USB2 PHY Configuration Register */
|
||||
|
@ -522,7 +523,6 @@ struct dwc3_event_buffer {
|
|||
* @trb_pool_dma: dma address of @trb_pool
|
||||
* @trb_enqueue: enqueue 'pointer' into TRB array
|
||||
* @trb_dequeue: dequeue 'pointer' into TRB array
|
||||
* @desc: usb_endpoint_descriptor pointer
|
||||
* @dwc: pointer to DWC controller
|
||||
* @saved_state: ep state saved during hibernation
|
||||
* @flags: endpoint flags (wedged, stalled, ...)
|
||||
|
@ -664,7 +664,7 @@ enum dwc3_link_state {
|
|||
* @bpl: DW0-3
|
||||
* @bph: DW4-7
|
||||
* @size: DW8-B
|
||||
* @trl: DWC-F
|
||||
* @ctrl: DWC-F
|
||||
*/
|
||||
struct dwc3_trb {
|
||||
u32 bpl;
|
||||
|
@ -674,16 +674,16 @@ struct dwc3_trb {
|
|||
} __packed;
|
||||
|
||||
/**
|
||||
* dwc3_hwparams - copy of HWPARAMS registers
|
||||
* @hwparams0 - GHWPARAMS0
|
||||
* @hwparams1 - GHWPARAMS1
|
||||
* @hwparams2 - GHWPARAMS2
|
||||
* @hwparams3 - GHWPARAMS3
|
||||
* @hwparams4 - GHWPARAMS4
|
||||
* @hwparams5 - GHWPARAMS5
|
||||
* @hwparams6 - GHWPARAMS6
|
||||
* @hwparams7 - GHWPARAMS7
|
||||
* @hwparams8 - GHWPARAMS8
|
||||
* struct dwc3_hwparams - copy of HWPARAMS registers
|
||||
* @hwparams0: GHWPARAMS0
|
||||
* @hwparams1: GHWPARAMS1
|
||||
* @hwparams2: GHWPARAMS2
|
||||
* @hwparams3: GHWPARAMS3
|
||||
* @hwparams4: GHWPARAMS4
|
||||
* @hwparams5: GHWPARAMS5
|
||||
* @hwparams6: GHWPARAMS6
|
||||
* @hwparams7: GHWPARAMS7
|
||||
* @hwparams8: GHWPARAMS8
|
||||
*/
|
||||
struct dwc3_hwparams {
|
||||
u32 hwparams0;
|
||||
|
@ -730,7 +730,8 @@ struct dwc3_hwparams {
|
|||
* @unaligned: true for OUT endpoints with length not divisible by maxp
|
||||
* @direction: IN or OUT direction flag
|
||||
* @mapped: true when request has been dma-mapped
|
||||
* @queued: true when request has been queued to HW
|
||||
* @started: request is started
|
||||
* @zero: wants a ZLP
|
||||
*/
|
||||
struct dwc3_request {
|
||||
struct usb_request request;
|
||||
|
@ -761,17 +762,23 @@ struct dwc3_scratchpad_array {
|
|||
|
||||
/**
|
||||
* struct dwc3 - representation of our controller
|
||||
* @drd_work - workqueue used for role swapping
|
||||
* @drd_work: workqueue used for role swapping
|
||||
* @ep0_trb: trb which is used for the ctrl_req
|
||||
* @bounce: address of bounce buffer
|
||||
* @scratchbuf: address of scratch buffer
|
||||
* @setup_buf: used while precessing STD USB requests
|
||||
* @ep0_trb: dma address of ep0_trb
|
||||
* @ep0_trb_addr: dma address of @ep0_trb
|
||||
* @bounce_addr: dma address of @bounce
|
||||
* @ep0_usb_req: dummy req used while handling STD USB requests
|
||||
* @scratch_addr: dma address of scratchbuf
|
||||
* @ep0_in_setup: one control transfer is completed and enter setup phase
|
||||
* @lock: for synchronizing
|
||||
* @dev: pointer to our struct device
|
||||
* @sysdev: pointer to the DMA-capable device
|
||||
* @xhci: pointer to our xHCI child
|
||||
* @event_buffer_list: a list of event buffers
|
||||
* @xhci_resources: struct resources for our @xhci child
|
||||
* @ev_buf: struct dwc3_event_buffer pointer
|
||||
* @eps: endpoint array
|
||||
* @gadget: device side representation of the peripheral controller
|
||||
* @gadget_driver: pointer to the gadget driver
|
||||
* @regs: base address for our registers
|
||||
|
@ -795,8 +802,6 @@ struct dwc3_scratchpad_array {
|
|||
* @usb2_generic_phy: pointer to USB2 PHY
|
||||
* @usb3_generic_phy: pointer to USB3 PHY
|
||||
* @ulpi: pointer to ulpi interface
|
||||
* @dcfg: saved contents of DCFG register
|
||||
* @gctl: saved contents of GCTL register
|
||||
* @isoch_delay: wValue from Set Isochronous Delay request;
|
||||
* @u2sel: parameter from Set SEL request.
|
||||
* @u2pel: parameter from Set SEL request.
|
||||
|
@ -830,7 +835,6 @@ struct dwc3_scratchpad_array {
|
|||
* @pending_events: true when we have pending IRQs to be handled
|
||||
* @pullups_connected: true when Run/Stop bit is set
|
||||
* @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround
|
||||
* @start_config_issued: true when StartConfig command has been issued
|
||||
* @three_stage_setup: set if we perform a three phase setup
|
||||
* @usb3_lpm_capable: set if hadrware supports Link Power Management
|
||||
* @disable_scramble_quirk: set if we enable the disable scramble quirk
|
||||
|
@ -845,11 +849,14 @@ struct dwc3_scratchpad_array {
|
|||
* @dis_u2_susphy_quirk: set if we disable usb2 suspend phy
|
||||
* @dis_enblslpm_quirk: set if we clear enblslpm in GUSB2PHYCFG,
|
||||
* disabling the suspend signal to the PHY.
|
||||
* @dis_rxdet_inp3_quirk: set if we disable Rx.Detect in P3
|
||||
* @dis_u2_freeclk_exists_quirk : set if we clear u2_freeclk_exists
|
||||
* in GUSB2PHYCFG, specify that USB2 PHY doesn't
|
||||
* provide a free-running PHY clock.
|
||||
* @dis_del_phy_power_chg_quirk: set if we disable delay phy power
|
||||
* change quirk.
|
||||
* @dis_tx_ipgap_linecheck_quirk: set if we disable u2mac linestate
|
||||
* check during HS transmit.
|
||||
* @tx_de_emphasis_quirk: set if we enable Tx de-emphasis quirk
|
||||
* @tx_de_emphasis: Tx de-emphasis value
|
||||
* 0 - -6dB de-emphasis
|
||||
|
@ -1004,6 +1011,7 @@ struct dwc3 {
|
|||
unsigned dis_rxdet_inp3_quirk:1;
|
||||
unsigned dis_u2_freeclk_exists_quirk:1;
|
||||
unsigned dis_del_phy_power_chg_quirk:1;
|
||||
unsigned dis_tx_ipgap_linecheck_quirk:1;
|
||||
|
||||
unsigned tx_de_emphasis_quirk:1;
|
||||
unsigned tx_de_emphasis:2;
|
||||
|
|
|
@ -173,9 +173,8 @@ static inline const char *dwc3_ep0_state_string(enum dwc3_ep0_state state)
|
|||
* @event: the event code
|
||||
*/
|
||||
static inline const char *
|
||||
dwc3_gadget_event_string(const struct dwc3_event_devt *event)
|
||||
dwc3_gadget_event_string(char *str, const struct dwc3_event_devt *event)
|
||||
{
|
||||
static char str[256];
|
||||
enum dwc3_link_state state = event->event_info & DWC3_LINK_STATE_MASK;
|
||||
|
||||
switch (event->type) {
|
||||
|
@ -223,15 +222,249 @@ dwc3_gadget_event_string(const struct dwc3_event_devt *event)
|
|||
return str;
|
||||
}
|
||||
|
||||
static inline void dwc3_decode_get_status(__u8 t, __u16 i, __u16 l, char *str)
|
||||
{
|
||||
switch (t & USB_RECIP_MASK) {
|
||||
case USB_RECIP_INTERFACE:
|
||||
sprintf(str, "Get Interface Status(Intf = %d, Length = %d)",
|
||||
i, l);
|
||||
break;
|
||||
case USB_RECIP_ENDPOINT:
|
||||
sprintf(str, "Get Endpoint Status(ep%d%s)",
|
||||
i & ~USB_DIR_IN,
|
||||
i & USB_DIR_IN ? "in" : "out");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dwc3_decode_set_clear_feature(__u8 t, __u8 b, __u16 v,
|
||||
__u16 i, char *str)
|
||||
{
|
||||
switch (t & USB_RECIP_MASK) {
|
||||
case USB_RECIP_DEVICE:
|
||||
sprintf(str, "%s Device Feature(%s%s)",
|
||||
b == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set",
|
||||
({char *s;
|
||||
switch (v) {
|
||||
case USB_DEVICE_SELF_POWERED:
|
||||
s = "Self Powered";
|
||||
break;
|
||||
case USB_DEVICE_REMOTE_WAKEUP:
|
||||
s = "Remote Wakeup";
|
||||
break;
|
||||
case USB_DEVICE_TEST_MODE:
|
||||
s = "Test Mode";
|
||||
break;
|
||||
default:
|
||||
s = "UNKNOWN";
|
||||
} s; }),
|
||||
v == USB_DEVICE_TEST_MODE ?
|
||||
({ char *s;
|
||||
switch (i) {
|
||||
case TEST_J:
|
||||
s = ": TEST_J";
|
||||
break;
|
||||
case TEST_K:
|
||||
s = ": TEST_K";
|
||||
break;
|
||||
case TEST_SE0_NAK:
|
||||
s = ": TEST_SE0_NAK";
|
||||
break;
|
||||
case TEST_PACKET:
|
||||
s = ": TEST_PACKET";
|
||||
break;
|
||||
case TEST_FORCE_EN:
|
||||
s = ": TEST_FORCE_EN";
|
||||
break;
|
||||
default:
|
||||
s = ": UNKNOWN";
|
||||
} s; }) : "");
|
||||
break;
|
||||
case USB_RECIP_INTERFACE:
|
||||
sprintf(str, "%s Interface Feature(%s)",
|
||||
b == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set",
|
||||
v == USB_INTRF_FUNC_SUSPEND ?
|
||||
"Function Suspend" : "UNKNOWN");
|
||||
break;
|
||||
case USB_RECIP_ENDPOINT:
|
||||
sprintf(str, "%s Endpoint Feature(%s ep%d%s)",
|
||||
b == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set",
|
||||
v == USB_ENDPOINT_HALT ? "Halt" : "UNKNOWN",
|
||||
i & ~USB_DIR_IN,
|
||||
i & USB_DIR_IN ? "in" : "out");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dwc3_decode_set_address(__u16 v, char *str)
|
||||
{
|
||||
sprintf(str, "Set Address(Addr = %02x)", v);
|
||||
}
|
||||
|
||||
static inline void dwc3_decode_get_set_descriptor(__u8 t, __u8 b, __u16 v,
|
||||
__u16 i, __u16 l, char *str)
|
||||
{
|
||||
sprintf(str, "%s %s Descriptor(Index = %d, Length = %d)",
|
||||
b == USB_REQ_GET_DESCRIPTOR ? "Get" : "Set",
|
||||
({ char *s;
|
||||
switch (v >> 8) {
|
||||
case USB_DT_DEVICE:
|
||||
s = "Device";
|
||||
break;
|
||||
case USB_DT_CONFIG:
|
||||
s = "Configuration";
|
||||
break;
|
||||
case USB_DT_STRING:
|
||||
s = "String";
|
||||
break;
|
||||
case USB_DT_INTERFACE:
|
||||
s = "Interface";
|
||||
break;
|
||||
case USB_DT_ENDPOINT:
|
||||
s = "Endpoint";
|
||||
break;
|
||||
case USB_DT_DEVICE_QUALIFIER:
|
||||
s = "Device Qualifier";
|
||||
break;
|
||||
case USB_DT_OTHER_SPEED_CONFIG:
|
||||
s = "Other Speed Config";
|
||||
break;
|
||||
case USB_DT_INTERFACE_POWER:
|
||||
s = "Interface Power";
|
||||
break;
|
||||
case USB_DT_OTG:
|
||||
s = "OTG";
|
||||
break;
|
||||
case USB_DT_DEBUG:
|
||||
s = "Debug";
|
||||
break;
|
||||
case USB_DT_INTERFACE_ASSOCIATION:
|
||||
s = "Interface Association";
|
||||
break;
|
||||
case USB_DT_BOS:
|
||||
s = "BOS";
|
||||
break;
|
||||
case USB_DT_DEVICE_CAPABILITY:
|
||||
s = "Device Capability";
|
||||
break;
|
||||
case USB_DT_PIPE_USAGE:
|
||||
s = "Pipe Usage";
|
||||
break;
|
||||
case USB_DT_SS_ENDPOINT_COMP:
|
||||
s = "SS Endpoint Companion";
|
||||
break;
|
||||
case USB_DT_SSP_ISOC_ENDPOINT_COMP:
|
||||
s = "SSP Isochronous Endpoint Companion";
|
||||
break;
|
||||
default:
|
||||
s = "UNKNOWN";
|
||||
break;
|
||||
} s; }), v & 0xff, l);
|
||||
}
|
||||
|
||||
|
||||
static inline void dwc3_decode_get_configuration(__u16 l, char *str)
|
||||
{
|
||||
sprintf(str, "Get Configuration(Length = %d)", l);
|
||||
}
|
||||
|
||||
static inline void dwc3_decode_set_configuration(__u8 v, char *str)
|
||||
{
|
||||
sprintf(str, "Set Configuration(Config = %d)", v);
|
||||
}
|
||||
|
||||
static inline void dwc3_decode_get_intf(__u16 i, __u16 l, char *str)
|
||||
{
|
||||
sprintf(str, "Get Interface(Intf = %d, Length = %d)", i, l);
|
||||
}
|
||||
|
||||
static inline void dwc3_decode_set_intf(__u8 v, __u16 i, char *str)
|
||||
{
|
||||
sprintf(str, "Set Interface(Intf = %d, Alt.Setting = %d)", i, v);
|
||||
}
|
||||
|
||||
static inline void dwc3_decode_synch_frame(__u16 i, __u16 l, char *str)
|
||||
{
|
||||
sprintf(str, "Synch Frame(Endpoint = %d, Length = %d)", i, l);
|
||||
}
|
||||
|
||||
static inline void dwc3_decode_set_sel(__u16 l, char *str)
|
||||
{
|
||||
sprintf(str, "Set SEL(Length = %d)", l);
|
||||
}
|
||||
|
||||
static inline void dwc3_decode_set_isoch_delay(__u8 v, char *str)
|
||||
{
|
||||
sprintf(str, "Set Isochronous Delay(Delay = %d ns)", v);
|
||||
}
|
||||
|
||||
/**
|
||||
* dwc3_decode_ctrl - returns a string represetion of ctrl request
|
||||
*/
|
||||
static inline const char *dwc3_decode_ctrl(char *str, __u8 bRequestType,
|
||||
__u8 bRequest, __u16 wValue, __u16 wIndex, __u16 wLength)
|
||||
{
|
||||
switch (bRequest) {
|
||||
case USB_REQ_GET_STATUS:
|
||||
dwc3_decode_get_status(bRequestType, wIndex, wLength, str);
|
||||
break;
|
||||
case USB_REQ_CLEAR_FEATURE:
|
||||
case USB_REQ_SET_FEATURE:
|
||||
dwc3_decode_set_clear_feature(bRequestType, bRequest, wValue,
|
||||
wIndex, str);
|
||||
break;
|
||||
case USB_REQ_SET_ADDRESS:
|
||||
dwc3_decode_set_address(wValue, str);
|
||||
break;
|
||||
case USB_REQ_GET_DESCRIPTOR:
|
||||
case USB_REQ_SET_DESCRIPTOR:
|
||||
dwc3_decode_get_set_descriptor(bRequestType, bRequest, wValue,
|
||||
wIndex, wLength, str);
|
||||
break;
|
||||
case USB_REQ_GET_CONFIGURATION:
|
||||
dwc3_decode_get_configuration(wLength, str);
|
||||
break;
|
||||
case USB_REQ_SET_CONFIGURATION:
|
||||
dwc3_decode_set_configuration(wValue, str);
|
||||
break;
|
||||
case USB_REQ_GET_INTERFACE:
|
||||
dwc3_decode_get_intf(wIndex, wLength, str);
|
||||
break;
|
||||
case USB_REQ_SET_INTERFACE:
|
||||
dwc3_decode_set_intf(wValue, wIndex, str);
|
||||
break;
|
||||
case USB_REQ_SYNCH_FRAME:
|
||||
dwc3_decode_synch_frame(wIndex, wLength, str);
|
||||
break;
|
||||
case USB_REQ_SET_SEL:
|
||||
dwc3_decode_set_sel(wLength, str);
|
||||
break;
|
||||
case USB_REQ_SET_ISOCH_DELAY:
|
||||
dwc3_decode_set_isoch_delay(wValue, str);
|
||||
break;
|
||||
default:
|
||||
sprintf(str, "%02x %02x %02x %02x %02x %02x %02x %02x",
|
||||
bRequestType, bRequest,
|
||||
cpu_to_le16(wValue) & 0xff,
|
||||
cpu_to_le16(wValue) >> 8,
|
||||
cpu_to_le16(wIndex) & 0xff,
|
||||
cpu_to_le16(wIndex) >> 8,
|
||||
cpu_to_le16(wLength) & 0xff,
|
||||
cpu_to_le16(wLength) >> 8);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* dwc3_ep_event_string - returns event name
|
||||
* @event: then event code
|
||||
*/
|
||||
static inline const char *
|
||||
dwc3_ep_event_string(const struct dwc3_event_depevt *event, u32 ep0state)
|
||||
dwc3_ep_event_string(char *str, const struct dwc3_event_depevt *event,
|
||||
u32 ep0state)
|
||||
{
|
||||
u8 epnum = event->endpoint_number;
|
||||
static char str[256];
|
||||
size_t len;
|
||||
int status;
|
||||
int ret;
|
||||
|
@ -332,14 +565,14 @@ static inline const char *dwc3_gadget_event_type_string(u8 event)
|
|||
}
|
||||
}
|
||||
|
||||
static inline const char *dwc3_decode_event(u32 event, u32 ep0state)
|
||||
static inline const char *dwc3_decode_event(char *str, u32 event, u32 ep0state)
|
||||
{
|
||||
const union dwc3_event evt = (union dwc3_event) event;
|
||||
|
||||
if (evt.type.is_devspec)
|
||||
return dwc3_gadget_event_string(&evt.devt);
|
||||
return dwc3_gadget_event_string(str, &evt.devt);
|
||||
else
|
||||
return dwc3_ep_event_string(&evt.depevt, ep0state);
|
||||
return dwc3_ep_event_string(str, &evt.depevt, ep0state);
|
||||
}
|
||||
|
||||
static inline const char *dwc3_ep_cmd_status_string(int status)
|
||||
|
|
|
@ -653,16 +653,13 @@ static int dwc3_ep_trb_ring_show(struct seq_file *s, void *unused)
|
|||
goto out;
|
||||
}
|
||||
|
||||
seq_printf(s, "enqueue pointer %d\n", dep->trb_enqueue);
|
||||
seq_printf(s, "dequeue pointer %d\n", dep->trb_dequeue);
|
||||
seq_printf(s, "\n--------------------------------------------------\n\n");
|
||||
seq_printf(s, "buffer_addr,size,type,ioc,isp_imi,csp,chn,lst,hwo\n");
|
||||
|
||||
for (i = 0; i < DWC3_TRB_NUM; i++) {
|
||||
struct dwc3_trb *trb = &dep->trb_pool[i];
|
||||
unsigned int type = DWC3_TRBCTL_TYPE(trb->ctrl);
|
||||
|
||||
seq_printf(s, "%08x%08x,%d,%s,%d,%d,%d,%d,%d,%d\n",
|
||||
seq_printf(s, "%08x%08x,%d,%s,%d,%d,%d,%d,%d,%d %c%c\n",
|
||||
trb->bph, trb->bpl, trb->size,
|
||||
dwc3_trb_type_string(type),
|
||||
!!(trb->ctrl & DWC3_TRB_CTRL_IOC),
|
||||
|
@ -670,7 +667,9 @@ static int dwc3_ep_trb_ring_show(struct seq_file *s, void *unused)
|
|||
!!(trb->ctrl & DWC3_TRB_CTRL_CSP),
|
||||
!!(trb->ctrl & DWC3_TRB_CTRL_CHN),
|
||||
!!(trb->ctrl & DWC3_TRB_CTRL_LST),
|
||||
!!(trb->ctrl & DWC3_TRB_CTRL_HWO));
|
||||
!!(trb->ctrl & DWC3_TRB_CTRL_HWO),
|
||||
dep->trb_enqueue == i ? 'E' : ' ',
|
||||
dep->trb_dequeue == i ? 'D' : ' ');
|
||||
}
|
||||
|
||||
out:
|
||||
|
|
|
@ -125,12 +125,16 @@ static int dwc3_exynos_probe(struct platform_device *pdev)
|
|||
dev_err(dev, "couldn't get clock\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
clk_prepare_enable(exynos->clk);
|
||||
ret = clk_prepare_enable(exynos->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
exynos->susp_clk = devm_clk_get(dev, "usbdrd30_susp_clk");
|
||||
if (IS_ERR(exynos->susp_clk))
|
||||
exynos->susp_clk = NULL;
|
||||
clk_prepare_enable(exynos->susp_clk);
|
||||
ret = clk_prepare_enable(exynos->susp_clk);
|
||||
if (ret)
|
||||
goto susp_clk_err;
|
||||
|
||||
if (of_device_is_compatible(node, "samsung,exynos7-dwusb3")) {
|
||||
exynos->axius_clk = devm_clk_get(dev, "usbdrd30_axius_clk");
|
||||
|
@ -139,7 +143,9 @@ static int dwc3_exynos_probe(struct platform_device *pdev)
|
|||
ret = -ENODEV;
|
||||
goto axius_clk_err;
|
||||
}
|
||||
clk_prepare_enable(exynos->axius_clk);
|
||||
ret = clk_prepare_enable(exynos->axius_clk);
|
||||
if (ret)
|
||||
goto axius_clk_err;
|
||||
} else {
|
||||
exynos->axius_clk = NULL;
|
||||
}
|
||||
|
@ -197,6 +203,7 @@ vdd33_err:
|
|||
clk_disable_unprepare(exynos->axius_clk);
|
||||
axius_clk_err:
|
||||
clk_disable_unprepare(exynos->susp_clk);
|
||||
susp_clk_err:
|
||||
clk_disable_unprepare(exynos->clk);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -230,7 +230,7 @@ static int st_dwc3_probe(struct platform_device *pdev)
|
|||
|
||||
dwc3_data->syscfg_reg_off = res->start;
|
||||
|
||||
dev_vdbg(&pdev->dev, "glue-logic addr 0x%p, syscfg-reg offset 0x%x\n",
|
||||
dev_vdbg(&pdev->dev, "glue-logic addr 0x%pK, syscfg-reg offset 0x%x\n",
|
||||
dwc3_data->glue_base, dwc3_data->syscfg_reg_off);
|
||||
|
||||
dwc3_data->rstc_pwrdn =
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/*
|
||||
* ep0.c - DesignWare USB3 DRD Controller Endpoint 0 Handling
|
||||
*
|
||||
* Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com
|
||||
|
@ -319,10 +319,16 @@ static int dwc3_ep0_handle_status(struct dwc3 *dwc,
|
|||
{
|
||||
struct dwc3_ep *dep;
|
||||
u32 recip;
|
||||
u32 value;
|
||||
u32 reg;
|
||||
u16 usb_status = 0;
|
||||
__le16 *response_pkt;
|
||||
|
||||
/* We don't support PTM_STATUS */
|
||||
value = le16_to_cpu(ctrl->wValue);
|
||||
if (value != 0)
|
||||
return -EINVAL;
|
||||
|
||||
recip = ctrl->bRequestType & USB_RECIP_MASK;
|
||||
switch (recip) {
|
||||
case USB_RECIP_DEVICE:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/*
|
||||
* gadget.c - DesignWare USB3 DRD Controller Gadget Framework Link
|
||||
*
|
||||
* Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com
|
||||
|
@ -36,13 +36,12 @@
|
|||
#include "io.h"
|
||||
|
||||
/**
|
||||
* dwc3_gadget_set_test_mode - Enables USB2 Test Modes
|
||||
* dwc3_gadget_set_test_mode - enables usb2 test modes
|
||||
* @dwc: pointer to our context structure
|
||||
* @mode: the mode to set (J, K SE0 NAK, Force Enable)
|
||||
*
|
||||
* Caller should take care of locking. This function will
|
||||
* return 0 on success or -EINVAL if wrong Test Selector
|
||||
* is passed
|
||||
* Caller should take care of locking. This function will return 0 on
|
||||
* success or -EINVAL if wrong Test Selector is passed.
|
||||
*/
|
||||
int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode)
|
||||
{
|
||||
|
@ -69,7 +68,7 @@ int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode)
|
|||
}
|
||||
|
||||
/**
|
||||
* dwc3_gadget_get_link_state - Gets current state of USB Link
|
||||
* dwc3_gadget_get_link_state - gets current state of usb link
|
||||
* @dwc: pointer to our context structure
|
||||
*
|
||||
* Caller should take care of locking. This function will
|
||||
|
@ -85,7 +84,7 @@ int dwc3_gadget_get_link_state(struct dwc3 *dwc)
|
|||
}
|
||||
|
||||
/**
|
||||
* dwc3_gadget_set_link_state - Sets USB Link to a particular State
|
||||
* dwc3_gadget_set_link_state - sets usb link to a particular state
|
||||
* @dwc: pointer to our context structure
|
||||
* @state: the state to put link into
|
||||
*
|
||||
|
@ -143,8 +142,8 @@ int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state)
|
|||
}
|
||||
|
||||
/**
|
||||
* dwc3_ep_inc_trb() - Increment a TRB index.
|
||||
* @index - Pointer to the TRB index to increment.
|
||||
* dwc3_ep_inc_trb - increment a trb index.
|
||||
* @index: Pointer to the TRB index to increment.
|
||||
*
|
||||
* The index should never point to the link TRB. After incrementing,
|
||||
* if it is point to the link TRB, wrap around to the beginning. The
|
||||
|
@ -157,16 +156,34 @@ static void dwc3_ep_inc_trb(u8 *index)
|
|||
*index = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* dwc3_ep_inc_enq - increment endpoint's enqueue pointer
|
||||
* @dep: The endpoint whose enqueue pointer we're incrementing
|
||||
*/
|
||||
static void dwc3_ep_inc_enq(struct dwc3_ep *dep)
|
||||
{
|
||||
dwc3_ep_inc_trb(&dep->trb_enqueue);
|
||||
}
|
||||
|
||||
/**
|
||||
* dwc3_ep_inc_deq - increment endpoint's dequeue pointer
|
||||
* @dep: The endpoint whose enqueue pointer we're incrementing
|
||||
*/
|
||||
static void dwc3_ep_inc_deq(struct dwc3_ep *dep)
|
||||
{
|
||||
dwc3_ep_inc_trb(&dep->trb_dequeue);
|
||||
}
|
||||
|
||||
/**
|
||||
* dwc3_gadget_giveback - call struct usb_request's ->complete callback
|
||||
* @dep: The endpoint to whom the request belongs to
|
||||
* @req: The request we're giving back
|
||||
* @status: completion code for the request
|
||||
*
|
||||
* Must be called with controller's lock held and interrupts disabled. This
|
||||
* function will unmap @req and call its ->complete() callback to notify upper
|
||||
* layers that it has completed.
|
||||
*/
|
||||
void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
|
||||
int status)
|
||||
{
|
||||
|
@ -193,6 +210,15 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
|
|||
pm_runtime_put(dwc->dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* dwc3_send_gadget_generic_command - issue a generic command for the controller
|
||||
* @dwc: pointer to the controller context
|
||||
* @cmd: the command to be issued
|
||||
* @param: command parameter
|
||||
*
|
||||
* Caller should take care of locking. Issue @cmd with a given @param to @dwc
|
||||
* and wait for its completion.
|
||||
*/
|
||||
int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param)
|
||||
{
|
||||
u32 timeout = 500;
|
||||
|
@ -225,6 +251,15 @@ int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param)
|
|||
|
||||
static int __dwc3_gadget_wakeup(struct dwc3 *dwc);
|
||||
|
||||
/**
|
||||
* dwc3_send_gadget_ep_cmd - issue an endpoint command
|
||||
* @dep: the endpoint to which the command is going to be issued
|
||||
* @cmd: the command to be issued
|
||||
* @params: parameters to the command
|
||||
*
|
||||
* Caller should handle locking. This function will issue @cmd with given
|
||||
* @params to @dep and wait for its completion.
|
||||
*/
|
||||
int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd,
|
||||
struct dwc3_gadget_ep_cmd_params *params)
|
||||
{
|
||||
|
@ -422,36 +457,38 @@ static void dwc3_free_trb_pool(struct dwc3_ep *dep)
|
|||
static int dwc3_gadget_set_xfer_resource(struct dwc3 *dwc, struct dwc3_ep *dep);
|
||||
|
||||
/**
|
||||
* dwc3_gadget_start_config - Configure EP resources
|
||||
* dwc3_gadget_start_config - configure ep resources
|
||||
* @dwc: pointer to our controller context structure
|
||||
* @dep: endpoint that is being enabled
|
||||
*
|
||||
* The assignment of transfer resources cannot perfectly follow the
|
||||
* data book due to the fact that the controller driver does not have
|
||||
* all knowledge of the configuration in advance. It is given this
|
||||
* information piecemeal by the composite gadget framework after every
|
||||
* SET_CONFIGURATION and SET_INTERFACE. Trying to follow the databook
|
||||
* programming model in this scenario can cause errors. For two
|
||||
* reasons:
|
||||
* Issue a %DWC3_DEPCMD_DEPSTARTCFG command to @dep. After the command's
|
||||
* completion, it will set Transfer Resource for all available endpoints.
|
||||
*
|
||||
* 1) The databook says to do DEPSTARTCFG for every SET_CONFIGURATION
|
||||
* and SET_INTERFACE (8.1.5). This is incorrect in the scenario of
|
||||
* multiple interfaces.
|
||||
* The assignment of transfer resources cannot perfectly follow the data book
|
||||
* due to the fact that the controller driver does not have all knowledge of the
|
||||
* configuration in advance. It is given this information piecemeal by the
|
||||
* composite gadget framework after every SET_CONFIGURATION and
|
||||
* SET_INTERFACE. Trying to follow the databook programming model in this
|
||||
* scenario can cause errors. For two reasons:
|
||||
*
|
||||
* 2) The databook does not mention doing more DEPXFERCFG for new
|
||||
* 1) The databook says to do %DWC3_DEPCMD_DEPSTARTCFG for every
|
||||
* %USB_REQ_SET_CONFIGURATION and %USB_REQ_SET_INTERFACE (8.1.5). This is
|
||||
* incorrect in the scenario of multiple interfaces.
|
||||
*
|
||||
* 2) The databook does not mention doing more %DWC3_DEPCMD_DEPXFERCFG for new
|
||||
* endpoint on alt setting (8.1.6).
|
||||
*
|
||||
* The following simplified method is used instead:
|
||||
*
|
||||
* All hardware endpoints can be assigned a transfer resource and this
|
||||
* setting will stay persistent until either a core reset or
|
||||
* hibernation. So whenever we do a DEPSTARTCFG(0) we can go ahead and
|
||||
* do DEPXFERCFG for every hardware endpoint as well. We are
|
||||
* All hardware endpoints can be assigned a transfer resource and this setting
|
||||
* will stay persistent until either a core reset or hibernation. So whenever we
|
||||
* do a %DWC3_DEPCMD_DEPSTARTCFG(0) we can go ahead and do
|
||||
* %DWC3_DEPCMD_DEPXFERCFG for every hardware endpoint as well. We are
|
||||
* guaranteed that there are as many transfer resources as endpoints.
|
||||
*
|
||||
* This function is called for each endpoint when it is being enabled
|
||||
* but is triggered only when called for EP0-out, which always happens
|
||||
* first, and which should only happen in one of the above conditions.
|
||||
* This function is called for each endpoint when it is being enabled but is
|
||||
* triggered only when called for EP0-out, which always happens first, and which
|
||||
* should only happen in one of the above conditions.
|
||||
*/
|
||||
static int dwc3_gadget_start_config(struct dwc3 *dwc, struct dwc3_ep *dep)
|
||||
{
|
||||
|
@ -569,11 +606,13 @@ static int dwc3_gadget_set_xfer_resource(struct dwc3 *dwc, struct dwc3_ep *dep)
|
|||
}
|
||||
|
||||
/**
|
||||
* __dwc3_gadget_ep_enable - Initializes a HW endpoint
|
||||
* __dwc3_gadget_ep_enable - initializes a hw endpoint
|
||||
* @dep: endpoint to be initialized
|
||||
* @desc: USB Endpoint Descriptor
|
||||
* @modify: if true, modify existing endpoint configuration
|
||||
* @restore: if true, restore endpoint configuration from scratch buffer
|
||||
*
|
||||
* Caller should take care of locking
|
||||
* Caller should take care of locking. Execute all necessary commands to
|
||||
* initialize a HW endpoint so it can be used by a gadget driver.
|
||||
*/
|
||||
static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep,
|
||||
bool modify, bool restore)
|
||||
|
@ -685,11 +724,13 @@ static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep)
|
|||
}
|
||||
|
||||
/**
|
||||
* __dwc3_gadget_ep_disable - Disables a HW endpoint
|
||||
* __dwc3_gadget_ep_disable - disables a hw endpoint
|
||||
* @dep: the endpoint to disable
|
||||
*
|
||||
* This function also removes requests which are currently processed ny the
|
||||
* hardware and those which are not yet scheduled.
|
||||
* This function undoes what __dwc3_gadget_ep_enable did and also removes
|
||||
* requests which are currently being processed by the hardware and those which
|
||||
* are not yet scheduled.
|
||||
*
|
||||
* Caller should take care of locking.
|
||||
*/
|
||||
static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep)
|
||||
|
@ -932,7 +973,7 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep,
|
|||
}
|
||||
|
||||
/**
|
||||
* dwc3_ep_prev_trb() - Returns the previous TRB in the ring
|
||||
* dwc3_ep_prev_trb - returns the previous TRB in the ring
|
||||
* @dep: The endpoint with the TRB ring
|
||||
* @index: The index of the current TRB in the ring
|
||||
*
|
||||
|
@ -953,7 +994,6 @@ static struct dwc3_trb *dwc3_ep_prev_trb(struct dwc3_ep *dep, u8 index)
|
|||
static u32 dwc3_calc_trbs_left(struct dwc3_ep *dep)
|
||||
{
|
||||
struct dwc3_trb *tmp;
|
||||
struct dwc3 *dwc = dep->dwc;
|
||||
u8 trbs_left;
|
||||
|
||||
/*
|
||||
|
@ -965,8 +1005,7 @@ static u32 dwc3_calc_trbs_left(struct dwc3_ep *dep)
|
|||
*/
|
||||
if (dep->trb_enqueue == dep->trb_dequeue) {
|
||||
tmp = dwc3_ep_prev_trb(dep, dep->trb_enqueue);
|
||||
if (dev_WARN_ONCE(dwc->dev, tmp->ctrl & DWC3_TRB_CTRL_HWO,
|
||||
"%s No TRBS left\n", dep->name))
|
||||
if (tmp->ctrl & DWC3_TRB_CTRL_HWO)
|
||||
return 0;
|
||||
|
||||
return DWC3_TRB_NUM - 1;
|
||||
|
@ -1101,6 +1140,17 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep)
|
|||
}
|
||||
|
||||
list_for_each_entry_safe(req, n, &dep->pending_list, list) {
|
||||
struct dwc3 *dwc = dep->dwc;
|
||||
int ret;
|
||||
|
||||
ret = usb_gadget_map_request_by_dev(dwc->sysdev, &req->request,
|
||||
dep->direction);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
req->sg = req->request.sg;
|
||||
req->num_pending_sgs = req->request.num_mapped_sgs;
|
||||
|
||||
if (req->num_pending_sgs > 0)
|
||||
dwc3_prepare_one_trb_sg(dep, req);
|
||||
else
|
||||
|
@ -1207,7 +1257,7 @@ static void dwc3_gadget_start_isoc(struct dwc3 *dwc,
|
|||
static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
|
||||
{
|
||||
struct dwc3 *dwc = dep->dwc;
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
if (!dep->endpoint.desc) {
|
||||
dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n",
|
||||
|
@ -1215,12 +1265,9 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
|
|||
return -ESHUTDOWN;
|
||||
}
|
||||
|
||||
if (WARN(req->dep != dep, "request %p belongs to '%s'\n",
|
||||
&req->request, req->dep->name)) {
|
||||
dev_err(dwc->dev, "%s: request %p belongs to '%s'\n",
|
||||
dep->name, &req->request, req->dep->name);
|
||||
if (WARN(req->dep != dep, "request %pK belongs to '%s'\n",
|
||||
&req->request, req->dep->name))
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pm_runtime_get(dwc->dev);
|
||||
|
||||
|
@ -1231,14 +1278,6 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
|
|||
|
||||
trace_dwc3_ep_queue(req);
|
||||
|
||||
ret = usb_gadget_map_request_by_dev(dwc->sysdev, &req->request,
|
||||
dep->direction);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
req->sg = req->request.sg;
|
||||
req->num_pending_sgs = req->request.num_mapped_sgs;
|
||||
|
||||
list_add_tail(&req->list, &dep->pending_list);
|
||||
|
||||
/*
|
||||
|
@ -1396,7 +1435,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep,
|
|||
}
|
||||
goto out1;
|
||||
}
|
||||
dev_err(dwc->dev, "request %p was not queued to %s\n",
|
||||
dev_err(dwc->dev, "request %pK was not queued to %s\n",
|
||||
request, ep->name);
|
||||
ret = -EINVAL;
|
||||
goto out0;
|
||||
|
@ -1741,8 +1780,8 @@ static irqreturn_t dwc3_interrupt(int irq, void *_dwc);
|
|||
static irqreturn_t dwc3_thread_interrupt(int irq, void *_dwc);
|
||||
|
||||
/**
|
||||
* dwc3_gadget_setup_nump - Calculate and initialize NUMP field of DCFG
|
||||
* dwc: pointer to our context structure
|
||||
* dwc3_gadget_setup_nump - calculate and initialize NUMP field of %DWC3_DCFG
|
||||
* @dwc: pointer to our context structure
|
||||
*
|
||||
* The following looks like complex but it's actually very simple. In order to
|
||||
* calculate the number of packets we can burst at once on OUT transfers, we're
|
||||
|
@ -1798,49 +1837,6 @@ static int __dwc3_gadget_start(struct dwc3 *dwc)
|
|||
dwc3_writel(dwc->regs, DWC3_DEV_IMOD(0), 0);
|
||||
}
|
||||
|
||||
reg = dwc3_readl(dwc->regs, DWC3_DCFG);
|
||||
reg &= ~(DWC3_DCFG_SPEED_MASK);
|
||||
|
||||
/**
|
||||
* WORKAROUND: DWC3 revision < 2.20a have an issue
|
||||
* which would cause metastability state on Run/Stop
|
||||
* bit if we try to force the IP to USB2-only mode.
|
||||
*
|
||||
* Because of that, we cannot configure the IP to any
|
||||
* speed other than the SuperSpeed
|
||||
*
|
||||
* Refers to:
|
||||
*
|
||||
* STAR#9000525659: Clock Domain Crossing on DCTL in
|
||||
* USB 2.0 Mode
|
||||
*/
|
||||
if (dwc->revision < DWC3_REVISION_220A) {
|
||||
reg |= DWC3_DCFG_SUPERSPEED;
|
||||
} else {
|
||||
switch (dwc->maximum_speed) {
|
||||
case USB_SPEED_LOW:
|
||||
reg |= DWC3_DCFG_LOWSPEED;
|
||||
break;
|
||||
case USB_SPEED_FULL:
|
||||
reg |= DWC3_DCFG_FULLSPEED;
|
||||
break;
|
||||
case USB_SPEED_HIGH:
|
||||
reg |= DWC3_DCFG_HIGHSPEED;
|
||||
break;
|
||||
case USB_SPEED_SUPER_PLUS:
|
||||
reg |= DWC3_DCFG_SUPERSPEED_PLUS;
|
||||
break;
|
||||
default:
|
||||
dev_err(dwc->dev, "invalid dwc->maximum_speed (%d)\n",
|
||||
dwc->maximum_speed);
|
||||
/* fall through */
|
||||
case USB_SPEED_SUPER:
|
||||
reg |= DWC3_DCFG_SUPERSPEED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dwc3_writel(dwc->regs, DWC3_DCFG, reg);
|
||||
|
||||
/*
|
||||
* We are telling dwc3 that we want to use DCFG.NUMP as ACK TP's NUMP
|
||||
* field instead of letting dwc3 itself calculate that automatically.
|
||||
|
@ -1972,6 +1968,63 @@ out:
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void dwc3_gadget_set_speed(struct usb_gadget *g,
|
||||
enum usb_device_speed speed)
|
||||
{
|
||||
struct dwc3 *dwc = gadget_to_dwc(g);
|
||||
unsigned long flags;
|
||||
u32 reg;
|
||||
|
||||
spin_lock_irqsave(&dwc->lock, flags);
|
||||
reg = dwc3_readl(dwc->regs, DWC3_DCFG);
|
||||
reg &= ~(DWC3_DCFG_SPEED_MASK);
|
||||
|
||||
/*
|
||||
* WORKAROUND: DWC3 revision < 2.20a have an issue
|
||||
* which would cause metastability state on Run/Stop
|
||||
* bit if we try to force the IP to USB2-only mode.
|
||||
*
|
||||
* Because of that, we cannot configure the IP to any
|
||||
* speed other than the SuperSpeed
|
||||
*
|
||||
* Refers to:
|
||||
*
|
||||
* STAR#9000525659: Clock Domain Crossing on DCTL in
|
||||
* USB 2.0 Mode
|
||||
*/
|
||||
if (dwc->revision < DWC3_REVISION_220A) {
|
||||
reg |= DWC3_DCFG_SUPERSPEED;
|
||||
} else {
|
||||
switch (speed) {
|
||||
case USB_SPEED_LOW:
|
||||
reg |= DWC3_DCFG_LOWSPEED;
|
||||
break;
|
||||
case USB_SPEED_FULL:
|
||||
reg |= DWC3_DCFG_FULLSPEED;
|
||||
break;
|
||||
case USB_SPEED_HIGH:
|
||||
reg |= DWC3_DCFG_HIGHSPEED;
|
||||
break;
|
||||
case USB_SPEED_SUPER:
|
||||
reg |= DWC3_DCFG_SUPERSPEED;
|
||||
break;
|
||||
case USB_SPEED_SUPER_PLUS:
|
||||
reg |= DWC3_DCFG_SUPERSPEED_PLUS;
|
||||
break;
|
||||
default:
|
||||
dev_err(dwc->dev, "invalid speed (%d)\n", speed);
|
||||
|
||||
if (dwc->revision & DWC3_REVISION_IS_DWC31)
|
||||
reg |= DWC3_DCFG_SUPERSPEED_PLUS;
|
||||
else
|
||||
reg |= DWC3_DCFG_SUPERSPEED;
|
||||
}
|
||||
}
|
||||
dwc3_writel(dwc->regs, DWC3_DCFG, reg);
|
||||
|
||||
spin_unlock_irqrestore(&dwc->lock, flags);
|
||||
}
|
||||
|
||||
static const struct usb_gadget_ops dwc3_gadget_ops = {
|
||||
.get_frame = dwc3_gadget_get_frame,
|
||||
.wakeup = dwc3_gadget_wakeup,
|
||||
|
@ -1979,19 +2032,21 @@ static const struct usb_gadget_ops dwc3_gadget_ops = {
|
|||
.pullup = dwc3_gadget_pullup,
|
||||
.udc_start = dwc3_gadget_start,
|
||||
.udc_stop = dwc3_gadget_stop,
|
||||
.udc_set_speed = dwc3_gadget_set_speed,
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 num)
|
||||
static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 total)
|
||||
{
|
||||
struct dwc3_ep *dep;
|
||||
u8 epnum;
|
||||
|
||||
INIT_LIST_HEAD(&dwc->gadget.ep_list);
|
||||
|
||||
for (epnum = 0; epnum < num; epnum++) {
|
||||
for (epnum = 0; epnum < total; epnum++) {
|
||||
bool direction = epnum & 1;
|
||||
u8 num = epnum >> 1;
|
||||
|
||||
dep = kzalloc(sizeof(*dep), GFP_KERNEL);
|
||||
if (!dep)
|
||||
|
@ -2003,7 +2058,7 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 num)
|
|||
dep->regs = dwc->regs + DWC3_DEP_BASE(epnum);
|
||||
dwc->eps[epnum] = dep;
|
||||
|
||||
snprintf(dep->name, sizeof(dep->name), "ep%d%s", epnum >> 1,
|
||||
snprintf(dep->name, sizeof(dep->name), "ep%u%s", num,
|
||||
direction ? "in" : "out");
|
||||
|
||||
dep->endpoint.name = dep->name;
|
||||
|
@ -2015,39 +2070,39 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 num)
|
|||
|
||||
spin_lock_init(&dep->lock);
|
||||
|
||||
if (epnum == 0 || epnum == 1) {
|
||||
if (num == 0) {
|
||||
usb_ep_set_maxpacket_limit(&dep->endpoint, 512);
|
||||
dep->endpoint.maxburst = 1;
|
||||
dep->endpoint.ops = &dwc3_gadget_ep0_ops;
|
||||
if (!epnum)
|
||||
if (!direction)
|
||||
dwc->gadget.ep0 = &dep->endpoint;
|
||||
} else if (direction) {
|
||||
int mdwidth;
|
||||
int kbytes;
|
||||
int size;
|
||||
int ret;
|
||||
int num;
|
||||
|
||||
mdwidth = DWC3_MDWIDTH(dwc->hwparams.hwparams0);
|
||||
/* MDWIDTH is represented in bits, we need it in bytes */
|
||||
mdwidth /= 8;
|
||||
|
||||
size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(epnum >> 1));
|
||||
size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(num));
|
||||
size = DWC3_GTXFIFOSIZ_TXFDEF(size);
|
||||
|
||||
/* FIFO Depth is in MDWDITH bytes. Multiply */
|
||||
size *= mdwidth;
|
||||
|
||||
num = size / 1024;
|
||||
if (num == 0)
|
||||
num = 1;
|
||||
kbytes = size / 1024;
|
||||
if (kbytes == 0)
|
||||
kbytes = 1;
|
||||
|
||||
/*
|
||||
* FIFO sizes account an extra MDWIDTH * (num + 1) bytes for
|
||||
* FIFO sizes account an extra MDWIDTH * (kbytes + 1) bytes for
|
||||
* internal overhead. We don't really know how these are used,
|
||||
* but documentation say it exists.
|
||||
*/
|
||||
size -= mdwidth * (num + 1);
|
||||
size /= num;
|
||||
size -= mdwidth * (kbytes + 1);
|
||||
size /= kbytes;
|
||||
|
||||
usb_ep_set_maxpacket_limit(&dep->endpoint, size);
|
||||
|
||||
|
@ -2073,7 +2128,7 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 num)
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (epnum == 0 || epnum == 1) {
|
||||
if (num == 0) {
|
||||
dep->endpoint.caps.type_control = true;
|
||||
} else {
|
||||
dep->endpoint.caps.type_iso = true;
|
||||
|
@ -2871,7 +2926,7 @@ static void dwc3_gadget_hibernation_interrupt(struct dwc3 *dwc,
|
|||
{
|
||||
unsigned int is_ss = evtinfo & BIT(4);
|
||||
|
||||
/**
|
||||
/*
|
||||
* WORKAROUND: DWC3 revison 2.20a with hibernation support
|
||||
* have a known issue which can cause USB CV TD.9.23 to fail
|
||||
* randomly.
|
||||
|
@ -2943,20 +2998,12 @@ static void dwc3_process_event_entry(struct dwc3 *dwc,
|
|||
{
|
||||
trace_dwc3_event(event->raw, dwc);
|
||||
|
||||
/* Endpoint IRQ, handle it and return early */
|
||||
if (event->type.is_devspec == 0) {
|
||||
/* depevt */
|
||||
return dwc3_endpoint_interrupt(dwc, &event->depevt);
|
||||
}
|
||||
|
||||
switch (event->type.type) {
|
||||
case DWC3_EVENT_TYPE_DEV:
|
||||
if (!event->type.is_devspec)
|
||||
dwc3_endpoint_interrupt(dwc, &event->depevt);
|
||||
else if (event->type.type == DWC3_EVENT_TYPE_DEV)
|
||||
dwc3_gadget_interrupt(dwc, &event->devt);
|
||||
break;
|
||||
/* REVISIT what to do with Carkit and I2C events ? */
|
||||
default:
|
||||
else
|
||||
dev_err(dwc->dev, "UNKNOWN IRQ type %d\n", event->raw);
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t dwc3_process_event_buf(struct dwc3_event_buffer *evt)
|
||||
|
@ -3110,7 +3157,7 @@ out:
|
|||
}
|
||||
|
||||
/**
|
||||
* dwc3_gadget_init - Initializes gadget related registers
|
||||
* dwc3_gadget_init - initializes gadget related registers
|
||||
* @dwc: pointer to our controller context structure
|
||||
*
|
||||
* Returns 0 on success otherwise negative errno.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/*
|
||||
* gadget.h - DesignWare USB3 DRD Gadget Header
|
||||
*
|
||||
* Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com
|
||||
|
@ -60,11 +60,25 @@ struct dwc3;
|
|||
|
||||
#define to_dwc3_request(r) (container_of(r, struct dwc3_request, request))
|
||||
|
||||
/**
|
||||
* next_request - gets the next request on the given list
|
||||
* @list: the request list to operate on
|
||||
*
|
||||
* Caller should take care of locking. This function return %NULL or the first
|
||||
* request available on @list.
|
||||
*/
|
||||
static inline struct dwc3_request *next_request(struct list_head *list)
|
||||
{
|
||||
return list_first_entry_or_null(list, struct dwc3_request, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* dwc3_gadget_move_started_request - move @req to the started_list
|
||||
* @req: the request to be moved
|
||||
*
|
||||
* Caller should take care of locking. This function will move @req from its
|
||||
* current list to the endpoint's started_list.
|
||||
*/
|
||||
static inline void dwc3_gadget_move_started_request(struct dwc3_request *req)
|
||||
{
|
||||
struct dwc3_ep *dep = req->dep;
|
||||
|
@ -87,10 +101,10 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol);
|
|||
|
||||
/**
|
||||
* dwc3_gadget_ep_get_transfer_index - Gets transfer index from HW
|
||||
* @dwc: DesignWare USB3 Pointer
|
||||
* @number: DWC endpoint number
|
||||
* @dep: dwc3 endpoint
|
||||
*
|
||||
* Caller should take care of locking
|
||||
* Caller should take care of locking. Returns the transfer resource
|
||||
* index for a given endpoint.
|
||||
*/
|
||||
static inline u32 dwc3_gadget_ep_get_transfer_index(struct dwc3_ep *dep)
|
||||
{
|
||||
|
|
|
@ -60,13 +60,15 @@ DECLARE_EVENT_CLASS(dwc3_log_event,
|
|||
TP_STRUCT__entry(
|
||||
__field(u32, event)
|
||||
__field(u32, ep0state)
|
||||
__dynamic_array(char, str, DWC3_MSG_MAX)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->event = event;
|
||||
__entry->ep0state = dwc->ep0state;
|
||||
),
|
||||
TP_printk("event (%08x): %s", __entry->event,
|
||||
dwc3_decode_event(__entry->event, __entry->ep0state))
|
||||
dwc3_decode_event(__get_str(str), __entry->event,
|
||||
__entry->ep0state))
|
||||
);
|
||||
|
||||
DEFINE_EVENT(dwc3_log_event, dwc3_event,
|
||||
|
@ -83,6 +85,7 @@ DECLARE_EVENT_CLASS(dwc3_log_ctrl,
|
|||
__field(__u16, wValue)
|
||||
__field(__u16, wIndex)
|
||||
__field(__u16, wLength)
|
||||
__dynamic_array(char, str, DWC3_MSG_MAX)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->bRequestType = ctrl->bRequestType;
|
||||
|
@ -91,10 +94,9 @@ DECLARE_EVENT_CLASS(dwc3_log_ctrl,
|
|||
__entry->wIndex = le16_to_cpu(ctrl->wIndex);
|
||||
__entry->wLength = le16_to_cpu(ctrl->wLength);
|
||||
),
|
||||
TP_printk("bRequestType %02x bRequest %02x wValue %04x wIndex %04x wLength %d",
|
||||
__entry->bRequestType, __entry->bRequest,
|
||||
__entry->wValue, __entry->wIndex,
|
||||
__entry->wLength
|
||||
TP_printk("%s", dwc3_decode_ctrl(__get_str(str), __entry->bRequestType,
|
||||
__entry->bRequest, __entry->wValue,
|
||||
__entry->wIndex, __entry->wLength)
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -107,7 +109,7 @@ DECLARE_EVENT_CLASS(dwc3_log_request,
|
|||
TP_PROTO(struct dwc3_request *req),
|
||||
TP_ARGS(req),
|
||||
TP_STRUCT__entry(
|
||||
__dynamic_array(char, name, DWC3_MSG_MAX)
|
||||
__string(name, req->dep->name)
|
||||
__field(struct dwc3_request *, req)
|
||||
__field(unsigned, actual)
|
||||
__field(unsigned, length)
|
||||
|
@ -117,7 +119,7 @@ DECLARE_EVENT_CLASS(dwc3_log_request,
|
|||
__field(int, no_interrupt)
|
||||
),
|
||||
TP_fast_assign(
|
||||
snprintf(__get_str(name), DWC3_MSG_MAX, "%s", req->dep->name);
|
||||
__assign_str(name, req->dep->name);
|
||||
__entry->req = req;
|
||||
__entry->actual = req->request.actual;
|
||||
__entry->length = req->request.length;
|
||||
|
@ -190,7 +192,7 @@ DECLARE_EVENT_CLASS(dwc3_log_gadget_ep_cmd,
|
|||
struct dwc3_gadget_ep_cmd_params *params, int cmd_status),
|
||||
TP_ARGS(dep, cmd, params, cmd_status),
|
||||
TP_STRUCT__entry(
|
||||
__dynamic_array(char, name, DWC3_MSG_MAX)
|
||||
__string(name, dep->name)
|
||||
__field(unsigned int, cmd)
|
||||
__field(u32, param0)
|
||||
__field(u32, param1)
|
||||
|
@ -198,7 +200,7 @@ DECLARE_EVENT_CLASS(dwc3_log_gadget_ep_cmd,
|
|||
__field(int, cmd_status)
|
||||
),
|
||||
TP_fast_assign(
|
||||
snprintf(__get_str(name), DWC3_MSG_MAX, "%s", dep->name);
|
||||
__assign_str(name, dep->name);
|
||||
__entry->cmd = cmd;
|
||||
__entry->param0 = params->param0;
|
||||
__entry->param1 = params->param1;
|
||||
|
@ -223,7 +225,7 @@ DECLARE_EVENT_CLASS(dwc3_log_trb,
|
|||
TP_PROTO(struct dwc3_ep *dep, struct dwc3_trb *trb),
|
||||
TP_ARGS(dep, trb),
|
||||
TP_STRUCT__entry(
|
||||
__dynamic_array(char, name, DWC3_MSG_MAX)
|
||||
__string(name, dep->name)
|
||||
__field(struct dwc3_trb *, trb)
|
||||
__field(u32, allocated)
|
||||
__field(u32, queued)
|
||||
|
@ -234,7 +236,7 @@ DECLARE_EVENT_CLASS(dwc3_log_trb,
|
|||
__field(u32, type)
|
||||
),
|
||||
TP_fast_assign(
|
||||
snprintf(__get_str(name), DWC3_MSG_MAX, "%s", dep->name);
|
||||
__assign_str(name, dep->name);
|
||||
__entry->trb = trb;
|
||||
__entry->allocated = dep->allocated_requests;
|
||||
__entry->queued = dep->queued_requests;
|
||||
|
@ -291,7 +293,7 @@ DECLARE_EVENT_CLASS(dwc3_log_ep,
|
|||
TP_PROTO(struct dwc3_ep *dep),
|
||||
TP_ARGS(dep),
|
||||
TP_STRUCT__entry(
|
||||
__dynamic_array(char, name, DWC3_MSG_MAX)
|
||||
__string(name, dep->name)
|
||||
__field(unsigned, maxpacket)
|
||||
__field(unsigned, maxpacket_limit)
|
||||
__field(unsigned, max_streams)
|
||||
|
@ -302,7 +304,7 @@ DECLARE_EVENT_CLASS(dwc3_log_ep,
|
|||
__field(u8, trb_dequeue)
|
||||
),
|
||||
TP_fast_assign(
|
||||
snprintf(__get_str(name), DWC3_MSG_MAX, "%s", dep->name);
|
||||
__assign_str(name, dep->name);
|
||||
__entry->maxpacket = dep->endpoint.maxpacket;
|
||||
__entry->maxpacket_limit = dep->endpoint.maxpacket_limit;
|
||||
__entry->max_streams = dep->endpoint.max_streams;
|
||||
|
|
|
@ -41,6 +41,12 @@ static int dwc3_ulpi_read(struct device *dev, u8 addr)
|
|||
u32 reg;
|
||||
int ret;
|
||||
|
||||
reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
|
||||
if (reg & DWC3_GUSB2PHYCFG_SUSPHY) {
|
||||
reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
|
||||
dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
|
||||
}
|
||||
|
||||
reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr);
|
||||
dwc3_writel(dwc->regs, DWC3_GUSB2PHYACC(0), reg);
|
||||
|
||||
|
@ -58,6 +64,12 @@ static int dwc3_ulpi_write(struct device *dev, u8 addr, u8 val)
|
|||
struct dwc3 *dwc = dev_get_drvdata(dev);
|
||||
u32 reg;
|
||||
|
||||
reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
|
||||
if (reg & DWC3_GUSB2PHYCFG_SUSPHY) {
|
||||
reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
|
||||
dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
|
||||
}
|
||||
|
||||
reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr);
|
||||
reg |= DWC3_GUSB2PHYACC_WRITE | val;
|
||||
dwc3_writel(dwc->regs, DWC3_GUSB2PHYACC(0), reg);
|
||||
|
|
|
@ -41,7 +41,7 @@ menuconfig USB_GADGET
|
|||
don't have this kind of hardware (except maybe inside Linux PDAs).
|
||||
|
||||
For more information, see <http://www.linux-usb.org/gadget> and
|
||||
the kernel DocBook documentation for this API.
|
||||
the kernel documentation for this API.
|
||||
|
||||
if USB_GADGET
|
||||
|
||||
|
@ -158,6 +158,9 @@ config USB_U_SERIAL
|
|||
config USB_U_ETHER
|
||||
tristate
|
||||
|
||||
config USB_U_AUDIO
|
||||
tristate
|
||||
|
||||
config USB_F_SERIAL
|
||||
tristate
|
||||
|
||||
|
@ -191,6 +194,9 @@ config USB_F_FS
|
|||
config USB_F_UAC1
|
||||
tristate
|
||||
|
||||
config USB_F_UAC1_LEGACY
|
||||
tristate
|
||||
|
||||
config USB_F_UAC2
|
||||
tristate
|
||||
|
||||
|
@ -368,12 +374,30 @@ config USB_CONFIGFS_F_UAC1
|
|||
depends on SND
|
||||
select USB_LIBCOMPOSITE
|
||||
select SND_PCM
|
||||
select USB_U_AUDIO
|
||||
select USB_F_UAC1
|
||||
help
|
||||
This Audio function implements 1 AudioControl interface,
|
||||
1 AudioStreaming Interface each for USB-OUT and USB-IN.
|
||||
This driver requires a real Audio codec to be present
|
||||
on the device.
|
||||
This driver doesn't expect any real Audio codec to be present
|
||||
on the device - the audio streams are simply sinked to and
|
||||
sourced from a virtual ALSA sound card created. The user-space
|
||||
application may choose to do whatever it wants with the data
|
||||
received from the USB Host and choose to provide whatever it
|
||||
wants as audio data to the USB Host.
|
||||
|
||||
config USB_CONFIGFS_F_UAC1_LEGACY
|
||||
bool "Audio Class 1.0 (legacy implementation)"
|
||||
depends on USB_CONFIGFS
|
||||
depends on SND
|
||||
select USB_LIBCOMPOSITE
|
||||
select SND_PCM
|
||||
select USB_F_UAC1_LEGACY
|
||||
help
|
||||
This Audio function implements 1 AudioControl interface,
|
||||
1 AudioStreaming Interface each for USB-OUT and USB-IN.
|
||||
This is a legacy driver and requires a real Audio codec
|
||||
to be present on the device.
|
||||
|
||||
config USB_CONFIGFS_F_UAC2
|
||||
bool "Audio Class 2.0"
|
||||
|
@ -381,6 +405,7 @@ config USB_CONFIGFS_F_UAC2
|
|||
depends on SND
|
||||
select USB_LIBCOMPOSITE
|
||||
select SND_PCM
|
||||
select USB_U_AUDIO
|
||||
select USB_F_UAC2
|
||||
help
|
||||
This Audio function is compatible with USB Audio Class
|
||||
|
|
|
@ -610,7 +610,6 @@ static int count_configs(struct usb_composite_dev *cdev, unsigned type)
|
|||
static int bos_desc(struct usb_composite_dev *cdev)
|
||||
{
|
||||
struct usb_ext_cap_descriptor *usb_ext;
|
||||
struct usb_ss_cap_descriptor *ss_cap;
|
||||
struct usb_dcd_config_params dcd_config_params;
|
||||
struct usb_bos_descriptor *bos = cdev->req->buf;
|
||||
|
||||
|
@ -636,29 +635,35 @@ static int bos_desc(struct usb_composite_dev *cdev)
|
|||
* The Superspeed USB Capability descriptor shall be implemented by all
|
||||
* SuperSpeed devices.
|
||||
*/
|
||||
ss_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
|
||||
bos->bNumDeviceCaps++;
|
||||
le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SS_CAP_SIZE);
|
||||
ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE;
|
||||
ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
|
||||
ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE;
|
||||
ss_cap->bmAttributes = 0; /* LTM is not supported yet */
|
||||
ss_cap->wSpeedSupported = cpu_to_le16(USB_LOW_SPEED_OPERATION |
|
||||
USB_FULL_SPEED_OPERATION |
|
||||
USB_HIGH_SPEED_OPERATION |
|
||||
USB_5GBPS_OPERATION);
|
||||
ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION;
|
||||
if (gadget_is_superspeed(cdev->gadget)) {
|
||||
struct usb_ss_cap_descriptor *ss_cap;
|
||||
|
||||
/* Get Controller configuration */
|
||||
if (cdev->gadget->ops->get_config_params)
|
||||
cdev->gadget->ops->get_config_params(&dcd_config_params);
|
||||
else {
|
||||
dcd_config_params.bU1devExitLat = USB_DEFAULT_U1_DEV_EXIT_LAT;
|
||||
dcd_config_params.bU2DevExitLat =
|
||||
cpu_to_le16(USB_DEFAULT_U2_DEV_EXIT_LAT);
|
||||
ss_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
|
||||
bos->bNumDeviceCaps++;
|
||||
le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SS_CAP_SIZE);
|
||||
ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE;
|
||||
ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
|
||||
ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE;
|
||||
ss_cap->bmAttributes = 0; /* LTM is not supported yet */
|
||||
ss_cap->wSpeedSupported = cpu_to_le16(USB_LOW_SPEED_OPERATION |
|
||||
USB_FULL_SPEED_OPERATION |
|
||||
USB_HIGH_SPEED_OPERATION |
|
||||
USB_5GBPS_OPERATION);
|
||||
ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION;
|
||||
|
||||
/* Get Controller configuration */
|
||||
if (cdev->gadget->ops->get_config_params) {
|
||||
cdev->gadget->ops->get_config_params(
|
||||
&dcd_config_params);
|
||||
} else {
|
||||
dcd_config_params.bU1devExitLat =
|
||||
USB_DEFAULT_U1_DEV_EXIT_LAT;
|
||||
dcd_config_params.bU2DevExitLat =
|
||||
cpu_to_le16(USB_DEFAULT_U2_DEV_EXIT_LAT);
|
||||
}
|
||||
ss_cap->bU1devExitLat = dcd_config_params.bU1devExitLat;
|
||||
ss_cap->bU2DevExitLat = dcd_config_params.bU2DevExitLat;
|
||||
}
|
||||
ss_cap->bU1devExitLat = dcd_config_params.bU1devExitLat;
|
||||
ss_cap->bU2DevExitLat = dcd_config_params.bU2DevExitLat;
|
||||
|
||||
/* The SuperSpeedPlus USB Device Capability descriptor */
|
||||
if (gadget_is_superspeed_plus(cdev->gadget)) {
|
||||
|
@ -1602,7 +1607,10 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
|
|||
cdev->desc.bcdUSB = cpu_to_le16(0x0210);
|
||||
}
|
||||
} else {
|
||||
cdev->desc.bcdUSB = cpu_to_le16(0x0200);
|
||||
if (gadget->lpm_capable)
|
||||
cdev->desc.bcdUSB = cpu_to_le16(0x0201);
|
||||
else
|
||||
cdev->desc.bcdUSB = cpu_to_le16(0x0200);
|
||||
}
|
||||
|
||||
value = min(w_length, (u16) sizeof cdev->desc);
|
||||
|
@ -1633,7 +1641,8 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
|
|||
value = min(w_length, (u16) value);
|
||||
break;
|
||||
case USB_DT_BOS:
|
||||
if (gadget_is_superspeed(gadget)) {
|
||||
if (gadget_is_superspeed(gadget) ||
|
||||
gadget->lpm_capable) {
|
||||
value = bos_desc(cdev);
|
||||
value = min(w_length, (u16) value);
|
||||
}
|
||||
|
|
|
@ -738,7 +738,7 @@ static inline struct gadget_info *os_desc_item_to_gadget_info(
|
|||
|
||||
static ssize_t os_desc_use_show(struct config_item *item, char *page)
|
||||
{
|
||||
return sprintf(page, "%d",
|
||||
return sprintf(page, "%d\n",
|
||||
os_desc_item_to_gadget_info(item)->use_os_desc);
|
||||
}
|
||||
|
||||
|
@ -762,7 +762,7 @@ static ssize_t os_desc_use_store(struct config_item *item, const char *page,
|
|||
|
||||
static ssize_t os_desc_b_vendor_code_show(struct config_item *item, char *page)
|
||||
{
|
||||
return sprintf(page, "%d",
|
||||
return sprintf(page, "0x%02x\n",
|
||||
os_desc_item_to_gadget_info(item)->b_vendor_code);
|
||||
}
|
||||
|
||||
|
@ -787,9 +787,13 @@ static ssize_t os_desc_b_vendor_code_store(struct config_item *item,
|
|||
static ssize_t os_desc_qw_sign_show(struct config_item *item, char *page)
|
||||
{
|
||||
struct gadget_info *gi = os_desc_item_to_gadget_info(item);
|
||||
int res;
|
||||
|
||||
memcpy(page, gi->qw_sign, OS_STRING_QW_SIGN_LEN);
|
||||
return OS_STRING_QW_SIGN_LEN;
|
||||
res = utf16s_to_utf8s((wchar_t *) gi->qw_sign, OS_STRING_QW_SIGN_LEN,
|
||||
UTF16_LITTLE_ENDIAN, page, PAGE_SIZE - 1);
|
||||
page[res++] = '\n';
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static ssize_t os_desc_qw_sign_store(struct config_item *item, const char *page,
|
||||
|
@ -900,7 +904,7 @@ static inline struct usb_os_desc_ext_prop
|
|||
|
||||
static ssize_t ext_prop_type_show(struct config_item *item, char *page)
|
||||
{
|
||||
return sprintf(page, "%d", to_usb_os_desc_ext_prop(item)->type);
|
||||
return sprintf(page, "%d\n", to_usb_os_desc_ext_prop(item)->type);
|
||||
}
|
||||
|
||||
static ssize_t ext_prop_type_store(struct config_item *item,
|
||||
|
|
|
@ -32,8 +32,11 @@ 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_U_AUDIO) += u_audio.o
|
||||
usb_f_uac1-y := f_uac1.o
|
||||
obj-$(CONFIG_USB_F_UAC1) += usb_f_uac1.o
|
||||
usb_f_uac1_legacy-y := f_uac1_legacy.o u_uac1_legacy.o
|
||||
obj-$(CONFIG_USB_F_UAC1_LEGACY) += usb_f_uac1_legacy.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 uvc_configfs.o
|
||||
|
|
|
@ -127,7 +127,6 @@ struct ffs_ep {
|
|||
struct ffs_epfile {
|
||||
/* Protects ep->ep and ep->req. */
|
||||
struct mutex mutex;
|
||||
wait_queue_head_t wait;
|
||||
|
||||
struct ffs_data *ffs;
|
||||
struct ffs_ep *ep; /* P: ffs->eps_lock */
|
||||
|
@ -889,7 +888,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
|
|||
if (file->f_flags & O_NONBLOCK)
|
||||
return -EAGAIN;
|
||||
|
||||
ret = wait_event_interruptible(epfile->wait, (ep = epfile->ep));
|
||||
ret = wait_event_interruptible(
|
||||
epfile->ffs->wait, (ep = epfile->ep));
|
||||
if (ret)
|
||||
return -EINTR;
|
||||
}
|
||||
|
@ -1189,6 +1189,7 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
|
|||
unsigned long value)
|
||||
{
|
||||
struct ffs_epfile *epfile = file->private_data;
|
||||
struct ffs_ep *ep;
|
||||
int ret;
|
||||
|
||||
ENTER();
|
||||
|
@ -1196,50 +1197,65 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
|
|||
if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
|
||||
return -ENODEV;
|
||||
|
||||
/* Wait for endpoint to be enabled */
|
||||
ep = epfile->ep;
|
||||
if (!ep) {
|
||||
if (file->f_flags & O_NONBLOCK)
|
||||
return -EAGAIN;
|
||||
|
||||
ret = wait_event_interruptible(
|
||||
epfile->ffs->wait, (ep = epfile->ep));
|
||||
if (ret)
|
||||
return -EINTR;
|
||||
}
|
||||
|
||||
spin_lock_irq(&epfile->ffs->eps_lock);
|
||||
if (likely(epfile->ep)) {
|
||||
switch (code) {
|
||||
case FUNCTIONFS_FIFO_STATUS:
|
||||
ret = usb_ep_fifo_status(epfile->ep->ep);
|
||||
break;
|
||||
case FUNCTIONFS_FIFO_FLUSH:
|
||||
usb_ep_fifo_flush(epfile->ep->ep);
|
||||
ret = 0;
|
||||
break;
|
||||
case FUNCTIONFS_CLEAR_HALT:
|
||||
ret = usb_ep_clear_halt(epfile->ep->ep);
|
||||
break;
|
||||
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];
|
||||
/* In the meantime, endpoint got disabled or changed. */
|
||||
if (epfile->ep != ep) {
|
||||
spin_unlock_irq(&epfile->ffs->eps_lock);
|
||||
return -ESHUTDOWN;
|
||||
}
|
||||
|
||||
spin_unlock_irq(&epfile->ffs->eps_lock);
|
||||
ret = copy_to_user((void *)value, desc, desc->bLength);
|
||||
if (ret)
|
||||
ret = -EFAULT;
|
||||
return ret;
|
||||
}
|
||||
switch (code) {
|
||||
case FUNCTIONFS_FIFO_STATUS:
|
||||
ret = usb_ep_fifo_status(epfile->ep->ep);
|
||||
break;
|
||||
case FUNCTIONFS_FIFO_FLUSH:
|
||||
usb_ep_fifo_flush(epfile->ep->ep);
|
||||
ret = 0;
|
||||
break;
|
||||
case FUNCTIONFS_CLEAR_HALT:
|
||||
ret = usb_ep_clear_halt(epfile->ep->ep);
|
||||
break;
|
||||
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:
|
||||
ret = -ENOTTY;
|
||||
desc_idx = 0;
|
||||
}
|
||||
} else {
|
||||
ret = -ENODEV;
|
||||
desc = epfile->ep->descs[desc_idx];
|
||||
|
||||
spin_unlock_irq(&epfile->ffs->eps_lock);
|
||||
ret = copy_to_user((void *)value, desc, desc->bLength);
|
||||
if (ret)
|
||||
ret = -EFAULT;
|
||||
return ret;
|
||||
}
|
||||
default:
|
||||
ret = -ENOTTY;
|
||||
}
|
||||
spin_unlock_irq(&epfile->ffs->eps_lock);
|
||||
|
||||
|
@ -1593,7 +1609,8 @@ static void ffs_data_put(struct ffs_data *ffs)
|
|||
pr_info("%s(): freeing\n", __func__);
|
||||
ffs_data_clear(ffs);
|
||||
BUG_ON(waitqueue_active(&ffs->ev.waitq) ||
|
||||
waitqueue_active(&ffs->ep0req_completion.wait));
|
||||
waitqueue_active(&ffs->ep0req_completion.wait) ||
|
||||
waitqueue_active(&ffs->wait));
|
||||
kfree(ffs->dev_name);
|
||||
kfree(ffs);
|
||||
}
|
||||
|
@ -1640,6 +1657,7 @@ static struct ffs_data *ffs_data_new(void)
|
|||
mutex_init(&ffs->mutex);
|
||||
spin_lock_init(&ffs->eps_lock);
|
||||
init_waitqueue_head(&ffs->ev.waitq);
|
||||
init_waitqueue_head(&ffs->wait);
|
||||
init_completion(&ffs->ep0req_completion);
|
||||
|
||||
/* XXX REVISIT need to update it in some places, or do we? */
|
||||
|
@ -1761,7 +1779,6 @@ static int ffs_epfiles_create(struct ffs_data *ffs)
|
|||
for (i = 1; i <= count; ++i, ++epfile) {
|
||||
epfile->ffs = ffs;
|
||||
mutex_init(&epfile->mutex);
|
||||
init_waitqueue_head(&epfile->wait);
|
||||
if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)
|
||||
sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]);
|
||||
else
|
||||
|
@ -1786,8 +1803,7 @@ static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count)
|
|||
ENTER();
|
||||
|
||||
for (; count; --count, ++epfile) {
|
||||
BUG_ON(mutex_is_locked(&epfile->mutex) ||
|
||||
waitqueue_active(&epfile->wait));
|
||||
BUG_ON(mutex_is_locked(&epfile->mutex));
|
||||
if (epfile->dentry) {
|
||||
d_delete(epfile->dentry);
|
||||
dput(epfile->dentry);
|
||||
|
@ -1874,11 +1890,11 @@ static int ffs_func_eps_enable(struct ffs_function *func)
|
|||
break;
|
||||
}
|
||||
|
||||
wake_up(&epfile->wait);
|
||||
|
||||
++ep;
|
||||
++epfile;
|
||||
}
|
||||
|
||||
wake_up_interruptible(&ffs->wait);
|
||||
spin_unlock_irqrestore(&func->ffs->eps_lock, flags);
|
||||
|
||||
return ret;
|
||||
|
|
|
@ -260,12 +260,13 @@ struct fsg_common {
|
|||
struct usb_gadget *gadget;
|
||||
struct usb_composite_dev *cdev;
|
||||
struct fsg_dev *fsg, *new_fsg;
|
||||
wait_queue_head_t io_wait;
|
||||
wait_queue_head_t fsg_wait;
|
||||
|
||||
/* filesem protects: backing files in use */
|
||||
struct rw_semaphore filesem;
|
||||
|
||||
/* lock protects: state, all the req_busy's */
|
||||
/* lock protects: state and thread_task */
|
||||
spinlock_t lock;
|
||||
|
||||
struct usb_ep *ep0; /* Copy of gadget->ep0 */
|
||||
|
@ -303,7 +304,6 @@ struct fsg_common {
|
|||
unsigned int running:1;
|
||||
unsigned int sysfs:1;
|
||||
|
||||
int thread_wakeup_needed;
|
||||
struct completion thread_notifier;
|
||||
struct task_struct *thread_task;
|
||||
|
||||
|
@ -355,7 +355,7 @@ typedef void (*fsg_routine_t)(struct fsg_dev *);
|
|||
|
||||
static int exception_in_progress(struct fsg_common *common)
|
||||
{
|
||||
return common->state > FSG_STATE_IDLE;
|
||||
return common->state > FSG_STATE_NORMAL;
|
||||
}
|
||||
|
||||
/* Make bulk-out requests be divisible by the maxpacket size */
|
||||
|
@ -393,20 +393,6 @@ static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep)
|
|||
|
||||
/* These routines may be called in process context or in_irq */
|
||||
|
||||
/* Caller must hold fsg->lock */
|
||||
static void wakeup_thread(struct fsg_common *common)
|
||||
{
|
||||
/*
|
||||
* Ensure the reading of thread_wakeup_needed
|
||||
* and the writing of bh->state are completed
|
||||
*/
|
||||
smp_mb();
|
||||
/* Tell the main thread that something has happened */
|
||||
common->thread_wakeup_needed = 1;
|
||||
if (common->thread_task)
|
||||
wake_up_process(common->thread_task);
|
||||
}
|
||||
|
||||
static void raise_exception(struct fsg_common *common, enum fsg_state new_state)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
@ -460,13 +446,9 @@ static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req)
|
|||
if (req->status == -ECONNRESET) /* Request was cancelled */
|
||||
usb_ep_fifo_flush(ep);
|
||||
|
||||
/* Hold the lock while we update the request and buffer states */
|
||||
smp_wmb();
|
||||
spin_lock(&common->lock);
|
||||
bh->inreq_busy = 0;
|
||||
bh->state = BUF_STATE_EMPTY;
|
||||
wakeup_thread(common);
|
||||
spin_unlock(&common->lock);
|
||||
/* Synchronize with the smp_load_acquire() in sleep_thread() */
|
||||
smp_store_release(&bh->state, BUF_STATE_EMPTY);
|
||||
wake_up(&common->io_wait);
|
||||
}
|
||||
|
||||
static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
|
@ -481,13 +463,9 @@ static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req)
|
|||
if (req->status == -ECONNRESET) /* Request was cancelled */
|
||||
usb_ep_fifo_flush(ep);
|
||||
|
||||
/* Hold the lock while we update the request and buffer states */
|
||||
smp_wmb();
|
||||
spin_lock(&common->lock);
|
||||
bh->outreq_busy = 0;
|
||||
bh->state = BUF_STATE_FULL;
|
||||
wakeup_thread(common);
|
||||
spin_unlock(&common->lock);
|
||||
/* Synchronize with the smp_load_acquire() in sleep_thread() */
|
||||
smp_store_release(&bh->state, BUF_STATE_FULL);
|
||||
wake_up(&common->io_wait);
|
||||
}
|
||||
|
||||
static int _fsg_common_get_max_lun(struct fsg_common *common)
|
||||
|
@ -532,7 +510,7 @@ static int fsg_setup(struct usb_function *f,
|
|||
* and reinitialize our state.
|
||||
*/
|
||||
DBG(fsg, "bulk reset request\n");
|
||||
raise_exception(fsg->common, FSG_STATE_RESET);
|
||||
raise_exception(fsg->common, FSG_STATE_PROTOCOL_RESET);
|
||||
return USB_GADGET_DELAYED_STATUS;
|
||||
|
||||
case US_BULK_GET_MAX_LUN:
|
||||
|
@ -563,43 +541,39 @@ static int fsg_setup(struct usb_function *f,
|
|||
/* All the following routines run in process context */
|
||||
|
||||
/* Use this for bulk or interrupt transfers, not ep0 */
|
||||
static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
|
||||
struct usb_request *req, int *pbusy,
|
||||
enum fsg_buffer_state *state)
|
||||
static int start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
|
||||
struct usb_request *req)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (ep == fsg->bulk_in)
|
||||
dump_msg(fsg, "bulk-in", req->buf, req->length);
|
||||
|
||||
spin_lock_irq(&fsg->common->lock);
|
||||
*pbusy = 1;
|
||||
*state = BUF_STATE_BUSY;
|
||||
spin_unlock_irq(&fsg->common->lock);
|
||||
|
||||
rc = usb_ep_queue(ep, req, GFP_KERNEL);
|
||||
if (rc == 0)
|
||||
return; /* All good, we're done */
|
||||
if (rc) {
|
||||
|
||||
*pbusy = 0;
|
||||
*state = BUF_STATE_EMPTY;
|
||||
/* We can't do much more than wait for a reset */
|
||||
req->status = 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);
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh)
|
||||
{
|
||||
if (!fsg_is_set(common))
|
||||
return false;
|
||||
start_transfer(common->fsg, common->fsg->bulk_in,
|
||||
bh->inreq, &bh->inreq_busy, &bh->state);
|
||||
bh->state = BUF_STATE_SENDING;
|
||||
if (start_transfer(common->fsg, common->fsg->bulk_in, bh->inreq))
|
||||
bh->state = BUF_STATE_EMPTY;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -607,37 +581,31 @@ static bool start_out_transfer(struct fsg_common *common, struct fsg_buffhd *bh)
|
|||
{
|
||||
if (!fsg_is_set(common))
|
||||
return false;
|
||||
start_transfer(common->fsg, common->fsg->bulk_out,
|
||||
bh->outreq, &bh->outreq_busy, &bh->state);
|
||||
bh->state = BUF_STATE_RECEIVING;
|
||||
if (start_transfer(common->fsg, common->fsg->bulk_out, bh->outreq))
|
||||
bh->state = BUF_STATE_FULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int sleep_thread(struct fsg_common *common, bool can_freeze)
|
||||
static int sleep_thread(struct fsg_common *common, bool can_freeze,
|
||||
struct fsg_buffhd *bh)
|
||||
{
|
||||
int rc = 0;
|
||||
int rc;
|
||||
|
||||
/* Wait until a signal arrives or we are woken up */
|
||||
for (;;) {
|
||||
if (can_freeze)
|
||||
try_to_freeze();
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
if (signal_pending(current)) {
|
||||
rc = -EINTR;
|
||||
break;
|
||||
}
|
||||
if (common->thread_wakeup_needed)
|
||||
break;
|
||||
schedule();
|
||||
}
|
||||
__set_current_state(TASK_RUNNING);
|
||||
common->thread_wakeup_needed = 0;
|
||||
|
||||
/*
|
||||
* Ensure the writing of thread_wakeup_needed
|
||||
* and the reading of bh->state are completed
|
||||
*/
|
||||
smp_mb();
|
||||
return rc;
|
||||
/* Wait until a signal arrives or bh is no longer busy */
|
||||
if (can_freeze)
|
||||
/*
|
||||
* synchronize with the smp_store_release(&bh->state) in
|
||||
* bulk_in_complete() or bulk_out_complete()
|
||||
*/
|
||||
rc = wait_event_freezable(common->io_wait,
|
||||
bh && smp_load_acquire(&bh->state) >=
|
||||
BUF_STATE_EMPTY);
|
||||
else
|
||||
rc = wait_event_interruptible(common->io_wait,
|
||||
bh && smp_load_acquire(&bh->state) >=
|
||||
BUF_STATE_EMPTY);
|
||||
return rc ? -EINTR : 0;
|
||||
}
|
||||
|
||||
|
||||
|
@ -697,11 +665,9 @@ static int do_read(struct fsg_common *common)
|
|||
|
||||
/* Wait for the next buffer to become available */
|
||||
bh = common->next_buffhd_to_fill;
|
||||
while (bh->state != BUF_STATE_EMPTY) {
|
||||
rc = sleep_thread(common, false);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
rc = sleep_thread(common, false, bh);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/*
|
||||
* If we were asked to read past the end of file,
|
||||
|
@ -878,84 +844,80 @@ static int do_write(struct fsg_common *common)
|
|||
bh = common->next_buffhd_to_drain;
|
||||
if (bh->state == BUF_STATE_EMPTY && !get_some_more)
|
||||
break; /* We stopped early */
|
||||
if (bh->state == BUF_STATE_FULL) {
|
||||
smp_rmb();
|
||||
common->next_buffhd_to_drain = bh->next;
|
||||
bh->state = BUF_STATE_EMPTY;
|
||||
|
||||
/* Did something go wrong with the transfer? */
|
||||
if (bh->outreq->status != 0) {
|
||||
curlun->sense_data = SS_COMMUNICATION_FAILURE;
|
||||
curlun->sense_data_info =
|
||||
file_offset >> curlun->blkbits;
|
||||
curlun->info_valid = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
amount = bh->outreq->actual;
|
||||
if (curlun->file_length - file_offset < amount) {
|
||||
LERROR(curlun,
|
||||
"write %u @ %llu beyond end %llu\n",
|
||||
amount, (unsigned long long)file_offset,
|
||||
(unsigned long long)curlun->file_length);
|
||||
amount = curlun->file_length - file_offset;
|
||||
}
|
||||
|
||||
/* Don't accept excess data. The spec doesn't say
|
||||
* what to do in this case. We'll ignore the error.
|
||||
*/
|
||||
amount = min(amount, bh->bulk_out_intended_length);
|
||||
|
||||
/* Don't write a partial block */
|
||||
amount = round_down(amount, curlun->blksize);
|
||||
if (amount == 0)
|
||||
goto empty_write;
|
||||
|
||||
/* Perform the write */
|
||||
file_offset_tmp = file_offset;
|
||||
nwritten = vfs_write(curlun->filp,
|
||||
(char __user *)bh->buf,
|
||||
amount, &file_offset_tmp);
|
||||
VLDBG(curlun, "file write %u @ %llu -> %d\n", amount,
|
||||
(unsigned long long)file_offset, (int)nwritten);
|
||||
if (signal_pending(current))
|
||||
return -EINTR; /* Interrupted! */
|
||||
|
||||
if (nwritten < 0) {
|
||||
LDBG(curlun, "error in file write: %d\n",
|
||||
(int)nwritten);
|
||||
nwritten = 0;
|
||||
} else if (nwritten < amount) {
|
||||
LDBG(curlun, "partial file write: %d/%u\n",
|
||||
(int)nwritten, amount);
|
||||
nwritten = round_down(nwritten, curlun->blksize);
|
||||
}
|
||||
file_offset += nwritten;
|
||||
amount_left_to_write -= nwritten;
|
||||
common->residue -= nwritten;
|
||||
|
||||
/* If an error occurred, report it and its position */
|
||||
if (nwritten < amount) {
|
||||
curlun->sense_data = SS_WRITE_ERROR;
|
||||
curlun->sense_data_info =
|
||||
file_offset >> curlun->blkbits;
|
||||
curlun->info_valid = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
empty_write:
|
||||
/* Did the host decide to stop early? */
|
||||
if (bh->outreq->actual < bh->bulk_out_intended_length) {
|
||||
common->short_packet_received = 1;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Wait for something to happen */
|
||||
rc = sleep_thread(common, false);
|
||||
/* Wait for the data to be received */
|
||||
rc = sleep_thread(common, false, bh);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
common->next_buffhd_to_drain = bh->next;
|
||||
bh->state = BUF_STATE_EMPTY;
|
||||
|
||||
/* Did something go wrong with the transfer? */
|
||||
if (bh->outreq->status != 0) {
|
||||
curlun->sense_data = SS_COMMUNICATION_FAILURE;
|
||||
curlun->sense_data_info =
|
||||
file_offset >> curlun->blkbits;
|
||||
curlun->info_valid = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
amount = bh->outreq->actual;
|
||||
if (curlun->file_length - file_offset < amount) {
|
||||
LERROR(curlun, "write %u @ %llu beyond end %llu\n",
|
||||
amount, (unsigned long long)file_offset,
|
||||
(unsigned long long)curlun->file_length);
|
||||
amount = curlun->file_length - file_offset;
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't accept excess data. The spec doesn't say
|
||||
* what to do in this case. We'll ignore the error.
|
||||
*/
|
||||
amount = min(amount, bh->bulk_out_intended_length);
|
||||
|
||||
/* Don't write a partial block */
|
||||
amount = round_down(amount, curlun->blksize);
|
||||
if (amount == 0)
|
||||
goto empty_write;
|
||||
|
||||
/* Perform the write */
|
||||
file_offset_tmp = file_offset;
|
||||
nwritten = vfs_write(curlun->filp, (char __user *)bh->buf,
|
||||
amount, &file_offset_tmp);
|
||||
VLDBG(curlun, "file write %u @ %llu -> %d\n", amount,
|
||||
(unsigned long long)file_offset, (int)nwritten);
|
||||
if (signal_pending(current))
|
||||
return -EINTR; /* Interrupted! */
|
||||
|
||||
if (nwritten < 0) {
|
||||
LDBG(curlun, "error in file write: %d\n",
|
||||
(int) nwritten);
|
||||
nwritten = 0;
|
||||
} else if (nwritten < amount) {
|
||||
LDBG(curlun, "partial file write: %d/%u\n",
|
||||
(int) nwritten, amount);
|
||||
nwritten = round_down(nwritten, curlun->blksize);
|
||||
}
|
||||
file_offset += nwritten;
|
||||
amount_left_to_write -= nwritten;
|
||||
common->residue -= nwritten;
|
||||
|
||||
/* If an error occurred, report it and its position */
|
||||
if (nwritten < amount) {
|
||||
curlun->sense_data = SS_WRITE_ERROR;
|
||||
curlun->sense_data_info =
|
||||
file_offset >> curlun->blkbits;
|
||||
curlun->info_valid = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
empty_write:
|
||||
/* Did the host decide to stop early? */
|
||||
if (bh->outreq->actual < bh->bulk_out_intended_length) {
|
||||
common->short_packet_received = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return -EIO; /* No default reply */
|
||||
|
@ -1480,7 +1442,7 @@ static int wedge_bulk_in_endpoint(struct fsg_dev *fsg)
|
|||
|
||||
static int throw_away_data(struct fsg_common *common)
|
||||
{
|
||||
struct fsg_buffhd *bh;
|
||||
struct fsg_buffhd *bh, *bh2;
|
||||
u32 amount;
|
||||
int rc;
|
||||
|
||||
|
@ -1488,26 +1450,10 @@ static int throw_away_data(struct fsg_common *common)
|
|||
bh->state != BUF_STATE_EMPTY || common->usb_amount_left > 0;
|
||||
bh = common->next_buffhd_to_drain) {
|
||||
|
||||
/* Throw away the data in a filled buffer */
|
||||
if (bh->state == BUF_STATE_FULL) {
|
||||
smp_rmb();
|
||||
bh->state = BUF_STATE_EMPTY;
|
||||
common->next_buffhd_to_drain = bh->next;
|
||||
|
||||
/* A short packet or an error ends everything */
|
||||
if (bh->outreq->actual < bh->bulk_out_intended_length ||
|
||||
bh->outreq->status != 0) {
|
||||
raise_exception(common,
|
||||
FSG_STATE_ABORT_BULK_OUT);
|
||||
return -EINTR;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Try to submit another request if we need one */
|
||||
bh = common->next_buffhd_to_fill;
|
||||
if (bh->state == BUF_STATE_EMPTY
|
||||
&& common->usb_amount_left > 0) {
|
||||
bh2 = common->next_buffhd_to_fill;
|
||||
if (bh2->state == BUF_STATE_EMPTY &&
|
||||
common->usb_amount_left > 0) {
|
||||
amount = min(common->usb_amount_left, FSG_BUFLEN);
|
||||
|
||||
/*
|
||||
|
@ -1515,19 +1461,30 @@ static int throw_away_data(struct fsg_common *common)
|
|||
* equal to the buffer size, which is divisible by
|
||||
* the bulk-out maxpacket size.
|
||||
*/
|
||||
set_bulk_out_req_length(common, bh, amount);
|
||||
if (!start_out_transfer(common, bh))
|
||||
set_bulk_out_req_length(common, bh2, amount);
|
||||
if (!start_out_transfer(common, bh2))
|
||||
/* Dunno what to do if common->fsg is NULL */
|
||||
return -EIO;
|
||||
common->next_buffhd_to_fill = bh->next;
|
||||
common->next_buffhd_to_fill = bh2->next;
|
||||
common->usb_amount_left -= amount;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Otherwise wait for something to happen */
|
||||
rc = sleep_thread(common, true);
|
||||
/* Wait for the data to be received */
|
||||
rc = sleep_thread(common, false, bh);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Throw away the data in a filled buffer */
|
||||
bh->state = BUF_STATE_EMPTY;
|
||||
common->next_buffhd_to_drain = bh->next;
|
||||
|
||||
/* A short packet or an error ends everything */
|
||||
if (bh->outreq->actual < bh->bulk_out_intended_length ||
|
||||
bh->outreq->status != 0) {
|
||||
raise_exception(common, FSG_STATE_ABORT_BULK_OUT);
|
||||
return -EINTR;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -1634,7 +1591,7 @@ static int finish_reply(struct fsg_common *common)
|
|||
return rc;
|
||||
}
|
||||
|
||||
static int send_status(struct fsg_common *common)
|
||||
static void send_status(struct fsg_common *common)
|
||||
{
|
||||
struct fsg_lun *curlun = common->curlun;
|
||||
struct fsg_buffhd *bh;
|
||||
|
@ -1645,11 +1602,9 @@ static int send_status(struct fsg_common *common)
|
|||
|
||||
/* Wait for the next buffer to become available */
|
||||
bh = common->next_buffhd_to_fill;
|
||||
while (bh->state != BUF_STATE_EMPTY) {
|
||||
rc = sleep_thread(common, true);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
rc = sleep_thread(common, false, bh);
|
||||
if (rc)
|
||||
return;
|
||||
|
||||
if (curlun) {
|
||||
sd = curlun->sense_data;
|
||||
|
@ -1683,10 +1638,10 @@ static int send_status(struct fsg_common *common)
|
|||
bh->inreq->zero = 0;
|
||||
if (!start_in_transfer(common, bh))
|
||||
/* Don't know what to do if common->fsg is NULL */
|
||||
return -EIO;
|
||||
return;
|
||||
|
||||
common->next_buffhd_to_fill = bh->next;
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1848,11 +1803,10 @@ static int do_scsi_command(struct fsg_common *common)
|
|||
/* Wait for the next buffer to become available for data or status */
|
||||
bh = common->next_buffhd_to_fill;
|
||||
common->next_buffhd_to_drain = bh;
|
||||
while (bh->state != BUF_STATE_EMPTY) {
|
||||
rc = sleep_thread(common, true);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
rc = sleep_thread(common, false, bh);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
common->phase_error = 0;
|
||||
common->short_packet_received = 0;
|
||||
|
||||
|
@ -2195,11 +2149,9 @@ static int get_next_command(struct fsg_common *common)
|
|||
|
||||
/* Wait for the next buffer to become available */
|
||||
bh = common->next_buffhd_to_fill;
|
||||
while (bh->state != BUF_STATE_EMPTY) {
|
||||
rc = sleep_thread(common, true);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
rc = sleep_thread(common, true, bh);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Queue a request to read a Bulk-only CBW */
|
||||
set_bulk_out_req_length(common, bh, US_BULK_CB_WRAP_LEN);
|
||||
|
@ -2214,12 +2166,10 @@ static int get_next_command(struct fsg_common *common)
|
|||
*/
|
||||
|
||||
/* Wait for the CBW to arrive */
|
||||
while (bh->state != BUF_STATE_FULL) {
|
||||
rc = sleep_thread(common, true);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
smp_rmb();
|
||||
rc = sleep_thread(common, true, bh);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = fsg_is_set(common) ? received_cbw(common->fsg, bh) : -EIO;
|
||||
bh->state = BUF_STATE_EMPTY;
|
||||
|
||||
|
@ -2371,9 +2321,11 @@ static void handle_exception(struct fsg_common *common)
|
|||
if (!sig)
|
||||
break;
|
||||
if (sig != SIGUSR1) {
|
||||
spin_lock_irq(&common->lock);
|
||||
if (common->state < FSG_STATE_EXIT)
|
||||
DBG(common, "Main thread exiting on signal\n");
|
||||
raise_exception(common, FSG_STATE_EXIT);
|
||||
common->state = FSG_STATE_EXIT;
|
||||
spin_unlock_irq(&common->lock);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2381,23 +2333,14 @@ static void handle_exception(struct fsg_common *common)
|
|||
if (likely(common->fsg)) {
|
||||
for (i = 0; i < common->fsg_num_buffers; ++i) {
|
||||
bh = &common->buffhds[i];
|
||||
if (bh->inreq_busy)
|
||||
if (bh->state == BUF_STATE_SENDING)
|
||||
usb_ep_dequeue(common->fsg->bulk_in, bh->inreq);
|
||||
if (bh->outreq_busy)
|
||||
if (bh->state == BUF_STATE_RECEIVING)
|
||||
usb_ep_dequeue(common->fsg->bulk_out,
|
||||
bh->outreq);
|
||||
}
|
||||
|
||||
/* Wait until everything is idle */
|
||||
for (;;) {
|
||||
int num_active = 0;
|
||||
for (i = 0; i < common->fsg_num_buffers; ++i) {
|
||||
bh = &common->buffhds[i];
|
||||
num_active += bh->inreq_busy + bh->outreq_busy;
|
||||
}
|
||||
if (num_active == 0)
|
||||
break;
|
||||
if (sleep_thread(common, true))
|
||||
/* Wait for a transfer to become idle */
|
||||
if (sleep_thread(common, false, bh))
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2422,10 +2365,9 @@ static void handle_exception(struct fsg_common *common)
|
|||
common->next_buffhd_to_drain = &common->buffhds[0];
|
||||
exception_req_tag = common->exception_req_tag;
|
||||
old_state = common->state;
|
||||
common->state = FSG_STATE_NORMAL;
|
||||
|
||||
if (old_state == FSG_STATE_ABORT_BULK_OUT)
|
||||
common->state = FSG_STATE_STATUS_PHASE;
|
||||
else {
|
||||
if (old_state != FSG_STATE_ABORT_BULK_OUT) {
|
||||
for (i = 0; i < ARRAY_SIZE(common->luns); ++i) {
|
||||
curlun = common->luns[i];
|
||||
if (!curlun)
|
||||
|
@ -2436,21 +2378,19 @@ static void handle_exception(struct fsg_common *common)
|
|||
curlun->sense_data_info = 0;
|
||||
curlun->info_valid = 0;
|
||||
}
|
||||
common->state = FSG_STATE_IDLE;
|
||||
}
|
||||
spin_unlock_irq(&common->lock);
|
||||
|
||||
/* Carry out any extra actions required for the exception */
|
||||
switch (old_state) {
|
||||
case FSG_STATE_ABORT_BULK_OUT:
|
||||
send_status(common);
|
||||
spin_lock_irq(&common->lock);
|
||||
if (common->state == FSG_STATE_STATUS_PHASE)
|
||||
common->state = FSG_STATE_IDLE;
|
||||
spin_unlock_irq(&common->lock);
|
||||
case FSG_STATE_NORMAL:
|
||||
break;
|
||||
|
||||
case FSG_STATE_RESET:
|
||||
case FSG_STATE_ABORT_BULK_OUT:
|
||||
send_status(common);
|
||||
break;
|
||||
|
||||
case FSG_STATE_PROTOCOL_RESET:
|
||||
/*
|
||||
* In case we were forced against our will to halt a
|
||||
* bulk endpoint, clear the halt now. (The SuperH UDC
|
||||
|
@ -2483,19 +2423,13 @@ static void handle_exception(struct fsg_common *common)
|
|||
break;
|
||||
|
||||
case FSG_STATE_EXIT:
|
||||
case FSG_STATE_TERMINATED:
|
||||
do_set_interface(common, NULL); /* Free resources */
|
||||
spin_lock_irq(&common->lock);
|
||||
common->state = FSG_STATE_TERMINATED; /* Stop the thread */
|
||||
spin_unlock_irq(&common->lock);
|
||||
break;
|
||||
|
||||
case FSG_STATE_INTERFACE_CHANGE:
|
||||
case FSG_STATE_DISCONNECT:
|
||||
case FSG_STATE_COMMAND_PHASE:
|
||||
case FSG_STATE_DATA_PHASE:
|
||||
case FSG_STATE_STATUS_PHASE:
|
||||
case FSG_STATE_IDLE:
|
||||
case FSG_STATE_TERMINATED:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -2534,33 +2468,17 @@ static int fsg_main_thread(void *common_)
|
|||
}
|
||||
|
||||
if (!common->running) {
|
||||
sleep_thread(common, true);
|
||||
sleep_thread(common, true, NULL);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (get_next_command(common))
|
||||
if (get_next_command(common) || exception_in_progress(common))
|
||||
continue;
|
||||
|
||||
spin_lock_irq(&common->lock);
|
||||
if (!exception_in_progress(common))
|
||||
common->state = FSG_STATE_DATA_PHASE;
|
||||
spin_unlock_irq(&common->lock);
|
||||
|
||||
if (do_scsi_command(common) || finish_reply(common))
|
||||
if (do_scsi_command(common) || exception_in_progress(common))
|
||||
continue;
|
||||
|
||||
spin_lock_irq(&common->lock);
|
||||
if (!exception_in_progress(common))
|
||||
common->state = FSG_STATE_STATUS_PHASE;
|
||||
spin_unlock_irq(&common->lock);
|
||||
|
||||
if (send_status(common))
|
||||
if (finish_reply(common) || exception_in_progress(common))
|
||||
continue;
|
||||
|
||||
spin_lock_irq(&common->lock);
|
||||
if (!exception_in_progress(common))
|
||||
common->state = FSG_STATE_IDLE;
|
||||
spin_unlock_irq(&common->lock);
|
||||
send_status(common);
|
||||
}
|
||||
|
||||
spin_lock_irq(&common->lock);
|
||||
|
@ -2680,6 +2598,7 @@ static struct fsg_common *fsg_common_setup(struct fsg_common *common)
|
|||
spin_lock_init(&common->lock);
|
||||
kref_init(&common->ref);
|
||||
init_completion(&common->thread_notifier);
|
||||
init_waitqueue_head(&common->io_wait);
|
||||
init_waitqueue_head(&common->fsg_wait);
|
||||
common->state = FSG_STATE_TERMINATED;
|
||||
memset(common->luns, 0, sizeof(common->luns));
|
||||
|
@ -2981,7 +2900,6 @@ static void fsg_common_release(struct kref *ref)
|
|||
if (common->state != FSG_STATE_TERMINATED) {
|
||||
raise_exception(common, FSG_STATE_EXIT);
|
||||
wait_for_completion(&common->thread_notifier);
|
||||
common->thread_task = NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(common->luns); ++i) {
|
||||
|
@ -3030,11 +2948,11 @@ static int fsg_bind(struct usb_configuration *c, struct usb_function *f)
|
|||
}
|
||||
|
||||
if (!common->thread_task) {
|
||||
common->state = FSG_STATE_IDLE;
|
||||
common->state = FSG_STATE_NORMAL;
|
||||
common->thread_task =
|
||||
kthread_create(fsg_main_thread, common, "file-storage");
|
||||
if (IS_ERR(common->thread_task)) {
|
||||
int ret = PTR_ERR(common->thread_task);
|
||||
ret = PTR_ERR(common->thread_task);
|
||||
common->thread_task = NULL;
|
||||
common->state = FSG_STATE_TERMINATED;
|
||||
return ret;
|
||||
|
|
File diff suppressed because it is too large
Load diff
1021
drivers/usb/gadget/function/f_uac1_legacy.c
Normal file
1021
drivers/usb/gadget/function/f_uac1_legacy.c
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -133,9 +133,10 @@ static inline bool fsg_lun_is_open(struct fsg_lun *curlun)
|
|||
#define FSG_MAX_LUNS 16
|
||||
|
||||
enum fsg_buffer_state {
|
||||
BUF_STATE_SENDING = -2,
|
||||
BUF_STATE_RECEIVING,
|
||||
BUF_STATE_EMPTY = 0,
|
||||
BUF_STATE_FULL,
|
||||
BUF_STATE_BUSY
|
||||
BUF_STATE_FULL
|
||||
};
|
||||
|
||||
struct fsg_buffhd {
|
||||
|
@ -151,23 +152,14 @@ struct fsg_buffhd {
|
|||
unsigned int bulk_out_intended_length;
|
||||
|
||||
struct usb_request *inreq;
|
||||
int inreq_busy;
|
||||
struct usb_request *outreq;
|
||||
int outreq_busy;
|
||||
};
|
||||
|
||||
enum fsg_state {
|
||||
/* This one isn't used anywhere */
|
||||
FSG_STATE_COMMAND_PHASE = -10,
|
||||
FSG_STATE_DATA_PHASE,
|
||||
FSG_STATE_STATUS_PHASE,
|
||||
|
||||
FSG_STATE_IDLE = 0,
|
||||
FSG_STATE_NORMAL,
|
||||
FSG_STATE_ABORT_BULK_OUT,
|
||||
FSG_STATE_RESET,
|
||||
FSG_STATE_INTERFACE_CHANGE,
|
||||
FSG_STATE_PROTOCOL_RESET,
|
||||
FSG_STATE_CONFIG_CHANGE,
|
||||
FSG_STATE_DISCONNECT,
|
||||
FSG_STATE_EXIT,
|
||||
FSG_STATE_TERMINATED
|
||||
};
|
||||
|
|
662
drivers/usb/gadget/function/u_audio.c
Normal file
662
drivers/usb/gadget/function/u_audio.c
Normal file
|
@ -0,0 +1,662 @@
|
|||
/*
|
||||
* u_audio.c -- interface to USB gadget "ALSA sound card" utilities
|
||||
*
|
||||
* Copyright (C) 2016
|
||||
* Author: Ruslan Bilovol <ruslan.bilovol@gmail.com>
|
||||
*
|
||||
* Sound card implementation was cut-and-pasted with changes
|
||||
* from f_uac2.c and has:
|
||||
* Copyright (C) 2011
|
||||
* Yadwinder Singh (yadi.brar01@gmail.com)
|
||||
* Jaswinder Singh (jaswinder.singh@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.
|
||||
*
|
||||
* 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 <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#include "u_audio.h"
|
||||
|
||||
#define BUFF_SIZE_MAX (PAGE_SIZE * 16)
|
||||
#define PRD_SIZE_MAX PAGE_SIZE
|
||||
#define MIN_PERIODS 4
|
||||
|
||||
struct uac_req {
|
||||
struct uac_rtd_params *pp; /* parent param */
|
||||
struct usb_request *req;
|
||||
};
|
||||
|
||||
/* Runtime data params for one stream */
|
||||
struct uac_rtd_params {
|
||||
struct snd_uac_chip *uac; /* parent chip */
|
||||
bool ep_enabled; /* if the ep is enabled */
|
||||
/* Size of the ring buffer */
|
||||
size_t dma_bytes;
|
||||
unsigned char *dma_area;
|
||||
|
||||
struct snd_pcm_substream *ss;
|
||||
|
||||
/* Ring buffer */
|
||||
ssize_t hw_ptr;
|
||||
|
||||
void *rbuf;
|
||||
|
||||
size_t period_size;
|
||||
|
||||
unsigned max_psize; /* MaxPacketSize of endpoint */
|
||||
struct uac_req *ureq;
|
||||
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
struct snd_uac_chip {
|
||||
struct g_audio *audio_dev;
|
||||
|
||||
struct uac_rtd_params p_prm;
|
||||
struct uac_rtd_params c_prm;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
static struct snd_pcm_hardware uac_pcm_hardware = {
|
||||
.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER
|
||||
| SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID
|
||||
| SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
|
||||
.rates = SNDRV_PCM_RATE_CONTINUOUS,
|
||||
.periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX,
|
||||
.buffer_bytes_max = BUFF_SIZE_MAX,
|
||||
.period_bytes_max = PRD_SIZE_MAX,
|
||||
.periods_min = MIN_PERIODS,
|
||||
};
|
||||
|
||||
static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
unsigned pending;
|
||||
unsigned long flags;
|
||||
unsigned int hw_ptr;
|
||||
bool update_alsa = false;
|
||||
int status = req->status;
|
||||
struct uac_req *ur = req->context;
|
||||
struct snd_pcm_substream *substream;
|
||||
struct uac_rtd_params *prm = ur->pp;
|
||||
struct snd_uac_chip *uac = prm->uac;
|
||||
|
||||
/* i/f shutting down */
|
||||
if (!prm->ep_enabled || req->status == -ESHUTDOWN)
|
||||
return;
|
||||
|
||||
/*
|
||||
* We can't really do much about bad xfers.
|
||||
* Afterall, the ISOCH xfers could fail legitimately.
|
||||
*/
|
||||
if (status)
|
||||
pr_debug("%s: iso_complete status(%d) %d/%d\n",
|
||||
__func__, status, req->actual, req->length);
|
||||
|
||||
substream = prm->ss;
|
||||
|
||||
/* Do nothing if ALSA isn't active */
|
||||
if (!substream)
|
||||
goto exit;
|
||||
|
||||
spin_lock_irqsave(&prm->lock, flags);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
/*
|
||||
* 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 = uac->p_pktsize;
|
||||
uac->p_residue += uac->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 (uac->p_residue / uac->p_interval >= uac->p_framesize) {
|
||||
req->length += uac->p_framesize;
|
||||
uac->p_residue -= uac->p_framesize *
|
||||
uac->p_interval;
|
||||
}
|
||||
|
||||
req->actual = req->length;
|
||||
}
|
||||
|
||||
pending = prm->hw_ptr % prm->period_size;
|
||||
pending += req->actual;
|
||||
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 */
|
||||
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(uac->card->dev, "%d Error!\n", __LINE__);
|
||||
|
||||
if (update_alsa)
|
||||
snd_pcm_period_elapsed(substream);
|
||||
}
|
||||
|
||||
static int uac_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_uac_chip *uac = snd_pcm_substream_chip(substream);
|
||||
struct uac_rtd_params *prm;
|
||||
struct g_audio *audio_dev;
|
||||
struct uac_params *params;
|
||||
unsigned long flags;
|
||||
int err = 0;
|
||||
|
||||
audio_dev = uac->audio_dev;
|
||||
params = &audio_dev->params;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
prm = &uac->p_prm;
|
||||
else
|
||||
prm = &uac->c_prm;
|
||||
|
||||
spin_lock_irqsave(&prm->lock, flags);
|
||||
|
||||
/* Reset */
|
||||
prm->hw_ptr = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
prm->ss = substream;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
prm->ss = NULL;
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&prm->lock, flags);
|
||||
|
||||
/* Clear buffer after Play stops */
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss)
|
||||
memset(prm->rbuf, 0, prm->max_psize * params->req_number);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t uac_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_uac_chip *uac = snd_pcm_substream_chip(substream);
|
||||
struct uac_rtd_params *prm;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
prm = &uac->p_prm;
|
||||
else
|
||||
prm = &uac->c_prm;
|
||||
|
||||
return bytes_to_frames(substream->runtime, prm->hw_ptr);
|
||||
}
|
||||
|
||||
static int uac_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_uac_chip *uac = snd_pcm_substream_chip(substream);
|
||||
struct uac_rtd_params *prm;
|
||||
int err;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
prm = &uac->p_prm;
|
||||
else
|
||||
prm = &uac->c_prm;
|
||||
|
||||
err = snd_pcm_lib_malloc_pages(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
if (err >= 0) {
|
||||
prm->dma_bytes = substream->runtime->dma_bytes;
|
||||
prm->dma_area = substream->runtime->dma_area;
|
||||
prm->period_size = params_period_bytes(hw_params);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int uac_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_uac_chip *uac = snd_pcm_substream_chip(substream);
|
||||
struct uac_rtd_params *prm;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
prm = &uac->p_prm;
|
||||
else
|
||||
prm = &uac->c_prm;
|
||||
|
||||
prm->dma_area = NULL;
|
||||
prm->dma_bytes = 0;
|
||||
prm->period_size = 0;
|
||||
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
}
|
||||
|
||||
static int uac_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_uac_chip *uac = snd_pcm_substream_chip(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct g_audio *audio_dev;
|
||||
struct uac_params *params;
|
||||
int p_ssize, c_ssize;
|
||||
int p_srate, c_srate;
|
||||
int p_chmask, c_chmask;
|
||||
|
||||
audio_dev = uac->audio_dev;
|
||||
params = &audio_dev->params;
|
||||
p_ssize = params->p_ssize;
|
||||
c_ssize = params->c_ssize;
|
||||
p_srate = params->p_srate;
|
||||
c_srate = params->c_srate;
|
||||
p_chmask = params->p_chmask;
|
||||
c_chmask = params->c_chmask;
|
||||
uac->p_residue = 0;
|
||||
|
||||
runtime->hw = uac_pcm_hardware;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
spin_lock_init(&uac->p_prm.lock);
|
||||
runtime->hw.rate_min = p_srate;
|
||||
switch (p_ssize) {
|
||||
case 3:
|
||||
runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE;
|
||||
break;
|
||||
case 4:
|
||||
runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
|
||||
break;
|
||||
default:
|
||||
runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
|
||||
break;
|
||||
}
|
||||
runtime->hw.channels_min = num_channels(p_chmask);
|
||||
runtime->hw.period_bytes_min = 2 * uac->p_prm.max_psize
|
||||
/ runtime->hw.periods_min;
|
||||
} else {
|
||||
spin_lock_init(&uac->c_prm.lock);
|
||||
runtime->hw.rate_min = c_srate;
|
||||
switch (c_ssize) {
|
||||
case 3:
|
||||
runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE;
|
||||
break;
|
||||
case 4:
|
||||
runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
|
||||
break;
|
||||
default:
|
||||
runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
|
||||
break;
|
||||
}
|
||||
runtime->hw.channels_min = num_channels(c_chmask);
|
||||
runtime->hw.period_bytes_min = 2 * uac->c_prm.max_psize
|
||||
/ runtime->hw.periods_min;
|
||||
}
|
||||
|
||||
runtime->hw.rate_max = runtime->hw.rate_min;
|
||||
runtime->hw.channels_max = runtime->hw.channels_min;
|
||||
|
||||
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ALSA cries without these function pointers */
|
||||
static int uac_pcm_null(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops uac_pcm_ops = {
|
||||
.open = uac_pcm_open,
|
||||
.close = uac_pcm_null,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = uac_pcm_hw_params,
|
||||
.hw_free = uac_pcm_hw_free,
|
||||
.trigger = uac_pcm_trigger,
|
||||
.pointer = uac_pcm_pointer,
|
||||
.prepare = uac_pcm_null,
|
||||
};
|
||||
|
||||
static inline void free_ep(struct uac_rtd_params *prm, struct usb_ep *ep)
|
||||
{
|
||||
struct snd_uac_chip *uac = prm->uac;
|
||||
struct g_audio *audio_dev;
|
||||
struct uac_params *params;
|
||||
int i;
|
||||
|
||||
if (!prm->ep_enabled)
|
||||
return;
|
||||
|
||||
prm->ep_enabled = false;
|
||||
|
||||
audio_dev = uac->audio_dev;
|
||||
params = &audio_dev->params;
|
||||
|
||||
for (i = 0; i < params->req_number; i++) {
|
||||
if (prm->ureq[i].req) {
|
||||
usb_ep_dequeue(ep, prm->ureq[i].req);
|
||||
usb_ep_free_request(ep, prm->ureq[i].req);
|
||||
prm->ureq[i].req = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (usb_ep_disable(ep))
|
||||
dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__);
|
||||
}
|
||||
|
||||
|
||||
int u_audio_start_capture(struct g_audio *audio_dev)
|
||||
{
|
||||
struct snd_uac_chip *uac = audio_dev->uac;
|
||||
struct usb_gadget *gadget = audio_dev->gadget;
|
||||
struct device *dev = &gadget->dev;
|
||||
struct usb_request *req;
|
||||
struct usb_ep *ep;
|
||||
struct uac_rtd_params *prm;
|
||||
struct uac_params *params = &audio_dev->params;
|
||||
int req_len, i;
|
||||
|
||||
ep = audio_dev->out_ep;
|
||||
prm = &uac->c_prm;
|
||||
config_ep_by_speed(gadget, &audio_dev->func, ep);
|
||||
req_len = prm->max_psize;
|
||||
|
||||
prm->ep_enabled = true;
|
||||
usb_ep_enable(ep);
|
||||
|
||||
for (i = 0; i < params->req_number; i++) {
|
||||
if (!prm->ureq[i].req) {
|
||||
req = usb_ep_alloc_request(ep, GFP_ATOMIC);
|
||||
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 = u_audio_iso_complete;
|
||||
req->buf = prm->rbuf + i * prm->max_psize;
|
||||
}
|
||||
|
||||
if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
|
||||
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(u_audio_start_capture);
|
||||
|
||||
void u_audio_stop_capture(struct g_audio *audio_dev)
|
||||
{
|
||||
struct snd_uac_chip *uac = audio_dev->uac;
|
||||
|
||||
free_ep(&uac->c_prm, audio_dev->out_ep);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(u_audio_stop_capture);
|
||||
|
||||
int u_audio_start_playback(struct g_audio *audio_dev)
|
||||
{
|
||||
struct snd_uac_chip *uac = audio_dev->uac;
|
||||
struct usb_gadget *gadget = audio_dev->gadget;
|
||||
struct device *dev = &gadget->dev;
|
||||
struct usb_request *req;
|
||||
struct usb_ep *ep;
|
||||
struct uac_rtd_params *prm;
|
||||
struct uac_params *params = &audio_dev->params;
|
||||
unsigned int factor, rate;
|
||||
const struct usb_endpoint_descriptor *ep_desc;
|
||||
int req_len, i;
|
||||
|
||||
ep = audio_dev->in_ep;
|
||||
prm = &uac->p_prm;
|
||||
config_ep_by_speed(gadget, &audio_dev->func, ep);
|
||||
|
||||
ep_desc = ep->desc;
|
||||
|
||||
/* pre-calculate the playback endpoint's interval */
|
||||
if (gadget->speed == USB_SPEED_FULL)
|
||||
factor = 1000;
|
||||
else
|
||||
factor = 8000;
|
||||
|
||||
/* pre-compute some values for iso_complete() */
|
||||
uac->p_framesize = params->p_ssize *
|
||||
num_channels(params->p_chmask);
|
||||
rate = params->p_srate * uac->p_framesize;
|
||||
uac->p_interval = factor / (1 << (ep_desc->bInterval - 1));
|
||||
uac->p_pktsize = min_t(unsigned int, rate / uac->p_interval,
|
||||
prm->max_psize);
|
||||
|
||||
if (uac->p_pktsize < prm->max_psize)
|
||||
uac->p_pktsize_residue = rate % uac->p_interval;
|
||||
else
|
||||
uac->p_pktsize_residue = 0;
|
||||
|
||||
req_len = uac->p_pktsize;
|
||||
uac->p_residue = 0;
|
||||
|
||||
prm->ep_enabled = true;
|
||||
usb_ep_enable(ep);
|
||||
|
||||
for (i = 0; i < params->req_number; i++) {
|
||||
if (!prm->ureq[i].req) {
|
||||
req = usb_ep_alloc_request(ep, GFP_ATOMIC);
|
||||
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 = u_audio_iso_complete;
|
||||
req->buf = prm->rbuf + i * prm->max_psize;
|
||||
}
|
||||
|
||||
if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
|
||||
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(u_audio_start_playback);
|
||||
|
||||
void u_audio_stop_playback(struct g_audio *audio_dev)
|
||||
{
|
||||
struct snd_uac_chip *uac = audio_dev->uac;
|
||||
|
||||
free_ep(&uac->p_prm, audio_dev->in_ep);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(u_audio_stop_playback);
|
||||
|
||||
int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
|
||||
const char *card_name)
|
||||
{
|
||||
struct snd_uac_chip *uac;
|
||||
struct snd_card *card;
|
||||
struct snd_pcm *pcm;
|
||||
struct uac_params *params;
|
||||
int p_chmask, c_chmask;
|
||||
int err;
|
||||
|
||||
if (!g_audio)
|
||||
return -EINVAL;
|
||||
|
||||
uac = kzalloc(sizeof(*uac), GFP_KERNEL);
|
||||
if (!uac)
|
||||
return -ENOMEM;
|
||||
g_audio->uac = uac;
|
||||
uac->audio_dev = g_audio;
|
||||
|
||||
params = &g_audio->params;
|
||||
p_chmask = params->p_chmask;
|
||||
c_chmask = params->c_chmask;
|
||||
|
||||
if (c_chmask) {
|
||||
struct uac_rtd_params *prm = &uac->c_prm;
|
||||
|
||||
uac->c_prm.uac = uac;
|
||||
prm->max_psize = g_audio->out_ep_maxpsize;
|
||||
|
||||
prm->ureq = kcalloc(params->req_number, sizeof(struct uac_req),
|
||||
GFP_KERNEL);
|
||||
if (!prm->ureq) {
|
||||
err = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
prm->rbuf = kcalloc(params->req_number, prm->max_psize,
|
||||
GFP_KERNEL);
|
||||
if (!prm->rbuf) {
|
||||
prm->max_psize = 0;
|
||||
err = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_chmask) {
|
||||
struct uac_rtd_params *prm = &uac->p_prm;
|
||||
|
||||
uac->p_prm.uac = uac;
|
||||
prm->max_psize = g_audio->in_ep_maxpsize;
|
||||
|
||||
prm->ureq = kcalloc(params->req_number, sizeof(struct uac_req),
|
||||
GFP_KERNEL);
|
||||
if (!prm->ureq) {
|
||||
err = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
prm->rbuf = kcalloc(params->req_number, prm->max_psize,
|
||||
GFP_KERNEL);
|
||||
if (!prm->rbuf) {
|
||||
prm->max_psize = 0;
|
||||
err = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* Choose any slot, with no id */
|
||||
err = snd_card_new(&g_audio->gadget->dev,
|
||||
-1, NULL, THIS_MODULE, 0, &card);
|
||||
if (err < 0)
|
||||
goto fail;
|
||||
|
||||
uac->card = card;
|
||||
|
||||
/*
|
||||
* Create first PCM device
|
||||
* Create a substream only for non-zero channel streams
|
||||
*/
|
||||
err = snd_pcm_new(uac->card, pcm_name, 0,
|
||||
p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm);
|
||||
if (err < 0)
|
||||
goto snd_fail;
|
||||
|
||||
strcpy(pcm->name, pcm_name);
|
||||
pcm->private_data = uac;
|
||||
uac->pcm = pcm;
|
||||
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac_pcm_ops);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac_pcm_ops);
|
||||
|
||||
strcpy(card->driver, card_name);
|
||||
strcpy(card->shortname, card_name);
|
||||
sprintf(card->longname, "%s %i", card_name, card->dev->id);
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
|
||||
snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX);
|
||||
|
||||
err = snd_card_register(card);
|
||||
|
||||
if (!err)
|
||||
return 0;
|
||||
|
||||
snd_fail:
|
||||
snd_card_free(card);
|
||||
fail:
|
||||
kfree(uac->p_prm.ureq);
|
||||
kfree(uac->c_prm.ureq);
|
||||
kfree(uac->p_prm.rbuf);
|
||||
kfree(uac->c_prm.rbuf);
|
||||
kfree(uac);
|
||||
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(g_audio_setup);
|
||||
|
||||
void g_audio_cleanup(struct g_audio *g_audio)
|
||||
{
|
||||
struct snd_uac_chip *uac;
|
||||
struct snd_card *card;
|
||||
|
||||
if (!g_audio || !g_audio->uac)
|
||||
return;
|
||||
|
||||
uac = g_audio->uac;
|
||||
card = uac->card;
|
||||
if (card)
|
||||
snd_card_free(card);
|
||||
|
||||
kfree(uac->p_prm.ureq);
|
||||
kfree(uac->c_prm.ureq);
|
||||
kfree(uac->p_prm.rbuf);
|
||||
kfree(uac->c_prm.rbuf);
|
||||
kfree(uac);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(g_audio_cleanup);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("USB gadget \"ALSA sound card\" utilities");
|
||||
MODULE_AUTHOR("Ruslan Bilovol");
|
95
drivers/usb/gadget/function/u_audio.h
Normal file
95
drivers/usb/gadget/function/u_audio.h
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* u_audio.h -- interface to USB gadget "ALSA sound card" utilities
|
||||
*
|
||||
* Copyright (C) 2016
|
||||
* Author: Ruslan Bilovol <ruslan.bilovol@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __U_AUDIO_H
|
||||
#define __U_AUDIO_H
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
|
||||
struct uac_params {
|
||||
/* playback */
|
||||
int p_chmask; /* channel mask */
|
||||
int p_srate; /* rate in Hz */
|
||||
int p_ssize; /* sample size */
|
||||
|
||||
/* capture */
|
||||
int c_chmask; /* channel mask */
|
||||
int c_srate; /* rate in Hz */
|
||||
int c_ssize; /* sample size */
|
||||
|
||||
int req_number; /* number of preallocated requests */
|
||||
};
|
||||
|
||||
struct g_audio {
|
||||
struct usb_function func;
|
||||
struct usb_gadget *gadget;
|
||||
|
||||
struct usb_ep *in_ep;
|
||||
struct usb_ep *out_ep;
|
||||
|
||||
/* Max packet size for all in_ep possible speeds */
|
||||
unsigned int in_ep_maxpsize;
|
||||
/* Max packet size for all out_ep possible speeds */
|
||||
unsigned int out_ep_maxpsize;
|
||||
|
||||
/* The ALSA Sound Card it represents on the USB-Client side */
|
||||
struct snd_uac_chip *uac;
|
||||
|
||||
struct uac_params params;
|
||||
};
|
||||
|
||||
static inline struct g_audio *func_to_g_audio(struct usb_function *f)
|
||||
{
|
||||
return container_of(f, struct g_audio, func);
|
||||
}
|
||||
|
||||
static inline uint num_channels(uint chanmask)
|
||||
{
|
||||
uint num = 0;
|
||||
|
||||
while (chanmask) {
|
||||
num += (chanmask & 1);
|
||||
chanmask >>= 1;
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
/*
|
||||
* g_audio_setup - initialize one virtual ALSA sound card
|
||||
* @g_audio: struct with filled params, in_ep_maxpsize, out_ep_maxpsize
|
||||
* @pcm_name: the id string for a PCM instance of this sound card
|
||||
* @card_name: name of this soundcard
|
||||
*
|
||||
* This sets up the single virtual ALSA sound card that may be exported by a
|
||||
* gadget driver using this framework.
|
||||
*
|
||||
* Context: may sleep
|
||||
*
|
||||
* Returns zero on success, or a negative error on failure.
|
||||
*/
|
||||
int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
|
||||
const char *card_name);
|
||||
void g_audio_cleanup(struct g_audio *g_audio);
|
||||
|
||||
int u_audio_start_capture(struct g_audio *g_audio);
|
||||
void u_audio_stop_capture(struct g_audio *g_audio);
|
||||
int u_audio_start_playback(struct g_audio *g_audio);
|
||||
void u_audio_stop_playback(struct g_audio *g_audio);
|
||||
|
||||
#endif /* __U_AUDIO_H */
|
|
@ -216,6 +216,9 @@ struct ffs_data {
|
|||
#define FFS_FL_CALL_CLOSED_CALLBACK 0
|
||||
#define FFS_FL_BOUND 1
|
||||
|
||||
/* For waking up blocked threads when function is enabled. */
|
||||
wait_queue_head_t wait;
|
||||
|
||||
/* Active function */
|
||||
struct ffs_function *func;
|
||||
|
||||
|
|
|
@ -1,82 +1,41 @@
|
|||
/*
|
||||
* u_uac1.h -- interface to USB gadget "ALSA AUDIO" utilities
|
||||
* u_uac1.h - Utility definitions for UAC1 function
|
||||
*
|
||||
* Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
|
||||
* Copyright (C) 2008 Analog Devices, Inc
|
||||
* Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@gmail.com>
|
||||
*
|
||||
* Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
* 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_AUDIO_H
|
||||
#define __U_AUDIO_H
|
||||
#ifndef __U_UAC1_H
|
||||
#define __U_UAC1_H
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/usb/audio.h>
|
||||
#include <linux/usb/composite.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.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
|
||||
#define UAC1_DEF_CCHMASK 0x3
|
||||
#define UAC1_DEF_CSRATE 48000
|
||||
#define UAC1_DEF_CSSIZE 2
|
||||
#define UAC1_DEF_PCHMASK 0x3
|
||||
#define UAC1_DEF_PSRATE 48000
|
||||
#define UAC1_DEF_PSSIZE 2
|
||||
#define UAC1_DEF_REQ_NUM 2
|
||||
|
||||
/*
|
||||
* This represents the USB side of an audio card device, managed by a USB
|
||||
* function which provides control and stream interfaces.
|
||||
*/
|
||||
|
||||
struct gaudio_snd_dev {
|
||||
struct gaudio *card;
|
||||
struct file *filp;
|
||||
struct snd_pcm_substream *substream;
|
||||
int access;
|
||||
int format;
|
||||
int channels;
|
||||
int rate;
|
||||
};
|
||||
|
||||
struct gaudio {
|
||||
struct usb_function func;
|
||||
struct usb_gadget *gadget;
|
||||
|
||||
/* ALSA sound device interfaces */
|
||||
struct gaudio_snd_dev control;
|
||||
struct gaudio_snd_dev playback;
|
||||
struct gaudio_snd_dev capture;
|
||||
|
||||
/* 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;
|
||||
int c_chmask;
|
||||
int c_srate;
|
||||
int c_ssize;
|
||||
int p_chmask;
|
||||
int p_srate;
|
||||
int p_ssize;
|
||||
int req_number;
|
||||
unsigned bound:1;
|
||||
unsigned fn_play_alloc:1;
|
||||
unsigned fn_cap_alloc:1;
|
||||
unsigned fn_cntl_alloc:1;
|
||||
|
||||
struct mutex lock;
|
||||
int refcnt;
|
||||
};
|
||||
|
||||
int gaudio_setup(struct gaudio *card);
|
||||
void gaudio_cleanup(struct gaudio *the_card);
|
||||
|
||||
size_t u_audio_playback(struct gaudio *card, void *buf, size_t count);
|
||||
int u_audio_get_playback_channels(struct gaudio *card);
|
||||
int u_audio_get_playback_rate(struct gaudio *card);
|
||||
|
||||
#endif /* __U_AUDIO_H */
|
||||
#endif /* __U_UAC1_H */
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include <linux/random.h>
|
||||
#include <linux/syscalls.h>
|
||||
|
||||
#include "u_uac1.h"
|
||||
#include "u_uac1_legacy.h"
|
||||
|
||||
/*
|
||||
* This component encapsulates the ALSA devices for USB audio gadget
|
||||
|
@ -205,10 +205,11 @@ 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;
|
||||
struct f_uac1_legacy_opts *opts;
|
||||
char *fn_play, *fn_cap, *fn_cntl;
|
||||
|
||||
opts = container_of(card->func.fi, struct f_uac1_opts, func_inst);
|
||||
opts = container_of(card->func.fi, struct f_uac1_legacy_opts,
|
||||
func_inst);
|
||||
fn_play = opts->fn_play;
|
||||
fn_cap = opts->fn_cap;
|
||||
fn_cntl = opts->fn_cntl;
|
82
drivers/usb/gadget/function/u_uac1_legacy.h
Normal file
82
drivers/usb/gadget/function/u_uac1_legacy.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* u_uac1.h -- interface to USB gadget "ALSA AUDIO" utilities
|
||||
*
|
||||
* Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
|
||||
* Copyright (C) 2008 Analog Devices, Inc
|
||||
*
|
||||
* Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#ifndef __U_UAC1_LEGACY_H
|
||||
#define __U_UAC1_LEGACY_H
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/usb/audio.h>
|
||||
#include <linux/usb/composite.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.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.
|
||||
*/
|
||||
|
||||
struct gaudio_snd_dev {
|
||||
struct gaudio *card;
|
||||
struct file *filp;
|
||||
struct snd_pcm_substream *substream;
|
||||
int access;
|
||||
int format;
|
||||
int channels;
|
||||
int rate;
|
||||
};
|
||||
|
||||
struct gaudio {
|
||||
struct usb_function func;
|
||||
struct usb_gadget *gadget;
|
||||
|
||||
/* ALSA sound device interfaces */
|
||||
struct gaudio_snd_dev control;
|
||||
struct gaudio_snd_dev playback;
|
||||
struct gaudio_snd_dev capture;
|
||||
|
||||
/* TODO */
|
||||
};
|
||||
|
||||
struct f_uac1_legacy_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 mutex lock;
|
||||
int refcnt;
|
||||
};
|
||||
|
||||
int gaudio_setup(struct gaudio *card);
|
||||
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_UAC1_LEGACY_H */
|
|
@ -54,8 +54,10 @@ config USB_AUDIO
|
|||
depends on SND
|
||||
select USB_LIBCOMPOSITE
|
||||
select SND_PCM
|
||||
select USB_F_UAC1 if GADGET_UAC1
|
||||
select USB_F_UAC1 if (GADGET_UAC1 && !GADGET_UAC1_LEGACY)
|
||||
select USB_F_UAC1_LEGACY if (GADGET_UAC1 && GADGET_UAC1_LEGACY)
|
||||
select USB_F_UAC2 if !GADGET_UAC1
|
||||
select USB_U_AUDIO if (USB_F_UAC2 || USB_F_UAC1)
|
||||
help
|
||||
This Gadget Audio driver is compatible with USB Audio Class
|
||||
specification 2.0. It implements 1 AudioControl interface,
|
||||
|
@ -73,10 +75,17 @@ config USB_AUDIO
|
|||
dynamically linked module called "g_audio".
|
||||
|
||||
config GADGET_UAC1
|
||||
bool "UAC 1.0 (Legacy)"
|
||||
bool "UAC 1.0"
|
||||
depends on USB_AUDIO
|
||||
help
|
||||
If you instead want older UAC Spec-1.0 driver that also has audio
|
||||
If you instead want older USB Audio Class specification 1.0 support
|
||||
with similar driver capabilities.
|
||||
|
||||
config GADGET_UAC1_LEGACY
|
||||
bool "UAC 1.0 (Legacy)"
|
||||
depends on GADGET_UAC1
|
||||
help
|
||||
If you instead want legacy UAC Spec-1.0 driver that also has audio
|
||||
paths hardwired to the Audio codec chip on-board and doesn't work
|
||||
without one.
|
||||
|
||||
|
|
|
@ -53,8 +53,41 @@ static int c_ssize = UAC2_DEF_CSSIZE;
|
|||
module_param(c_ssize, uint, S_IRUGO);
|
||||
MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)");
|
||||
#else
|
||||
#ifndef CONFIG_GADGET_UAC1_LEGACY
|
||||
#include "u_uac1.h"
|
||||
|
||||
/* Playback(USB-IN) Default Stereo - Fl/Fr */
|
||||
static int p_chmask = UAC1_DEF_PCHMASK;
|
||||
module_param(p_chmask, uint, S_IRUGO);
|
||||
MODULE_PARM_DESC(p_chmask, "Playback Channel Mask");
|
||||
|
||||
/* Playback Default 48 KHz */
|
||||
static int p_srate = UAC1_DEF_PSRATE;
|
||||
module_param(p_srate, uint, S_IRUGO);
|
||||
MODULE_PARM_DESC(p_srate, "Playback Sampling Rate");
|
||||
|
||||
/* Playback Default 16bits/sample */
|
||||
static int p_ssize = UAC1_DEF_PSSIZE;
|
||||
module_param(p_ssize, uint, S_IRUGO);
|
||||
MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)");
|
||||
|
||||
/* Capture(USB-OUT) Default Stereo - Fl/Fr */
|
||||
static int c_chmask = UAC1_DEF_CCHMASK;
|
||||
module_param(c_chmask, uint, S_IRUGO);
|
||||
MODULE_PARM_DESC(c_chmask, "Capture Channel Mask");
|
||||
|
||||
/* Capture Default 48 KHz */
|
||||
static int c_srate = UAC1_DEF_CSRATE;
|
||||
module_param(c_srate, uint, S_IRUGO);
|
||||
MODULE_PARM_DESC(c_srate, "Capture Sampling Rate");
|
||||
|
||||
/* Capture Default 16bits/sample */
|
||||
static int c_ssize = UAC1_DEF_CSSIZE;
|
||||
module_param(c_ssize, uint, S_IRUGO);
|
||||
MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)");
|
||||
#else /* CONFIG_GADGET_UAC1_LEGACY */
|
||||
#include "u_uac1_legacy.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");
|
||||
|
@ -78,6 +111,7 @@ 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 /* CONFIG_GADGET_UAC1_LEGACY */
|
||||
#endif
|
||||
|
||||
/* string IDs are assigned dynamically */
|
||||
|
@ -125,7 +159,7 @@ static struct usb_device_descriptor device_desc = {
|
|||
|
||||
/* .bcdUSB = DYNAMIC */
|
||||
|
||||
#ifdef CONFIG_GADGET_UAC1
|
||||
#ifdef CONFIG_GADGET_UAC1_LEGACY
|
||||
.bDeviceClass = USB_CLASS_PER_INTERFACE,
|
||||
.bDeviceSubClass = 0,
|
||||
.bDeviceProtocol = 0,
|
||||
|
@ -207,7 +241,11 @@ static int audio_bind(struct usb_composite_dev *cdev)
|
|||
#ifndef CONFIG_GADGET_UAC1
|
||||
struct f_uac2_opts *uac2_opts;
|
||||
#else
|
||||
#ifndef CONFIG_GADGET_UAC1_LEGACY
|
||||
struct f_uac1_opts *uac1_opts;
|
||||
#else
|
||||
struct f_uac1_legacy_opts *uac1_opts;
|
||||
#endif
|
||||
#endif
|
||||
int status;
|
||||
|
||||
|
@ -216,7 +254,11 @@ static int audio_bind(struct usb_composite_dev *cdev)
|
|||
if (IS_ERR(fi_uac2))
|
||||
return PTR_ERR(fi_uac2);
|
||||
#else
|
||||
#ifndef CONFIG_GADGET_UAC1_LEGACY
|
||||
fi_uac1 = usb_get_function_instance("uac1");
|
||||
#else
|
||||
fi_uac1 = usb_get_function_instance("uac1_legacy");
|
||||
#endif
|
||||
if (IS_ERR(fi_uac1))
|
||||
return PTR_ERR(fi_uac1);
|
||||
#endif
|
||||
|
@ -231,13 +273,24 @@ static int audio_bind(struct usb_composite_dev *cdev)
|
|||
uac2_opts->c_ssize = c_ssize;
|
||||
uac2_opts->req_number = UAC2_DEF_REQ_NUM;
|
||||
#else
|
||||
#ifndef CONFIG_GADGET_UAC1_LEGACY
|
||||
uac1_opts = container_of(fi_uac1, struct f_uac1_opts, func_inst);
|
||||
uac1_opts->p_chmask = p_chmask;
|
||||
uac1_opts->p_srate = p_srate;
|
||||
uac1_opts->p_ssize = p_ssize;
|
||||
uac1_opts->c_chmask = c_chmask;
|
||||
uac1_opts->c_srate = c_srate;
|
||||
uac1_opts->c_ssize = c_ssize;
|
||||
uac1_opts->req_number = UAC1_DEF_REQ_NUM;
|
||||
#else /* CONFIG_GADGET_UAC1_LEGACY */
|
||||
uac1_opts = container_of(fi_uac1, struct f_uac1_legacy_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 /* CONFIG_GADGET_UAC1_LEGACY */
|
||||
#endif
|
||||
|
||||
status = usb_string_ids_tab(cdev, strings_dev);
|
||||
|
|
|
@ -210,7 +210,6 @@ static int msg_bind(struct usb_composite_dev *cdev)
|
|||
usb_composite_overwrite_options(cdev, &coverwrite);
|
||||
dev_info(&cdev->gadget->dev,
|
||||
DRIVER_DESC ", version: " DRIVER_VERSION "\n");
|
||||
set_bit(0, &msg_registered);
|
||||
return 0;
|
||||
|
||||
fail_otg_desc:
|
||||
|
@ -257,7 +256,12 @@ MODULE_LICENSE("GPL");
|
|||
|
||||
static int __init msg_init(void)
|
||||
{
|
||||
return usb_composite_probe(&msg_driver);
|
||||
int ret;
|
||||
|
||||
ret = usb_composite_probe(&msg_driver);
|
||||
set_bit(0, &msg_registered);
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(msg_init);
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ config USB_LPC32XX
|
|||
|
||||
config USB_ATMEL_USBA
|
||||
tristate "Atmel USBA"
|
||||
depends on ((AVR32 && !OF) || ARCH_AT91)
|
||||
depends on ARCH_AT91
|
||||
help
|
||||
USBA is the integrated high-speed USB Device controller on
|
||||
the AT32AP700x, some AT91SAM9 and AT91CAP9 processors from Atmel.
|
||||
|
@ -256,7 +256,7 @@ config USB_MV_U3D
|
|||
controller, which support super speed USB peripheral.
|
||||
|
||||
config USB_SNP_CORE
|
||||
depends on USB_AMD5536UDC
|
||||
depends on (USB_AMD5536UDC || USB_SNP_UDC_PLAT)
|
||||
tristate
|
||||
help
|
||||
This enables core driver support for Synopsys USB 2.0 Device
|
||||
|
@ -269,6 +269,20 @@ config USB_SNP_CORE
|
|||
This IP is different to the High Speed OTG IP that can be enabled
|
||||
by selecting USB_DWC2 or USB_DWC3 options.
|
||||
|
||||
config USB_SNP_UDC_PLAT
|
||||
tristate "Synopsys USB 2.0 Device controller"
|
||||
depends on (USB_GADGET && OF)
|
||||
select USB_GADGET_DUALSPEED
|
||||
select USB_SNP_CORE
|
||||
default ARCH_BCM_IPROC
|
||||
help
|
||||
This adds Platform Device support for Synopsys Designware core
|
||||
AHB subsystem USB2.0 Device Controller (UDC).
|
||||
|
||||
This driver works with UDCs integrated into Broadcom's Northstar2
|
||||
and Cygnus SoCs.
|
||||
|
||||
If unsure, say N.
|
||||
#
|
||||
# Controllers available in both integrated and discrete versions
|
||||
#
|
||||
|
|
|
@ -10,7 +10,7 @@ obj-$(CONFIG_USB_GADGET) += udc-core.o
|
|||
obj-$(CONFIG_USB_DUMMY_HCD) += dummy_hcd.o
|
||||
obj-$(CONFIG_USB_NET2272) += net2272.o
|
||||
obj-$(CONFIG_USB_NET2280) += net2280.o
|
||||
obj-$(CONFIG_USB_SNP_CORE) += amd5536udc.o
|
||||
obj-$(CONFIG_USB_SNP_CORE) += snps_udc_core.o
|
||||
obj-$(CONFIG_USB_AMD5536UDC) += amd5536udc_pci.o
|
||||
obj-$(CONFIG_USB_PXA25X) += pxa25x_udc.o
|
||||
obj-$(CONFIG_USB_PXA27X) += pxa27x_udc.o
|
||||
|
@ -37,4 +37,5 @@ 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
|
||||
obj-$(CONFIG_USB_SNP_UDC_PLAT) += snps_udc_plat.o
|
||||
obj-$(CONFIG_USB_BDC_UDC) += bdc/
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
/* debug control */
|
||||
/* #define UDC_VERBOSE */
|
||||
|
||||
#include <linux/extcon.h>
|
||||
#include <linux/usb/ch9.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
|
||||
|
@ -28,6 +29,9 @@
|
|||
#define UDC_HSA0_REV 1
|
||||
#define UDC_HSB1_REV 2
|
||||
|
||||
/* Broadcom chip rev. */
|
||||
#define UDC_BCM_REV 10
|
||||
|
||||
/*
|
||||
* SETUP usb commands
|
||||
* needed, because some SETUP's are handled in hw, but must be passed to
|
||||
|
@ -112,6 +116,7 @@
|
|||
#define UDC_DEVCTL_BRLEN_MASK 0x00ff0000
|
||||
#define UDC_DEVCTL_BRLEN_OFS 16
|
||||
|
||||
#define UDC_DEVCTL_SRX_FLUSH 14
|
||||
#define UDC_DEVCTL_CSR_DONE 13
|
||||
#define UDC_DEVCTL_DEVNAK 12
|
||||
#define UDC_DEVCTL_SD 10
|
||||
|
@ -563,6 +568,16 @@ struct udc {
|
|||
u16 cur_config;
|
||||
u16 cur_intf;
|
||||
u16 cur_alt;
|
||||
|
||||
/* for platform device and extcon support */
|
||||
struct device *dev;
|
||||
struct phy *udc_phy;
|
||||
struct extcon_dev *edev;
|
||||
struct extcon_specific_cable_nb extcon_nb;
|
||||
struct notifier_block nb;
|
||||
struct delayed_work drd_work;
|
||||
struct workqueue_struct *drd_wq;
|
||||
u32 conn_type;
|
||||
};
|
||||
|
||||
#define to_amd5536_udc(g) (container_of((g), struct udc, gadget))
|
||||
|
@ -578,6 +593,7 @@ int udc_enable_dev_setup_interrupts(struct udc *dev);
|
|||
int udc_mask_unused_interrupts(struct udc *dev);
|
||||
irqreturn_t udc_irq(int irq, void *pdev);
|
||||
void gadget_release(struct device *pdev);
|
||||
void empty_req_queue(struct udc_ep *ep);
|
||||
void udc_basic_init(struct udc *dev);
|
||||
void free_dma_pools(struct udc *dev);
|
||||
int init_dma_pools(struct udc *dev);
|
||||
|
@ -639,7 +655,7 @@ MODULE_PARM_DESC(use_fullspeed, "true for fullspeed only");
|
|||
|
||||
/* debug macros ------------------------------------------------------------*/
|
||||
|
||||
#define DBG(udc , args...) dev_dbg(&(udc)->pdev->dev, args)
|
||||
#define DBG(udc , args...) dev_dbg(udc->dev, args)
|
||||
|
||||
#ifdef UDC_VERBOSE
|
||||
#define VDBG DBG
|
||||
|
|
|
@ -168,6 +168,7 @@ static int udc_pci_probe(
|
|||
dev->phys_addr = resource;
|
||||
dev->irq = pdev->irq;
|
||||
dev->pdev = pdev;
|
||||
dev->dev = &pdev->dev;
|
||||
|
||||
/* general probing */
|
||||
if (udc_probe(dev)) {
|
||||
|
|
|
@ -152,7 +152,7 @@ static int regs_dbg_open(struct inode *inode, struct file *file)
|
|||
|
||||
spin_lock_irq(&udc->lock);
|
||||
for (i = 0; i < inode->i_size / 4; i++)
|
||||
data[i] = usba_io_readl(udc->regs + i * 4);
|
||||
data[i] = readl_relaxed(udc->regs + i * 4);
|
||||
spin_unlock_irq(&udc->lock);
|
||||
|
||||
file->private_data = data;
|
||||
|
@ -1369,7 +1369,7 @@ static int handle_ep0_setup(struct usba_udc *udc, struct usba_ep *ep,
|
|||
if (crq->wLength != cpu_to_le16(sizeof(status)))
|
||||
goto stall;
|
||||
ep->state = DATA_STAGE_IN;
|
||||
usba_io_writew(status, ep->fifo);
|
||||
writew_relaxed(status, ep->fifo);
|
||||
usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -43,13 +43,8 @@
|
|||
#define USBA_REMOTE_WAKE_UP (1 << 10)
|
||||
#define USBA_PULLD_DIS (1 << 11)
|
||||
|
||||
#if defined(CONFIG_AVR32)
|
||||
#define USBA_ENABLE_MASK USBA_EN_USBA
|
||||
#define USBA_DISABLE_MASK 0
|
||||
#elif defined(CONFIG_ARCH_AT91)
|
||||
#define USBA_ENABLE_MASK (USBA_EN_USBA | USBA_PULLD_DIS)
|
||||
#define USBA_DISABLE_MASK USBA_DETACH
|
||||
#endif /* CONFIG_ARCH_AT91 */
|
||||
|
||||
/* Bitfields in FNUM */
|
||||
#define USBA_MICRO_FRAME_NUM_OFFSET 0
|
||||
|
@ -191,28 +186,18 @@
|
|||
| USBA_BF(name, value))
|
||||
|
||||
/* Register access macros */
|
||||
#ifdef CONFIG_AVR32
|
||||
#define usba_io_readl __raw_readl
|
||||
#define usba_io_writel __raw_writel
|
||||
#define usba_io_writew __raw_writew
|
||||
#else
|
||||
#define usba_io_readl readl_relaxed
|
||||
#define usba_io_writel writel_relaxed
|
||||
#define usba_io_writew writew_relaxed
|
||||
#endif
|
||||
|
||||
#define usba_readl(udc, reg) \
|
||||
usba_io_readl((udc)->regs + USBA_##reg)
|
||||
readl_relaxed((udc)->regs + USBA_##reg)
|
||||
#define usba_writel(udc, reg, value) \
|
||||
usba_io_writel((value), (udc)->regs + USBA_##reg)
|
||||
writel_relaxed((value), (udc)->regs + USBA_##reg)
|
||||
#define usba_ep_readl(ep, reg) \
|
||||
usba_io_readl((ep)->ep_regs + USBA_EPT_##reg)
|
||||
readl_relaxed((ep)->ep_regs + USBA_EPT_##reg)
|
||||
#define usba_ep_writel(ep, reg, value) \
|
||||
usba_io_writel((value), (ep)->ep_regs + USBA_EPT_##reg)
|
||||
writel_relaxed((value), (ep)->ep_regs + USBA_EPT_##reg)
|
||||
#define usba_dma_readl(ep, reg) \
|
||||
usba_io_readl((ep)->dma_regs + USBA_DMA_##reg)
|
||||
readl_relaxed((ep)->dma_regs + USBA_DMA_##reg)
|
||||
#define usba_dma_writel(ep, reg, value) \
|
||||
usba_io_writel((value), (ep)->dma_regs + USBA_DMA_##reg)
|
||||
writel_relaxed((value), (ep)->dma_regs + USBA_DMA_##reg)
|
||||
|
||||
/* Calculate base address for a given endpoint or DMA controller */
|
||||
#define USBA_EPT_BASE(x) (0x100 + (x) * 0x20)
|
||||
|
|
|
@ -475,7 +475,7 @@ static int bdc_probe(struct platform_device *pdev)
|
|||
bdc->dev = dev;
|
||||
dev_dbg(bdc->dev, "bdc->regs: %p irq=%d\n", bdc->regs, bdc->irq);
|
||||
|
||||
temp = bdc_readl(bdc->regs, BDC_BDCSC);
|
||||
temp = bdc_readl(bdc->regs, BDC_BDCCAP1);
|
||||
if ((temp & BDC_P64) &&
|
||||
!dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) {
|
||||
dev_dbg(bdc->dev, "Using 64-bit address\n");
|
||||
|
|
|
@ -140,10 +140,8 @@ int usb_ep_disable(struct usb_ep *ep)
|
|||
goto out;
|
||||
|
||||
ret = ep->ops->disable(ep);
|
||||
if (ret) {
|
||||
ret = ret;
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
ep->enabled = false;
|
||||
|
||||
|
@ -1066,6 +1064,23 @@ static inline void usb_gadget_udc_stop(struct usb_udc *udc)
|
|||
udc->gadget->ops->udc_stop(udc->gadget);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb_gadget_udc_set_speed - tells usb device controller speed supported by
|
||||
* current driver
|
||||
* @udc: The device we want to set maximum speed
|
||||
* @speed: The maximum speed to allowed to run
|
||||
*
|
||||
* This call is issued by the UDC Class driver before calling
|
||||
* usb_gadget_udc_start() in order to make sure that we don't try to
|
||||
* connect on speeds the gadget driver doesn't support.
|
||||
*/
|
||||
static inline void usb_gadget_udc_set_speed(struct usb_udc *udc,
|
||||
enum usb_device_speed speed)
|
||||
{
|
||||
if (udc->gadget->ops->udc_set_speed)
|
||||
udc->gadget->ops->udc_set_speed(udc->gadget, speed);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb_udc_release - release the usb_udc struct
|
||||
* @dev: the dev member within usb_udc
|
||||
|
@ -1299,6 +1314,9 @@ static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *dri
|
|||
udc->dev.driver = &driver->driver;
|
||||
udc->gadget->dev.driver = &driver->driver;
|
||||
|
||||
if (driver->max_speed < udc->gadget->max_speed)
|
||||
usb_gadget_udc_set_speed(udc, driver->max_speed);
|
||||
|
||||
ret = driver->bind(udc->gadget, driver);
|
||||
if (ret)
|
||||
goto err1;
|
||||
|
@ -1451,6 +1469,18 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr,
|
|||
}
|
||||
static DEVICE_ATTR_RO(state);
|
||||
|
||||
static ssize_t function_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct usb_udc *udc = container_of(dev, struct usb_udc, dev);
|
||||
struct usb_gadget_driver *drv = udc->driver;
|
||||
|
||||
if (!drv || !drv->function)
|
||||
return 0;
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", drv->function);
|
||||
}
|
||||
static DEVICE_ATTR_RO(function);
|
||||
|
||||
#define USB_UDC_SPEED_ATTR(name, param) \
|
||||
ssize_t name##_show(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf) \
|
||||
|
@ -1486,6 +1516,7 @@ static struct attribute *usb_udc_attrs[] = {
|
|||
&dev_attr_srp.attr,
|
||||
&dev_attr_soft_connect.attr,
|
||||
&dev_attr_state.attr,
|
||||
&dev_attr_function.attr,
|
||||
&dev_attr_current_speed.attr,
|
||||
&dev_attr_maximum_speed.attr,
|
||||
|
||||
|
|
|
@ -881,22 +881,6 @@ static int dummy_pullup(struct usb_gadget *_gadget, int value)
|
|||
unsigned long flags;
|
||||
|
||||
dum = gadget_dev_to_dummy(&_gadget->dev);
|
||||
|
||||
if (value && dum->driver) {
|
||||
if (mod_data.is_super_speed)
|
||||
dum->gadget.speed = dum->driver->max_speed;
|
||||
else if (mod_data.is_high_speed)
|
||||
dum->gadget.speed = min_t(u8, USB_SPEED_HIGH,
|
||||
dum->driver->max_speed);
|
||||
else
|
||||
dum->gadget.speed = USB_SPEED_FULL;
|
||||
dummy_udc_update_ep0(dum);
|
||||
|
||||
if (dum->gadget.speed < dum->driver->max_speed)
|
||||
dev_dbg(udc_dev(dum), "This device can perform faster"
|
||||
" if you connect it to a %s port...\n",
|
||||
usb_speed_string(dum->driver->max_speed));
|
||||
}
|
||||
dum_hcd = gadget_to_dummy_hcd(_gadget);
|
||||
|
||||
spin_lock_irqsave(&dum->lock, flags);
|
||||
|
@ -908,6 +892,28 @@ static int dummy_pullup(struct usb_gadget *_gadget, int value)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void dummy_udc_set_speed(struct usb_gadget *_gadget,
|
||||
enum usb_device_speed speed)
|
||||
{
|
||||
struct dummy *dum;
|
||||
|
||||
dum = gadget_dev_to_dummy(&_gadget->dev);
|
||||
|
||||
if (mod_data.is_super_speed)
|
||||
dum->gadget.speed = min_t(u8, USB_SPEED_SUPER, speed);
|
||||
else if (mod_data.is_high_speed)
|
||||
dum->gadget.speed = min_t(u8, USB_SPEED_HIGH, speed);
|
||||
else
|
||||
dum->gadget.speed = USB_SPEED_FULL;
|
||||
|
||||
dummy_udc_update_ep0(dum);
|
||||
|
||||
if (dum->gadget.speed < speed)
|
||||
dev_dbg(udc_dev(dum), "This device can perform faster"
|
||||
" if you connect it to a %s port...\n",
|
||||
usb_speed_string(speed));
|
||||
}
|
||||
|
||||
static int dummy_udc_start(struct usb_gadget *g,
|
||||
struct usb_gadget_driver *driver);
|
||||
static int dummy_udc_stop(struct usb_gadget *g);
|
||||
|
@ -919,6 +925,7 @@ static const struct usb_gadget_ops dummy_ops = {
|
|||
.pullup = dummy_pullup,
|
||||
.udc_start = dummy_udc_start,
|
||||
.udc_stop = dummy_udc_stop,
|
||||
.udc_set_speed = dummy_udc_set_speed,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
|
|
@ -960,9 +960,9 @@ static const struct usb_ep_ops mv_ep_ops = {
|
|||
.fifo_flush = mv_ep_fifo_flush, /* flush fifo */
|
||||
};
|
||||
|
||||
static void udc_clock_enable(struct mv_udc *udc)
|
||||
static int udc_clock_enable(struct mv_udc *udc)
|
||||
{
|
||||
clk_prepare_enable(udc->clk);
|
||||
return clk_prepare_enable(udc->clk);
|
||||
}
|
||||
|
||||
static void udc_clock_disable(struct mv_udc *udc)
|
||||
|
@ -1070,7 +1070,10 @@ static int mv_udc_enable_internal(struct mv_udc *udc)
|
|||
return 0;
|
||||
|
||||
dev_dbg(&udc->dev->dev, "enable udc\n");
|
||||
udc_clock_enable(udc);
|
||||
retval = udc_clock_enable(udc);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
if (udc->pdata->phy_init) {
|
||||
retval = udc->pdata->phy_init(udc->phy_regs);
|
||||
if (retval) {
|
||||
|
|
|
@ -3566,7 +3566,6 @@ static void net2280_remove(struct pci_dev *pdev)
|
|||
BUG_ON(dev->driver);
|
||||
|
||||
/* then clean up the resources we allocated during probe() */
|
||||
net2280_led_shutdown(dev);
|
||||
if (dev->requests) {
|
||||
int i;
|
||||
for (i = 1; i < 5; i++) {
|
||||
|
@ -3581,8 +3580,10 @@ static void net2280_remove(struct pci_dev *pdev)
|
|||
free_irq(pdev->irq, dev);
|
||||
if (dev->quirks & PLX_PCIE)
|
||||
pci_disable_msi(pdev);
|
||||
if (dev->regs)
|
||||
if (dev->regs) {
|
||||
net2280_led_shutdown(dev);
|
||||
iounmap(dev->regs);
|
||||
}
|
||||
if (dev->region)
|
||||
release_mem_region(pci_resource_start(pdev, 0),
|
||||
pci_resource_len(pdev, 0));
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/extcon.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
@ -27,6 +28,8 @@
|
|||
#define USB3_AXI_INT_ENA 0x00c
|
||||
#define USB3_DMA_INT_STA 0x010
|
||||
#define USB3_DMA_INT_ENA 0x014
|
||||
#define USB3_DMA_CH0_CON(n) (0x030 + ((n) - 1) * 0x10) /* n = 1 to 4 */
|
||||
#define USB3_DMA_CH0_PRD_ADR(n) (0x034 + ((n) - 1) * 0x10) /* n = 1 to 4 */
|
||||
#define USB3_USB_COM_CON 0x200
|
||||
#define USB3_USB20_CON 0x204
|
||||
#define USB3_USB30_CON 0x208
|
||||
|
@ -64,6 +67,22 @@
|
|||
/* AXI_INT_ENA and AXI_INT_STA */
|
||||
#define AXI_INT_DMAINT BIT(31)
|
||||
#define AXI_INT_EPCINT BIT(30)
|
||||
/* PRD's n = from 1 to 4 */
|
||||
#define AXI_INT_PRDEN_CLR_STA_SHIFT(n) (16 + (n) - 1)
|
||||
#define AXI_INT_PRDERR_STA_SHIFT(n) (0 + (n) - 1)
|
||||
#define AXI_INT_PRDEN_CLR_STA(n) (1 << AXI_INT_PRDEN_CLR_STA_SHIFT(n))
|
||||
#define AXI_INT_PRDERR_STA(n) (1 << AXI_INT_PRDERR_STA_SHIFT(n))
|
||||
|
||||
/* DMA_INT_ENA and DMA_INT_STA */
|
||||
#define DMA_INT(n) BIT(n)
|
||||
|
||||
/* DMA_CH0_CONn */
|
||||
#define DMA_CON_PIPE_DIR BIT(15) /* 1: In Transfer */
|
||||
#define DMA_CON_PIPE_NO_SHIFT 8
|
||||
#define DMA_CON_PIPE_NO_MASK GENMASK(12, DMA_CON_PIPE_NO_SHIFT)
|
||||
#define DMA_COM_PIPE_NO(n) (((n) << DMA_CON_PIPE_NO_SHIFT) & \
|
||||
DMA_CON_PIPE_NO_MASK)
|
||||
#define DMA_CON_PRD_EN BIT(0)
|
||||
|
||||
/* LCLKSEL */
|
||||
#define LCLKSEL_LSEL BIT(18)
|
||||
|
@ -231,8 +250,50 @@
|
|||
#define USB3_EP0_BUF_SIZE 8
|
||||
#define USB3_MAX_NUM_PIPES 30
|
||||
#define USB3_WAIT_US 3
|
||||
#define USB3_DMA_NUM_SETTING_AREA 4
|
||||
/*
|
||||
* To avoid double-meaning of "0" (xferred 65536 bytes or received zlp if
|
||||
* buffer size is 65536), this driver uses the maximum size per a entry is
|
||||
* 32768 bytes.
|
||||
*/
|
||||
#define USB3_DMA_MAX_XFER_SIZE 32768
|
||||
#define USB3_DMA_PRD_SIZE 4096
|
||||
|
||||
struct renesas_usb3;
|
||||
|
||||
/* Physical Region Descriptor Table */
|
||||
struct renesas_usb3_prd {
|
||||
u32 word1;
|
||||
#define USB3_PRD1_E BIT(30) /* the end of chain */
|
||||
#define USB3_PRD1_U BIT(29) /* completion of transfer */
|
||||
#define USB3_PRD1_D BIT(28) /* Error occurred */
|
||||
#define USB3_PRD1_INT BIT(27) /* Interrupt occurred */
|
||||
#define USB3_PRD1_LST BIT(26) /* Last Packet */
|
||||
#define USB3_PRD1_B_INC BIT(24)
|
||||
#define USB3_PRD1_MPS_8 0
|
||||
#define USB3_PRD1_MPS_16 BIT(21)
|
||||
#define USB3_PRD1_MPS_32 BIT(22)
|
||||
#define USB3_PRD1_MPS_64 (BIT(22) | BIT(21))
|
||||
#define USB3_PRD1_MPS_512 BIT(23)
|
||||
#define USB3_PRD1_MPS_1024 (BIT(23) | BIT(21))
|
||||
#define USB3_PRD1_MPS_RESERVED (BIT(23) | BIT(22) | BIT(21))
|
||||
#define USB3_PRD1_SIZE_MASK GENMASK(15, 0)
|
||||
|
||||
u32 bap;
|
||||
};
|
||||
#define USB3_DMA_NUM_PRD_ENTRIES (USB3_DMA_PRD_SIZE / \
|
||||
sizeof(struct renesas_usb3_prd))
|
||||
#define USB3_DMA_MAX_XFER_SIZE_ALL_PRDS (USB3_DMA_PRD_SIZE / \
|
||||
sizeof(struct renesas_usb3_prd) * \
|
||||
USB3_DMA_MAX_XFER_SIZE)
|
||||
|
||||
struct renesas_usb3_dma {
|
||||
struct renesas_usb3_prd *prd;
|
||||
dma_addr_t prd_dma;
|
||||
int num; /* Setting area number (from 1 to 4) */
|
||||
bool used;
|
||||
};
|
||||
|
||||
struct renesas_usb3_request {
|
||||
struct usb_request req;
|
||||
struct list_head queue;
|
||||
|
@ -242,6 +303,7 @@ struct renesas_usb3_request {
|
|||
struct renesas_usb3_ep {
|
||||
struct usb_ep ep;
|
||||
struct renesas_usb3 *usb3;
|
||||
struct renesas_usb3_dma *dma;
|
||||
int num;
|
||||
char ep_name[USB3_EP_NAME_SIZE];
|
||||
struct list_head queue;
|
||||
|
@ -270,6 +332,8 @@ struct renesas_usb3 {
|
|||
struct renesas_usb3_ep *usb3_ep;
|
||||
int num_usb3_eps;
|
||||
|
||||
struct renesas_usb3_dma dma[USB3_DMA_NUM_SETTING_AREA];
|
||||
|
||||
spinlock_t lock;
|
||||
int disabled_count;
|
||||
|
||||
|
@ -298,8 +362,18 @@ struct renesas_usb3 {
|
|||
(i) < (usb3)->num_usb3_eps; \
|
||||
(i)++, usb3_ep = usb3_get_ep(usb3, (i)))
|
||||
|
||||
#define usb3_get_dma(usb3, i) (&(usb3)->dma[i])
|
||||
#define usb3_for_each_dma(usb3, dma, i) \
|
||||
for ((i) = 0, dma = usb3_get_dma((usb3), (i)); \
|
||||
(i) < USB3_DMA_NUM_SETTING_AREA; \
|
||||
(i)++, dma = usb3_get_dma((usb3), (i)))
|
||||
|
||||
static const char udc_name[] = "renesas_usb3";
|
||||
|
||||
static bool use_dma = 1;
|
||||
module_param(use_dma, bool, 0644);
|
||||
MODULE_PARM_DESC(use_dma, "use dedicated DMAC");
|
||||
|
||||
static void usb3_write(struct renesas_usb3 *usb3, u32 data, u32 offs)
|
||||
{
|
||||
iowrite32(data, usb3->reg + offs);
|
||||
|
@ -1059,6 +1133,273 @@ static void usb3_start_pipe0(struct renesas_usb3_ep *usb3_ep,
|
|||
usb3_p0_xfer(usb3_ep, usb3_req);
|
||||
}
|
||||
|
||||
static void usb3_enable_dma_pipen(struct renesas_usb3 *usb3)
|
||||
{
|
||||
usb3_set_bit(usb3, PN_CON_DATAIF_EN, USB3_PN_CON);
|
||||
}
|
||||
|
||||
static void usb3_disable_dma_pipen(struct renesas_usb3 *usb3)
|
||||
{
|
||||
usb3_clear_bit(usb3, PN_CON_DATAIF_EN, USB3_PN_CON);
|
||||
}
|
||||
|
||||
static void usb3_enable_dma_irq(struct renesas_usb3 *usb3, int num)
|
||||
{
|
||||
usb3_set_bit(usb3, DMA_INT(num), USB3_DMA_INT_ENA);
|
||||
}
|
||||
|
||||
static void usb3_disable_dma_irq(struct renesas_usb3 *usb3, int num)
|
||||
{
|
||||
usb3_clear_bit(usb3, DMA_INT(num), USB3_DMA_INT_ENA);
|
||||
}
|
||||
|
||||
static u32 usb3_dma_mps_to_prd_word1(struct renesas_usb3_ep *usb3_ep)
|
||||
{
|
||||
switch (usb3_ep->ep.maxpacket) {
|
||||
case 8:
|
||||
return USB3_PRD1_MPS_8;
|
||||
case 16:
|
||||
return USB3_PRD1_MPS_16;
|
||||
case 32:
|
||||
return USB3_PRD1_MPS_32;
|
||||
case 64:
|
||||
return USB3_PRD1_MPS_64;
|
||||
case 512:
|
||||
return USB3_PRD1_MPS_512;
|
||||
case 1024:
|
||||
return USB3_PRD1_MPS_1024;
|
||||
default:
|
||||
return USB3_PRD1_MPS_RESERVED;
|
||||
}
|
||||
}
|
||||
|
||||
static bool usb3_dma_get_setting_area(struct renesas_usb3_ep *usb3_ep,
|
||||
struct renesas_usb3_request *usb3_req)
|
||||
{
|
||||
struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep);
|
||||
struct renesas_usb3_dma *dma;
|
||||
int i;
|
||||
bool ret = false;
|
||||
|
||||
if (usb3_req->req.length > USB3_DMA_MAX_XFER_SIZE_ALL_PRDS) {
|
||||
dev_dbg(usb3_to_dev(usb3), "%s: the length is too big (%d)\n",
|
||||
__func__, usb3_req->req.length);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The driver doesn't handle zero-length packet via dmac */
|
||||
if (!usb3_req->req.length)
|
||||
return false;
|
||||
|
||||
if (usb3_dma_mps_to_prd_word1(usb3_ep) == USB3_PRD1_MPS_RESERVED)
|
||||
return false;
|
||||
|
||||
usb3_for_each_dma(usb3, dma, i) {
|
||||
if (dma->used)
|
||||
continue;
|
||||
|
||||
if (usb_gadget_map_request(&usb3->gadget, &usb3_req->req,
|
||||
usb3_ep->dir_in) < 0)
|
||||
break;
|
||||
|
||||
dma->used = true;
|
||||
usb3_ep->dma = dma;
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usb3_dma_put_setting_area(struct renesas_usb3_ep *usb3_ep,
|
||||
struct renesas_usb3_request *usb3_req)
|
||||
{
|
||||
struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep);
|
||||
int i;
|
||||
struct renesas_usb3_dma *dma;
|
||||
|
||||
usb3_for_each_dma(usb3, dma, i) {
|
||||
if (usb3_ep->dma == dma) {
|
||||
usb_gadget_unmap_request(&usb3->gadget, &usb3_req->req,
|
||||
usb3_ep->dir_in);
|
||||
dma->used = false;
|
||||
usb3_ep->dma = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void usb3_dma_fill_prd(struct renesas_usb3_ep *usb3_ep,
|
||||
struct renesas_usb3_request *usb3_req)
|
||||
{
|
||||
struct renesas_usb3_prd *cur_prd = usb3_ep->dma->prd;
|
||||
u32 remain = usb3_req->req.length;
|
||||
u32 dma = usb3_req->req.dma;
|
||||
u32 len;
|
||||
int i = 0;
|
||||
|
||||
do {
|
||||
len = min_t(u32, remain, USB3_DMA_MAX_XFER_SIZE) &
|
||||
USB3_PRD1_SIZE_MASK;
|
||||
cur_prd->word1 = usb3_dma_mps_to_prd_word1(usb3_ep) |
|
||||
USB3_PRD1_B_INC | len;
|
||||
cur_prd->bap = dma;
|
||||
remain -= len;
|
||||
dma += len;
|
||||
if (!remain || (i + 1) < USB3_DMA_NUM_PRD_ENTRIES)
|
||||
break;
|
||||
|
||||
cur_prd++;
|
||||
i++;
|
||||
} while (1);
|
||||
|
||||
cur_prd->word1 |= USB3_PRD1_E | USB3_PRD1_INT;
|
||||
if (usb3_ep->dir_in)
|
||||
cur_prd->word1 |= USB3_PRD1_LST;
|
||||
}
|
||||
|
||||
static void usb3_dma_kick_prd(struct renesas_usb3_ep *usb3_ep)
|
||||
{
|
||||
struct renesas_usb3_dma *dma = usb3_ep->dma;
|
||||
struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep);
|
||||
u32 dma_con = DMA_COM_PIPE_NO(usb3_ep->num) | DMA_CON_PRD_EN;
|
||||
|
||||
if (usb3_ep->dir_in)
|
||||
dma_con |= DMA_CON_PIPE_DIR;
|
||||
|
||||
wmb(); /* prd entries should be in system memory here */
|
||||
|
||||
usb3_write(usb3, 1 << usb3_ep->num, USB3_DMA_INT_STA);
|
||||
usb3_write(usb3, AXI_INT_PRDEN_CLR_STA(dma->num) |
|
||||
AXI_INT_PRDERR_STA(dma->num), USB3_AXI_INT_STA);
|
||||
|
||||
usb3_write(usb3, dma->prd_dma, USB3_DMA_CH0_PRD_ADR(dma->num));
|
||||
usb3_write(usb3, dma_con, USB3_DMA_CH0_CON(dma->num));
|
||||
usb3_enable_dma_irq(usb3, usb3_ep->num);
|
||||
}
|
||||
|
||||
static void usb3_dma_stop_prd(struct renesas_usb3_ep *usb3_ep)
|
||||
{
|
||||
struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep);
|
||||
struct renesas_usb3_dma *dma = usb3_ep->dma;
|
||||
|
||||
usb3_disable_dma_irq(usb3, usb3_ep->num);
|
||||
usb3_write(usb3, 0, USB3_DMA_CH0_CON(dma->num));
|
||||
}
|
||||
|
||||
static int usb3_dma_update_status(struct renesas_usb3_ep *usb3_ep,
|
||||
struct renesas_usb3_request *usb3_req)
|
||||
{
|
||||
struct renesas_usb3_prd *cur_prd = usb3_ep->dma->prd;
|
||||
struct usb_request *req = &usb3_req->req;
|
||||
u32 remain, len;
|
||||
int i = 0;
|
||||
int status = 0;
|
||||
|
||||
rmb(); /* The controller updated prd entries */
|
||||
|
||||
do {
|
||||
if (cur_prd->word1 & USB3_PRD1_D)
|
||||
status = -EIO;
|
||||
if (cur_prd->word1 & USB3_PRD1_E)
|
||||
len = req->length % USB3_DMA_MAX_XFER_SIZE;
|
||||
else
|
||||
len = USB3_DMA_MAX_XFER_SIZE;
|
||||
remain = cur_prd->word1 & USB3_PRD1_SIZE_MASK;
|
||||
req->actual += len - remain;
|
||||
|
||||
if (cur_prd->word1 & USB3_PRD1_E ||
|
||||
(i + 1) < USB3_DMA_NUM_PRD_ENTRIES)
|
||||
break;
|
||||
|
||||
cur_prd++;
|
||||
i++;
|
||||
} while (1);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static bool usb3_dma_try_start(struct renesas_usb3_ep *usb3_ep,
|
||||
struct renesas_usb3_request *usb3_req)
|
||||
{
|
||||
struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep);
|
||||
|
||||
if (!use_dma)
|
||||
return false;
|
||||
|
||||
if (usb3_dma_get_setting_area(usb3_ep, usb3_req)) {
|
||||
usb3_pn_stop(usb3);
|
||||
usb3_enable_dma_pipen(usb3);
|
||||
usb3_dma_fill_prd(usb3_ep, usb3_req);
|
||||
usb3_dma_kick_prd(usb3_ep);
|
||||
usb3_pn_start(usb3);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int usb3_dma_try_stop(struct renesas_usb3_ep *usb3_ep,
|
||||
struct renesas_usb3_request *usb3_req)
|
||||
{
|
||||
struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep);
|
||||
unsigned long flags;
|
||||
int status = 0;
|
||||
|
||||
spin_lock_irqsave(&usb3->lock, flags);
|
||||
if (!usb3_ep->dma)
|
||||
goto out;
|
||||
|
||||
if (!usb3_pn_change(usb3, usb3_ep->num))
|
||||
usb3_disable_dma_pipen(usb3);
|
||||
usb3_dma_stop_prd(usb3_ep);
|
||||
status = usb3_dma_update_status(usb3_ep, usb3_req);
|
||||
usb3_dma_put_setting_area(usb3_ep, usb3_req);
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&usb3->lock, flags);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int renesas_usb3_dma_free_prd(struct renesas_usb3 *usb3,
|
||||
struct device *dev)
|
||||
{
|
||||
int i;
|
||||
struct renesas_usb3_dma *dma;
|
||||
|
||||
usb3_for_each_dma(usb3, dma, i) {
|
||||
if (dma->prd) {
|
||||
dma_free_coherent(dev, USB3_DMA_MAX_XFER_SIZE,
|
||||
dma->prd, dma->prd_dma);
|
||||
dma->prd = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int renesas_usb3_dma_alloc_prd(struct renesas_usb3 *usb3,
|
||||
struct device *dev)
|
||||
{
|
||||
int i;
|
||||
struct renesas_usb3_dma *dma;
|
||||
|
||||
if (!use_dma)
|
||||
return 0;
|
||||
|
||||
usb3_for_each_dma(usb3, dma, i) {
|
||||
dma->prd = dma_alloc_coherent(dev, USB3_DMA_PRD_SIZE,
|
||||
&dma->prd_dma, GFP_KERNEL);
|
||||
if (!dma->prd) {
|
||||
renesas_usb3_dma_free_prd(usb3, dev);
|
||||
return -ENOMEM;
|
||||
}
|
||||
dma->num = i + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usb3_start_pipen(struct renesas_usb3_ep *usb3_ep,
|
||||
struct renesas_usb3_request *usb3_req)
|
||||
{
|
||||
|
@ -1078,6 +1419,10 @@ static void usb3_start_pipen(struct renesas_usb3_ep *usb3_ep,
|
|||
goto out;
|
||||
|
||||
usb3_ep->started = true;
|
||||
|
||||
if (usb3_dma_try_start(usb3_ep, usb3_req))
|
||||
goto out;
|
||||
|
||||
usb3_pn_start(usb3);
|
||||
|
||||
if (usb3_ep->dir_in) {
|
||||
|
@ -1603,12 +1948,49 @@ static void usb3_irq_epc(struct renesas_usb3 *usb3)
|
|||
}
|
||||
}
|
||||
|
||||
static void usb3_irq_dma_int(struct renesas_usb3 *usb3, u32 dma_sta)
|
||||
{
|
||||
struct renesas_usb3_ep *usb3_ep;
|
||||
struct renesas_usb3_request *usb3_req;
|
||||
int i, status;
|
||||
|
||||
for (i = 0; i < usb3->num_usb3_eps; i++) {
|
||||
if (!(dma_sta & DMA_INT(i)))
|
||||
continue;
|
||||
|
||||
usb3_ep = usb3_get_ep(usb3, i);
|
||||
if (!(usb3_read(usb3, USB3_AXI_INT_STA) &
|
||||
AXI_INT_PRDEN_CLR_STA(usb3_ep->dma->num)))
|
||||
continue;
|
||||
|
||||
usb3_req = usb3_get_request(usb3_ep);
|
||||
status = usb3_dma_try_stop(usb3_ep, usb3_req);
|
||||
usb3_request_done_pipen(usb3, usb3_ep, usb3_req, status);
|
||||
}
|
||||
}
|
||||
|
||||
static void usb3_irq_dma(struct renesas_usb3 *usb3)
|
||||
{
|
||||
u32 dma_sta = usb3_read(usb3, USB3_DMA_INT_STA);
|
||||
|
||||
dma_sta &= usb3_read(usb3, USB3_DMA_INT_ENA);
|
||||
if (dma_sta) {
|
||||
usb3_write(usb3, dma_sta, USB3_DMA_INT_STA);
|
||||
usb3_irq_dma_int(usb3, dma_sta);
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t renesas_usb3_irq(int irq, void *_usb3)
|
||||
{
|
||||
struct renesas_usb3 *usb3 = _usb3;
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
u32 axi_int_sta = usb3_read(usb3, USB3_AXI_INT_STA);
|
||||
|
||||
if (axi_int_sta & AXI_INT_DMAINT) {
|
||||
usb3_irq_dma(usb3);
|
||||
ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
if (axi_int_sta & AXI_INT_EPCINT) {
|
||||
usb3_irq_epc(usb3);
|
||||
ret = IRQ_HANDLED;
|
||||
|
@ -1708,6 +2090,7 @@ static int renesas_usb3_ep_disable(struct usb_ep *_ep)
|
|||
usb3_req = usb3_get_request(usb3_ep);
|
||||
if (!usb3_req)
|
||||
break;
|
||||
usb3_dma_try_stop(usb3_ep, usb3_req);
|
||||
usb3_request_done(usb3_ep, usb3_req, -ESHUTDOWN);
|
||||
} while (1);
|
||||
|
||||
|
@ -1755,6 +2138,7 @@ static int renesas_usb3_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
|
|||
dev_dbg(usb3_to_dev(usb3), "ep_dequeue: ep%2d, %u\n", usb3_ep->num,
|
||||
_req->length);
|
||||
|
||||
usb3_dma_try_stop(usb3_ep, usb3_req);
|
||||
usb3_request_done_pipen(usb3, usb3_ep, usb3_req, -ECONNRESET);
|
||||
|
||||
return 0;
|
||||
|
@ -1917,6 +2301,7 @@ static int renesas_usb3_remove(struct platform_device *pdev)
|
|||
device_remove_file(&pdev->dev, &dev_attr_role);
|
||||
|
||||
usb_del_gadget_udc(&usb3->gadget);
|
||||
renesas_usb3_dma_free_prd(usb3, &pdev->dev);
|
||||
|
||||
__renesas_usb3_ep_free_request(usb3->ep0_req);
|
||||
|
||||
|
@ -2111,6 +2496,10 @@ static int renesas_usb3_probe(struct platform_device *pdev)
|
|||
if (!usb3->ep0_req)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = renesas_usb3_dma_alloc_prd(usb3, &pdev->dev);
|
||||
if (ret < 0)
|
||||
goto err_alloc_prd;
|
||||
|
||||
ret = usb_add_gadget_udc(&pdev->dev, &usb3->gadget);
|
||||
if (ret < 0)
|
||||
goto err_add_udc;
|
||||
|
@ -2129,6 +2518,9 @@ err_dev_create:
|
|||
usb_del_gadget_udc(&usb3->gadget);
|
||||
|
||||
err_add_udc:
|
||||
renesas_usb3_dma_free_prd(usb3, &pdev->dev);
|
||||
|
||||
err_alloc_prd:
|
||||
__renesas_usb3_ep_free_request(usb3->ep0_req);
|
||||
|
||||
return ret;
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
#include "amd5536udc.h"
|
||||
|
||||
static void udc_tasklet_disconnect(unsigned long);
|
||||
static void empty_req_queue(struct udc_ep *);
|
||||
static void udc_setup_endpoints(struct udc *dev);
|
||||
static void udc_soft_reset(struct udc *dev);
|
||||
static struct udc_request *udc_alloc_bna_dummy(struct udc_ep *ep);
|
||||
|
@ -209,18 +208,18 @@ static void print_regs(struct udc *dev)
|
|||
if (use_dma && use_dma_ppb && !use_dma_ppb_du) {
|
||||
DBG(dev, "DMA mode = PPBNDU (packet per buffer "
|
||||
"WITHOUT desc. update)\n");
|
||||
dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "PPBNDU");
|
||||
dev_info(dev->dev, "DMA mode (%s)\n", "PPBNDU");
|
||||
} else if (use_dma && use_dma_ppb && use_dma_ppb_du) {
|
||||
DBG(dev, "DMA mode = PPBDU (packet per buffer "
|
||||
"WITH desc. update)\n");
|
||||
dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "PPBDU");
|
||||
dev_info(dev->dev, "DMA mode (%s)\n", "PPBDU");
|
||||
}
|
||||
if (use_dma && use_dma_bufferfill_mode) {
|
||||
DBG(dev, "DMA mode = BF (buffer fill mode)\n");
|
||||
dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "BF");
|
||||
dev_info(dev->dev, "DMA mode (%s)\n", "BF");
|
||||
}
|
||||
if (!use_dma)
|
||||
dev_info(&dev->pdev->dev, "FIFO mode\n");
|
||||
dev_info(dev->dev, "FIFO mode\n");
|
||||
DBG(dev, "-------------------------------------------------------\n");
|
||||
}
|
||||
|
||||
|
@ -1244,7 +1243,7 @@ finished:
|
|||
}
|
||||
|
||||
/* Empty request queue of an endpoint; caller holds spinlock */
|
||||
static void empty_req_queue(struct udc_ep *ep)
|
||||
void empty_req_queue(struct udc_ep *ep)
|
||||
{
|
||||
struct udc_request *req;
|
||||
|
||||
|
@ -1256,6 +1255,7 @@ static void empty_req_queue(struct udc_ep *ep)
|
|||
complete_req(ep, req, -ESHUTDOWN);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(empty_req_queue);
|
||||
|
||||
/* Dequeues a request packet, called by gadget driver */
|
||||
static int udc_dequeue(struct usb_ep *usbep, struct usb_request *usbreq)
|
||||
|
@ -1623,8 +1623,11 @@ static void udc_setup_endpoints(struct udc *dev)
|
|||
/* Bringup after Connect event, initial bringup to be ready for ep0 events */
|
||||
static void usb_connect(struct udc *dev)
|
||||
{
|
||||
/* Return if already connected */
|
||||
if (dev->connected)
|
||||
return;
|
||||
|
||||
dev_info(&dev->pdev->dev, "USB Connect\n");
|
||||
dev_info(dev->dev, "USB Connect\n");
|
||||
|
||||
dev->connected = 1;
|
||||
|
||||
|
@ -1641,8 +1644,11 @@ static void usb_connect(struct udc *dev)
|
|||
*/
|
||||
static void usb_disconnect(struct udc *dev)
|
||||
{
|
||||
/* Return if already disconnected */
|
||||
if (!dev->connected)
|
||||
return;
|
||||
|
||||
dev_info(&dev->pdev->dev, "USB Disconnect\n");
|
||||
dev_info(dev->dev, "USB Disconnect\n");
|
||||
|
||||
dev->connected = 0;
|
||||
|
||||
|
@ -1715,11 +1721,15 @@ static void udc_soft_reset(struct udc *dev)
|
|||
/* device int. status reset */
|
||||
writel(UDC_DEV_MSK_DISABLE, &dev->regs->irqsts);
|
||||
|
||||
spin_lock_irqsave(&udc_irq_spinlock, flags);
|
||||
writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg);
|
||||
readl(&dev->regs->cfg);
|
||||
spin_unlock_irqrestore(&udc_irq_spinlock, flags);
|
||||
|
||||
/* Don't do this for Broadcom UDC since this is a reserved
|
||||
* bit.
|
||||
*/
|
||||
if (dev->chiprev != UDC_BCM_REV) {
|
||||
spin_lock_irqsave(&udc_irq_spinlock, flags);
|
||||
writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg);
|
||||
readl(&dev->regs->cfg);
|
||||
spin_unlock_irqrestore(&udc_irq_spinlock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
/* RDE timer callback to set RDE bit */
|
||||
|
@ -2106,7 +2116,7 @@ static irqreturn_t udc_data_out_isr(struct udc *dev, int ep_ix)
|
|||
}
|
||||
/* HE event ? */
|
||||
if (tmp & AMD_BIT(UDC_EPSTS_HE)) {
|
||||
dev_err(&dev->pdev->dev, "HE ep%dout occurred\n", ep->num);
|
||||
dev_err(dev->dev, "HE ep%dout occurred\n", ep->num);
|
||||
|
||||
/* clear HE */
|
||||
writel(tmp | AMD_BIT(UDC_EPSTS_HE), &ep->regs->sts);
|
||||
|
@ -2305,7 +2315,7 @@ static irqreturn_t udc_data_in_isr(struct udc *dev, int ep_ix)
|
|||
if (use_dma) {
|
||||
/* BNA ? */
|
||||
if (epsts & AMD_BIT(UDC_EPSTS_BNA)) {
|
||||
dev_err(&dev->pdev->dev,
|
||||
dev_err(dev->dev,
|
||||
"BNA ep%din occurred - DESPTR = %08lx\n",
|
||||
ep->num,
|
||||
(unsigned long) readl(&ep->regs->desptr));
|
||||
|
@ -2318,7 +2328,7 @@ static irqreturn_t udc_data_in_isr(struct udc *dev, int ep_ix)
|
|||
}
|
||||
/* HE event ? */
|
||||
if (epsts & AMD_BIT(UDC_EPSTS_HE)) {
|
||||
dev_err(&dev->pdev->dev,
|
||||
dev_err(dev->dev,
|
||||
"HE ep%dn occurred - DESPTR = %08lx\n",
|
||||
ep->num, (unsigned long) readl(&ep->regs->desptr));
|
||||
|
||||
|
@ -2956,7 +2966,7 @@ __acquires(dev->lock)
|
|||
|
||||
/* link up all endpoints */
|
||||
udc_setup_endpoints(dev);
|
||||
dev_info(&dev->pdev->dev, "Connect: %s\n",
|
||||
dev_info(dev->dev, "Connect: %s\n",
|
||||
usb_speed_string(dev->gadget.speed));
|
||||
|
||||
/* init ep 0 */
|
||||
|
@ -3097,7 +3107,7 @@ int init_dma_pools(struct udc *dev)
|
|||
}
|
||||
|
||||
/* DMA setup */
|
||||
dev->data_requests = dma_pool_create("data_requests", NULL,
|
||||
dev->data_requests = dma_pool_create("data_requests", dev->dev,
|
||||
sizeof(struct udc_data_dma), 0, 0);
|
||||
if (!dev->data_requests) {
|
||||
DBG(dev, "can't get request data pool\n");
|
||||
|
@ -3108,7 +3118,7 @@ int init_dma_pools(struct udc *dev)
|
|||
dev->ep[UDC_EP0IN_IX].dma = &dev->regs->ctl;
|
||||
|
||||
/* dma desc for setup data */
|
||||
dev->stp_requests = dma_pool_create("setup requests", NULL,
|
||||
dev->stp_requests = dma_pool_create("setup requests", dev->dev,
|
||||
sizeof(struct udc_stp_dma), 0, 0);
|
||||
if (!dev->stp_requests) {
|
||||
DBG(dev, "can't get stp request pool\n");
|
||||
|
@ -3168,24 +3178,30 @@ int udc_probe(struct udc *dev)
|
|||
/* init registers, interrupts, ... */
|
||||
startup_registers(dev);
|
||||
|
||||
dev_info(&dev->pdev->dev, "%s\n", mod_desc);
|
||||
dev_info(dev->dev, "%s\n", mod_desc);
|
||||
|
||||
snprintf(tmp, sizeof(tmp), "%d", dev->irq);
|
||||
dev_info(&dev->pdev->dev,
|
||||
"irq %s, pci mem %08lx, chip rev %02x(Geode5536 %s)\n",
|
||||
tmp, dev->phys_addr, dev->chiprev,
|
||||
(dev->chiprev == UDC_HSA0_REV) ? "A0" : "B1");
|
||||
strcpy(tmp, UDC_DRIVER_VERSION_STRING);
|
||||
if (dev->chiprev == UDC_HSA0_REV) {
|
||||
dev_err(&dev->pdev->dev, "chip revision is A0; too old\n");
|
||||
retval = -ENODEV;
|
||||
goto finished;
|
||||
|
||||
/* Print this device info for AMD chips only*/
|
||||
if (dev->chiprev == UDC_HSA0_REV ||
|
||||
dev->chiprev == UDC_HSB1_REV) {
|
||||
dev_info(dev->dev, "irq %s, pci mem %08lx, chip rev %02x(Geode5536 %s)\n",
|
||||
tmp, dev->phys_addr, dev->chiprev,
|
||||
(dev->chiprev == UDC_HSA0_REV) ?
|
||||
"A0" : "B1");
|
||||
strcpy(tmp, UDC_DRIVER_VERSION_STRING);
|
||||
if (dev->chiprev == UDC_HSA0_REV) {
|
||||
dev_err(dev->dev, "chip revision is A0; too old\n");
|
||||
retval = -ENODEV;
|
||||
goto finished;
|
||||
}
|
||||
dev_info(dev->dev,
|
||||
"driver version: %s(for Geode5536 B1)\n", tmp);
|
||||
}
|
||||
dev_info(&dev->pdev->dev,
|
||||
"driver version: %s(for Geode5536 B1)\n", tmp);
|
||||
|
||||
udc = dev;
|
||||
|
||||
retval = usb_add_gadget_udc_release(&udc->pdev->dev, &dev->gadget,
|
||||
retval = usb_add_gadget_udc_release(udc->dev, &dev->gadget,
|
||||
gadget_release);
|
||||
if (retval)
|
||||
goto finished;
|
344
drivers/usb/gadget/udc/snps_udc_plat.c
Normal file
344
drivers/usb/gadget/udc/snps_udc_plat.c
Normal file
|
@ -0,0 +1,344 @@
|
|||
/*
|
||||
* snps_udc_plat.c - Synopsys UDC Platform Driver
|
||||
*
|
||||
* Copyright (C) 2016 Broadcom
|
||||
*
|
||||
* 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 version 2.
|
||||
*
|
||||
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
||||
* kind, whether express or implied; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/extcon.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/dmapool.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include "amd5536udc.h"
|
||||
|
||||
/* description */
|
||||
#define UDC_MOD_DESCRIPTION "Synopsys UDC platform driver"
|
||||
|
||||
void start_udc(struct udc *udc)
|
||||
{
|
||||
if (udc->driver) {
|
||||
dev_info(udc->dev, "Connecting...\n");
|
||||
udc_enable_dev_setup_interrupts(udc);
|
||||
udc_basic_init(udc);
|
||||
udc->connected = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void stop_udc(struct udc *udc)
|
||||
{
|
||||
int tmp;
|
||||
u32 reg;
|
||||
|
||||
spin_lock(&udc->lock);
|
||||
|
||||
/* Flush the receieve fifo */
|
||||
reg = readl(&udc->regs->ctl);
|
||||
reg |= AMD_BIT(UDC_DEVCTL_SRX_FLUSH);
|
||||
writel(reg, &udc->regs->ctl);
|
||||
|
||||
reg = readl(&udc->regs->ctl);
|
||||
reg &= ~(AMD_BIT(UDC_DEVCTL_SRX_FLUSH));
|
||||
writel(reg, &udc->regs->ctl);
|
||||
dev_dbg(udc->dev, "ep rx queue flushed\n");
|
||||
|
||||
/* Mask interrupts. Required more so when the
|
||||
* UDC is connected to a DRD phy.
|
||||
*/
|
||||
udc_mask_unused_interrupts(udc);
|
||||
|
||||
/* Disconnect gadget driver */
|
||||
if (udc->driver) {
|
||||
spin_unlock(&udc->lock);
|
||||
udc->driver->disconnect(&udc->gadget);
|
||||
spin_lock(&udc->lock);
|
||||
|
||||
/* empty queues */
|
||||
for (tmp = 0; tmp < UDC_EP_NUM; tmp++)
|
||||
empty_req_queue(&udc->ep[tmp]);
|
||||
}
|
||||
udc->connected = 0;
|
||||
|
||||
spin_unlock(&udc->lock);
|
||||
dev_info(udc->dev, "Device disconnected\n");
|
||||
}
|
||||
|
||||
void udc_drd_work(struct work_struct *work)
|
||||
{
|
||||
struct udc *udc;
|
||||
|
||||
udc = container_of(to_delayed_work(work),
|
||||
struct udc, drd_work);
|
||||
|
||||
if (udc->conn_type) {
|
||||
dev_dbg(udc->dev, "idle -> device\n");
|
||||
start_udc(udc);
|
||||
} else {
|
||||
dev_dbg(udc->dev, "device -> idle\n");
|
||||
stop_udc(udc);
|
||||
}
|
||||
}
|
||||
|
||||
static int usbd_connect_notify(struct notifier_block *self,
|
||||
unsigned long event, void *ptr)
|
||||
{
|
||||
struct udc *udc = container_of(self, struct udc, nb);
|
||||
|
||||
dev_dbg(udc->dev, "%s: event: %lu\n", __func__, event);
|
||||
|
||||
udc->conn_type = event;
|
||||
|
||||
schedule_delayed_work(&udc->drd_work, 0);
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static int udc_plat_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *res;
|
||||
struct udc *udc;
|
||||
int ret;
|
||||
|
||||
udc = devm_kzalloc(dev, sizeof(*udc), GFP_KERNEL);
|
||||
if (!udc)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_init(&udc->lock);
|
||||
udc->dev = dev;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
udc->virt_addr = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(udc->regs))
|
||||
return PTR_ERR(udc->regs);
|
||||
|
||||
/* udc csr registers base */
|
||||
udc->csr = udc->virt_addr + UDC_CSR_ADDR;
|
||||
|
||||
/* dev registers base */
|
||||
udc->regs = udc->virt_addr + UDC_DEVCFG_ADDR;
|
||||
|
||||
/* ep registers base */
|
||||
udc->ep_regs = udc->virt_addr + UDC_EPREGS_ADDR;
|
||||
|
||||
/* fifo's base */
|
||||
udc->rxfifo = (u32 __iomem *)(udc->virt_addr + UDC_RXFIFO_ADDR);
|
||||
udc->txfifo = (u32 __iomem *)(udc->virt_addr + UDC_TXFIFO_ADDR);
|
||||
|
||||
udc->phys_addr = (unsigned long)res->start;
|
||||
|
||||
udc->irq = irq_of_parse_and_map(dev->of_node, 0);
|
||||
if (udc->irq <= 0) {
|
||||
dev_err(dev, "Can't parse and map interrupt\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
udc->udc_phy = devm_of_phy_get_by_index(dev, dev->of_node, 0);
|
||||
if (IS_ERR(udc->udc_phy)) {
|
||||
dev_err(dev, "Failed to obtain phy from device tree\n");
|
||||
return PTR_ERR(udc->udc_phy);
|
||||
}
|
||||
|
||||
ret = phy_init(udc->udc_phy);
|
||||
if (ret) {
|
||||
dev_err(dev, "UDC phy init failed");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = phy_power_on(udc->udc_phy);
|
||||
if (ret) {
|
||||
dev_err(dev, "UDC phy power on failed");
|
||||
phy_exit(udc->udc_phy);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Register for extcon if supported */
|
||||
if (of_get_property(dev->of_node, "extcon", NULL)) {
|
||||
udc->edev = extcon_get_edev_by_phandle(dev, 0);
|
||||
if (IS_ERR(udc->edev)) {
|
||||
if (PTR_ERR(udc->edev) == -EPROBE_DEFER)
|
||||
return -EPROBE_DEFER;
|
||||
dev_err(dev, "Invalid or missing extcon\n");
|
||||
ret = PTR_ERR(udc->edev);
|
||||
goto exit_phy;
|
||||
}
|
||||
|
||||
udc->nb.notifier_call = usbd_connect_notify;
|
||||
ret = extcon_register_notifier(udc->edev, EXTCON_USB,
|
||||
&udc->nb);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Can't register extcon device\n");
|
||||
goto exit_phy;
|
||||
}
|
||||
|
||||
ret = extcon_get_cable_state_(udc->edev, EXTCON_USB);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Can't get cable state\n");
|
||||
goto exit_extcon;
|
||||
} else if (ret) {
|
||||
udc->conn_type = ret;
|
||||
}
|
||||
INIT_DELAYED_WORK(&udc->drd_work, udc_drd_work);
|
||||
}
|
||||
|
||||
/* init dma pools */
|
||||
if (use_dma) {
|
||||
ret = init_dma_pools(udc);
|
||||
if (ret != 0)
|
||||
goto exit_extcon;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(dev, udc->irq, udc_irq, IRQF_SHARED,
|
||||
"snps-udc", udc);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Request irq %d failed for UDC\n", udc->irq);
|
||||
goto exit_dma;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, udc);
|
||||
udc->chiprev = UDC_BCM_REV;
|
||||
|
||||
if (udc_probe(udc)) {
|
||||
ret = -ENODEV;
|
||||
goto exit_dma;
|
||||
}
|
||||
dev_info(dev, "Synopsys UDC platform driver probe successful\n");
|
||||
|
||||
return 0;
|
||||
|
||||
exit_dma:
|
||||
if (use_dma)
|
||||
free_dma_pools(udc);
|
||||
exit_extcon:
|
||||
if (udc->edev)
|
||||
extcon_unregister_notifier(udc->edev, EXTCON_USB, &udc->nb);
|
||||
exit_phy:
|
||||
if (udc->udc_phy) {
|
||||
phy_power_off(udc->udc_phy);
|
||||
phy_exit(udc->udc_phy);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int udc_plat_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct udc *dev;
|
||||
|
||||
dev = platform_get_drvdata(pdev);
|
||||
|
||||
usb_del_gadget_udc(&dev->gadget);
|
||||
/* gadget driver must not be registered */
|
||||
if (WARN_ON(dev->driver))
|
||||
return 0;
|
||||
|
||||
/* dma pool cleanup */
|
||||
free_dma_pools(dev);
|
||||
|
||||
udc_remove(dev);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
if (dev->drd_wq) {
|
||||
flush_workqueue(dev->drd_wq);
|
||||
destroy_workqueue(dev->drd_wq);
|
||||
}
|
||||
|
||||
phy_power_off(dev->udc_phy);
|
||||
phy_exit(dev->udc_phy);
|
||||
extcon_unregister_notifier(dev->edev, EXTCON_USB, &dev->nb);
|
||||
|
||||
dev_info(&pdev->dev, "Synopsys UDC platform driver removed\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int udc_plat_suspend(struct device *dev)
|
||||
{
|
||||
struct udc *udc;
|
||||
|
||||
udc = dev_get_drvdata(dev);
|
||||
stop_udc(udc);
|
||||
|
||||
if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) {
|
||||
dev_dbg(udc->dev, "device -> idle\n");
|
||||
stop_udc(udc);
|
||||
}
|
||||
phy_power_off(udc->udc_phy);
|
||||
phy_exit(udc->udc_phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int udc_plat_resume(struct device *dev)
|
||||
{
|
||||
struct udc *udc;
|
||||
int ret;
|
||||
|
||||
udc = dev_get_drvdata(dev);
|
||||
|
||||
ret = phy_init(udc->udc_phy);
|
||||
if (ret) {
|
||||
dev_err(udc->dev, "UDC phy init failure");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = phy_power_on(udc->udc_phy);
|
||||
if (ret) {
|
||||
dev_err(udc->dev, "UDC phy power on failure");
|
||||
phy_exit(udc->udc_phy);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) {
|
||||
dev_dbg(udc->dev, "idle -> device\n");
|
||||
start_udc(udc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
static const struct dev_pm_ops udc_plat_pm_ops = {
|
||||
.suspend = udc_plat_suspend,
|
||||
.resume = udc_plat_resume,
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_OF)
|
||||
static const struct of_device_id of_udc_match[] = {
|
||||
{ .compatible = "brcm,ns2-udc", },
|
||||
{ .compatible = "brcm,cygnus-udc", },
|
||||
{ .compatible = "brcm,iproc-udc", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_udc_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver udc_plat_driver = {
|
||||
.probe = udc_plat_probe,
|
||||
.remove = udc_plat_remove,
|
||||
.driver = {
|
||||
.name = "snps-udc-plat",
|
||||
.of_match_table = of_match_ptr(of_udc_match),
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
.pm = &udc_plat_pm_ops,
|
||||
#endif
|
||||
},
|
||||
};
|
||||
module_platform_driver(udc_plat_driver);
|
||||
|
||||
MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION);
|
||||
MODULE_AUTHOR("Broadcom");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -1151,7 +1151,7 @@ static int xudc_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
|
|||
break;
|
||||
}
|
||||
if (&req->usb_req != _req) {
|
||||
spin_unlock_irqrestore(&ep->udc->lock, flags);
|
||||
spin_unlock_irqrestore(&udc->lock, flags);
|
||||
return -EINVAL;
|
||||
}
|
||||
xudc_done(ep, req, -ECONNRESET);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
menu "USB Physical Layer drivers"
|
||||
|
||||
config USB_PHY
|
||||
select EXTCON
|
||||
def_bool n
|
||||
|
||||
#
|
||||
|
@ -109,7 +110,7 @@ config OMAP_OTG
|
|||
|
||||
config TAHVO_USB
|
||||
tristate "Tahvo USB transceiver driver"
|
||||
depends on MFD_RETU && EXTCON
|
||||
depends on MFD_RETU
|
||||
depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y'
|
||||
select USB_PHY
|
||||
help
|
||||
|
@ -141,7 +142,6 @@ config USB_MSM_OTG
|
|||
depends on (USB || USB_GADGET) && (ARCH_QCOM || COMPILE_TEST)
|
||||
depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y'
|
||||
depends on RESET_CONTROLLER
|
||||
depends on EXTCON
|
||||
select USB_PHY
|
||||
help
|
||||
Enable this to support the USB OTG transceiver on Qualcomm chips. It
|
||||
|
@ -155,7 +155,7 @@ config USB_MSM_OTG
|
|||
config USB_QCOM_8X16_PHY
|
||||
tristate "Qualcomm APQ8016/MSM8916 on-chip USB PHY controller support"
|
||||
depends on ARCH_QCOM || COMPILE_TEST
|
||||
depends on RESET_CONTROLLER && EXTCON
|
||||
depends on RESET_CONTROLLER
|
||||
select USB_PHY
|
||||
select USB_ULPI_VIEWPORT
|
||||
help
|
||||
|
|
|
@ -145,17 +145,6 @@ struct msm_otg_platform_data {
|
|||
void (*setup_gpio)(enum usb_otg_state state);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct msm_usb_cable - structure for exteternal connector cable
|
||||
* state tracking
|
||||
* @nb: hold event notification callback
|
||||
* @conn: used for notification registration
|
||||
*/
|
||||
struct msm_usb_cable {
|
||||
struct notifier_block nb;
|
||||
struct extcon_dev *extcon;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct msm_otg: OTG driver data. Shared by HCD and DCD.
|
||||
* @otg: USB OTG Transceiver structure.
|
||||
|
@ -215,9 +204,6 @@ struct msm_otg {
|
|||
|
||||
bool manual_pullup;
|
||||
|
||||
struct msm_usb_cable vbus;
|
||||
struct msm_usb_cable id;
|
||||
|
||||
struct gpio_desc *switch_gpio;
|
||||
struct notifier_block reboot;
|
||||
};
|
||||
|
@ -1612,8 +1598,8 @@ MODULE_DEVICE_TABLE(of, msm_otg_dt_match);
|
|||
static int msm_otg_vbus_notifier(struct notifier_block *nb, unsigned long event,
|
||||
void *ptr)
|
||||
{
|
||||
struct msm_usb_cable *vbus = container_of(nb, struct msm_usb_cable, nb);
|
||||
struct msm_otg *motg = container_of(vbus, struct msm_otg, vbus);
|
||||
struct usb_phy *usb_phy = container_of(nb, struct usb_phy, vbus_nb);
|
||||
struct msm_otg *motg = container_of(usb_phy, struct msm_otg, phy);
|
||||
|
||||
if (event)
|
||||
set_bit(B_SESS_VLD, &motg->inputs);
|
||||
|
@ -1636,8 +1622,8 @@ static int msm_otg_vbus_notifier(struct notifier_block *nb, unsigned long event,
|
|||
static int msm_otg_id_notifier(struct notifier_block *nb, unsigned long event,
|
||||
void *ptr)
|
||||
{
|
||||
struct msm_usb_cable *id = container_of(nb, struct msm_usb_cable, nb);
|
||||
struct msm_otg *motg = container_of(id, struct msm_otg, id);
|
||||
struct usb_phy *usb_phy = container_of(nb, struct usb_phy, id_nb);
|
||||
struct msm_otg *motg = container_of(usb_phy, struct msm_otg, phy);
|
||||
|
||||
if (event)
|
||||
clear_bit(ID, &motg->inputs);
|
||||
|
@ -1652,7 +1638,6 @@ static int msm_otg_id_notifier(struct notifier_block *nb, unsigned long event,
|
|||
static int msm_otg_read_dt(struct platform_device *pdev, struct msm_otg *motg)
|
||||
{
|
||||
struct msm_otg_platform_data *pdata;
|
||||
struct extcon_dev *ext_id, *ext_vbus;
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct property *prop;
|
||||
int len, ret, words;
|
||||
|
@ -1708,54 +1693,6 @@ static int msm_otg_read_dt(struct platform_device *pdev, struct msm_otg *motg)
|
|||
if (IS_ERR(motg->switch_gpio))
|
||||
return PTR_ERR(motg->switch_gpio);
|
||||
|
||||
ext_id = ERR_PTR(-ENODEV);
|
||||
ext_vbus = ERR_PTR(-ENODEV);
|
||||
if (of_property_read_bool(node, "extcon")) {
|
||||
|
||||
/* Each one of them is not mandatory */
|
||||
ext_vbus = extcon_get_edev_by_phandle(&pdev->dev, 0);
|
||||
if (IS_ERR(ext_vbus) && PTR_ERR(ext_vbus) != -ENODEV)
|
||||
return PTR_ERR(ext_vbus);
|
||||
|
||||
ext_id = extcon_get_edev_by_phandle(&pdev->dev, 1);
|
||||
if (IS_ERR(ext_id) && PTR_ERR(ext_id) != -ENODEV)
|
||||
return PTR_ERR(ext_id);
|
||||
}
|
||||
|
||||
if (!IS_ERR(ext_vbus)) {
|
||||
motg->vbus.extcon = ext_vbus;
|
||||
motg->vbus.nb.notifier_call = msm_otg_vbus_notifier;
|
||||
ret = devm_extcon_register_notifier(&pdev->dev, ext_vbus,
|
||||
EXTCON_USB, &motg->vbus.nb);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "register VBUS notifier failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = extcon_get_state(ext_vbus, EXTCON_USB);
|
||||
if (ret)
|
||||
set_bit(B_SESS_VLD, &motg->inputs);
|
||||
else
|
||||
clear_bit(B_SESS_VLD, &motg->inputs);
|
||||
}
|
||||
|
||||
if (!IS_ERR(ext_id)) {
|
||||
motg->id.extcon = ext_id;
|
||||
motg->id.nb.notifier_call = msm_otg_id_notifier;
|
||||
ret = devm_extcon_register_notifier(&pdev->dev, ext_id,
|
||||
EXTCON_USB_HOST, &motg->id.nb);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "register ID notifier failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = extcon_get_state(ext_id, EXTCON_USB_HOST);
|
||||
if (ret)
|
||||
clear_bit(ID, &motg->inputs);
|
||||
else
|
||||
set_bit(ID, &motg->inputs);
|
||||
}
|
||||
|
||||
prop = of_find_property(node, "qcom,phy-init-sequence", &len);
|
||||
if (!prop || !len)
|
||||
return 0;
|
||||
|
@ -1932,6 +1869,8 @@ static int msm_otg_probe(struct platform_device *pdev)
|
|||
phy->init = msm_phy_init;
|
||||
phy->notify_disconnect = msm_phy_notify_disconnect;
|
||||
phy->type = USB_PHY_TYPE_USB2;
|
||||
phy->vbus_nb.notifier_call = msm_otg_vbus_notifier;
|
||||
phy->id_nb.notifier_call = msm_otg_id_notifier;
|
||||
|
||||
phy->io_ops = &msm_otg_io_ops;
|
||||
|
||||
|
@ -1947,6 +1886,18 @@ static int msm_otg_probe(struct platform_device *pdev)
|
|||
goto disable_ldo;
|
||||
}
|
||||
|
||||
ret = extcon_get_state(phy->edev, EXTCON_USB);
|
||||
if (ret)
|
||||
set_bit(B_SESS_VLD, &motg->inputs);
|
||||
else
|
||||
clear_bit(B_SESS_VLD, &motg->inputs);
|
||||
|
||||
ret = extcon_get_state(phy->id_edev, EXTCON_USB_HOST);
|
||||
if (ret)
|
||||
clear_bit(ID, &motg->inputs);
|
||||
else
|
||||
set_bit(ID, &motg->inputs);
|
||||
|
||||
platform_set_drvdata(pdev, motg);
|
||||
device_init_wakeup(&pdev->dev, 1);
|
||||
|
||||
|
|
|
@ -69,9 +69,6 @@ struct phy_8x16 {
|
|||
|
||||
struct reset_control *phy_reset;
|
||||
|
||||
struct extcon_dev *vbus_edev;
|
||||
struct notifier_block vbus_notify;
|
||||
|
||||
struct gpio_desc *switch_gpio;
|
||||
struct notifier_block reboot_notify;
|
||||
};
|
||||
|
@ -131,7 +128,8 @@ static int phy_8x16_vbus_off(struct phy_8x16 *qphy)
|
|||
static int phy_8x16_vbus_notify(struct notifier_block *nb, unsigned long event,
|
||||
void *ptr)
|
||||
{
|
||||
struct phy_8x16 *qphy = container_of(nb, struct phy_8x16, vbus_notify);
|
||||
struct usb_phy *usb_phy = container_of(nb, struct usb_phy, vbus_nb);
|
||||
struct phy_8x16 *qphy = container_of(usb_phy, struct phy_8x16, phy);
|
||||
|
||||
if (event)
|
||||
phy_8x16_vbus_on(qphy);
|
||||
|
@ -187,7 +185,7 @@ static int phy_8x16_init(struct usb_phy *phy)
|
|||
val = ULPI_PWR_OTG_COMP_DISABLE;
|
||||
usb_phy_io_write(phy, val, ULPI_SET(ULPI_PWR_CLK_MNG_REG));
|
||||
|
||||
state = extcon_get_state(qphy->vbus_edev, EXTCON_USB);
|
||||
state = extcon_get_state(qphy->phy.edev, EXTCON_USB);
|
||||
if (state)
|
||||
phy_8x16_vbus_on(qphy);
|
||||
else
|
||||
|
@ -289,15 +287,13 @@ static int phy_8x16_probe(struct platform_device *pdev)
|
|||
phy->io_priv = qphy->regs + HSPHY_ULPI_VIEWPORT;
|
||||
phy->io_ops = &ulpi_viewport_access_ops;
|
||||
phy->type = USB_PHY_TYPE_USB2;
|
||||
phy->vbus_nb.notifier_call = phy_8x16_vbus_notify;
|
||||
phy->id_nb.notifier_call = NULL;
|
||||
|
||||
ret = phy_8x16_read_devicetree(qphy);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
qphy->vbus_edev = extcon_get_edev_by_phandle(phy->dev, 0);
|
||||
if (IS_ERR(qphy->vbus_edev))
|
||||
return PTR_ERR(qphy->vbus_edev);
|
||||
|
||||
ret = clk_set_rate(qphy->core_clk, INT_MAX);
|
||||
if (ret < 0)
|
||||
dev_dbg(phy->dev, "Can't boost core clock\n");
|
||||
|
@ -315,12 +311,6 @@ static int phy_8x16_probe(struct platform_device *pdev)
|
|||
if (WARN_ON(ret))
|
||||
goto off_clks;
|
||||
|
||||
qphy->vbus_notify.notifier_call = phy_8x16_vbus_notify;
|
||||
ret = devm_extcon_register_notifier(&pdev->dev, qphy->vbus_edev,
|
||||
EXTCON_USB, &qphy->vbus_notify);
|
||||
if (ret < 0)
|
||||
goto off_power;
|
||||
|
||||
ret = usb_add_phy_dev(&qphy->phy);
|
||||
if (ret)
|
||||
goto off_power;
|
||||
|
|
|
@ -100,6 +100,54 @@ static int devm_usb_phy_match(struct device *dev, void *res, void *match_data)
|
|||
return *phy == match_data;
|
||||
}
|
||||
|
||||
static int usb_add_extcon(struct usb_phy *x)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (of_property_read_bool(x->dev->of_node, "extcon")) {
|
||||
x->edev = extcon_get_edev_by_phandle(x->dev, 0);
|
||||
if (IS_ERR(x->edev))
|
||||
return PTR_ERR(x->edev);
|
||||
|
||||
x->id_edev = extcon_get_edev_by_phandle(x->dev, 1);
|
||||
if (IS_ERR(x->id_edev)) {
|
||||
x->id_edev = NULL;
|
||||
dev_info(x->dev, "No separate ID extcon device\n");
|
||||
}
|
||||
|
||||
if (x->vbus_nb.notifier_call) {
|
||||
ret = devm_extcon_register_notifier(x->dev, x->edev,
|
||||
EXTCON_USB,
|
||||
&x->vbus_nb);
|
||||
if (ret < 0) {
|
||||
dev_err(x->dev,
|
||||
"register VBUS notifier failed\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (x->id_nb.notifier_call) {
|
||||
struct extcon_dev *id_ext;
|
||||
|
||||
if (x->id_edev)
|
||||
id_ext = x->id_edev;
|
||||
else
|
||||
id_ext = x->edev;
|
||||
|
||||
ret = devm_extcon_register_notifier(x->dev, id_ext,
|
||||
EXTCON_USB_HOST,
|
||||
&x->id_nb);
|
||||
if (ret < 0) {
|
||||
dev_err(x->dev,
|
||||
"register ID notifier failed\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* devm_usb_get_phy - find the USB PHY
|
||||
* @dev - device that requests this phy
|
||||
|
@ -388,6 +436,10 @@ int usb_add_phy(struct usb_phy *x, enum usb_phy_type type)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = usb_add_extcon(x);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier);
|
||||
|
||||
spin_lock_irqsave(&phy_lock, flags);
|
||||
|
@ -422,12 +474,17 @@ int usb_add_phy_dev(struct usb_phy *x)
|
|||
{
|
||||
struct usb_phy_bind *phy_bind;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
if (!x->dev) {
|
||||
dev_err(x->dev, "no device provided for PHY\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = usb_add_extcon(x);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier);
|
||||
|
||||
spin_lock_irqsave(&phy_lock, flags);
|
||||
|
|
|
@ -304,6 +304,7 @@ struct usb_gadget_ops {
|
|||
int (*udc_start)(struct usb_gadget *,
|
||||
struct usb_gadget_driver *);
|
||||
int (*udc_stop)(struct usb_gadget *);
|
||||
void (*udc_set_speed)(struct usb_gadget *, enum usb_device_speed);
|
||||
struct usb_ep *(*match_ep)(struct usb_gadget *,
|
||||
struct usb_endpoint_descriptor *,
|
||||
struct usb_ss_ep_comp_descriptor *);
|
||||
|
@ -352,6 +353,8 @@ struct usb_gadget_ops {
|
|||
* @deactivated: True if gadget is deactivated - in deactivated state it cannot
|
||||
* be connected.
|
||||
* @connected: True if gadget is connected.
|
||||
* @lpm_capable: If the gadget max_speed is FULL or HIGH, this flag
|
||||
* indicates that it supports LPM as per the LPM ECN & errata.
|
||||
*
|
||||
* Gadgets have a mostly-portable "gadget driver" implementing device
|
||||
* functions, handling all usb configurations and interfaces. Gadget
|
||||
|
@ -404,6 +407,7 @@ struct usb_gadget {
|
|||
unsigned is_selfpowered:1;
|
||||
unsigned deactivated:1;
|
||||
unsigned connected:1;
|
||||
unsigned lpm_capable:1;
|
||||
};
|
||||
#define work_to_gadget(w) (container_of((w), struct usb_gadget, work))
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#ifndef __LINUX_USB_PHY_H
|
||||
#define __LINUX_USB_PHY_H
|
||||
|
||||
#include <linux/extcon.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/usb.h>
|
||||
|
||||
|
@ -85,6 +86,12 @@ struct usb_phy {
|
|||
struct usb_phy_io_ops *io_ops;
|
||||
void __iomem *io_priv;
|
||||
|
||||
/* to support extcon device */
|
||||
struct extcon_dev *edev;
|
||||
struct extcon_dev *id_edev;
|
||||
struct notifier_block vbus_nb;
|
||||
struct notifier_block id_nb;
|
||||
|
||||
/* for notification of usb_phy_events */
|
||||
struct atomic_notifier_head notifier;
|
||||
|
||||
|
|
|
@ -275,13 +275,14 @@ struct usb_functionfs_event {
|
|||
#define FUNCTIONFS_INTERFACE_REVMAP _IO('g', 128)
|
||||
|
||||
/*
|
||||
* Returns real bEndpointAddress of an endpoint. If function is not
|
||||
* active returns -ENODEV.
|
||||
* Returns real bEndpointAddress of an endpoint. If endpoint shuts down
|
||||
* during the call, returns -ESHUTDOWN.
|
||||
*/
|
||||
#define FUNCTIONFS_ENDPOINT_REVMAP _IO('g', 129)
|
||||
|
||||
/*
|
||||
* Returns endpoint descriptor. If function is not active returns -ENODEV.
|
||||
* Returns endpoint descriptor. If endpoint shuts down during the call,
|
||||
* returns -ESHUTDOWN.
|
||||
*/
|
||||
#define FUNCTIONFS_ENDPOINT_DESC _IOR('g', 130, \
|
||||
struct usb_endpoint_descriptor)
|
||||
|
|
|
@ -387,15 +387,17 @@ int main (int argc, char **argv)
|
|||
/* pick defaults that works with all speeds, without short packets.
|
||||
*
|
||||
* Best per-frame data rates:
|
||||
* high speed, bulk 512 * 13 * 8 = 53248
|
||||
* interrupt 1024 * 3 * 8 = 24576
|
||||
* full speed, bulk/intr 64 * 19 = 1216
|
||||
* interrupt 64 * 1 = 64
|
||||
* low speed, interrupt 8 * 1 = 8
|
||||
* super speed,bulk 1024 * 16 * 8 = 131072
|
||||
* interrupt 1024 * 3 * 8 = 24576
|
||||
* high speed, bulk 512 * 13 * 8 = 53248
|
||||
* interrupt 1024 * 3 * 8 = 24576
|
||||
* full speed, bulk/intr 64 * 19 = 1216
|
||||
* interrupt 64 * 1 = 64
|
||||
* low speed, interrupt 8 * 1 = 8
|
||||
*/
|
||||
param.iterations = 1000;
|
||||
param.length = 1024;
|
||||
param.vary = 512;
|
||||
param.vary = 1024;
|
||||
param.sglen = 32;
|
||||
|
||||
/* for easy use when hotplugging */
|
||||
|
@ -457,7 +459,7 @@ usage:
|
|||
"\t-c iterations default 1000\n"
|
||||
"\t-s transfer length default 1024\n"
|
||||
"\t-g sglen default 32\n"
|
||||
"\t-v vary default 512\n",
|
||||
"\t-v vary default 1024\n",
|
||||
argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue